From 3e49a2f85070b2fb672c1e0fdba281a4ea3aebe6 Mon Sep 17 00:00:00 2001 From: Joonas Lahtinen Date: Wed, 10 Jun 2026 09:03:14 +0300 Subject: [PATCH 001/577] drm/i915/gem: Fix phys BO pread/pwrite with offset sg_page() returns struct page pointer not (void *) so the scaling of pread/pwrite is wrong for phys BO and wrong parts of BO would be accessed if non-zero offset is used. Last impacted platform with overlay or cursor planes using phys mapping was Gen3/945G/Lakeport. Reported-by: Matthew Wilcox (Oracle) Fixes: c6790dc22312 ("drm/i915: Wean off drm_pci_alloc/drm_pci_free") Cc: # v4.5+ Cc: Tvrtko Ursulin Cc: Simona Vetter Cc: Jani Nikula Cc: Rodrigo Vivi Signed-off-by: Joonas Lahtinen Reviewed-by: Tvrtko Ursulin Link: https://patch.msgid.link/20260610060314.26111-1-joonas.lahtinen@linux.intel.com --- drivers/gpu/drm/i915/gem/i915_gem_phys.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/i915/gem/i915_gem_phys.c b/drivers/gpu/drm/i915/gem/i915_gem_phys.c index e375afbf458e..d53129eb5603 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_phys.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_phys.c @@ -18,6 +18,17 @@ #include "i915_gem_tiling.h" #include "i915_scatterlist.h" +/* Abuse scatterlist to store pointer instead of struct page. */ +static inline void __set_phys_vaddr(struct scatterlist *sg, void *vaddr) +{ + sg_assign_page(sg, (struct page *)vaddr); +} + +static inline void *__get_phys_vaddr(struct scatterlist *sg) +{ + return (void *)sg_page(sg); +} + static int i915_gem_object_get_pages_phys(struct drm_i915_gem_object *obj) { struct address_space *mapping = obj->base.filp->f_mapping; @@ -58,7 +69,7 @@ static int i915_gem_object_get_pages_phys(struct drm_i915_gem_object *obj) sg->offset = 0; sg->length = obj->base.size; - sg_assign_page(sg, (struct page *)vaddr); + __set_phys_vaddr(sg, vaddr); sg_dma_address(sg) = dma; sg_dma_len(sg) = obj->base.size; @@ -99,7 +110,7 @@ i915_gem_object_put_pages_phys(struct drm_i915_gem_object *obj, struct sg_table *pages) { dma_addr_t dma = sg_dma_address(pages->sgl); - void *vaddr = sg_page(pages->sgl); + void *vaddr = __get_phys_vaddr(pages->sgl); __i915_gem_object_release_shmem(obj, pages, false); @@ -139,7 +150,7 @@ i915_gem_object_put_pages_phys(struct drm_i915_gem_object *obj, int i915_gem_object_pwrite_phys(struct drm_i915_gem_object *obj, const struct drm_i915_gem_pwrite *args) { - void *vaddr = sg_page(obj->mm.pages->sgl) + args->offset; + void *vaddr = __get_phys_vaddr(obj->mm.pages->sgl) + args->offset; char __user *user_data = u64_to_user_ptr(args->data_ptr); struct drm_i915_private *i915 = to_i915(obj->base.dev); int err; @@ -170,7 +181,7 @@ int i915_gem_object_pwrite_phys(struct drm_i915_gem_object *obj, int i915_gem_object_pread_phys(struct drm_i915_gem_object *obj, const struct drm_i915_gem_pread *args) { - void *vaddr = sg_page(obj->mm.pages->sgl) + args->offset; + void *vaddr = __get_phys_vaddr(obj->mm.pages->sgl) + args->offset; char __user *user_data = u64_to_user_ptr(args->data_ptr); int err; From 15b9353deff3cf72331c387780de3cf9c316b643 Mon Sep 17 00:00:00 2001 From: Joonas Lahtinen Date: Mon, 22 Jun 2026 16:25:39 +0300 Subject: [PATCH 002/577] drm/i915/gem: Add missing nospec on parallel submit slot Add missing Spectre mitigation for userspace controlled parallel submission slot. Discovered using AI-assisted static analysis confirmed by Intel Product Security. Reported-by: Martin Hodo Fixes: e5e32171a2cf ("drm/i915/guc: Connect UAPI to GuC multi-lrc interface") Cc: Matthew Brost Cc: Tvrtko Ursulin Signed-off-by: Joonas Lahtinen Reviewed-by: Matthew Brost Reviewed-by: Tvrtko Ursulin Cc: # v5.16+ Link: https://patch.msgid.link/20260622132539.165558-1-joonas.lahtinen@linux.intel.com --- drivers/gpu/drm/i915/gem/i915_gem_context.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/i915/gem/i915_gem_context.c b/drivers/gpu/drm/i915/gem/i915_gem_context.c index 6ac0f23570f3..aeafe1742d30 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_context.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_context.c @@ -613,6 +613,7 @@ set_proto_ctx_engines_parallel_submit(struct i915_user_extension __user *base, return -EINVAL; } + slot = array_index_nospec(slot, set->num_engines); if (set->engines[slot].type != I915_GEM_ENGINE_TYPE_INVALID) { drm_dbg(&i915->drm, "Invalid placement[%d], already occupied\n", slot); From 6029bc064f0b1bac184203a50fbaaf070fa18832 Mon Sep 17 00:00:00 2001 From: Joonas Lahtinen Date: Wed, 24 Jun 2026 12:09:40 +0300 Subject: [PATCH 003/577] drm/i915: Return NULL on error in active_instance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Avoid returning &node->base when node is NULL due to OOM during GFP_ATOMIC allocation. Discovered using AI-assisted static analysis confirmed by Intel Product Security. Reported-by: Martin Hodo Fixes: bfaae47db3c0 ("drm/i915: make lockdep slightly happier about execbuf.") Cc: Maarten Lankhorst Cc: Thomas Hellström Cc: Simona Vetter Cc: # v5.13+ Signed-off-by: Joonas Lahtinen Reviewed-by: Sebastian Brzezinka Reviewed-by: Maarten Lankhorst Link: https://patch.msgid.link/20260624090940.74840-1-joonas.lahtinen@linux.intel.com --- drivers/gpu/drm/i915/i915_active.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/i915/i915_active.c b/drivers/gpu/drm/i915/i915_active.c index 5cb7a72774a0..aa77def0bc0d 100644 --- a/drivers/gpu/drm/i915/i915_active.c +++ b/drivers/gpu/drm/i915/i915_active.c @@ -318,7 +318,7 @@ active_instance(struct i915_active *ref, u64 idx) */ node = kmem_cache_alloc(slab_cache, GFP_ATOMIC); if (!node) - goto out; + goto err; __i915_active_fence_init(&node->base, NULL, node_retire); node->ref = ref; @@ -332,6 +332,11 @@ out: spin_unlock_irq(&ref->tree_lock); return &node->base; + +err: + spin_unlock_irq(&ref->tree_lock); + + return NULL; } void __i915_active_init(struct i915_active *ref, From 36eda5b5c2d40da41cc0a5403c26986237cf9e87 Mon Sep 17 00:00:00 2001 From: Joonas Lahtinen Date: Wed, 1 Jul 2026 10:55:55 +0300 Subject: [PATCH 004/577] drm/i915/gem: Fix NULL deref in I915_CONTEXT_PARAM_SSEU Setting context engine slot N into I915_ENGINE_CLASS_INVALID / I915_ENGINE_CLASS_INVALID_NONE and attempting to apply I915_CONTEXT_PARAM_SSEU to the same slot N will deref NULL. Fix that. Discovered using AI-assisted static analysis confirmed by Intel Product Security. Reported-by: Martin Hodo Fixes: d4433c7600f7 ("drm/i915/gem: Use the proto-context to handle create parameters (v5)") Cc: Faith Ekstrand Cc: Simona Vetter Cc: Tvrtko Ursulin Cc: Maarten Lankhorst Cc: # v5.15+ Signed-off-by: Joonas Lahtinen Reviewed-by: Maarten Lankhorst Reviewed-by: Andi Shyti Link: https://patch.msgid.link/20260701075555.52142-1-joonas.lahtinen@linux.intel.com --- drivers/gpu/drm/i915/gem/i915_gem_context.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/i915/gem/i915_gem_context.c b/drivers/gpu/drm/i915/gem/i915_gem_context.c index aeafe1742d30..347d1f2c05f5 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_context.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_context.c @@ -850,7 +850,7 @@ static int set_proto_ctx_sseu(struct drm_i915_file_private *fpriv, pe = &pc->user_engines[idx]; /* Only render engine supports RPCS configuration. */ - if (pe->engine->class != RENDER_CLASS) + if (!pe->engine || pe->engine->class != RENDER_CLASS) return -EINVAL; sseu = &pe->sseu; From cf302c074e35131165d8e641c7191df3d22d8e23 Mon Sep 17 00:00:00 2001 From: Andi Shyti Date: Fri, 26 Jun 2026 23:52:32 +0200 Subject: [PATCH 005/577] drm/i915/gt: Return bool values from a boolean helper intel_has_gpu_reset() returns logically correct values by returning a function pointer when GPU reset is supported and NULL otherwise. However, as a boolean helper, it is more appropriate to return explicit true or false values. Signed-off-by: Andi Shyti Reviewed-by: Sebastian Brzezinka Reviewed-by: Raag Jadav Link: https://lore.kernel.org/r/20260626215232.861416-1-andi.shyti@linux.intel.com --- drivers/gpu/drm/i915/gt/intel_reset.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/i915/gt/intel_reset.c b/drivers/gpu/drm/i915/gt/intel_reset.c index b2cf672564dd..6053f3e8aa27 100644 --- a/drivers/gpu/drm/i915/gt/intel_reset.c +++ b/drivers/gpu/drm/i915/gt/intel_reset.c @@ -797,7 +797,7 @@ static int __intel_gt_reset(struct intel_gt *gt, intel_engine_mask_t engine_mask bool intel_has_gpu_reset(const struct intel_gt *gt) { if (!gt->i915->params.reset) - return NULL; + return false; return intel_get_gpu_reset(gt); } From 61c45e4c0db8f477737597571d6b2aa4f7d493ef Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Wed, 1 Jul 2026 16:52:59 +0300 Subject: [PATCH 006/577] 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 007/577] 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 008/577] 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 009/577] 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 010/577] 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 011/577] 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 012/577] 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 013/577] 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 014/577] 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 4f2a12f2d50e9f48227656e4dcbd6423506be31d Mon Sep 17 00:00:00 2001 From: Joonas Lahtinen Date: Wed, 1 Jul 2026 14:45:13 +0300 Subject: [PATCH 015/577] drm/i915/gt: Fix NULL deref on sched_engine alloc failure Avoid using intel_context_put() before intel_context_init() in execlists_create_virtual() as the kref_put() inside would lead to NULL deref on the IOCTL path when sched_engine allocation fails. Discovered using AI-assisted static analysis confirmed by Intel Product Security. Reported-by: Martin Hodo Fixes: 3e28d37146db ("drm/i915: Move priolist to new i915_sched_engine object") Cc: Matthew Brost Cc: Daniele Ceraolo Spurio Cc: Tvrtko Ursulin Cc: # v5.15+ Signed-off-by: Joonas Lahtinen Reviewed-by: Andi Shyti Signed-off-by: Tvrtko Ursulin Link: https://lore.kernel.org/r/20260701114513.221254-1-joonas.lahtinen@linux.intel.com --- .../drm/i915/gt/intel_execlists_submission.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/i915/gt/intel_execlists_submission.c b/drivers/gpu/drm/i915/gt/intel_execlists_submission.c index 1359fc9cb88e..e693b0c9d2a3 100644 --- a/drivers/gpu/drm/i915/gt/intel_execlists_submission.c +++ b/drivers/gpu/drm/i915/gt/intel_execlists_submission.c @@ -3932,11 +3932,11 @@ execlists_create_virtual(struct intel_engine_cs **siblings, unsigned int count, struct drm_i915_private *i915 = siblings[0]->i915; struct virtual_engine *ve; unsigned int n; - int err; + int err = -ENOMEM; ve = kzalloc_flex(*ve, siblings, count); if (!ve) - return ERR_PTR(-ENOMEM); + goto err; ve->base.i915 = i915; ve->base.gt = siblings[0]->gt; @@ -3968,10 +3968,8 @@ execlists_create_virtual(struct intel_engine_cs **siblings, unsigned int count, intel_engine_init_execlists(&ve->base); ve->base.sched_engine = i915_sched_engine_create(ENGINE_VIRTUAL); - if (!ve->base.sched_engine) { - err = -ENOMEM; - goto err_put; - } + if (!ve->base.sched_engine) + goto err_noput; ve->base.sched_engine->private_data = &ve->base; ve->base.cops = &virtual_context_ops; @@ -3987,10 +3985,8 @@ execlists_create_virtual(struct intel_engine_cs **siblings, unsigned int count, intel_context_init(&ve->context, &ve->base); ve->base.breadcrumbs = intel_breadcrumbs_create(NULL); - if (!ve->base.breadcrumbs) { - err = -ENOMEM; + if (!ve->base.breadcrumbs) goto err_put; - } for (n = 0; n < count; n++) { struct intel_engine_cs *sibling = siblings[n]; @@ -4065,8 +4061,13 @@ execlists_create_virtual(struct intel_engine_cs **siblings, unsigned int count, virtual_engine_initial_hint(ve); return &ve->context; +err_noput: + kfree(ve); + goto err; + err_put: intel_context_put(&ve->context); +err: return ERR_PTR(err); } From aa65e0a4b51b3b54b53e4142aaa2d997aa1061ff Mon Sep 17 00:00:00 2001 From: Joonas Lahtinen Date: Wed, 1 Jul 2026 10:30:30 +0300 Subject: [PATCH 016/577] drm/i915/gem: Do not leak siblings[] on proto context error After a successful BALANCE/PARALLEL_SUBMIT extension on context creation, error during processing of next user extension leaks the siblings[] array. Fix that. Discovered using AI-assisted static analysis confirmed by Intel Product Security. Reported-by: Martin Hodo Fixes: d4433c7600f7 ("drm/i915/gem: Use the proto-context to handle create parameters (v5)") Cc: Faith Ekstrand Cc: Simona Vetter Cc: Tvrtko Ursulin Cc: Maarten Lankhorst Cc: # v5.15+ Signed-off-by: Joonas Lahtinen Reviewed-by: Maarten Lankhorst Signed-off-by: Tvrtko Ursulin Link: https://lore.kernel.org/r/20260701073030.44850-1-joonas.lahtinen@linux.intel.com --- drivers/gpu/drm/i915/gem/i915_gem_context.c | 22 +++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/i915/gem/i915_gem_context.c b/drivers/gpu/drm/i915/gem/i915_gem_context.c index 347d1f2c05f5..c58ffa5a8fa6 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_context.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_context.c @@ -769,8 +769,8 @@ static int set_proto_ctx_engines(struct drm_i915_file_private *fpriv, struct intel_engine_cs *engine; if (copy_from_user(&ci, &user->engines[n], sizeof(ci))) { - kfree(set.engines); - return -EFAULT; + err = -EFAULT; + goto err; } memset(&set.engines[n], 0, sizeof(set.engines[n])); @@ -786,8 +786,8 @@ static int set_proto_ctx_engines(struct drm_i915_file_private *fpriv, drm_dbg(&i915->drm, "Invalid engine[%d]: { class:%d, instance:%d }\n", n, ci.engine_class, ci.engine_instance); - kfree(set.engines); - return -ENOENT; + err = -ENOENT; + goto err; } set.engines[n].type = I915_GEM_ENGINE_TYPE_PHYSICAL; @@ -800,15 +800,21 @@ static int set_proto_ctx_engines(struct drm_i915_file_private *fpriv, set_proto_ctx_engines_extensions, ARRAY_SIZE(set_proto_ctx_engines_extensions), &set); - if (err) { - kfree(set.engines); - return err; - } + if (err) + goto err_extensions; pc->num_user_engines = set.num_engines; pc->user_engines = set.engines; return 0; + +err_extensions: + for (n = 0; n < set.num_engines; n++) + kfree(set.engines[n].siblings); +err: + kfree(set.engines); + + return err; } static int set_proto_ctx_sseu(struct drm_i915_file_private *fpriv, From cd0121502ce61a2efec23fb61b90ffec300be281 Mon Sep 17 00:00:00 2001 From: Dibin Moolakadan Subrahmanian Date: Wed, 1 Jul 2026 15:36:50 +0530 Subject: [PATCH 017/577] 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 ca1dc06173fd7ecc229009d916dff5bd815c1320 Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Mon, 6 Jul 2026 15:14:08 +0800 Subject: [PATCH 018/577] drm/i915/guc: Return NULL for missing multi-lrc parent multi_lrc_create_parent() returns ERR_PTR(0) when there are not enough engines in the class to create a parallel context. ERR_PTR(0) evaluates to NULL, and the only caller already treats NULL as the non-error "not enough engines" case. Return NULL directly to make the non-error path explicit. Signed-off-by: Linmao Li Signed-off-by: Tvrtko Ursulin Link: https://lore.kernel.org/r/20260706071412.559909-1-lilinmao@kylinos.cn --- drivers/gpu/drm/i915/gt/uc/selftest_guc_multi_lrc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/i915/gt/uc/selftest_guc_multi_lrc.c b/drivers/gpu/drm/i915/gt/uc/selftest_guc_multi_lrc.c index 28e8a092f4e7..18077801b2e6 100644 --- a/drivers/gpu/drm/i915/gt/uc/selftest_guc_multi_lrc.c +++ b/drivers/gpu/drm/i915/gt/uc/selftest_guc_multi_lrc.c @@ -44,7 +44,7 @@ multi_lrc_create_parent(struct intel_gt *gt, u8 class, } if (i <= 1) - return ERR_PTR(0); + return NULL; logical_sort(siblings, i); From a78d11e7859b87a0058e9b22d93deff17edd1a22 Mon Sep 17 00:00:00 2001 From: Suraj Kandpal Date: Fri, 3 Jul 2026 08:56:54 +0530 Subject: [PATCH 019/577] 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 020/577] 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 021/577] 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 022/577] 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 023/577] 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 024/577] 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 025/577] 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 026/577] 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 027/577] 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 028/577] 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 029/577] 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 030/577] 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 031/577] 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 14a2012a490258f3f93857bc4f1b203405964be7 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 5 Jul 2026 16:02:25 +0800 Subject: [PATCH 032/577] drm/i915/gt: use correct selftest config symbol intel_engine_user.c checks CONFIG_DRM_I915_SELFTESTS before running the engine UABI isolation check. Kconfig defines DRM_I915_SELFTEST, without the trailing "S", and the rest of i915 uses CONFIG_DRM_I915_SELFTEST. Because CONFIG_DRM_I915_SELFTESTS is not backed by any Kconfig symbol, the IS_ENABLED() test is always false. Use the existing selftest symbol so the debug/selftest guarded path can be reached when selftests are enabled. This is a source-level fix. It does not claim dynamic hardware reproduction; the evidence is the Kconfig definition and the inconsistent guard in intel_engine_user.c. Fixes: 750e76b4f9f6 ("drm/i915/gt: Move the [class][inst] lookup for engines onto the GT") Signed-off-by: Pengpeng Hou Signed-off-by: Tvrtko Ursulin Link: https://lore.kernel.org/r/20260705080225.436-1-pengpeng@iscas.ac.cn --- drivers/gpu/drm/i915/gt/intel_engine_user.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/i915/gt/intel_engine_user.c b/drivers/gpu/drm/i915/gt/intel_engine_user.c index be4bbff1a57c..d5190e11b270 100644 --- a/drivers/gpu/drm/i915/gt/intel_engine_user.c +++ b/drivers/gpu/drm/i915/gt/intel_engine_user.c @@ -259,7 +259,7 @@ void intel_engines_driver_register(struct drm_i915_private *i915) p = &prev->rb_right; } - if (IS_ENABLED(CONFIG_DRM_I915_SELFTESTS) && + if (IS_ENABLED(CONFIG_DRM_I915_SELFTEST) && IS_ENABLED(CONFIG_DRM_I915_DEBUG_GEM)) { struct intel_engine_cs *engine; unsigned int isolation; From 69505f3f9595ae00791a0f7191ca2877736a4df6 Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Wed, 1 Jul 2026 18:31:41 +0300 Subject: [PATCH 033/577] 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 034/577] 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 035/577] 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 036/577] 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 037/577] 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 038/577] 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 039/577] 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 040/577] 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 041/577] 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 042/577] 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 043/577] 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 044/577] 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 045/577] 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 046/577] 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 047/577] 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 048/577] 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 049/577] 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 050/577] 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 051/577] 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 052/577] 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 053/577] 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 054/577] 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 055/577] 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 ba40db36971a59f197b41219e8ffc6ce9998df82 Mon Sep 17 00:00:00 2001 From: Honglei Huang Date: Tue, 30 Jun 2026 18:21:23 +0800 Subject: [PATCH 056/577] drm/gpusvm: split MM state flags out of drm_gpusvm_pages_flags drm_gpusvm_pages_flags currently mixes two status: - MM / virtual-address state: whether the range has been (partially) unmapped by the Linux MM, these follow the lifetime of the VMA and are a single per VA range fact. - Device mapping state: has_devmem_pages and has_dma_mapping, which describe the current page mapping status held by device itself. Keeping both on the pages object blurs the semantics of the abstraction of pages and VA range. So move the MM state flags onto the range, and keep drm_gpusvm_pages_flags strictly for mapping state. - Introduce drm_gpusvm_range_flags { migrate_devmem, unmapped, partial_unmap } on drm_gpusvm_range. - Shrink drm_gpusvm_pages_flags to just has_devmem_pages and has_dma_mapping. Side effect: drivers now need to check the unmap flags in the driver itself to avoid handling the unmapped pages. Mirror that bit onto drm_gpusvm_pages so the framework can still short circuit drm_gpusvm_get_pages() under the notifier lock, and make drm_gpusvm_range_set_unmapped() propagate it to the backing pages. This follows Matt's review fixup for the v0 series; see the Link below. Like drm_gpusvm_pages_flags, drm_gpusvm_range_flags unions its bits with a u16 __flags member. Build the new value in a local copy and publish it with a single WRITE_ONCE() on __flags, and have the lockless readers use READ_ONCE(), so concurrent bitfield access stays data-race free and KCSAN quiet. Suggested-by: Matthew Brost Reviewed-by: Matthew Brost Link: https://gitlab.freedesktop.org/mbrost/xe-kernel-driver-svn-perf-6-15-2025/-/commit/623f6a50c037d9e44f6c9fbe6859a0ba7ad50177 Signed-off-by: Honglei Huang Signed-off-by: Matthew Brost Link: https://patch.msgid.link/20260630102127.392396-2-honghuan@amd.com --- drivers/gpu/drm/drm_gpusvm.c | 26 +++++++++++++++++++++++--- drivers/gpu/drm/xe/xe_svm.c | 22 +++++++++++++++++----- include/drm/drm_gpusvm.h | 30 +++++++++++++++++++++++++----- 3 files changed, 65 insertions(+), 13 deletions(-) diff --git a/drivers/gpu/drm/drm_gpusvm.c b/drivers/gpu/drm/drm_gpusvm.c index 958cb605aedd..abdfdaaf5e23 100644 --- a/drivers/gpu/drm/drm_gpusvm.c +++ b/drivers/gpu/drm/drm_gpusvm.c @@ -641,7 +641,7 @@ drm_gpusvm_range_alloc(struct drm_gpusvm *gpusvm, range->itree.last = ALIGN(fault_addr + 1, chunk_size) - 1; INIT_LIST_HEAD(&range->entry); range->pages.notifier_seq = LONG_MAX; - range->pages.flags.migrate_devmem = migrate_devmem ? 1 : 0; + range->flags.migrate_devmem = migrate_devmem ? 1 : 0; return range; } @@ -1784,20 +1784,40 @@ EXPORT_SYMBOL_GPL(drm_gpusvm_has_mapping); /** * drm_gpusvm_range_set_unmapped() - Mark a GPU SVM range as unmapped * @range: Pointer to the GPU SVM range structure. + * @pages: Pointer to the GPU SVM pages structure(s). + * @pages_count: Number of GPU SVM pages structure(s) passed in. * @mmu_range: Pointer to the MMU notifier range structure. * * This function marks a GPU SVM range as unmapped and sets the partial_unmap flag * if the range partially falls within the provided MMU notifier range. */ void drm_gpusvm_range_set_unmapped(struct drm_gpusvm_range *range, + struct drm_gpusvm_pages *pages, + unsigned int pages_count, const struct mmu_notifier_range *mmu_range) { + struct drm_gpusvm_range_flags range_flags = { + .__flags = range->flags.__flags, + }; + unsigned int i; + lockdep_assert_held_write(&range->gpusvm->notifier_lock); - range->pages.flags.unmapped = true; + range_flags.unmapped = true; + for (i = 0; i < pages_count; ++i) { + struct drm_gpusvm_pages_flags flags = { + .__flags = pages[i].flags.__flags, + }; + + flags.unmapped = true; + /* WRITE_ONCE pairs with READ_ONCE for opportunistic checks */ + WRITE_ONCE(pages[i].flags.__flags, flags.__flags); + } if (drm_gpusvm_range_start(range) < mmu_range->start || drm_gpusvm_range_end(range) > mmu_range->end) - range->pages.flags.partial_unmap = true; + range_flags.partial_unmap = true; + /* WRITE_ONCE pairs with READ_ONCE for opportunistic checks */ + WRITE_ONCE(range->flags.__flags, range_flags.__flags); } EXPORT_SYMBOL_GPL(drm_gpusvm_range_set_unmapped); diff --git a/drivers/gpu/drm/xe/xe_svm.c b/drivers/gpu/drm/xe/xe_svm.c index e0f0c23d172d..28a5d95945c3 100644 --- a/drivers/gpu/drm/xe/xe_svm.c +++ b/drivers/gpu/drm/xe/xe_svm.c @@ -134,7 +134,8 @@ xe_svm_garbage_collector_add_range(struct xe_vm *vm, struct xe_svm_range *range, range_debug(range, "GARBAGE COLLECTOR ADD"); - drm_gpusvm_range_set_unmapped(&range->base, mmu_range); + drm_gpusvm_range_set_unmapped(&range->base, &range->base.pages, 1, + mmu_range); spin_lock(&vm->svm.garbage_collector.lock); if (list_empty(&range->garbage_collector_link)) @@ -166,7 +167,7 @@ xe_svm_range_notifier_event_begin(struct xe_vm *vm, struct drm_gpusvm_range *r, range_debug(range, "NOTIFIER"); /* Skip if already unmapped or if no binding exist */ - if (range->base.pages.flags.unmapped || !range->tile_present) + if (range->base.flags.unmapped || !range->tile_present) return 0; range_debug(range, "NOTIFIER - EXECUTE"); @@ -1135,8 +1136,12 @@ bool xe_svm_range_needs_migrate_to_vram(struct xe_svm_range *range, struct xe_vm { struct xe_vm *vm = range_to_vm(&range->base); u64 range_size = xe_svm_range_size(range); + struct drm_gpusvm_range_flags flags = { + /* READ_ONCE pairs with WRITE_ONCE in drm_gpusvm_range_set_unmapped() */ + .__flags = READ_ONCE(range->base.flags.__flags), + }; - if (!range->base.pages.flags.migrate_devmem || !dpagemap) + if (!flags.migrate_devmem || !dpagemap) return false; xe_assert(vm->xe, IS_DGFX(vm->xe)); @@ -1220,6 +1225,7 @@ static int __xe_svm_handle_pagefault(struct xe_vm *vm, struct xe_vma *vma, struct xe_validation_ctx vctx; struct drm_exec exec; struct xe_svm_range *range; + struct drm_gpusvm_range_flags range_flags; struct dma_fence *fence; struct drm_pagemap *dpagemap; struct xe_tile *tile = gt_to_tile(gt); @@ -1248,7 +1254,9 @@ retry: xe_svm_range_fault_count_stats_incr(gt, range); - if (ctx.devmem_only && !range->base.pages.flags.migrate_devmem) + /* READ_ONCE pairs with WRITE_ONCE in drm_gpusvm_range_set_unmapped() */ + range_flags.__flags = READ_ONCE(range->base.flags.__flags); + if (ctx.devmem_only && !range_flags.migrate_devmem) return -EACCES; if (xe_svm_range_is_valid(range, tile, ctx.devmem_only, dpagemap)) { @@ -1620,8 +1628,12 @@ int xe_svm_alloc_vram(struct xe_svm_range *range, const struct drm_gpusvm_ctx *c struct xe_device *xe = vm->xe; int err, retries = 1; bool write_locked = false; + struct drm_gpusvm_range_flags flags = { + /* READ_ONCE pairs with WRITE_ONCE in drm_gpusvm_range_set_unmapped() */ + .__flags = READ_ONCE(range->base.flags.__flags), + }; - xe_assert(range_to_vm(&range->base)->xe, range->base.pages.flags.migrate_devmem); + xe_assert(range_to_vm(&range->base)->xe, flags.migrate_devmem); range_debug(range, "ALLOCATE VRAM"); migration_state = drm_gpusvm_scan_mm(&range->base, diff --git a/include/drm/drm_gpusvm.h b/include/drm/drm_gpusvm.h index 8a4d7134a9a7..251a7266a73f 100644 --- a/include/drm/drm_gpusvm.h +++ b/include/drm/drm_gpusvm.h @@ -109,9 +109,7 @@ struct drm_gpusvm_notifier { /** * struct drm_gpusvm_pages_flags - Structure representing a GPU SVM pages flags * - * @migrate_devmem: Flag indicating whether the pages can be migrated to device memory * @unmapped: Flag indicating if the pages has been unmapped - * @partial_unmap: Flag indicating if the pages has been partially unmapped * @has_devmem_pages: Flag indicating if the pages has devmem pages * @has_dma_mapping: Flag indicating if the pages has a DMA mapping * @__flags: Flags for pages in u16 form (used for READ_ONCE) @@ -119,11 +117,8 @@ struct drm_gpusvm_notifier { struct drm_gpusvm_pages_flags { union { struct { - /* All flags below must be set upon creation */ - u16 migrate_devmem : 1; /* All flags below must be set / cleared under notifier lock */ u16 unmapped : 1; - u16 partial_unmap : 1; u16 has_devmem_pages : 1; u16 has_dma_mapping : 1; }; @@ -151,6 +146,27 @@ struct drm_gpusvm_pages { struct drm_gpusvm_pages_flags flags; }; +/** + * struct drm_gpusvm_range_flags - Range-level GPU SVM flags + * + * @migrate_devmem: Flag indicating whether the range can be migrated to device memory + * @unmapped: Flag indicating if the range has been unmapped + * @partial_unmap: Flag indicating if the range has been partially unmapped + * @__flags: All flags in u16 form (used for READ_ONCE) + */ +struct drm_gpusvm_range_flags { + union { + struct { + /* All flags below must be set upon creation */ + u16 migrate_devmem : 1; + /* All flags below must be set / cleared under notifier lock */ + u16 unmapped : 1; + u16 partial_unmap : 1; + }; + u16 __flags; + }; +}; + /** * struct drm_gpusvm_range - Structure representing a GPU SVM range * @@ -160,6 +176,7 @@ struct drm_gpusvm_pages { * @itree: Interval tree node for the range (inserted in GPU SVM notifier) * @entry: List entry to fast interval tree traversal * @pages: The pages for this range. + * @flags: Flags for range see &struct drm_gpusvm_range_flags * * This structure represents a GPU SVM range used for tracking memory ranges * mapped in a DRM device. @@ -171,6 +188,7 @@ struct drm_gpusvm_range { struct interval_tree_node itree; struct list_head entry; struct drm_gpusvm_pages pages; + struct drm_gpusvm_range_flags flags; }; /** @@ -310,6 +328,8 @@ drm_gpusvm_range_find(struct drm_gpusvm_notifier *notifier, unsigned long start, unsigned long end); void drm_gpusvm_range_set_unmapped(struct drm_gpusvm_range *range, + struct drm_gpusvm_pages *pages, + unsigned int pages_count, const struct mmu_notifier_range *mmu_range); int drm_gpusvm_get_pages(struct drm_gpusvm *gpusvm, From 1d0c4c15c2a62fc15bf69b9e72c30461b2e9346f Mon Sep 17 00:00:00 2001 From: Honglei Huang Date: Tue, 30 Jun 2026 18:21:24 +0800 Subject: [PATCH 057/577] drm/gpusvm: embed struct drm_device into drm_gpusvm_pages drm_gpusvm_pages is the layer that actually represents physical pages/mappings it owns the dma_addr array, the dma_iova_state... With the previous patch, so drm_gpusvm_pages is now strictly about physical pages and their DMA view. Since now the drm_gpusvm_pages instance is inherently bound to one specific drm_device, make that ownership explicit by giving drm_gpusvm_pages its own drm_device handle, and drive all DMA through it instead of through the gpusvm: - Add drm to struct drm_gpusvm_pages and route all DMA in drm_gpusvm_get_pages() / __drm_gpusvm_unmap_pages() through svm_pages->drm instead of gpusvm->drm. - Bind svm_pages->drm where the pages object is initialised (drm_gpusvm_range_alloc() and the xe userptr setup) and require it to be set on entry to drm_gpusvm_get_pages(); the dma device is immutable for the lifetime of the pages instance. A later patch introduces drm_gpusvm_init_pages() to centralise this. Suggested-by: Matthew Brost Reviewed-by: Matthew Brost Signed-off-by: Honglei Huang Signed-off-by: Matthew Brost Link: https://patch.msgid.link/20260630102127.392396-3-honghuan@amd.com --- drivers/gpu/drm/drm_gpusvm.c | 30 ++++++++++++++++++++---------- drivers/gpu/drm/xe/xe_userptr.c | 2 ++ include/drm/drm_gpusvm.h | 2 ++ 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/drm_gpusvm.c b/drivers/gpu/drm/drm_gpusvm.c index abdfdaaf5e23..604554e40f62 100644 --- a/drivers/gpu/drm/drm_gpusvm.c +++ b/drivers/gpu/drm/drm_gpusvm.c @@ -641,6 +641,7 @@ drm_gpusvm_range_alloc(struct drm_gpusvm *gpusvm, range->itree.last = ALIGN(fault_addr + 1, chunk_size) - 1; INIT_LIST_HEAD(&range->entry); range->pages.notifier_seq = LONG_MAX; + range->pages.drm = gpusvm->drm; range->flags.migrate_devmem = migrate_devmem ? 1 : 0; return range; @@ -1135,11 +1136,16 @@ static void __drm_gpusvm_unmap_pages(struct drm_gpusvm *gpusvm, unsigned long npages) { struct drm_pagemap *dpagemap = svm_pages->dpagemap; - struct device *dev = gpusvm->drm->dev; + struct device *dev; unsigned long i, j; lockdep_assert_held(&gpusvm->notifier_lock); + if (!svm_pages->drm) + return; + + dev = svm_pages->drm->dev; + if (svm_pages->flags.has_dma_mapping) { struct drm_gpusvm_pages_flags flags = { .__flags = svm_pages->flags.__flags, @@ -1421,6 +1427,9 @@ int drm_gpusvm_get_pages(struct drm_gpusvm *gpusvm, DMA_BIDIRECTIONAL; struct dma_iova_state *state = &svm_pages->state; + if (!svm_pages->drm) + return -EINVAL; + retry: if (time_after(jiffies, timeout)) return -EBUSY; @@ -1520,7 +1529,7 @@ map_pages: pagemap = page_pgmap(page); dpagemap = drm_pagemap_page_to_dpagemap(page); - if (drm_WARN_ON(gpusvm->drm, !dpagemap)) { + if (drm_WARN_ON(svm_pages->drm, !dpagemap)) { /* * Raced. This is not supposed to happen * since hmm_range_fault() should've migrated @@ -1532,10 +1541,10 @@ map_pages: } svm_pages->dma_addr[j] = dpagemap->ops->device_map(dpagemap, - gpusvm->drm->dev, + svm_pages->drm->dev, page, order, dma_dir); - if (dma_mapping_error(gpusvm->drm->dev, + if (dma_mapping_error(svm_pages->drm->dev, svm_pages->dma_addr[j].addr)) { err = -EFAULT; goto err_unmap; @@ -1555,11 +1564,11 @@ map_pages: } if (!i) - dma_iova_try_alloc(gpusvm->drm->dev, state, + dma_iova_try_alloc(svm_pages->drm->dev, state, 0, npages * PAGE_SIZE); if (dma_use_iova(state)) { - err = dma_iova_link(gpusvm->drm->dev, state, + err = dma_iova_link(svm_pages->drm->dev, state, hmm_pfn_to_phys(pfns[i]), svm_pages->state_offset, PAGE_SIZE << order, @@ -1570,11 +1579,11 @@ map_pages: addr = state->addr + svm_pages->state_offset; svm_pages->state_offset += PAGE_SIZE << order; } else { - addr = dma_map_page(gpusvm->drm->dev, + addr = dma_map_page(svm_pages->drm->dev, page, 0, PAGE_SIZE << order, dma_dir); - if (dma_mapping_error(gpusvm->drm->dev, addr)) { + if (dma_mapping_error(svm_pages->drm->dev, addr)) { err = -EFAULT; goto err_unmap; } @@ -1590,7 +1599,7 @@ map_pages: } if (dma_use_iova(state)) { - err = dma_iova_sync(gpusvm->drm->dev, state, 0, + err = dma_iova_sync(svm_pages->drm->dev, state, 0, svm_pages->state_offset); if (err) goto err_unmap; @@ -1640,7 +1649,8 @@ int drm_gpusvm_range_get_pages(struct drm_gpusvm *gpusvm, struct drm_gpusvm_range *range, const struct drm_gpusvm_ctx *ctx) { - return drm_gpusvm_get_pages(gpusvm, &range->pages, gpusvm->mm, + return drm_gpusvm_get_pages(gpusvm, &range->pages, + gpusvm->mm, &range->notifier->notifier, drm_gpusvm_range_start(range), drm_gpusvm_range_end(range), ctx); diff --git a/drivers/gpu/drm/xe/xe_userptr.c b/drivers/gpu/drm/xe/xe_userptr.c index 1d2ab678faf5..d409c82104f4 100644 --- a/drivers/gpu/drm/xe/xe_userptr.c +++ b/drivers/gpu/drm/xe/xe_userptr.c @@ -390,6 +390,7 @@ int xe_userptr_setup(struct xe_userptr_vma *uvma, unsigned long start, unsigned long range) { struct xe_userptr *userptr = &uvma->userptr; + struct xe_vm *vm = xe_vma_vm(&uvma->vma); int err; INIT_LIST_HEAD(&userptr->invalidate_link); @@ -402,6 +403,7 @@ int xe_userptr_setup(struct xe_userptr_vma *uvma, unsigned long start, return err; userptr->pages.notifier_seq = LONG_MAX; + userptr->pages.drm = &vm->xe->drm; return 0; } diff --git a/include/drm/drm_gpusvm.h b/include/drm/drm_gpusvm.h index 251a7266a73f..842353afb27b 100644 --- a/include/drm/drm_gpusvm.h +++ b/include/drm/drm_gpusvm.h @@ -129,6 +129,7 @@ struct drm_gpusvm_pages_flags { /** * struct drm_gpusvm_pages - Structure representing a GPU SVM mapped pages * + * @drm: The DRM device that owns the dma mappings * @dma_addr: Device address array * @dpagemap: The struct drm_pagemap of the device pages we're dma-mapping. * Note this is assuming only one drm_pagemap per range is allowed. @@ -138,6 +139,7 @@ struct drm_gpusvm_pages_flags { * @flags: Flags for the range; see &struct drm_gpusvm_pages_flags */ struct drm_gpusvm_pages { + struct drm_device *drm; struct drm_pagemap_addr *dma_addr; struct drm_pagemap *dpagemap; struct dma_iova_state state; From 24c339202c0bc83e9e9e945eeee618ececc6a88b Mon Sep 17 00:00:00 2001 From: Honglei Huang Date: Tue, 30 Jun 2026 18:21:25 +0800 Subject: [PATCH 058/577] drm/xe: have xe_svm_range embed one drm_gpusvm_pages With drm_gpusvm_pages now self contained, make xe stop relying on the drm_gpusvm_range pages and take responsibility for the page lifecycle on the driver side. Driver side (xe): - Embed struct drm_gpusvm_pages in xe_svm_range and route all xe accesses through it instead of range->base.pages. - Initialise the embedded pages via drm_gpusvm_init_pages(), which binds the owning &xe->drm up front, and take over the page lifecycle: xe_svm_range_get_pages() calls drm_gpusvm_get_pages() directly; the notifier event_end and xe_svm_range_free() paths drive unmap/free on the embedded pages object. - Convert the open-coded userptr pages init in xe_userptr_setup() to the same drm_gpusvm_init_pages() helper. - Switch xe_svm_range_pages_valid() to drm_gpusvm_pages_valid(). Framework side (drm_gpusvm): - Add a small inline drm_gpusvm_init_pages() helper that records the owning drm_device and initialises the per-pages state, giving drivers a single hook to extend. - Export drm_gpusvm_pages_valid() to let driver owned pages can query mapping state without going through a range. - Lifecycle change: drm_gpusvm_range_remove() no longer *triggers* unmap/free of the embedded pages. The unmap/free logic itself stays in the framework -- drm_gpusvm_free_pages() still performs the DMA unmap (as an idempotent backstop) and frees the dma_addr array -- but the driver now owns *when* it runs, since the driver owns the drm_gpusvm_pages object. Side effect / contract: a driver that owns a drm_gpusvm_pages is now responsible for its lifecycle: drm_gpusvm_init_pages() before first use, and drm_gpusvm_free_pages() when the owner goes away. Xe does the latter from its ops->range_free callback, which the framework invokes once the range refcount drops to zero in drm_gpusvm_range_remove(). The timely DMA unmap for the IOMMU security model still happens in the notifier invalidate path via drm_gpusvm_unmap_pages(); the unmap inside drm_gpusvm_free_pages() is only a backstop for pages that were never invalidated. Suggested-by: Matthew Brost Reviewed-by: Matthew Brost Signed-off-by: Honglei Huang Signed-off-by: Matthew Brost Link: https://patch.msgid.link/20260630102127.392396-4-honghuan@amd.com --- drivers/gpu/drm/drm_gpusvm.c | 16 ++++++++----- drivers/gpu/drm/xe/xe_pt.c | 2 +- drivers/gpu/drm/xe/xe_svm.c | 42 ++++++++++++++++++++++++++------- drivers/gpu/drm/xe/xe_svm.h | 12 ++++++---- drivers/gpu/drm/xe/xe_userptr.c | 5 ++-- include/drm/drm_gpusvm.h | 20 ++++++++++++++++ 6 files changed, 74 insertions(+), 23 deletions(-) diff --git a/drivers/gpu/drm/drm_gpusvm.c b/drivers/gpu/drm/drm_gpusvm.c index 604554e40f62..b0e9a2db1080 100644 --- a/drivers/gpu/drm/drm_gpusvm.c +++ b/drivers/gpu/drm/drm_gpusvm.c @@ -1228,12 +1228,17 @@ EXPORT_SYMBOL_GPL(drm_gpusvm_free_pages); * This function removes the specified GPU SVM range and also removes the parent * GPU SVM notifier if no more ranges remain in the notifier. The caller must * hold a lock to protect range and notifier removal. + * + * This function does not unmap or free the drm_gpusvm_pages, the driver owns + * that lifecycle. The caller must DMA unmap the range's pages before calling + * this function, so a range is never removed from the MMU interval tree while + * still DMA mapped. Typically the driver calls drm_gpusvm_unmap_pages() first. + * And the range_free callback's drm_gpusvm_free_pages() is a final fallback safe + * net. */ void drm_gpusvm_range_remove(struct drm_gpusvm *gpusvm, struct drm_gpusvm_range *range) { - unsigned long npages = npages_in_range(drm_gpusvm_range_start(range), - drm_gpusvm_range_end(range)); struct drm_gpusvm_notifier *notifier; drm_gpusvm_driver_lock_held(gpusvm); @@ -1245,8 +1250,6 @@ void drm_gpusvm_range_remove(struct drm_gpusvm *gpusvm, return; drm_gpusvm_notifier_lock(gpusvm); - __drm_gpusvm_unmap_pages(gpusvm, &range->pages, npages); - __drm_gpusvm_free_pages(gpusvm, &range->pages); __drm_gpusvm_range_remove(notifier, range); drm_gpusvm_notifier_unlock(gpusvm); @@ -1325,13 +1328,14 @@ EXPORT_SYMBOL_GPL(drm_gpusvm_range_put); * * Return: True if GPU SVM range has valid pages, False otherwise */ -static bool drm_gpusvm_pages_valid(struct drm_gpusvm *gpusvm, - struct drm_gpusvm_pages *svm_pages) +bool drm_gpusvm_pages_valid(struct drm_gpusvm *gpusvm, + struct drm_gpusvm_pages *svm_pages) { lockdep_assert_held(&gpusvm->notifier_lock); return svm_pages->flags.has_devmem_pages || svm_pages->flags.has_dma_mapping; } +EXPORT_SYMBOL_GPL(drm_gpusvm_pages_valid); /** * drm_gpusvm_range_pages_valid() - GPU SVM range pages valid diff --git a/drivers/gpu/drm/xe/xe_pt.c b/drivers/gpu/drm/xe/xe_pt.c index 5fdad444009f..c48ddc888cb0 100644 --- a/drivers/gpu/drm/xe/xe_pt.c +++ b/drivers/gpu/drm/xe/xe_pt.c @@ -760,7 +760,7 @@ xe_pt_stage_bind(struct xe_tile *tile, struct xe_vma *vma, return -EAGAIN; } if (xe_svm_range_has_dma_mapping(range)) { - xe_res_first_dma(range->base.pages.dma_addr, 0, + xe_res_first_dma(range->pages.dma_addr, 0, xe_svm_range_size(range), &curs); xe_svm_range_debug(range, "BIND PREPARE - MIXED"); diff --git a/drivers/gpu/drm/xe/xe_svm.c b/drivers/gpu/drm/xe/xe_svm.c index 28a5d95945c3..8b48a942f3e6 100644 --- a/drivers/gpu/drm/xe/xe_svm.c +++ b/drivers/gpu/drm/xe/xe_svm.c @@ -66,7 +66,7 @@ static bool xe_svm_range_in_vram(struct xe_svm_range *range) struct drm_gpusvm_pages_flags flags = { /* Pairs with WRITE_ONCE in drm_gpusvm.c */ - .__flags = READ_ONCE(range->base.pages.flags.__flags), + .__flags = READ_ONCE(range->pages.flags.__flags), }; return flags.has_devmem_pages; @@ -96,7 +96,7 @@ static struct xe_vm *range_to_vm(struct drm_gpusvm_range *r) (r__)->base.gpusvm, \ xe_svm_range_in_vram((r__)) ? 1 : 0, \ xe_svm_range_has_vram_binding((r__)) ? 1 : 0, \ - (r__)->base.pages.notifier_seq, \ + (r__)->pages.notifier_seq, \ xe_svm_range_start((r__)), xe_svm_range_end((r__)), \ xe_svm_range_size((r__))) @@ -115,6 +115,7 @@ xe_svm_range_alloc(struct drm_gpusvm *gpusvm) return NULL; INIT_LIST_HEAD(&range->garbage_collector_link); + drm_gpusvm_init_pages(&range->pages, &gpusvm_to_vm(gpusvm)->xe->drm); xe_vm_get(gpusvm_to_vm(gpusvm)); return &range->base; @@ -122,8 +123,10 @@ xe_svm_range_alloc(struct drm_gpusvm *gpusvm) static void xe_svm_range_free(struct drm_gpusvm_range *range) { + drm_gpusvm_free_pages(range->gpusvm, &(to_xe_range(range)->pages), + drm_gpusvm_range_size(range) >> PAGE_SHIFT); xe_vm_put(range_to_vm(range)); - kfree(range); + kfree(to_xe_range(range)); } static void @@ -134,7 +137,7 @@ xe_svm_garbage_collector_add_range(struct xe_vm *vm, struct xe_svm_range *range, range_debug(range, "GARBAGE COLLECTOR ADD"); - drm_gpusvm_range_set_unmapped(&range->base, &range->base.pages, 1, + drm_gpusvm_range_set_unmapped(&range->base, &range->pages, 1, mmu_range); spin_lock(&vm->svm.garbage_collector.lock); @@ -209,7 +212,8 @@ xe_svm_range_notifier_event_end(struct xe_vm *vm, struct drm_gpusvm_range *r, xe_svm_assert_in_notifier(vm); - drm_gpusvm_range_unmap_pages(&vm->svm.gpusvm, r, &ctx); + drm_gpusvm_unmap_pages(&vm->svm.gpusvm, &(to_xe_range(r)->pages), + drm_gpusvm_range_size(r) >> PAGE_SHIFT, &ctx); if (!xe_vm_is_closed(vm) && mmu_range->event == MMU_NOTIFY_UNMAP) xe_svm_garbage_collector_add_range(vm, to_xe_range(r), mmu_range); @@ -300,6 +304,7 @@ range_notifier_event_end: static int __xe_svm_garbage_collector(struct xe_vm *vm, struct xe_svm_range *range) { + struct drm_gpusvm_ctx ctx = { .in_notifier = false, }; struct dma_fence *fence; range_debug(range, "GARBAGE COLLECTOR"); @@ -311,6 +316,10 @@ static int __xe_svm_garbage_collector(struct xe_vm *vm, return PTR_ERR(fence); dma_fence_put(fence); + drm_gpusvm_unmap_pages(&vm->svm.gpusvm, &range->pages, + drm_gpusvm_range_size(&range->base) >> PAGE_SHIFT, + &ctx); + drm_gpusvm_range_remove(&vm->svm.gpusvm, &range->base); return 0; @@ -945,15 +954,28 @@ void xe_svm_close(struct xe_vm *vm) */ void xe_svm_fini(struct xe_vm *vm) { + struct drm_gpusvm_notifier *notifier, *next; + struct drm_gpusvm_ctx ctx = { .in_notifier = false, }; + xe_assert(vm->xe, xe_vm_is_closed(vm)); + drm_gpusvm_for_each_notifier_safe(notifier, next, &vm->svm.gpusvm, 0, LONG_MAX) { + struct drm_gpusvm_range *range, *__next; + + drm_gpusvm_for_each_range_safe(range, __next, notifier, 0, LONG_MAX) + drm_gpusvm_unmap_pages(&vm->svm.gpusvm, + &(to_xe_range(range)->pages), + drm_gpusvm_range_size(range) >> PAGE_SHIFT, + &ctx); + } + drm_gpusvm_fini(&vm->svm.gpusvm); } static bool xe_svm_range_has_pagemap_locked(const struct xe_svm_range *range, const struct drm_pagemap *dpagemap) { - return range->base.pages.dpagemap == dpagemap; + return range->pages.dpagemap == dpagemap; } static bool xe_svm_range_has_pagemap(struct xe_svm_range *range, @@ -1018,7 +1040,7 @@ bool xe_svm_range_validate(struct xe_vm *vm, if (dpagemap) ret = ret && xe_svm_range_has_pagemap_locked(range, dpagemap); else - ret = ret && !range->base.pages.dpagemap; + ret = ret && !range->pages.dpagemap; xe_svm_notifier_unlock(vm); @@ -1513,7 +1535,11 @@ int xe_svm_range_get_pages(struct xe_vm *vm, struct xe_svm_range *range, { int err = 0; - err = drm_gpusvm_range_get_pages(&vm->svm.gpusvm, &range->base, ctx); + err = drm_gpusvm_get_pages(&vm->svm.gpusvm, &range->pages, + vm->svm.gpusvm.mm, + &range->base.notifier->notifier, + drm_gpusvm_range_start(&range->base), + drm_gpusvm_range_end(&range->base), ctx); if (err == -EOPNOTSUPP) { range_debug(range, "PAGE FAULT - EVICT PAGES"); drm_gpusvm_range_evict(&vm->svm.gpusvm, &range->base); diff --git a/drivers/gpu/drm/xe/xe_svm.h b/drivers/gpu/drm/xe/xe_svm.h index 3ca46a6f98c7..c8f4a7ba0f4f 100644 --- a/drivers/gpu/drm/xe/xe_svm.h +++ b/drivers/gpu/drm/xe/xe_svm.h @@ -31,6 +31,8 @@ struct xe_vram_region; struct xe_svm_range { /** @base: base drm_gpusvm_range */ struct drm_gpusvm_range base; + /** @pages: Page/DMA mapping state for this range (single drm_device). */ + struct drm_gpusvm_pages pages; /** * @garbage_collector_link: Link into VM's garbage collect SVM range * list. Protected by VM's garbage collect lock. @@ -74,7 +76,7 @@ struct xe_pagemap { */ static inline bool xe_svm_range_pages_valid(struct xe_svm_range *range) { - return drm_gpusvm_range_pages_valid(range->base.gpusvm, &range->base); + return drm_gpusvm_pages_valid(range->base.gpusvm, &range->pages); } int xe_devm_add(struct xe_tile *tile, struct xe_vram_region *vr); @@ -132,7 +134,7 @@ void *xe_svm_private_page_owner(struct xe_vm *vm, bool force_smem); static inline bool xe_svm_range_has_dma_mapping(struct xe_svm_range *range) { lockdep_assert_held(&range->base.gpusvm->notifier_lock); - return range->base.pages.flags.has_dma_mapping; + return range->pages.flags.has_dma_mapping; } /** @@ -210,10 +212,10 @@ struct xe_vram_region; struct xe_svm_range { struct { struct interval_tree_node itree; - struct { - const struct drm_pagemap_addr *dma_addr; - } pages; } base; + struct { + const struct drm_pagemap_addr *dma_addr; + } pages; u32 tile_present; u32 tile_invalidated; }; diff --git a/drivers/gpu/drm/xe/xe_userptr.c b/drivers/gpu/drm/xe/xe_userptr.c index d409c82104f4..8b2d461ea0b2 100644 --- a/drivers/gpu/drm/xe/xe_userptr.c +++ b/drivers/gpu/drm/xe/xe_userptr.c @@ -396,15 +396,14 @@ int xe_userptr_setup(struct xe_userptr_vma *uvma, unsigned long start, INIT_LIST_HEAD(&userptr->invalidate_link); INIT_LIST_HEAD(&userptr->repin_link); + drm_gpusvm_init_pages(&userptr->pages, &vm->xe->drm); + err = mmu_interval_notifier_insert(&userptr->notifier, current->mm, start, range, &vma_userptr_notifier_ops); if (err) return err; - userptr->pages.notifier_seq = LONG_MAX; - userptr->pages.drm = &vm->xe->drm; - return 0; } diff --git a/include/drm/drm_gpusvm.h b/include/drm/drm_gpusvm.h index 842353afb27b..3f38283111cc 100644 --- a/include/drm/drm_gpusvm.h +++ b/include/drm/drm_gpusvm.h @@ -310,6 +310,9 @@ void drm_gpusvm_range_put(struct drm_gpusvm_range *range); bool drm_gpusvm_range_pages_valid(struct drm_gpusvm *gpusvm, struct drm_gpusvm_range *range); +bool drm_gpusvm_pages_valid(struct drm_gpusvm *gpusvm, + struct drm_gpusvm_pages *svm_pages); + int drm_gpusvm_range_get_pages(struct drm_gpusvm *gpusvm, struct drm_gpusvm_range *range, const struct drm_gpusvm_ctx *ctx); @@ -350,6 +353,23 @@ void drm_gpusvm_free_pages(struct drm_gpusvm *gpusvm, struct drm_gpusvm_pages *svm_pages, unsigned long npages); +/** + * drm_gpusvm_init_pages() - Initialize a freshly allocated drm_gpusvm_pages + * @svm_pages: Pointer to the drm_gpusvm_pages to initialize. + * @drm: The DRM device that will own DMA mappings for this pages object. + * + * Drivers that embed one or more drm_gpusvm_pages in their own range + * structure must call this once on each pages instance after allocation, + * before the first drm_gpusvm_get_pages() / unmap / free. + */ +static inline void drm_gpusvm_init_pages(struct drm_gpusvm_pages *svm_pages, + struct drm_device *drm) +{ + memset(svm_pages, 0, sizeof(*svm_pages)); + svm_pages->drm = drm; + svm_pages->notifier_seq = LONG_MAX; +} + /** * enum drm_gpusvm_scan_result - Scan result from the drm_gpusvm_scan_mm() function. * @DRM_GPUSVM_SCAN_UNPOPULATED: At least one page was not present or inaccessible. From e19d32c2b32dbb3b15e761cfd9dd35c38ec2c911 Mon Sep 17 00:00:00 2001 From: Honglei Huang Date: Tue, 30 Jun 2026 18:21:26 +0800 Subject: [PATCH 059/577] drm/gpusvm: move struct drm_gpusvm_pages out of struct drm_gpusvm_range Since the pages the physical pages and MM VA range has been abstractly separated. Unbinding a single form of physical page from the MM VA range, brings flexibility to the drm gpu SVM framework, transfer the way of management of MM and device physical pages to the driver layer. framework's range embedded pages object and its range level wrappers have no users left. Remove the following: - Drop pages in drm_gpusvm_range. - Drop drm_gpusvm_range_pages_valid(), drm_gpusvm_range_get_pages() and drm_gpusvm_range_unmap_pages(); drivers should use the drm_gpusvm_pages helpers (drm_gpusvm_pages_valid, drm_gpusvm_get_pages, drm_gpusvm_unmap_pages) directly on a pages object they own. - Drop the notifier_seq seeding in drm_gpusvm_range_alloc(); drivers initialise notifier_seq on their own pages object. Update the DOC: overview to match the new model: document struct drm_gpusvm_pages and the driver owned 1:1 / N:1 layouts, and rewrite the usage examples to operate on a driver embedded pages object by the drm_gpusvm_pages helpers instead of the removed range level wrappers. Suggested-by: Matthew Brost Reviewed-by: Matthew Brost Signed-off-by: Honglei Huang Signed-off-by: Matthew Brost Link: https://patch.msgid.link/20260630102127.392396-5-honghuan@amd.com --- drivers/gpu/drm/drm_gpusvm.c | 169 +++++++++++++++++++---------------- include/drm/drm_gpusvm.h | 13 --- 2 files changed, 90 insertions(+), 92 deletions(-) diff --git a/drivers/gpu/drm/drm_gpusvm.c b/drivers/gpu/drm/drm_gpusvm.c index b0e9a2db1080..e0fd0b2fcc5e 100644 --- a/drivers/gpu/drm/drm_gpusvm.c +++ b/drivers/gpu/drm/drm_gpusvm.c @@ -48,6 +48,47 @@ * event. As mentioned above, ranges are tracked in a notifier's Red-Black * tree. * + * - Pages: + * struct drm_gpusvm_pages holds the DMA mapping state for a range of + * CPU virtual addresses: the DMA mapped device addresses, + * the device private pagemap, the IOVA state, the per mapping + * notifier sequence number, and the drm_device that owns the DMA + * mappings. + * A driver embeds one or more struct drm_gpusvm_pages alongside its + * struct drm_gpusvm_range, choosing one of two layouts: + * + * 1:1 - one drm_gpusvm_pages per range (one drm_device). Simplest + * layout; to mirror a VA range on several devices a driver uses a + * separate range (and notifier) per device, so the HMM fault is taken + * once per device. + * + * N:1 - one drm_gpusvm_pages per drm_device, all sharing one range and + * notifier; only the per-device DMA mapping differs. The instances must + * sit in contiguous memory so a single drm_gpusvm_range_set_unmapped() + * can mark them all. A driver can keep one instance inline for the single + * device case and switch to a heap array only when more devices join, + * e.g.: + * + * .. code-block:: c + * + * struct driver_range { + * struct drm_gpusvm_range base; + * unsigned int num_pages; // 1: inline_pages, >1: pages[] + * union { + * struct drm_gpusvm_pages inline_pages; + * struct drm_gpusvm_pages *pages; + * }; + * }; + * + * In the N:1 case the driver allocates the pages array with a zeroing + * allocator (e.g. kcalloc(num_pages, ...)), initialises each entry with + * drm_gpusvm_init_pages(), and frees each entry with + * drm_gpusvm_free_pages() plus the array itself, from its range free + * callback. Each drm_gpusvm_pages is mapped independently by their own + * drm_device. + * Each drm_gpusvm_pages must be zero-initialised and initialised with + * drm_gpusvm_init_pages(), called once per entry. + * * - Operations: * Define the interface for driver-specific GPU SVM operations such as * range allocation, notifier allocation, and invalidations. @@ -92,7 +133,7 @@ * range RB tree and list, as well as the range's DMA mappings and sequence * number. GPU SVM manages all necessary locking and unlocking operations, * except for the recheck range's pages being valid - * (drm_gpusvm_range_pages_valid) when the driver is committing GPU bindings. + * (drm_gpusvm_pages_valid) when the driver is committing GPU bindings. * This lock corresponds to the ``driver->update`` lock mentioned in * Documentation/mm/hmm.rst. Future revisions may transition from a GPU SVM * global lock to a per-notifier lock if finer-grained locking is deemed @@ -140,15 +181,20 @@ * * .. code-block:: c * - * int driver_bind_range(struct drm_gpusvm *gpusvm, struct drm_gpusvm_range *range) + * struct driver_range { + * struct drm_gpusvm_range base; + * struct drm_gpusvm_pages pages; + * }; + * + * int driver_bind_range(struct drm_gpusvm *gpusvm, struct driver_range *drange) * { * int err = 0; * - * driver_alloc_and_setup_memory_for_bind(gpusvm, range); + * driver_alloc_and_setup_memory_for_bind(gpusvm, drange); * * drm_gpusvm_notifier_lock(gpusvm); - * if (drm_gpusvm_range_pages_valid(range)) - * driver_commit_bind(gpusvm, range); + * if (drm_gpusvm_pages_valid(gpusvm, &drange->pages)) + * driver_commit_bind(gpusvm, drange); * else * err = -EAGAIN; * drm_gpusvm_notifier_unlock(gpusvm); @@ -160,6 +206,8 @@ * unsigned long gpuva_start, unsigned long gpuva_end) * { * struct drm_gpusvm_ctx ctx = {}; + * struct driver_range *drange; + * struct drm_gpusvm_range *range; * int err; * * driver_svm_lock(); @@ -174,6 +222,7 @@ * err = PTR_ERR(range); * goto unlock; * } + * drange = container_of(range, struct driver_range, base); * * if (driver_migration_policy(range)) { * err = drm_pagemap_populate_mm(driver_choose_drm_pagemap(), @@ -183,7 +232,10 @@ * goto retry; * } * - * err = drm_gpusvm_range_get_pages(gpusvm, range, &ctx); + * err = drm_gpusvm_get_pages(gpusvm, &drange->pages, + * gpusvm->mm, &range->notifier->notifier, + * drm_gpusvm_range_start(range), + * drm_gpusvm_range_end(range), &ctx); * if (err == -EOPNOTSUPP || err == -EFAULT || err == -EPERM) { // CPU mappings changed * if (err == -EOPNOTSUPP) * drm_gpusvm_range_evict(gpusvm, range); @@ -192,7 +244,7 @@ * goto unlock; * } * - * err = driver_bind_range(gpusvm, range); + * err = driver_bind_range(gpusvm, drange); * if (err == -EAGAIN) // CPU mappings changed * goto retry * @@ -205,6 +257,21 @@ * * .. code-block:: c * + * // The driver owns the drm_gpusvm_pages lifecycle. ops->range_free is + * // the final fallback: drm_gpusvm_free_pages() unmaps any + * // lingering DMA mapping and a no-op if already unmapped and frees the + * // dma_addr array. The normal flow is to DMA unmap before + * // drm_gpusvm_range_remove() (before the range leaves the tree). + * void driver_range_free(struct drm_gpusvm_range *range) + * { + * struct driver_range *drange = + * container_of(range, struct driver_range, base); + * + * drm_gpusvm_free_pages(range->gpusvm, &drange->pages, + * drm_gpusvm_range_size(range) >> PAGE_SHIFT); + * kfree(drange); + * } + * * void __driver_garbage_collector(struct drm_gpusvm *gpusvm, * struct drm_gpusvm_range *range) * { @@ -215,6 +282,14 @@ * drm_gpusvm_range_evict(gpusvm, range); * * driver_unbind_range(range); + * // The pages must be DMA unmapped before drm_gpusvm_range_remove() + * // , so a range is never off the MMU interval tree while still DMA + * // mapped as the original drmsvm design flow. Otherwise a concurrent CPU + * // munmap's notifier could miss this range and free pages still mapped + * // for device DMA. This is the normal unmap point. + * drm_gpusvm_unmap_pages(gpusvm, &drange->pages, + * drm_gpusvm_range_size(range) >> PAGE_SHIFT, + * &(struct drm_gpusvm_ctx){ .in_notifier = false }); * drm_gpusvm_range_remove(gpusvm, range); * } * @@ -236,17 +311,22 @@ * { * struct drm_gpusvm_ctx ctx = { .in_notifier = true, }; * struct drm_gpusvm_range *range = NULL; + * struct driver_range *drange; * * driver_invalidate_device_pages(gpusvm, mmu_range->start, mmu_range->end); * * drm_gpusvm_for_each_range(range, notifier, mmu_range->start, * mmu_range->end) { - * drm_gpusvm_range_unmap_pages(gpusvm, range, &ctx); + * drange = container_of(range, struct driver_range, base); + * + * drm_gpusvm_unmap_pages(gpusvm, &drange->pages, + * drm_gpusvm_range_size(range) >> PAGE_SHIFT, + * &ctx); * * if (mmu_range->event != MMU_NOTIFY_UNMAP) * continue; * - * drm_gpusvm_range_set_unmapped(range, mmu_range); + * drm_gpusvm_range_set_unmapped(range, &drange->pages, 1, mmu_range); * driver_garbage_collector_add(gpusvm, range); * } * } @@ -640,8 +720,6 @@ drm_gpusvm_range_alloc(struct drm_gpusvm *gpusvm, range->itree.start = ALIGN_DOWN(fault_addr, chunk_size); range->itree.last = ALIGN(fault_addr + 1, chunk_size) - 1; INIT_LIST_HEAD(&range->entry); - range->pages.notifier_seq = LONG_MAX; - range->pages.drm = gpusvm->drm; range->flags.migrate_devmem = migrate_devmem ? 1 : 0; return range; @@ -930,7 +1008,7 @@ retry: * mallocs 16k but the CPU VMA is ~128k which results in 64k SVM * ranges. When migrating the SVM ranges, some processes fail in * drm_pagemap_migrate_to_devmem with 'migrate.cpages != npages' - * and then upon drm_gpusvm_range_get_pages device pages from + * and then upon drm_gpusvm_get_pages device pages from * other processes are collected + faulted in which creates all * sorts of problems. Unsure exactly how this happening, also * problem goes away if 'xe_exec_system_allocator --r @@ -1337,27 +1415,6 @@ bool drm_gpusvm_pages_valid(struct drm_gpusvm *gpusvm, } EXPORT_SYMBOL_GPL(drm_gpusvm_pages_valid); -/** - * drm_gpusvm_range_pages_valid() - GPU SVM range pages valid - * @gpusvm: Pointer to the GPU SVM structure - * @range: Pointer to the GPU SVM range structure - * - * This function determines if a GPU SVM range pages are valid. Expected be - * called holding gpusvm->notifier_lock and as the last step before committing a - * GPU binding. This is akin to a notifier seqno check in the HMM documentation - * but due to wider notifiers (i.e., notifiers which span multiple ranges) this - * function is required for finer grained checking (i.e., per range) if pages - * are valid. - * - * Return: True if GPU SVM range has valid pages, False otherwise - */ -bool drm_gpusvm_range_pages_valid(struct drm_gpusvm *gpusvm, - struct drm_gpusvm_range *range) -{ - return drm_gpusvm_pages_valid(gpusvm, &range->pages); -} -EXPORT_SYMBOL_GPL(drm_gpusvm_range_pages_valid); - /** * drm_gpusvm_pages_valid_unlocked() - GPU SVM pages valid unlocked * @gpusvm: Pointer to the GPU SVM structure @@ -1638,29 +1695,6 @@ err_free: } EXPORT_SYMBOL_GPL(drm_gpusvm_get_pages); -/** - * drm_gpusvm_range_get_pages() - Get pages for a GPU SVM range - * @gpusvm: Pointer to the GPU SVM structure - * @range: Pointer to the GPU SVM range structure - * @ctx: GPU SVM context - * - * This function gets pages for a GPU SVM range and ensures they are mapped for - * DMA access. - * - * Return: 0 on success, negative error code on failure. - */ -int drm_gpusvm_range_get_pages(struct drm_gpusvm *gpusvm, - struct drm_gpusvm_range *range, - const struct drm_gpusvm_ctx *ctx) -{ - return drm_gpusvm_get_pages(gpusvm, &range->pages, - gpusvm->mm, - &range->notifier->notifier, - drm_gpusvm_range_start(range), - drm_gpusvm_range_end(range), ctx); -} -EXPORT_SYMBOL_GPL(drm_gpusvm_range_get_pages); - /** * drm_gpusvm_unmap_pages() - Unmap GPU svm pages * @gpusvm: Pointer to the GPU SVM structure @@ -1691,29 +1725,6 @@ void drm_gpusvm_unmap_pages(struct drm_gpusvm *gpusvm, } EXPORT_SYMBOL_GPL(drm_gpusvm_unmap_pages); -/** - * drm_gpusvm_range_unmap_pages() - Unmap pages associated with a GPU SVM range - * @gpusvm: Pointer to the GPU SVM structure - * @range: Pointer to the GPU SVM range structure - * @ctx: GPU SVM context - * - * This function unmaps pages associated with a GPU SVM range. If @in_notifier - * is set, it is assumed that gpusvm->notifier_lock is held in write mode; if it - * is clear, it acquires gpusvm->notifier_lock in read mode. Must be called on - * each GPU SVM range attached to notifier in gpusvm->ops->invalidate for IOMMU - * security model. - */ -void drm_gpusvm_range_unmap_pages(struct drm_gpusvm *gpusvm, - struct drm_gpusvm_range *range, - const struct drm_gpusvm_ctx *ctx) -{ - unsigned long npages = npages_in_range(drm_gpusvm_range_start(range), - drm_gpusvm_range_end(range)); - - return drm_gpusvm_unmap_pages(gpusvm, &range->pages, npages, ctx); -} -EXPORT_SYMBOL_GPL(drm_gpusvm_range_unmap_pages); - /** * drm_gpusvm_range_evict() - Evict GPU SVM range * @gpusvm: Pointer to the GPU SVM structure diff --git a/include/drm/drm_gpusvm.h b/include/drm/drm_gpusvm.h index 3f38283111cc..2862104aa1b2 100644 --- a/include/drm/drm_gpusvm.h +++ b/include/drm/drm_gpusvm.h @@ -177,7 +177,6 @@ struct drm_gpusvm_range_flags { * @refcount: Reference count for the range * @itree: Interval tree node for the range (inserted in GPU SVM notifier) * @entry: List entry to fast interval tree traversal - * @pages: The pages for this range. * @flags: Flags for range see &struct drm_gpusvm_range_flags * * This structure represents a GPU SVM range used for tracking memory ranges @@ -189,7 +188,6 @@ struct drm_gpusvm_range { struct kref refcount; struct interval_tree_node itree; struct list_head entry; - struct drm_gpusvm_pages pages; struct drm_gpusvm_range_flags flags; }; @@ -307,20 +305,9 @@ drm_gpusvm_range_get(struct drm_gpusvm_range *range); void drm_gpusvm_range_put(struct drm_gpusvm_range *range); -bool drm_gpusvm_range_pages_valid(struct drm_gpusvm *gpusvm, - struct drm_gpusvm_range *range); - bool drm_gpusvm_pages_valid(struct drm_gpusvm *gpusvm, struct drm_gpusvm_pages *svm_pages); -int drm_gpusvm_range_get_pages(struct drm_gpusvm *gpusvm, - struct drm_gpusvm_range *range, - const struct drm_gpusvm_ctx *ctx); - -void drm_gpusvm_range_unmap_pages(struct drm_gpusvm *gpusvm, - struct drm_gpusvm_range *range, - const struct drm_gpusvm_ctx *ctx); - bool drm_gpusvm_has_mapping(struct drm_gpusvm *gpusvm, unsigned long start, unsigned long end); From 808db118eb2b93727f4a8f300480e7c7ff4046fe Mon Sep 17 00:00:00 2001 From: Honglei Huang Date: Tue, 30 Jun 2026 18:21:27 +0800 Subject: [PATCH 060/577] drm/gpusvm: let the drm_gpusvm core context purely MM level The core mechanism of drm_gpusvm is HMM, which is fundamentally an MM side subsystem. A drm_device, enters the picture on the device side at DMA mapping / GPU bind. So move struct drm_device from struct drm_gpusvm in drm_gpusvm. Let drm_gpusvm keep its core neutral and leave device side decisions to the driver. Make drm_gpusvm a pure MM level object. - Move the drm_device from struct drm_gpusvm. drm_device now stored in drm_gpusvm_pages. - Drop the drm parameter from drm_gpusvm_init() - Update the xe call sites in xe_svm_init() and other callers. drm_device does not disappear from the framework, it is relocated onto each drm_gpusvm_pages where DMA actually happens. Suggested-by: Matthew Brost Reviewed-by: Matthew Brost Signed-off-by: Honglei Huang Signed-off-by: Matthew Brost Link: https://patch.msgid.link/20260630102127.392396-6-honghuan@amd.com --- drivers/gpu/drm/drm_gpusvm.c | 8 ++++---- drivers/gpu/drm/xe/xe_svm.c | 4 ++-- drivers/gpu/drm/xe/xe_svm.h | 2 +- include/drm/drm_gpusvm.h | 4 +--- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/drm_gpusvm.c b/drivers/gpu/drm/drm_gpusvm.c index e0fd0b2fcc5e..fcfe635bc195 100644 --- a/drivers/gpu/drm/drm_gpusvm.c +++ b/drivers/gpu/drm/drm_gpusvm.c @@ -439,7 +439,6 @@ static const struct mmu_interval_notifier_ops drm_gpusvm_notifier_ops = { * drm_gpusvm_init() - Initialize the GPU SVM. * @gpusvm: Pointer to the GPU SVM structure. * @name: Name of the GPU SVM. - * @drm: Pointer to the DRM device structure. * @mm: Pointer to the mm_struct for the address space. * @mm_start: Start address of GPU SVM. * @mm_range: Range of the GPU SVM. @@ -453,7 +452,9 @@ static const struct mmu_interval_notifier_ops drm_gpusvm_notifier_ops = { * This function initializes the GPU SVM. * * Note: If only using the simple drm_gpusvm_pages API (get/unmap/free), - * then only @gpusvm, @name, and @drm are expected. However, the same base + * then only @gpusvm and @name are expected. The @drm drm_device for dma + * mappings is bound per-pages via drm_gpusvm_init_pages() before the first + * drm_gpusvm_get_pages() call. However, the same base * @gpusvm can also be used with both modes together in which case the full * setup is needed, where the core drm_gpusvm_pages API will simply never use * the other fields. @@ -461,7 +462,7 @@ static const struct mmu_interval_notifier_ops drm_gpusvm_notifier_ops = { * Return: 0 on success, a negative error code on failure. */ int drm_gpusvm_init(struct drm_gpusvm *gpusvm, - const char *name, struct drm_device *drm, + const char *name, struct mm_struct *mm, unsigned long mm_start, unsigned long mm_range, unsigned long notifier_size, @@ -479,7 +480,6 @@ int drm_gpusvm_init(struct drm_gpusvm *gpusvm, } gpusvm->name = name; - gpusvm->drm = drm; gpusvm->mm = mm; gpusvm->mm_start = mm_start; gpusvm->mm_range = mm_range; diff --git a/drivers/gpu/drm/xe/xe_svm.c b/drivers/gpu/drm/xe/xe_svm.c index 8b48a942f3e6..b228a737cfd6 100644 --- a/drivers/gpu/drm/xe/xe_svm.c +++ b/drivers/gpu/drm/xe/xe_svm.c @@ -911,7 +911,7 @@ int xe_svm_init(struct xe_vm *vm) return err; } - err = drm_gpusvm_init(&vm->svm.gpusvm, "Xe SVM", &vm->xe->drm, + err = drm_gpusvm_init(&vm->svm.gpusvm, "Xe SVM", current->mm, 0, vm->size, xe_modparam.svm_notifier_size * SZ_1M, &gpusvm_ops, fault_chunk_sizes, @@ -925,7 +925,7 @@ int xe_svm_init(struct xe_vm *vm) } } else { err = drm_gpusvm_init(&vm->svm.gpusvm, "Xe SVM (simple)", - &vm->xe->drm, NULL, 0, 0, 0, NULL, + NULL, 0, 0, 0, NULL, NULL, 0); } diff --git a/drivers/gpu/drm/xe/xe_svm.h b/drivers/gpu/drm/xe/xe_svm.h index c8f4a7ba0f4f..a921556d3466 100644 --- a/drivers/gpu/drm/xe/xe_svm.h +++ b/drivers/gpu/drm/xe/xe_svm.h @@ -235,7 +235,7 @@ static inline int xe_svm_init(struct xe_vm *vm) { #if IS_ENABLED(CONFIG_DRM_GPUSVM) - return drm_gpusvm_init(&vm->svm.gpusvm, "Xe SVM (simple)", &vm->xe->drm, + return drm_gpusvm_init(&vm->svm.gpusvm, "Xe SVM (simple)", NULL, 0, 0, 0, NULL, NULL, 0); #else return 0; diff --git a/include/drm/drm_gpusvm.h b/include/drm/drm_gpusvm.h index 2862104aa1b2..b7d987bf76aa 100644 --- a/include/drm/drm_gpusvm.h +++ b/include/drm/drm_gpusvm.h @@ -195,7 +195,6 @@ struct drm_gpusvm_range { * struct drm_gpusvm - GPU SVM structure * * @name: Name of the GPU SVM - * @drm: Pointer to the DRM device structure * @mm: Pointer to the mm_struct for the address space * @mm_start: Start address of GPU SVM * @mm_range: Range of the GPU SVM @@ -219,7 +218,6 @@ struct drm_gpusvm_range { */ struct drm_gpusvm { const char *name; - struct drm_device *drm; struct mm_struct *mm; unsigned long mm_start; unsigned long mm_range; @@ -271,7 +269,7 @@ struct drm_gpusvm_ctx { }; int drm_gpusvm_init(struct drm_gpusvm *gpusvm, - const char *name, struct drm_device *drm, + const char *name, struct mm_struct *mm, unsigned long mm_start, unsigned long mm_range, unsigned long notifier_size, From 104c00917264c5b9571072471e3a8689cd1a2c4d Mon Sep 17 00:00:00 2001 From: Tvrtko Ursulin Date: Thu, 2 Jul 2026 15:37:38 +0100 Subject: [PATCH 061/577] drm/panthor: Remove redundant drm_sched_job_cleanup() from the .free_job callback After calling drm_sched_job_cleanup(), the free job callback releases its reference to the job, where the act of dropping the last reference will also call the drm_sched_job_cleanup() helper. We can therefore remove the redundant call from the .free_job callback. But we have to leave the "if (job->base.s_fence)" guard in job_release(), since that one not only handles the above described double cleanup, but also deals with all job cleanup paths which happen before the point the job was armed. Signed-off-by: Tvrtko Ursulin Cc: Boris Brezillon Cc: Liviu Dudau Cc: Steven Price Reviewed-by: Steven Price Signed-off-by: Tvrtko Ursulin Link: https://lore.kernel.org/r/20260702143745.79293-2-tvrtko.ursulin@igalia.com [tursulin: fixup commit text s/it's/its/] --- drivers/gpu/drm/panthor/panthor_sched.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/gpu/drm/panthor/panthor_sched.c b/drivers/gpu/drm/panthor/panthor_sched.c index 298b046c95ed..5832dccfc093 100644 --- a/drivers/gpu/drm/panthor/panthor_sched.c +++ b/drivers/gpu/drm/panthor/panthor_sched.c @@ -3446,7 +3446,6 @@ queue_timedout_job(struct drm_sched_job *sched_job) static void queue_free_job(struct drm_sched_job *sched_job) { - drm_sched_job_cleanup(sched_job); panthor_job_put(sched_job); } From c03adcddd5413e829e9cf63e8a7074a4c3d19d12 Mon Sep 17 00:00:00 2001 From: Diogo Silva Date: Tue, 7 Jul 2026 18:14:55 +0200 Subject: [PATCH 062/577] drm/qxl: remove dependency on DRM simple helpers Simple KMS helper are deprecated since they only add an intermediate layer between drivers and the atomic modesetting. This patch removes the drm_simple_encoder_init() helper usage in the qxl display driver by open coding it and using the encoder atomic helpers directly. This is a step to eventually get rid of this simple KMS helper, once all drivers that use it have been converted. Signed-off-by: Diogo Silva Reviewed-by: Thomas Zimmermann Signed-off-by: Thomas Zimmermann Link: https://patch.msgid.link/20260707-qxl-simple-v2-1-08d21bc74a41@gmail.com --- drivers/gpu/drm/qxl/qxl_display.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/qxl/qxl_display.c b/drivers/gpu/drm/qxl/qxl_display.c index a026bd35ef48..7f4178800afd 100644 --- a/drivers/gpu/drm/qxl/qxl_display.c +++ b/drivers/gpu/drm/qxl/qxl_display.c @@ -31,12 +31,12 @@ #include #include #include +#include #include #include #include #include #include -#include #include #include #include @@ -1095,6 +1095,10 @@ static const struct drm_connector_helper_funcs qxl_connector_helper_funcs = { .best_encoder = qxl_best_encoder, }; +static const struct drm_encoder_funcs qxl_encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + static enum drm_connector_status qxl_conn_detect( struct drm_connector *connector, bool force) @@ -1169,10 +1173,10 @@ static int qdev_output_init(struct drm_device *dev, int num_output) drm_connector_init(dev, &qxl_output->base, &qxl_connector_funcs, DRM_MODE_CONNECTOR_VIRTUAL); - ret = drm_simple_encoder_init(dev, &qxl_output->enc, - DRM_MODE_ENCODER_VIRTUAL); + ret = drm_encoder_init(dev, &qxl_output->enc, &qxl_encoder_funcs, + DRM_MODE_ENCODER_VIRTUAL, NULL); if (ret) { - drm_err(dev, "drm_simple_encoder_init() failed, error %d\n", + drm_err(dev, "drm_encoder_init() failed, error %d\n", ret); goto err_drm_connector_cleanup; } From a9f950adfe2147318d75e7a6eab5e814851802ac Mon Sep 17 00:00:00 2001 From: Laxman Acharya Padhya Date: Sat, 4 Jul 2026 12:51:48 +0545 Subject: [PATCH 063/577] drm/panel: samsung-s6d16d0: Power off on prepare failure If enabling tearing mode or exiting sleep mode fails after the regulator is enabled, s6d16d0_prepare() returns without asserting reset or disabling the supply. Since the DRM panel core leaves the panel unprepared, a later unprepare call skips the driver callback and the supply remains enabled. Assert reset and disable the supply before returning the DSI command error. Fixes: ac1d6d74884e ("drm/panel: Add driver for Samsung S6D16D0 panel") Assisted-by: Codex:gpt-5 Signed-off-by: Laxman Acharya Padhya Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260704070648.35249-1-acharyalaxman8848@gmail.com --- drivers/gpu/drm/panel/panel-samsung-s6d16d0.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/panel/panel-samsung-s6d16d0.c b/drivers/gpu/drm/panel/panel-samsung-s6d16d0.c index 1b14aa4efe35..54a65abf7e89 100644 --- a/drivers/gpu/drm/panel/panel-samsung-s6d16d0.c +++ b/drivers/gpu/drm/panel/panel-samsung-s6d16d0.c @@ -88,16 +88,22 @@ static int s6d16d0_prepare(struct drm_panel *panel) MIPI_DSI_DCS_TEAR_MODE_VBLANK); if (ret) { dev_err(s6->dev, "failed to enable vblank TE (%d)\n", ret); - return ret; + goto err_power_off; } /* Exit sleep mode and power on */ ret = mipi_dsi_dcs_exit_sleep_mode(dsi); if (ret) { dev_err(s6->dev, "failed to exit sleep mode (%d)\n", ret); - return ret; + goto err_power_off; } return 0; + +err_power_off: + gpiod_set_value_cansleep(s6->reset_gpio, 1); + regulator_disable(s6->supply); + + return ret; } static int s6d16d0_enable(struct drm_panel *panel) From a284476db2653ae893f46cbea408eb412db54eb0 Mon Sep 17 00:00:00 2001 From: Ben Dooks Date: Tue, 23 Jun 2026 11:46:17 +0100 Subject: [PATCH 064/577] drm/panfrost: include panfrost_drv.h for panfrost_transparent_hugepage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The panfrost_transparent_hugepage variable is declared in panfrost_drv.h but the panfrost_drv.c does not incldue this header. Fix the following sparse warning by including panfrost_drv.h : drivers/gpu/drm/panfrost/panfrost_drv.c:958:6: warning: symbol 'panfrost_transparent_hugepage' was not declared. Should it be static? Signed-off-by: Ben Dooks Reviewed-by: Steven Price Reviewed-by: Adrián Larumbe Reviewed-by: Boris Brezillon Link: https://patch.msgid.link/20260623104617.768289-1-ben.dooks@codethink.co.uk Signed-off-by: Adrián Larumbe --- drivers/gpu/drm/panfrost/panfrost_drv.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/panfrost/panfrost_drv.c b/drivers/gpu/drm/panfrost/panfrost_drv.c index 784e36d72c2b..36cc2e67a308 100644 --- a/drivers/gpu/drm/panfrost/panfrost_drv.c +++ b/drivers/gpu/drm/panfrost/panfrost_drv.c @@ -21,6 +21,7 @@ #include #include "panfrost_device.h" +#include "panfrost_drv.h" #include "panfrost_gem.h" #include "panfrost_mmu.h" #include "panfrost_job.h" From 34e27b90552acf21bd73aac395667c8aae6da480 Mon Sep 17 00:00:00 2001 From: Dave Airlie Date: Mon, 6 Jul 2026 13:05:20 +1000 Subject: [PATCH 065/577] nouveau/instmem: use iomapping interface for instmem handling This avoids constant need to ioremap when instobjs move at least on 64-bit systems. This create the io mapping on first use, because creating it at init time causes a resource mapping error, because nouveau hasn't kicked simpledrm off the hardware yet, but ioremap_wc the whole BAR causes an overlap with BOOTFB/simpledrm. I think the resource system could do better here, but it's easier to just delay creating the mapping until first use. Signed-off-by: Dave Airlie Link: https://patch.msgid.link/20260706030520.857104-1-airlied@gmail.com Signed-off-by: Danilo Krummrich --- .../nouveau/nvkm/subdev/gsp/rm/r535/fbsr.c | 1 + .../drm/nouveau/nvkm/subdev/instmem/nv50.c | 39 ++++++++++++++----- .../drm/nouveau/nvkm/subdev/instmem/priv.h | 1 + 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/fbsr.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/fbsr.c index 700cea5def35..f128330f30d7 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/fbsr.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/fbsr.c @@ -297,6 +297,7 @@ r535_fbsr = { static void * r535_instmem_dtor(struct nvkm_instmem *imem) { + nv50_instmem_dtor(imem); kfree(imem->func); return imem; } diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/instmem/nv50.c b/drivers/gpu/drm/nouveau/nvkm/subdev/instmem/nv50.c index 6fbaa1e5876d..f4489efc94a7 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/instmem/nv50.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/instmem/nv50.c @@ -30,10 +30,14 @@ #include #include +#include + struct nv50_instmem { struct nvkm_instmem base; u64 addr; + struct io_mapping iomap; + /* Mappings that can be evicted when BAR2 space has been exhausted. */ struct list_head lru; }; @@ -124,7 +128,6 @@ nv50_instobj_kmap(struct nv50_instobj *iobj, struct nvkm_vmm *vmm) struct nv50_instobj *eobj; struct nvkm_memory *memory = &iobj->base.memory; struct nvkm_subdev *subdev = &imem->base.subdev; - struct nvkm_device *device = subdev->device; struct nvkm_vma *bar = NULL, *ebar; u64 size = nvkm_memory_size(memory); void *emap; @@ -155,7 +158,7 @@ nv50_instobj_kmap(struct nv50_instobj *iobj, struct nvkm_vmm *vmm) mutex_unlock(&imem->base.mutex); if (!eobj) break; - iounmap(emap); + io_mapping_unmap(emap); nvkm_vmm_put(vmm, &ebar); } @@ -172,8 +175,7 @@ nv50_instobj_kmap(struct nv50_instobj *iobj, struct nvkm_vmm *vmm) /* Make the mapping visible to the host. */ iobj->bar = bar; - iobj->map = ioremap_wc(device->func->resource_addr(device, NVKM_BAR2_INST) + - (u32)iobj->bar->addr, size); + iobj->map = io_mapping_map_wc(&imem->iomap, (u32)iobj->bar->addr, size); if (!iobj->map) { nvkm_warn(subdev, "PRAMIN ioremap failed\n"); nvkm_vmm_put(vmm, &iobj->bar); @@ -188,6 +190,16 @@ nv50_instobj_map(struct nvkm_memory *memory, u64 offset, struct nvkm_vmm *vmm, return nvkm_memory_map(memory, offset, vmm, vma, argv, argc); } +static bool +check_io_mapping(struct nv50_instmem *imem) +{ + struct nvkm_device *device = imem->base.subdev.device; + + return io_mapping_init_wc(&imem->iomap, + device->func->resource_addr(device, NVKM_BAR2_INST), + device->func->resource_size(device, NVKM_BAR2_INST)) != NULL; +} + static void nv50_instobj_release(struct nvkm_memory *memory) { @@ -239,7 +251,7 @@ nv50_instobj_acquire(struct nvkm_memory *memory) /* Attempt to get a direct CPU mapping of the object. */ if ((vmm = nvkm_bar_bar2_vmm(imem->subdev.device))) { - if (!iobj->map) + if (!iobj->map && iobj->imem->iomap.size) nv50_instobj_kmap(iobj, vmm); map = iobj->map; } @@ -277,7 +289,12 @@ nv50_instobj_boot(struct nvkm_memory *memory, struct nvkm_vmm *vmm) iobj->lru.next = NULL; } - nv50_instobj_kmap(iobj, vmm); + /* + * boot is only called on BAR2, if we can't remap the complete + * BAR it's unlikely things are functioning well. + */ + if (check_io_mapping(iobj->imem)) + nv50_instobj_kmap(iobj, vmm); nvkm_instmem_boot(imem); mutex_unlock(&imem->mutex); } @@ -330,7 +347,7 @@ nv50_instobj_dtor(struct nvkm_memory *memory) if (map) { struct nvkm_vmm *vmm = nvkm_bar_bar2_vmm(imem->subdev.device); - iounmap(map); + io_mapping_unmap(map); if (likely(vmm)) /* Can be NULL during BAR destructor. */ nvkm_vmm_put(vmm, &bar); } @@ -406,10 +423,14 @@ nv50_instmem_fini(struct nvkm_instmem *base) nv50_instmem(base)->addr = ~0ULL; } -static void * +void * nv50_instmem_dtor(struct nvkm_instmem *base) { - return nv50_instmem(base); + struct nv50_instmem *imem = nv50_instmem(base); + + if (imem->iomap.size) + io_mapping_fini(&imem->iomap); + return imem; } static const struct nvkm_instmem_func diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/instmem/priv.h b/drivers/gpu/drm/nouveau/nvkm/subdev/instmem/priv.h index 87bbdd786eaa..b423f01f5fcd 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/instmem/priv.h +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/instmem/priv.h @@ -26,6 +26,7 @@ int nv50_instobj_new(struct nvkm_instmem *, u32 size, u32 align, bool zero, struct nvkm_memory **); int nv50_instobj_wrap(struct nvkm_instmem *, struct nvkm_memory *vram, struct nvkm_memory **bar2); +void *nv50_instmem_dtor(struct nvkm_instmem *base); void nvkm_instmem_ctor(const struct nvkm_instmem_func *, struct nvkm_device *, enum nvkm_subdev_type, int, struct nvkm_instmem *); From 5958ff1a715dd885210ce7208f1aa1fa930e8671 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Antinori?= Date: Mon, 6 Jul 2026 19:43:35 -0300 Subject: [PATCH 066/577] drm/panel: novatek-nt37801: Use mipi_dsi_*_multi() functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace mipi_dsi_* functions with their non-deprecated mipi_dsi_*_multi counterparts. This change reduces error-checking boilerplate and improves readability. Signed-off-by: Nicolás Antinori Reviewed-by: Douglas Anderson Signed-off-by: Douglas Anderson Link: https://patch.msgid.link/20260706224414.1015766-1-nico.antinori.7@gmail.com --- drivers/gpu/drm/panel/panel-novatek-nt37801.c | 106 ++++++++---------- 1 file changed, 46 insertions(+), 60 deletions(-) diff --git a/drivers/gpu/drm/panel/panel-novatek-nt37801.c b/drivers/gpu/drm/panel/panel-novatek-nt37801.c index 861e999250f9..5e46c4c6d771 100644 --- a/drivers/gpu/drm/panel/panel-novatek-nt37801.c +++ b/drivers/gpu/drm/panel/panel-novatek-nt37801.c @@ -51,61 +51,56 @@ static void novatek_nt37801_reset(struct novatek_nt37801 *ctx) mipi_dsi_dcs_write_seq_multi((dsi_ctx), NT37801_DCS_SWITCH_PAGE, \ 0x55, 0xaa, 0x52, 0x08, (page)) -static int novatek_nt37801_on(struct novatek_nt37801 *ctx) +static void novatek_nt37801_on(struct mipi_dsi_multi_context *dsi_ctx) { - struct mipi_dsi_device *dsi = ctx->dsi; - struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; + dsi_ctx->dsi->mode_flags |= MIPI_DSI_MODE_LPM; - dsi->mode_flags |= MIPI_DSI_MODE_LPM; - - novatek_nt37801_switch_page(&dsi_ctx, 0x01); - mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x01); - mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xc5, 0x0b, 0x0b, 0x0b); - mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0xaa, 0x55, 0xa5, 0x80); - mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x02); - mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf5, 0x10); - mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x1b); - mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf4, 0x55); - mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x18); - mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf8, 0x19); - mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x0f); - mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfc, 0x00); - mipi_dsi_dcs_set_column_address_multi(&dsi_ctx, 0x0000, 0x059f); - mipi_dsi_dcs_set_page_address_multi(&dsi_ctx, 0x0000, 0x0c7f); - mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x90, 0x03, 0x03); - mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x91, + novatek_nt37801_switch_page(dsi_ctx, 0x01); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x6f, 0x01); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xc5, 0x0b, 0x0b, 0x0b); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xff, 0xaa, 0x55, 0xa5, 0x80); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x6f, 0x02); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xf5, 0x10); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x6f, 0x1b); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xf4, 0x55); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x6f, 0x18); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xf8, 0x19); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x6f, 0x0f); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xfc, 0x00); + mipi_dsi_dcs_set_column_address_multi(dsi_ctx, 0x0000, 0x059f); + mipi_dsi_dcs_set_page_address_multi(dsi_ctx, 0x0000, 0x0c7f); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x90, 0x03, 0x03); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x91, 0x89, 0x28, 0x00, 0x28, 0xc2, 0x00, 0x02, 0x68, 0x04, 0x6c, 0x00, 0x0a, 0x02, 0x77, 0x01, 0xe9, 0x10, 0xf0); - mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0xaa, 0x55, 0xa5, 0x81); - mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x23); - mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfb, + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xff, 0xaa, 0x55, 0xa5, 0x81); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x6f, 0x23); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xfb, 0x00, 0x01, 0x00, 0x11, 0x33, 0x33, 0x33, 0x55, 0x57, 0xd0, 0x00, 0x00, 0x44, 0x56, 0x77, 0x78, 0x9a, 0xbc, 0xdd, 0xf0); - mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x06); - mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf3, 0xdc); - mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_SET_GAMMA_CURVE, 0x00); - mipi_dsi_dcs_set_tear_on_multi(&dsi_ctx, MIPI_DSI_DCS_TEAR_MODE_VBLANK); - mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3b, 0x00, 0x18, 0x00, 0x10); - mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_CONTROL_DISPLAY, + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x6f, 0x06); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xf3, 0xdc); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, MIPI_DCS_SET_GAMMA_CURVE, 0x00); + mipi_dsi_dcs_set_tear_on_multi(dsi_ctx, MIPI_DSI_DCS_TEAR_MODE_VBLANK); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x3b, 0x00, 0x18, 0x00, 0x10); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x20); - mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x51, + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x51, 0x07, 0xff, 0x07, 0xff, 0x0f, 0xff); - mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5a, 0x01); - mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5f, 0x00); - mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x9c, 0x01); - mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_MEMORY_START); - mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2f, 0x00); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x5a, 0x01); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x5f, 0x00); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x9c, 0x01); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, MIPI_DCS_WRITE_MEMORY_START); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x2f, 0x00); - novatek_nt37801_switch_page(&dsi_ctx, 0x01); - mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb2, 0x55, 0x01, 0xff, 0x03); - mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); - mipi_dsi_msleep(&dsi_ctx, 120); - mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); - mipi_dsi_msleep(&dsi_ctx, 20); - - return dsi_ctx.accum_err; + novatek_nt37801_switch_page(dsi_ctx, 0x01); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xb2, 0x55, 0x01, 0xff, 0x03); + mipi_dsi_dcs_exit_sleep_mode_multi(dsi_ctx); + mipi_dsi_msleep(dsi_ctx, 120); + mipi_dsi_dcs_set_display_on_multi(dsi_ctx); + mipi_dsi_msleep(dsi_ctx, 20); } static int novatek_nt37801_off(struct novatek_nt37801 *ctx) @@ -127,7 +122,8 @@ static int novatek_nt37801_off(struct novatek_nt37801 *ctx) static int novatek_nt37801_prepare(struct drm_panel *panel) { struct novatek_nt37801 *ctx = to_novatek_nt37801(panel); - struct device *dev = &ctx->dsi->dev; + struct mipi_dsi_device *dsi = ctx->dsi; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; struct drm_dsc_picture_parameter_set pps; int ret; @@ -137,22 +133,12 @@ static int novatek_nt37801_prepare(struct drm_panel *panel) return ret; novatek_nt37801_reset(ctx); - - ret = novatek_nt37801_on(ctx); - if (ret < 0) - goto err; - + novatek_nt37801_on(&dsi_ctx); drm_dsc_pps_payload_pack(&pps, &ctx->dsc); - - ret = mipi_dsi_picture_parameter_set(ctx->dsi, &pps); - if (ret < 0) { - dev_err(panel->dev, "failed to transmit PPS: %d\n", ret); - goto err; - } - - ret = mipi_dsi_compression_mode(ctx->dsi, true); - if (ret < 0) { - dev_err(dev, "failed to enable compression mode: %d\n", ret); + mipi_dsi_picture_parameter_set_multi(&dsi_ctx, &pps); + mipi_dsi_compression_mode_multi(&dsi_ctx, true); + if (dsi_ctx.accum_err) { + ret = dsi_ctx.accum_err; goto err; } From 0c59cc78241c10e5f02d92b28d811b0435e706a7 Mon Sep 17 00:00:00 2001 From: Vidya Srinivas Date: Thu, 18 Jun 2026 23:48:37 +0530 Subject: [PATCH 067/577] 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 068/577] 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 @@ nofb: * 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 069/577] 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 @@ err: } +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 070/577] 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 071/577] 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 072/577] 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 073/577] 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 074/577] 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 075/577] 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 076/577] 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 077/577] 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 078/577] 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 079/577] 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 080/577] 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 e73e34a4a4c2a9d4359202a2ed02f4930684dd01 Mon Sep 17 00:00:00 2001 From: Luca Ceresoli Date: Wed, 1 Jul 2026 19:19:38 +0200 Subject: [PATCH 081/577] drm: of: fix wording in comment Fix a comment according to English grammar. Link: https://lore.kernel.org/all/DI9ZFQUNMSBU.214AU8467OK76@bootlin.com/ Suggested-by: Laurent Pinchart Reviewed-by: Louis Chauvet Link: https://patch.msgid.link/20260701-drm-fix-graph-wording-v1-1-295605c5777b@bootlin.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/drm_of.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/drm_of.c b/drivers/gpu/drm/drm_of.c index 96eef327bf7e..1f4cff6bb4a7 100644 --- a/drivers/gpu/drm/drm_of.c +++ b/drivers/gpu/drm/drm_of.c @@ -297,7 +297,7 @@ int drm_of_find_panel_or_bridge(const struct device_node *np, /* * of_graph_get_remote_node() produces a noisy error message if port * node isn't found and the absence of the port is a legit case here, - * so at first we silently check whether graph presents in the + * so at first we silently check whether a graph is present in the * device-tree node. */ if (!of_graph_is_present(np)) From 7a8ece0e0d7006164c2ba51f2726964cc1818117 Mon Sep 17 00:00:00 2001 From: Luca Ceresoli Date: Wed, 1 Jul 2026 19:19:39 +0200 Subject: [PATCH 082/577] drm/bridge: samsung-dsim: fix wording in comment Fix a comment according to English grammar. Link: https://lore.kernel.org/all/DI9ZFQUNMSBU.214AU8467OK76@bootlin.com/ Suggested-by: Laurent Pinchart Reviewed-by: Louis Chauvet Link: https://patch.msgid.link/20260701-drm-fix-graph-wording-v1-2-295605c5777b@bootlin.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/samsung-dsim.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/bridge/samsung-dsim.c b/drivers/gpu/drm/bridge/samsung-dsim.c index 5788066d479e..25ab475309e8 100644 --- a/drivers/gpu/drm/bridge/samsung-dsim.c +++ b/drivers/gpu/drm/bridge/samsung-dsim.c @@ -1919,7 +1919,7 @@ static int samsung_dsim_host_attach(struct mipi_dsi_host *host, /* * of_graph_get_remote_node() produces a noisy error message if port * node isn't found and the absence of the port is a legit case here, - * so at first we silently check whether graph presents in the + * so at first we silently check whether a graph is present in the * device-tree node. */ if (!of_graph_is_present(np)) From e8113d658235369fb31584c58538a5d2bc6fe867 Mon Sep 17 00:00:00 2001 From: Luca Ceresoli Date: Wed, 1 Jul 2026 19:19:40 +0200 Subject: [PATCH 083/577] drm/bridge: fix wording in comment Fix a comment according to English grammar. Reviewed-by: Louis Chauvet Link: https://patch.msgid.link/20260701-drm-fix-graph-wording-v1-3-295605c5777b@bootlin.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/drm_bridge.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c index 6ead9b75ae3d..38afa3bc2b7c 100644 --- a/drivers/gpu/drm/drm_bridge.c +++ b/drivers/gpu/drm/drm_bridge.c @@ -1717,7 +1717,7 @@ struct drm_bridge *of_drm_get_bridge_by_endpoint(const struct device_node *np, /* * of_graph_get_remote_node() produces a noisy error message if port * node isn't found and the absence of the port is a legit case here, - * so at first we silently check whether graph is present in the + * so at first we silently check whether a graph is present in the * device-tree node. */ if (!of_graph_is_present(np)) From f7768b46b980ba263a39515a8d9e60ef258dc786 Mon Sep 17 00:00:00 2001 From: John Harrison Date: Mon, 13 Jul 2026 16:41:38 -0700 Subject: [PATCH 084/577] 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 12f5090aaa3cc82ce2d8ca0d7bb2b86817587ace Mon Sep 17 00:00:00 2001 From: Luca Ceresoli Date: Tue, 30 Jun 2026 17:34:04 +0200 Subject: [PATCH 085/577] drm/bridge: rename drm_for_each_bridge_in_chain_scoped() to drm_for_each_bridge_in_chain() drm_for_each_bridge_in_chain_scoped() was added in commit e46efc6a7d28 ("drm/bridge: add drm_for_each_bridge_in_chain_scoped()") to provide a safer alternative to drm_for_each_bridge_in_chain(). Following commits converted all users to the _scoped variant. Finally commit 2f08387a444c ("drm/bridge: remove drm_for_each_bridge_in_chain()") removed the old drm_for_each_bridge_in_chain() macro. It's time to rename drm_for_each_bridge_in_chain_scoped() back to the original name. Reviewed-by: Louis Chauvet Link: https://patch.msgid.link/20260630-drm-bridge-alloc-getput-for_each_bridge-2-v2-1-e0a1094cd1eb@bootlin.com Signed-off-by: Luca Ceresoli --- .clang-format | 2 +- drivers/gpu/drm/display/drm_bridge_connector.c | 4 ++-- drivers/gpu/drm/drm_atomic.c | 2 +- drivers/gpu/drm/drm_bridge.c | 2 +- drivers/gpu/drm/tests/drm_bridge_test.c | 2 +- include/drm/drm_bridge.h | 11 +++++------ 6 files changed, 11 insertions(+), 12 deletions(-) diff --git a/.clang-format b/.clang-format index 6a3de86ab27a..5ef5743b77c9 100644 --- a/.clang-format +++ b/.clang-format @@ -167,7 +167,7 @@ ForEachMacros: - 'drm_connector_for_each_possible_encoder' - 'drm_exec_for_each_locked_object' - 'drm_exec_for_each_locked_object_reverse' - - 'drm_for_each_bridge_in_chain_scoped' + - 'drm_for_each_bridge_in_chain' - 'drm_for_each_connector_iter' - 'drm_for_each_crtc' - 'drm_for_each_crtc_reverse' diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c index 92f8a2d7aab4..8b54069fa53a 100644 --- a/drivers/gpu/drm/display/drm_bridge_connector.c +++ b/drivers/gpu/drm/display/drm_bridge_connector.c @@ -147,7 +147,7 @@ static void drm_bridge_connector_hpd_notify(struct drm_connector *connector, to_drm_bridge_connector(connector); /* Notify all bridges in the pipeline of hotplug events. */ - drm_for_each_bridge_in_chain_scoped(bridge_connector->encoder, bridge) { + drm_for_each_bridge_in_chain(bridge_connector->encoder, bridge) { if (bridge->funcs->hpd_notify) bridge->funcs->hpd_notify(bridge, connector, status); } @@ -854,7 +854,7 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm, * detection are available, we don't support hotplug detection at all. */ connector_type = DRM_MODE_CONNECTOR_Unknown; - drm_for_each_bridge_in_chain_scoped(encoder, bridge) { + drm_for_each_bridge_in_chain(encoder, bridge) { if (!bridge->interlace_allowed) connector->interlace_allowed = false; if (!bridge->ycbcr_420_allowed) diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c index 735ab7badc2e..e5c8ef06caed 100644 --- a/drivers/gpu/drm/drm_atomic.c +++ b/drivers/gpu/drm/drm_atomic.c @@ -1554,7 +1554,7 @@ drm_atomic_add_encoder_bridges(struct drm_atomic_commit *state, "Adding all bridges for [encoder:%d:%s] to %p\n", encoder->base.id, encoder->name, state); - drm_for_each_bridge_in_chain_scoped(encoder, bridge) { + drm_for_each_bridge_in_chain(encoder, bridge) { /* Skip bridges that don't implement the atomic state hooks. */ if (!bridge->funcs->atomic_duplicate_state) continue; diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c index 38afa3bc2b7c..07914b91a76c 100644 --- a/drivers/gpu/drm/drm_bridge.c +++ b/drivers/gpu/drm/drm_bridge.c @@ -1817,7 +1817,7 @@ static int encoder_bridges_show(struct seq_file *m, void *data) struct drm_printer p = drm_seq_file_printer(m); unsigned int idx = 0; - drm_for_each_bridge_in_chain_scoped(encoder, bridge) + drm_for_each_bridge_in_chain(encoder, bridge) drm_bridge_debugfs_show_bridge(&p, bridge, idx++, false, true); return 0; diff --git a/drivers/gpu/drm/tests/drm_bridge_test.c b/drivers/gpu/drm/tests/drm_bridge_test.c index 10a32620e962..430d9bc67a10 100644 --- a/drivers/gpu/drm/tests/drm_bridge_test.c +++ b/drivers/gpu/drm/tests/drm_bridge_test.c @@ -1071,7 +1071,7 @@ static void drm_test_bridge_chain_verify_fmt(struct kunit *test, struct drm_bridge_state *bstate; unsigned int i = 0; - drm_for_each_bridge_in_chain_scoped(&priv->encoder, bridge) { + drm_for_each_bridge_in_chain(&priv->encoder, bridge) { KUNIT_ASSERT_LT(test, i, num_expected); bstate = drm_bridge_get_current_state(bridge); diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h index 18f3db367dc1..0fadc39dfc12 100644 --- a/include/drm/drm_bridge.h +++ b/include/drm/drm_bridge.h @@ -1474,9 +1474,9 @@ static inline struct drm_bridge *__drm_for_each_bridge_in_chain_next(struct drm_ DEFINE_FREE(__drm_for_each_bridge_in_chain_cleanup, struct drm_bridge *, if (_T) { mutex_unlock(&_T->encoder->bridge_chain_mutex); drm_bridge_put(_T); }) -/* Internal to drm_for_each_bridge_in_chain_scoped() */ +/* Internal to drm_for_each_bridge_in_chain() */ static inline struct drm_bridge * -__drm_for_each_bridge_in_chain_scoped_start(struct drm_encoder *encoder) +__drm_for_each_bridge_in_chain_start(struct drm_encoder *encoder) { mutex_lock(&encoder->bridge_chain_mutex); @@ -1489,8 +1489,7 @@ __drm_for_each_bridge_in_chain_scoped_start(struct drm_encoder *encoder) } /** - * drm_for_each_bridge_in_chain_scoped - iterate over all bridges attached - * to an encoder + * drm_for_each_bridge_in_chain - iterate over all bridges attached to an encoder * @encoder: the encoder to iterate bridges on * @bridge: a bridge pointer updated to point to the current bridge at each * iteration @@ -1500,9 +1499,9 @@ __drm_for_each_bridge_in_chain_scoped_start(struct drm_encoder *encoder) * Automatically gets/puts the bridge reference while iterating and locks * the encoder chain mutex to prevent chain modifications while iterating. */ -#define drm_for_each_bridge_in_chain_scoped(encoder, bridge) \ +#define drm_for_each_bridge_in_chain(encoder, bridge) \ for (struct drm_bridge *bridge __free(__drm_for_each_bridge_in_chain_cleanup) = \ - __drm_for_each_bridge_in_chain_scoped_start((encoder)); \ + __drm_for_each_bridge_in_chain_start((encoder)); \ bridge; \ bridge = __drm_for_each_bridge_in_chain_next(bridge)) \ From 511ee3e5f7e4e82b6a5a0f9aa16895ab9a96fd08 Mon Sep 17 00:00:00 2001 From: Henrik Grimler Date: Tue, 2 Jun 2026 20:36:48 +0200 Subject: [PATCH 086/577] drm/bridge: sii9234: fix some typos in comments and messages Fix spelling and formatting so that the code is easier to follow, and so that it is more searchable. Reviewed-by: Dmitry Baryshkov Signed-off-by: Henrik Grimler Reviewed-by: Luca Ceresoli Link: https://patch.msgid.link/20260602-exynos4-sii9234-driver-v4-1-9b2fc38e104a@grimler.se Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/sii9234.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/bridge/sii9234.c b/drivers/gpu/drm/bridge/sii9234.c index bb1bed03eb5b..930117bbba87 100644 --- a/drivers/gpu/drm/bridge/sii9234.c +++ b/drivers/gpu/drm/bridge/sii9234.c @@ -339,7 +339,7 @@ static int sii9234_cbus_reset(struct sii9234 *ctx) return sii9234_clear_error(ctx); } -/* Require to chek mhl imformation of samsung in cbus_init_register */ +/* Require to check mhl information of samsung in cbus_init_register */ static int sii9234_cbus_init(struct sii9234 *ctx) { cbus_writeb(ctx, 0x07, 0xF2); @@ -614,7 +614,7 @@ static void sii9234_cable_out(struct sii9234 *ctx) disable_irq(to_i2c_client(ctx->dev)->irq); tpi_writeb(ctx, TPI_DPD_REG, 0); - /* Turn on&off hpd festure for only QCT HDMI */ + /* Turn on&off hpd feature for only QCT HDMI */ sii9234_hw_off(ctx); ctx->state = ST_OFF; @@ -708,7 +708,7 @@ static enum sii9234_state sii9234_rsen_change(struct sii9234 *ctx) { int value; - /* Work_around code to handle wrong interrupt */ + /* Workaround code to handle wrong interrupt */ if (ctx->state != ST_RGND_1K) { dev_err(ctx->dev, "RSEN_HIGH without RGND_1K\n"); return ST_FAILURE; @@ -723,9 +723,9 @@ static enum sii9234_state sii9234_rsen_change(struct sii9234 *ctx) } dev_dbg(ctx->dev, "RSEN lost\n"); /* - * Once RSEN loss is confirmed,we need to check - * based on cable status and chip power status,whether - * it is SINK Loss(HDMI cable not connected, TV Off) + * Once RSEN loss is confirmed, we need to check + * based on cable status and chip power status, whether + * it is SINK Loss (HDMI cable not connected, TV Off) * or MHL cable disconnection * TODO: Define the below mhl_disconnection() */ @@ -820,7 +820,7 @@ static int sii9234_init_resources(struct sii9234 *ctx, int ret; if (!ctx->dev->of_node) { - dev_err(ctx->dev, "not DT device\n"); + dev_err(ctx->dev, "no DT device\n"); return -ENODEV; } From a3bbe1f4877d372da713b79764787e01306b12aa Mon Sep 17 00:00:00 2001 From: Henrik Grimler Date: Tue, 2 Jun 2026 20:36:49 +0200 Subject: [PATCH 087/577] drm/bridge: sii9234: use dev_err_probe where applicable In case of error during resource acquisition the driver should print an error message only if it is not deferred probe. Use dev_err_probe helper to handle this, which will also record defer probe reason for debugging. Reviewed-by: Dmitry Baryshkov Signed-off-by: Henrik Grimler Reviewed-by: Luca Ceresoli Link: https://patch.msgid.link/20260602-exynos4-sii9234-driver-v4-2-9b2fc38e104a@grimler.se Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/sii9234.c | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/drivers/gpu/drm/bridge/sii9234.c b/drivers/gpu/drm/bridge/sii9234.c index 930117bbba87..e43248e515b3 100644 --- a/drivers/gpu/drm/bridge/sii9234.c +++ b/drivers/gpu/drm/bridge/sii9234.c @@ -825,21 +825,17 @@ static int sii9234_init_resources(struct sii9234 *ctx, } ctx->gpio_reset = devm_gpiod_get(ctx->dev, "reset", GPIOD_OUT_LOW); - if (IS_ERR(ctx->gpio_reset)) { - dev_err(ctx->dev, "failed to get reset gpio from DT\n"); - return PTR_ERR(ctx->gpio_reset); - } + if (IS_ERR(ctx->gpio_reset)) + return dev_err_probe(ctx->dev, PTR_ERR(ctx->gpio_reset), + "failed to get reset gpio from DT\n"); ctx->supplies[0].supply = "avcc12"; ctx->supplies[1].supply = "avcc33"; ctx->supplies[2].supply = "iovcc18"; ctx->supplies[3].supply = "cvcc12"; ret = devm_regulator_bulk_get(ctx->dev, 4, ctx->supplies); - if (ret) { - if (ret != -EPROBE_DEFER) - dev_err(ctx->dev, "regulator_bulk failed\n"); - return ret; - } + if (ret) + return dev_err_probe(ctx->dev, ret, "regulator_bulk failed\n"); ctx->client[I2C_MHL] = client; @@ -911,10 +907,9 @@ static int sii9234_probe(struct i2c_client *client) sii9234_irq_thread, IRQF_TRIGGER_HIGH | IRQF_ONESHOT, "sii9234", ctx); - if (ret < 0) { - dev_err(dev, "failed to install IRQ handler\n"); - return ret; - } + if (ret < 0) + return dev_err_probe(dev, ret, + "failed to install IRQ handler\n"); ret = sii9234_init_resources(ctx, client); if (ret < 0) From 9813e158d13d51d20d9257879bf79da7abcc9107 Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Tue, 26 May 2026 15:16:59 -0300 Subject: [PATCH 088/577] drm/drm_blend: allow blend mode property without PREMULTI Some hardware only supports the COVERAGE blend mode and lacks PREMULTI support entirely. DRM currently requires that PREMULTI is present when creating a blend mode property, which prevents such drivers from being properly upstreamed. Remove this restriction and allow drivers to create a blend mode property without PREMULTI, enabling support for hardware that implements only COVERAGE blend mode. This does not introduce a regression, as no existing upstream drivers expose only COVERAGE. However, userspace that wants to support such kind of hardware in the future will have to check the supported blend modes instead of assuming PREMULTI is always supported. Signed-off-by: Leandro Ribeiro Acked-by: Pekka Paalanen Reviewed-by: Daniel Stone Link: https://patch.msgid.link/20260526181700.25310-2-leandro.ribeiro@collabora.com Signed-off-by: Daniel Stone --- drivers/gpu/drm/drm_blend.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/drm_blend.c b/drivers/gpu/drm/drm_blend.c index 2f0d1ba285be..d17ae964fb21 100644 --- a/drivers/gpu/drm/drm_blend.c +++ b/drivers/gpu/drm/drm_blend.c @@ -563,10 +563,10 @@ EXPORT_SYMBOL(drm_atomic_normalize_zpos); /** * drm_plane_create_blend_mode_property - create a new blend mode property * @plane: drm plane - * @supported_modes: bitmask of supported modes, must include - * BIT(DRM_MODE_BLEND_PREMULTI). Current DRM assumption is - * that alpha is premultiplied, and old userspace can break if - * the property defaults to anything else. + * @supported_modes: bitmask of supported modes. When + * BIT(DRM_MODE_BLEND_PREMULTI) is included, it will be used + * as the default. Otherwise, the default will fallback to one + * of the supported modes. * * This creates a new property describing the blend mode. * @@ -599,13 +599,14 @@ int drm_plane_create_blend_mode_property(struct drm_plane *plane, { DRM_MODE_BLEND_PREMULTI, "Pre-multiplied" }, { DRM_MODE_BLEND_COVERAGE, "Coverage" }, }; + unsigned int default_mode; unsigned int valid_mode_mask = BIT(DRM_MODE_BLEND_PIXEL_NONE) | BIT(DRM_MODE_BLEND_PREMULTI) | BIT(DRM_MODE_BLEND_COVERAGE); int i; if (WARN_ON((supported_modes & ~valid_mode_mask) || - ((supported_modes & BIT(DRM_MODE_BLEND_PREMULTI)) == 0))) + (supported_modes == 0))) return -EINVAL; prop = drm_property_create(dev, DRM_MODE_PROP_ENUM, @@ -630,7 +631,14 @@ int drm_plane_create_blend_mode_property(struct drm_plane *plane, } } - drm_object_attach_property(&plane->base, prop, DRM_MODE_BLEND_PREMULTI); + if (supported_modes & BIT(DRM_MODE_BLEND_PREMULTI)) + default_mode = DRM_MODE_BLEND_PREMULTI; + else if (supported_modes & BIT(DRM_MODE_BLEND_COVERAGE)) + default_mode = DRM_MODE_BLEND_COVERAGE; + else + default_mode = DRM_MODE_BLEND_PIXEL_NONE; + + drm_object_attach_property(&plane->base, prop, default_mode); plane->blend_mode_property = prop; return 0; From 860e748bddcc9291cbdd23e801640aeeba30cc44 Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Tue, 26 May 2026 15:17:00 -0300 Subject: [PATCH 089/577] drm: ensure blend mode supported if pixel format with alpha exposed Before "drm/drm_blend: allow blend mode property without PREMULTI", userspace would have to assume that only PREMULTI was supported by drivers that didn't expose the blend mode property. But now userspace shouldn't rely on that, as they can't count with drivers always supporting PREMULTI. Warn if a driver exposes pixel formats with alpha but doesn't expose the blend mode property. This way userspace doesn't have to guess. Drivers triggering this warning must be fixed. Signed-off-by: Leandro Ribeiro Acked-by: Pekka Paalanen Reviewed-by: Daniel Stone Link: https://patch.msgid.link/20260526181700.25310-3-leandro.ribeiro@collabora.com Signed-off-by: Daniel Stone --- drivers/gpu/drm/drm_mode_config.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/drivers/gpu/drm/drm_mode_config.c b/drivers/gpu/drm/drm_mode_config.c index f432f485a914..3bcc7bf0900c 100644 --- a/drivers/gpu/drm/drm_mode_config.c +++ b/drivers/gpu/drm/drm_mode_config.c @@ -857,6 +857,25 @@ static void validate_encoder_possible_crtcs(struct drm_encoder *encoder) encoder->possible_crtcs, crtc_mask); } +static void validate_blend_mode_for_alpha_formats(struct drm_plane *plane) +{ + const struct drm_format_info *fmt; + u32 i; + + /* blend mode property supported, no need to check anything */ + if (plane->blend_mode_property) + return; + + for (i = 0; i < plane->format_count; i++) { + fmt = drm_format_info(plane->format_types[i]); + if (fmt->has_alpha) { + WARN(1, "[PLANE:%d:%s] pixel format with alpha exposed but blend mode not setup", + plane->base.id, plane->name); + break; + } + } +} + void drm_mode_config_validate(struct drm_device *dev) { struct drm_encoder *encoder; @@ -915,6 +934,8 @@ void drm_mode_config_validate(struct drm_device *dev) drm_for_each_plane(plane, dev) { if (plane->type == DRM_PLANE_TYPE_PRIMARY) num_primary++; + + validate_blend_mode_for_alpha_formats(plane); } WARN(num_primary != dev->mode_config.num_crtc, From 658422c7ba2e78c8fb4d30c1d5475cc444709e74 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Thu, 30 Oct 2025 16:38:10 -0400 Subject: [PATCH 090/577] drm/amdgpu: update mmhub 4.2.0 client list Update to the proper client list for mmhub 4.2.0. v2: fix typo (Alex) Reviewed-by: Mukul Joshi Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/mmhub_v4_2_0.c | 65 ++++++++++++----------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/mmhub_v4_2_0.c b/drivers/gpu/drm/amd/amdgpu/mmhub_v4_2_0.c index 49b7f16a941f..5827c758b373 100644 --- a/drivers/gpu/drm/amd/amdgpu/mmhub_v4_2_0.c +++ b/drivers/gpu/drm/amd/amdgpu/mmhub_v4_2_0.c @@ -36,40 +36,41 @@ static const char *mmhub_client_ids_v4_2_0[][2] = { [0][0] = "VMC", - [4][0] = "DCEDMC", - [5][0] = "DCEVGA", - [6][0] = "MP0", - [7][0] = "MP1", + [2][0] = "MPNHT", + [7][0] = "MPIFOE", [8][0] = "MPIO", - [16][0] = "HDP", - [17][0] = "LSDMA", - [18][0] = "JPEG", - [19][0] = "VCNU0", - [21][0] = "VSCH", - [22][0] = "VCNU1", - [23][0] = "VCN1", - [32+20][0] = "VCN0", - [2][1] = "DBGUNBIO", - [3][1] = "DCEDWB", - [4][1] = "DCEDMC", - [5][1] = "DCEVGA", - [6][1] = "MP0", - [7][1] = "MP1", + [11][0] = "JPEG0", + [12][0] = "VCN0", + [13][0] = "VCNU0", + [14][0] = "VSCH0", + [15][0] = "LSDMA", + [32+5][0] = "MPRAS", + [32+6][0] = "MP1", + [32+7][0] = "MP0", + [32+11][0] = "JPEG1", + [32+12][0] = "VCN1", + [32+13][0] = "VCNU1", + [32+14][0] = "VSCH1", + [2][1] = "MPNHT", + [3][1] = "DBGU0", + [7][1] = "MPIFOE", [8][1] = "MPIO", - [10][1] = "DBGU0", - [11][1] = "DBGU1", - [12][1] = "DBGU2", - [13][1] = "DBGU3", - [14][1] = "XDP", - [15][1] = "OSSSYS", - [16][1] = "HDP", - [17][1] = "LSDMA", - [18][1] = "JPEG", - [19][1] = "VCNU0", - [20][1] = "VCN0", - [21][1] = "VSCH", - [22][1] = "VCNU1", - [23][1] = "VCN1", + [10][1] = "UTCL2_NHT", + [11][1] = "JPEG0", + [12][1] = "VCN0", + [13][1] = "VCNU0", + [14][1] = "VSCH0", + [15][1] = "LSDMA", + [32+3][1] = "DBGU1", + [32+4][1] = "DBGU2", + [32+5][1] = "MPRAS", + [32+6][1] = "MP1", + [32+7][1] = "MP0", + [32+8][1] = "IH", + [32+11][1] = "JPEG1", + [32+12][1] = "VCN1", + [32+13][1] = "VCNU1", + [32+14][1] = "VSCH1", }; static int mmhub_v4_2_0_get_xgmi_info(struct amdgpu_device *adev) From f8ee6447e7ec1d75d6663c817e45566dd01f440b Mon Sep 17 00:00:00 2001 From: Roman Li Date: Wed, 13 May 2026 21:49:15 -0400 Subject: [PATCH 091/577] drm/amdgpu/discovery: Fix device family for DCN42 GC 11.7.0 and 11.7.1 should map to AMDGPU_FAMILY_GC_11_5_4 for DCN42. Fixes: cf591e67c095 ("drm/amdgpu: add support for GC IP version 11.7.0") Fixes: a928d8d81ec5 ("drm/amdgpu: add support for GC IP version 11.7.1") Signed-off-by: Roman Li Acked-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c index d9edac5f321d..44e7d2e3e6df 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c @@ -3367,9 +3367,11 @@ int amdgpu_discovery_set_ip_blocks(struct amdgpu_device *adev) case IP_VERSION(11, 5, 3): case IP_VERSION(11, 5, 4): case IP_VERSION(11, 5, 6): + adev->family = AMDGPU_FAMILY_GC_11_5_0; + break; case IP_VERSION(11, 7, 0): case IP_VERSION(11, 7, 1): - adev->family = AMDGPU_FAMILY_GC_11_5_0; + adev->family = AMDGPU_FAMILY_GC_11_5_4; break; case IP_VERSION(12, 0, 0): case IP_VERSION(12, 0, 1): From b48b1cad84609ed422f8d4086205cc06d425487e Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Tue, 16 Jun 2026 09:34:09 -0600 Subject: [PATCH 092/577] drm/amd/display: Fix KUnit backlight tests for CACP [WHY] A change was added in amdgpu_dm_should_create_sysfs() to check link->panel_type instead of caps->aux_support, and removed the OLED exclusion from the ABM property attach path in amdgpu_dm_setup_backlight_device(). This broke three KUnit tests: - dm_test_should_create_sysfs_no_backlight_index and dm_test_should_create_sysfs_pwm_backlight returned false because kzalloc zeroes panel_type but PANEL_TYPE_LCD is 1. - dm_test_setup_backlight_device_oled_success crashed with a NULL pointer dereference in drm_object_attach_property since abm_level_property was NULL. [WHY] Set panel_type to PANEL_TYPE_LCD in the two sysfs tests and skip the ABM property attach path in the OLED setup test by setting amdgpu_dm_abm_level to 0. Cc: Chenyu Chen Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c index fff50c1325c6..53df4b668f2e 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c @@ -1026,6 +1026,7 @@ static void dm_test_should_create_sysfs_no_backlight_index(struct kunit *test) amdgpu_dm_set_abm_level_param(-1); setup_test_connector(test, &fixture, -1, SIGNAL_TYPE_EDP); fixture.aconnector->base.connector_type = DRM_MODE_CONNECTOR_eDP; + fixture.link->panel_type = PANEL_TYPE_LCD; KUNIT_EXPECT_TRUE(test, amdgpu_dm_should_create_sysfs(fixture.aconnector)); @@ -1063,6 +1064,7 @@ static void dm_test_should_create_sysfs_pwm_backlight(struct kunit *test) amdgpu_dm_set_abm_level_param(-1); setup_test_connector(test, &fixture, 0, SIGNAL_TYPE_EDP); fixture.aconnector->base.connector_type = DRM_MODE_CONNECTOR_eDP; + fixture.link->panel_type = PANEL_TYPE_LCD; fixture.adev->dm.backlight_caps[0].aux_support = false; KUNIT_EXPECT_TRUE(test, amdgpu_dm_should_create_sysfs(fixture.aconnector)); @@ -1147,11 +1149,13 @@ static void dm_test_setup_backlight_device_oled_success(struct kunit *test) struct dm_backlight_connector_fixture fixture = {}; struct amdgpu_display_manager *dm; int saved_backlight = amdgpu_dm_get_backlight_param(); + int saved_abm_level = amdgpu_dm_get_abm_level_param(); amdgpu_dm_set_backlight_param(-1); + /* Skip ABM property attach (requires full DRM object setup) */ + amdgpu_dm_set_abm_level_param(0); setup_test_connector(test, &fixture, -1, SIGNAL_TYPE_EDP); fixture.link->type = dc_connection_single; - /* OLED panel avoids the ABM property attach path */ fixture.link->dpcd_sink_ext_caps.bits.oled = 1; dm = &fixture.adev->dm; dm->adev = fixture.adev; @@ -1166,6 +1170,7 @@ static void dm_test_setup_backlight_device_oled_success(struct kunit *test) KUNIT_EXPECT_TRUE(test, dm->backlight_caps[0].aux_support); amdgpu_dm_set_backlight_param(saved_backlight); + amdgpu_dm_set_abm_level_param(saved_abm_level); } static struct kunit_case dm_backlight_test_cases[] = { From 5b69770e945caa1bef6c4848bb279ec09d3e9d4f Mon Sep 17 00:00:00 2001 From: Dillon Varone Date: Tue, 23 Jun 2026 15:42:34 -0400 Subject: [PATCH 093/577] drm/amd/display: Generalize DMUB memory flushes from host [WHY&HOW] Add DMUB service context to aid in platform abstraction when flushing memory from the host. Reviewed-by: Nicholas Kazlauskas Signed-off-by: Dillon Varone Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_dmub.c | 5 +- drivers/gpu/drm/amd/display/dc/dc_dmub_srv.c | 4 +- .../gpu/drm/amd/display/dc/dce/dmub_abm_lcd.c | 4 +- drivers/gpu/drm/amd/display/dmub/dmub_srv.h | 9 ++- .../gpu/drm/amd/display/dmub/src/dmub_srv.c | 55 +++++++++++-------- 5 files changed, 46 insertions(+), 31 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c index 2692d1890ba2..00ea0dbd0f3f 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c @@ -147,7 +147,7 @@ int dm_dmub_hw_init(struct amdgpu_device *adev) struct dmub_srv_hw_params hw_params; enum dmub_status status; const unsigned char *fw_inst_const, *fw_bss_data; - u32 i, fw_inst_const_size, fw_bss_data_size; + u32 fw_inst_const_size, fw_bss_data_size; bool has_hw_support; if (!dmub_srv) @@ -243,8 +243,7 @@ int dm_dmub_hw_init(struct amdgpu_device *adev) if (dmcu) hw_params.psp_version = dmcu->psp_version; - for (i = 0; i < fb_info->num_fb; ++i) - hw_params.fb[i] = &fb_info->fb[i]; + hw_params.fb_info = fb_info; /* Enable usb4 dpia in the FW APU */ if (dc->caps.is_apu && diff --git a/drivers/gpu/drm/amd/display/dc/dc_dmub_srv.c b/drivers/gpu/drm/amd/display/dc/dc_dmub_srv.c index 68ed0e16639d..80a21d7cae90 100644 --- a/drivers/gpu/drm/amd/display/dc/dc_dmub_srv.c +++ b/drivers/gpu/drm/amd/display/dc/dc_dmub_srv.c @@ -1908,6 +1908,8 @@ static void dc_dmub_srv_ib_based_fams2_update_config(struct dc *dc, config->global.features.bits.enable = enable && context->bw_ctx.bw.dcn.fams2_global_config.features.bits.enable; config->global.features.bits.enable_ppt_check = dc->debug.fams2_config.bits.enable_ppt_check; + dmub_srv_flush_buffer_mem(dc->ctx->dmub_srv->dmub, &dc->ctx->dmub_srv->dmub->ib_mem_gart); + dm_execute_dmub_cmd_list(dc->ctx, 1, &cmd, DM_DMUB_WAIT_TYPE_WAIT); } @@ -2073,7 +2075,7 @@ bool dc_dmub_srv_ips_query_residency_info(const struct dc_context *ctx, uint8_t union dmub_rb_cmd cmd; uint32_t bytes = sizeof(struct dmub_ips_residency_info); - dmub_flush_buffer_mem(&ctx->dmub_srv->dmub->scratch_mem_fb); + dmub_srv_flush_buffer_mem(ctx->dmub_srv->dmub, &ctx->dmub_srv->dmub->scratch_mem_fb); memset(&cmd, 0, sizeof(cmd)); cmd.ips_query_residency_info.header.type = DMUB_CMD__IPS; diff --git a/drivers/gpu/drm/amd/display/dc/dce/dmub_abm_lcd.c b/drivers/gpu/drm/amd/display/dc/dce/dmub_abm_lcd.c index 2f4d368bd3fd..f60e43ba6060 100644 --- a/drivers/gpu/drm/amd/display/dc/dce/dmub_abm_lcd.c +++ b/drivers/gpu/drm/amd/display/dc/dce/dmub_abm_lcd.c @@ -166,7 +166,7 @@ void dmub_abm_init_config(struct abm *abm, uint8_t panel_mask = 0x01 << inst; // TODO: Optimize by only reading back final 4 bytes - dmub_flush_buffer_mem(&dc->dmub_srv->dmub->scratch_mem_fb); + dmub_srv_flush_buffer_mem(dc->dmub_srv->dmub, &dc->dmub_srv->dmub->scratch_mem_fb); // Copy iramtable into cw7 memcpy(dc->dmub_srv->dmub->scratch_mem_fb.cpu_addr, (void *)src, bytes); @@ -227,7 +227,7 @@ bool dmub_abm_save_restore( unsigned int bytes = sizeof(struct abm_save_restore); // TODO: Optimize by only reading back final 4 bytes - dmub_flush_buffer_mem(&dc->dmub_srv->dmub->scratch_mem_fb); + dmub_srv_flush_buffer_mem(dc->dmub_srv->dmub, &dc->dmub_srv->dmub->scratch_mem_fb); // Copy iramtable into cw7 memcpy(dc->dmub_srv->dmub->scratch_mem_fb.cpu_addr, (void *)pData, bytes); diff --git a/drivers/gpu/drm/amd/display/dmub/dmub_srv.h b/drivers/gpu/drm/amd/display/dmub/dmub_srv.h index f4d05dcfef29..94d4b40ee16a 100644 --- a/drivers/gpu/drm/amd/display/dmub/dmub_srv.h +++ b/drivers/gpu/drm/amd/display/dmub/dmub_srv.h @@ -329,7 +329,7 @@ struct dmub_soc_fb_info { * @load_inst_const: true if DMUB should load inst const fw */ struct dmub_srv_hw_params { - struct dmub_fb *fb[DMUB_WINDOW_TOTAL]; + struct dmub_srv_fb_info *fb_info; struct dmub_soc_fb_info soc_fb_info; uint32_t psp_version; bool load_inst_const; @@ -592,9 +592,11 @@ struct dmub_srv { void *user_ctx; uint32_t fw_version; bool is_virtual; + bool no_ext_reg_access; struct dmub_fb scratch_mem_fb; struct dmub_fb ib_mem_gart; struct dmub_fb cursor_offload_fb; + const struct dmub_srv_fb_info *fb_info; volatile struct dmub_shared_state_feature_block *shared_state; volatile struct dmub_cursor_offload_v1 *cursor_offload_v1; volatile const struct dmub_fw_state *fw_state; @@ -954,14 +956,15 @@ enum dmub_status dmub_srv_get_gpint_dataout(struct dmub_srv *dmub, uint32_t *dataout); /** - * dmub_flush_buffer_mem() - Read back entire frame buffer region. + * dmub_srv_flush_buffer_mem() - Read back entire frame buffer region. * This ensures that the write from x86 has been flushed and will not * hang the DMCUB. + * @dmub: the dmub service * @fb: frame buffer to flush * * Can be called after software initialization. */ -void dmub_flush_buffer_mem(const struct dmub_fb *fb); +void dmub_srv_flush_buffer_mem(struct dmub_srv *dmub, const struct dmub_fb *fb); /** * dmub_srv_get_fw_boot_status() - Returns the DMUB boot status bits. diff --git a/drivers/gpu/drm/amd/display/dmub/src/dmub_srv.c b/drivers/gpu/drm/amd/display/dmub/src/dmub_srv.c index 7463d2ae5055..5a1a2276c432 100644 --- a/drivers/gpu/drm/amd/display/dmub/src/dmub_srv.c +++ b/drivers/gpu/drm/amd/display/dmub/src/dmub_srv.c @@ -96,7 +96,7 @@ static inline uint32_t dmub_align(uint32_t val, uint32_t factor) return (val + factor - 1) / factor * factor; } -void dmub_flush_buffer_mem(const struct dmub_fb *fb) +void dmub_srv_flush_buffer_mem(struct dmub_srv *dmub, const struct dmub_fb *fb) { const uint8_t *base = (const uint8_t *)fb->cpu_addr; uint8_t buf[64]; @@ -114,6 +114,8 @@ void dmub_flush_buffer_mem(const struct dmub_fb *fb) /* Read anything leftover into the buffer. */ if (end < fb->size) dmub_memcpy(buf, base + pos, fb->size - end); + + (void)dmub; } static const struct dmub_fw_meta_info * @@ -748,31 +750,38 @@ enum dmub_status dmub_srv_is_hw_init(struct dmub_srv *dmub, bool *is_hw_init) enum dmub_status dmub_srv_hw_init(struct dmub_srv *dmub, const struct dmub_srv_hw_params *params) { - struct dmub_fb *inst_fb = params->fb[DMUB_WINDOW_0_INST_CONST]; - struct dmub_fb *stack_fb = params->fb[DMUB_WINDOW_1_STACK]; - struct dmub_fb *data_fb = params->fb[DMUB_WINDOW_2_BSS_DATA]; - struct dmub_fb *bios_fb = params->fb[DMUB_WINDOW_3_VBIOS]; - struct dmub_fb *mail_fb = params->fb[DMUB_WINDOW_4_MAILBOX]; - struct dmub_fb *tracebuff_fb = params->fb[DMUB_WINDOW_5_TRACEBUFF]; - struct dmub_fb *fw_state_fb = params->fb[DMUB_WINDOW_6_FW_STATE]; - struct dmub_fb *shared_state_fb = params->fb[DMUB_WINDOW_SHARED_STATE]; + struct dmub_fb *inst_fb; + struct dmub_fb *stack_fb; + struct dmub_fb *data_fb; + struct dmub_fb *bios_fb; + struct dmub_fb *mail_fb; + struct dmub_fb *tracebuff_fb; + struct dmub_fb *fw_state_fb; + struct dmub_fb *shared_state_fb; struct dmub_rb_init_params rb_params, outbox0_rb_params; struct dmub_window cw0, cw1, cw2, cw3, cw4, cw5, cw6, region6; struct dmub_region inbox1, outbox1, outbox0; - uint32_t i; - if (!dmub->sw_init) return DMUB_STATUS_INVALID; - for (i = 0; i < DMUB_WINDOW_TOTAL; ++i) { - if (!params->fb[i]) { - ASSERT(0); - return DMUB_STATUS_INVALID; - } + if (!params->fb_info || params->fb_info->num_fb < DMUB_WINDOW_TOTAL) { + ASSERT(0); + return DMUB_STATUS_INVALID; } + inst_fb = ¶ms->fb_info->fb[DMUB_WINDOW_0_INST_CONST]; + stack_fb = ¶ms->fb_info->fb[DMUB_WINDOW_1_STACK]; + data_fb = ¶ms->fb_info->fb[DMUB_WINDOW_2_BSS_DATA]; + bios_fb = ¶ms->fb_info->fb[DMUB_WINDOW_3_VBIOS]; + mail_fb = ¶ms->fb_info->fb[DMUB_WINDOW_4_MAILBOX]; + tracebuff_fb = ¶ms->fb_info->fb[DMUB_WINDOW_5_TRACEBUFF]; + fw_state_fb = ¶ms->fb_info->fb[DMUB_WINDOW_6_FW_STATE]; + shared_state_fb = ¶ms->fb_info->fb[DMUB_WINDOW_SHARED_STATE]; + + dmub->fb_info = params->fb_info; + memcpy(&dmub->soc_fb_info, ¶ms->soc_fb_info, sizeof(params->soc_fb_info)); dmub->psp_version = params->psp_version; @@ -800,7 +809,7 @@ enum dmub_status dmub_srv_hw_init(struct dmub_srv *dmub, * flushed yet. This only occurs in backdoor loading. */ if (params->mem_access_type == DMUB_MEMORY_ACCESS_CPU) - dmub_flush_buffer_mem(inst_fb); + dmub_srv_flush_buffer_mem(dmub, inst_fb); if (params->fw_in_system_memory && dmub->hw_funcs.backdoor_load_zfb_mode) dmub->hw_funcs.backdoor_load_zfb_mode(dmub, &cw0, &cw1); @@ -851,10 +860,10 @@ enum dmub_status dmub_srv_hw_init(struct dmub_srv *dmub, dmub->shared_state = shared_state_fb->cpu_addr; - dmub->scratch_mem_fb = *params->fb[DMUB_WINDOW_7_SCRATCH_MEM]; - dmub->ib_mem_gart = *params->fb[DMUB_WINDOW_IB_MEM]; + dmub->scratch_mem_fb = params->fb_info->fb[DMUB_WINDOW_7_SCRATCH_MEM]; + dmub->ib_mem_gart = params->fb_info->fb[DMUB_WINDOW_IB_MEM]; - dmub->cursor_offload_fb = *params->fb[DMUB_WINDOW_CURSOR_OFFLOAD]; + dmub->cursor_offload_fb = params->fb_info->fb[DMUB_WINDOW_CURSOR_OFFLOAD]; dmub->cursor_offload_v1 = (struct dmub_cursor_offload_v1 *)dmub->cursor_offload_fb.cpu_addr; if (dmub->hw_funcs.setup_windows) @@ -1014,13 +1023,14 @@ enum dmub_status dmub_srv_wait_for_hw_pwr_up(struct dmub_srv *dmub, enum dmub_status dmub_srv_wait_for_auto_load(struct dmub_srv *dmub, uint32_t timeout_us) { + const uint32_t delay_us = 100; uint32_t i; bool hw_on = true; if (!dmub->hw_init) return DMUB_STATUS_INVALID; - for (i = 0; i <= timeout_us; i += 100) { + for (i = 0; i <= timeout_us; i += delay_us) { union dmub_fw_boot_status status = dmub->hw_funcs.get_fw_status(dmub); if (dmub->hw_funcs.is_hw_powered_up) @@ -1029,7 +1039,7 @@ enum dmub_status dmub_srv_wait_for_auto_load(struct dmub_srv *dmub, if (status.bits.dal_fw && status.bits.mailbox_rdy && hw_on) return DMUB_STATUS_OK; - udelay(100); + udelay(delay_us); } return DMUB_STATUS_TIMEOUT; @@ -1258,6 +1268,7 @@ bool dmub_srv_get_diagnostic_data(struct dmub_srv *dmub) if (!dmub || !dmub->hw_funcs.get_diagnostic_data) return false; dmub->hw_funcs.get_diagnostic_data(dmub); + return true; } From 7b0cc308521696107828b0c6c72c5793fa279cbd Mon Sep 17 00:00:00 2001 From: Chenyu Chen Date: Thu, 5 Mar 2026 20:11:50 +0800 Subject: [PATCH 094/577] drm/amd/display: Enable CACP on Linux [Why] Enable OLED panels to save more power on the Linux platform by enabling CACP support on Linux. [How] Update abm_feature_support() to return ABM_CACP_SUPPORT for OLED panels. Enable ABM property for all eDP panels including OLED to support CACP via set_abm_level. Reviewed-by: Sun peng (Leo) Li Signed-off-by: Chenyu Chen Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../display/amdgpu_dm/amdgpu_dm_backlight.c | 23 +- .../display/amdgpu_dm/amdgpu_dm_connector.c | 53 ++++- drivers/gpu/drm/amd/display/dc/dc.h | 1 + drivers/gpu/drm/amd/display/dc/dc_types.h | 8 + drivers/gpu/drm/amd/display/dc/dce/Makefile | 4 +- drivers/gpu/drm/amd/display/dc/dce/dmub_abm.c | 73 +++++- .../drm/amd/display/dc/dce/dmub_abm_cacp.c | 213 ++++++++++++++++++ .../drm/amd/display/dc/dce/dmub_abm_cacp.h | 20 ++ .../amd/display/dc/hwss/dcn21/dcn21_hwseq.c | 40 +++- .../amd/display/dc/hwss/dcn21/dcn21_hwseq.h | 2 + .../amd/display/dc/hwss/dcn31/dcn31_hwseq.c | 8 + drivers/gpu/drm/amd/display/dc/inc/hw/abm.h | 3 + .../drm/amd/display/modules/inc/mod_power.h | 11 + .../drm/amd/display/modules/power/power_abm.c | 126 +++++++++++ .../amd/display/modules/power/power_helpers.h | 10 + 15 files changed, 566 insertions(+), 29 deletions(-) create mode 100644 drivers/gpu/drm/amd/display/dc/dce/dmub_abm_cacp.c create mode 100644 drivers/gpu/drm/amd/display/dc/dce/dmub_abm_cacp.h diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c index 33f4be403a65..11d54897a894 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c @@ -559,7 +559,6 @@ EXPORT_IF_KUNIT(amdgpu_dm_update_connector_ext_caps); void amdgpu_dm_setup_backlight_device(struct amdgpu_display_manager *dm, struct amdgpu_dm_connector *aconnector) { - struct amdgpu_dm_backlight_caps *caps; struct dc_link *link = aconnector->dc_link; int bl_idx = dm->num_of_edps; @@ -579,10 +578,12 @@ void amdgpu_dm_setup_backlight_device(struct amdgpu_display_manager *dm, dm->num_of_edps++; amdgpu_dm_update_connector_ext_caps(aconnector); - caps = &dm->backlight_caps[aconnector->bl_idx]; - /* Only offer ABM property when non-OLED and user didn't turn off by module parameter */ - if (caps->ext_caps && !caps->ext_caps->bits.oled && amdgpu_dm_abm_level < 0) + /* Offer ABM property when user didn't turn off by module parameter. + * OLED panels are included to support CACP (Content Adaptive + * Contrast and Power) feature via set_abm_level. + */ + if (amdgpu_dm_abm_level < 0) drm_object_attach_property(&aconnector->base.base, dm->adev->mode_info.abm_level_property, ABM_SYSFS_CONTROL); @@ -666,22 +667,16 @@ const struct attribute_group amdgpu_group = { bool amdgpu_dm_should_create_sysfs(struct amdgpu_dm_connector *amdgpu_dm_connector) { + struct dc_link *link = amdgpu_dm_connector->dc_link; + if (amdgpu_dm_abm_level >= 0) return false; if (amdgpu_dm_connector->base.connector_type != DRM_MODE_CONNECTOR_eDP) return false; - /* check for OLED panels */ - if (amdgpu_dm_connector->bl_idx >= 0) { - struct drm_device *drm = amdgpu_dm_connector->base.dev; - struct amdgpu_display_manager *dm = &drm_to_adev(drm)->dm; - struct amdgpu_dm_backlight_caps *caps; - - caps = &dm->backlight_caps[amdgpu_dm_connector->bl_idx]; - if (caps->aux_support) - return false; - } + if (link->panel_type != PANEL_TYPE_LCD && !link->panel_config.cacp.cacp_supported) + return false; return true; } diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index 88f938a8f24f..57be01c60f7d 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c @@ -392,32 +392,31 @@ amdgpu_dm_find_first_crtc_matching_connector(struct drm_atomic_commit *state, return NULL; } -static void dm_set_panel_type(struct amdgpu_dm_connector *aconnector) +static void amdgpu_dm_set_panel_type(struct amdgpu_dm_connector *aconnector) { struct drm_connector *connector = &aconnector->base; struct drm_display_info *display_info = &connector->display_info; struct dc_link *link = aconnector->dc_link; struct amdgpu_device *adev; + enum dc_panel_type panel_type = PANEL_TYPE_NONE; adev = drm_to_adev(connector->dev); - link->panel_type = PANEL_TYPE_NONE; - switch (display_info->amd_vsdb.panel_type) { case AMD_VSDB_PANEL_TYPE_OLED: - link->panel_type = PANEL_TYPE_OLED; + panel_type = PANEL_TYPE_OLED; break; case AMD_VSDB_PANEL_TYPE_MINILED: - link->panel_type = PANEL_TYPE_MINILED; + panel_type = PANEL_TYPE_MINILED; break; } /* If VSDB didn't determine panel type, check DPCD ext caps */ - if (link->panel_type == PANEL_TYPE_NONE) { + if (panel_type == PANEL_TYPE_NONE) { if (link->dpcd_sink_ext_caps.bits.miniled == 1) - link->panel_type = PANEL_TYPE_MINILED; + panel_type = PANEL_TYPE_MINILED; if (link->dpcd_sink_ext_caps.bits.oled == 1) - link->panel_type = PANEL_TYPE_OLED; + panel_type = PANEL_TYPE_OLED; } /* If VSDB and DPCD didn't determine panel type, check DID */ @@ -428,7 +427,7 @@ static void dm_set_panel_type(struct amdgpu_dm_connector *aconnector) link->panel_type = PANEL_TYPE_OLED; } - if (link->panel_type == PANEL_TYPE_NONE) { + if (panel_type == PANEL_TYPE_NONE) { struct drm_amd_vsdb_info *vsdb = &display_info->amd_vsdb; u32 lum1_max = vsdb->luminance_range1.max_luminance; u32 lum2_max = vsdb->luminance_range2.max_luminance; @@ -437,9 +436,14 @@ static void dm_set_panel_type(struct amdgpu_dm_connector *aconnector) link->local_sink->edid_caps.manufacturer_id == DDC_MANUFACTURERNAME_SAMSUNG && lum1_max >= ((lum2_max * 3) / 2)) - link->panel_type = PANEL_TYPE_MINILED; + panel_type = PANEL_TYPE_MINILED; } + if (panel_type != PANEL_TYPE_NONE) + link->panel_type = panel_type; + else + link->panel_type = PANEL_TYPE_LCD; + if (link->panel_type == PANEL_TYPE_OLED) drm_object_property_set_value(&connector->base, adev_to_drm(adev)->mode_config.panel_type_property, @@ -456,6 +460,32 @@ static void dm_set_panel_type(struct amdgpu_dm_connector *aconnector) drm_dbg_kms(aconnector->base.dev, "Panel type: %d\n", link->panel_type); } +static void amdgpu_dm_update_cacp_caps(struct amdgpu_dm_connector *aconnector) +{ + struct amdgpu_device *adev = drm_to_adev(aconnector->base.dev); + struct dc_link *link = aconnector->dc_link; + + link->panel_config.cacp.cacp_supported = true; + + if (amdgpu_ip_version(adev, DCE_HWIP, 0) < IP_VERSION(3, 1, 4) || + amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(3, 1, 6)) { + link->panel_config.cacp.cacp_supported = false; + return; + } + + if (link->connector_signal != SIGNAL_TYPE_EDP && + link->connector_signal != SIGNAL_TYPE_LVDS) { + link->panel_config.cacp.cacp_supported = false; + return; + } + + if (link->panel_type == PANEL_TYPE_LCD) + link->panel_config.cacp.cacp_supported = false; + + drm_dbg_kms(aconnector->base.dev, "cacp_supported: %d\n", + link->panel_config.cacp.cacp_supported); +} + DEFINE_FREE(sink_release, struct dc_sink *, if (_T) dc_sink_release(_T)) void amdgpu_dm_update_connector_after_detect( @@ -581,7 +611,8 @@ void amdgpu_dm_update_connector_after_detect( amdgpu_dm_update_freesync_caps(connector, aconnector->drm_edid, true); amdgpu_dm_update_connector_ext_caps(aconnector); - dm_set_panel_type(aconnector); + amdgpu_dm_set_panel_type(aconnector); + amdgpu_dm_update_cacp_caps(aconnector); if (aconnector->hdmi_comp_auto) { if (sink->sink_signal != SIGNAL_TYPE_HDMI_FRL) diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h index 13c1f7cd9d7d..2b59a0e9ca38 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -632,6 +632,7 @@ enum visual_confirm { VISUAL_CONFIRM_SMARTMUX_DGPU = 10, VISUAL_CONFIRM_REPLAY = 12, VISUAL_CONFIRM_SUBVP = 14, + VISUAL_CONFIRM_ABM = 15, VISUAL_CONFIRM_MCLK_SWITCH = 16, VISUAL_CONFIRM_FAMS2 = 19, VISUAL_CONFIRM_HW_CURSOR = 20, diff --git a/drivers/gpu/drm/amd/display/dc/dc_types.h b/drivers/gpu/drm/amd/display/dc/dc_types.h index 90dd1ae7e953..5ba7a2fffcf0 100644 --- a/drivers/gpu/drm/amd/display/dc/dc_types.h +++ b/drivers/gpu/drm/amd/display/dc/dc_types.h @@ -1347,6 +1347,14 @@ struct dc_panel_config { struct ilr { bool optimize_edp_link_rate; /* eDP ILR */ } ilr; + /* CACP*/ + struct cacp { + unsigned int cacp_supported; + unsigned int cacp_control_mode; + unsigned int strscl_valid; + unsigned int strscl_sdr[4]; + unsigned int strscl_hdr[4]; + } cacp; /* Adaptive VariBright*/ struct adaptive_vb { bool disable_adaptive_vb; diff --git a/drivers/gpu/drm/amd/display/dc/dce/Makefile b/drivers/gpu/drm/amd/display/dc/dce/Makefile index 986e0e7abbc2..72bf70d89b74 100644 --- a/drivers/gpu/drm/amd/display/dc/dce/Makefile +++ b/drivers/gpu/drm/amd/display/dc/dce/Makefile @@ -23,14 +23,14 @@ # Makefile for common 'dce' logic # HW object file under this folder follow similar pattern for HW programming # - register offset and/or shift + mask stored in the dec_hw struct -# - register programming through common macros that look up register +# - register programming through common macros that look up register # offset/shift/mask stored in dce_hw struct DCE = dce_audio.o dce_stream_encoder.o dce_link_encoder.o \ dce_mem_input.o dce_clock_source.o dce_scl_filters.o dce_transform.o \ dce_opp.o dce_dmcu.o dce_abm.o dce_ipp.o dce_aux.o \ dce_i2c.o dce_i2c_hw.o dce_i2c_sw.o dmub_psr.o dmub_abm.o dmub_abm_lcd.o dce_panel_cntl.o \ -dmub_hw_lock_mgr.o dmub_outbox.o dmub_replay.o +dmub_hw_lock_mgr.o dmub_outbox.o dmub_replay.o dmub_abm_cacp.o AMD_DAL_DCE = $(addprefix $(AMDDALPATH)/dc/dce/,$(DCE)) diff --git a/drivers/gpu/drm/amd/display/dc/dce/dmub_abm.c b/drivers/gpu/drm/amd/display/dc/dce/dmub_abm.c index 93550c5e4d02..2df6e82b794f 100644 --- a/drivers/gpu/drm/amd/display/dc/dce/dmub_abm.c +++ b/drivers/gpu/drm/amd/display/dc/dce/dmub_abm.c @@ -25,6 +25,7 @@ #include "dmub_abm.h" #include "dmub_abm_lcd.h" +#include "dmub_abm_cacp.h" #include "dc.h" #include "core_types.h" #include "dmub_cmd.h" @@ -36,6 +37,7 @@ #define ABM_FEATURE_NO_SUPPORT 0 #define ABM_LCD_SUPPORT 1 +#define ABM_CACP_SUPPORT 2 static unsigned int abm_feature_support(struct abm *abm, unsigned int panel_inst) { @@ -52,15 +54,48 @@ static unsigned int abm_feature_support(struct abm *abm, unsigned int panel_inst } if (i < edp_num) { - ret = ABM_LCD_SUPPORT; + if (edp_links[panel_inst]->panel_config.cacp.cacp_supported) + ret = ABM_CACP_SUPPORT; + else if ((edp_links[panel_inst]->panel_type == PANEL_TYPE_LCD) || + (edp_links[panel_inst]->panel_type == PANEL_TYPE_MINILED)) + ret = ABM_LCD_SUPPORT; } return ret; } +static enum dc_panel_type abm_get_paneltype(struct abm *abm, unsigned int panel_inst) +{ + struct dc_context *dc = abm->ctx; + struct dc_link *edp_links[MAX_NUM_EDP]; + unsigned int i, edp_num; + enum dc_panel_type ret = PANEL_TYPE_NONE; + + dc_get_edp_links(dc->dc, edp_links, &edp_num); + + for (i = 0; i < edp_num; i++) { + if (edp_links[i]->link_status.link_active + && panel_inst == i) + break; + } + + if (i < edp_num) + ret = edp_links[panel_inst]->panel_type; + + return ret; +} + static void dmub_abm_init_ex(struct abm *abm, uint32_t backlight, uint32_t user_level) { + unsigned int i = 0; + uint8_t panel_mask = 0; + dmub_abm_init(abm, backlight, user_level); + for (i = 0; i < MAX_NUM_EDP; i++) + panel_mask |= (0x01 << i); + + if (panel_mask) + dmub_cacp_enable_fractional_pwm(abm, panel_mask); } static unsigned int dmub_abm_get_current_backlight_ex(struct abm *abm) @@ -82,17 +117,23 @@ static bool dmub_abm_set_level_ex(struct abm *abm, uint32_t level) bool ret = false; unsigned int feature_support, i; uint8_t panel_mask0 = 0; + uint8_t panel_mask1 = 0; for (i = 0; i < MAX_NUM_EDP; i++) { feature_support = abm_feature_support(abm, i); if (feature_support == ABM_LCD_SUPPORT) panel_mask0 |= (0x01 << i); + else if (feature_support == ABM_CACP_SUPPORT) + panel_mask1 |= (0x01 << i); } if (panel_mask0) ret = dmub_abm_set_level(abm, level, panel_mask0); + if (panel_mask1) + ret = dmub_cacp_set_level(abm, level, panel_mask1); + return ret; } @@ -107,6 +148,8 @@ static bool dmub_abm_init_config_ex(struct abm *abm, if (feature_support == ABM_LCD_SUPPORT) dmub_abm_init_config(abm, src, bytes, inst); + else if (feature_support == ABM_CACP_SUPPORT) + dmub_cacp_init(abm, src, bytes, inst); return true; } @@ -120,6 +163,8 @@ static bool dmub_abm_set_pause_ex(struct abm *abm, bool pause, unsigned int pane if (feature_support == ABM_LCD_SUPPORT) ret = dmub_abm_set_pause(abm, pause, panel_inst, stream_inst); + else if (feature_support == ABM_CACP_SUPPORT) + ret = dmub_cacp_set_pause(abm, pause, panel_inst, stream_inst); return ret; } @@ -163,6 +208,25 @@ static bool dmub_abm_set_pipe_ex(struct abm *abm, if (feature_support == ABM_LCD_SUPPORT) ret = dmub_abm_set_pipe(abm, otg_inst, option, panel_inst, pwrseq_inst); + else if (feature_support == ABM_CACP_SUPPORT) + ret = dmub_cacp_set_pipe(abm, otg_inst, option, panel_inst, pwrseq_inst); + + return ret; +} + +static bool dmub_abm_set_event_ex(struct abm *abm, unsigned int full_screen, unsigned int trans_info, + unsigned int hdr_mode, unsigned int scaling_enable, unsigned int scaling_strength_map, + unsigned int panel_inst) +{ + bool ret = false; + unsigned int feature_support; + + feature_support = abm_feature_support(abm, panel_inst); + + if (feature_support == ABM_LCD_SUPPORT) + ret = dmub_abm_set_event(abm, scaling_enable, scaling_strength_map, panel_inst); + else if (feature_support == ABM_CACP_SUPPORT) + ret = dmub_cacp_set_event(abm, full_screen, trans_info, hdr_mode, scaling_enable, panel_inst); return ret; } @@ -176,11 +240,17 @@ static bool dmub_abm_set_backlight_level_pwm_ex(struct abm *abm, (void)controller_id; bool ret = false; unsigned int feature_support; + enum dc_panel_type panel_type = PANEL_TYPE_NONE; feature_support = abm_feature_support(abm, panel_inst); if (feature_support == ABM_LCD_SUPPORT) ret = dmub_abm_set_backlight_level(abm, backlight_pwm_u16_16, frame_ramp, panel_inst); + else if (feature_support == ABM_CACP_SUPPORT) { + panel_type = abm_get_paneltype(abm, panel_inst); + if (panel_type == PANEL_TYPE_MINILED) + ret = dmub_cacp_set_backlight_level(abm, backlight_pwm_u16_16, frame_ramp, panel_inst); + } return ret; } @@ -194,6 +264,7 @@ static const struct abm_funcs abm_funcs = { .set_abm_pause = dmub_abm_set_pause_ex, .save_restore = dmub_abm_save_restore_ex, .set_pipe_ex = dmub_abm_set_pipe_ex, + .set_abm_event = dmub_abm_set_event_ex, .set_backlight_level_pwm = dmub_abm_set_backlight_level_pwm_ex, }; diff --git a/drivers/gpu/drm/amd/display/dc/dce/dmub_abm_cacp.c b/drivers/gpu/drm/amd/display/dc/dce/dmub_abm_cacp.c new file mode 100644 index 000000000000..9f58fa2e25fd --- /dev/null +++ b/drivers/gpu/drm/amd/display/dc/dce/dmub_abm_cacp.c @@ -0,0 +1,213 @@ +/* Copyright (C) 2022 Advanced Micro Devices, Inc. All rights reserved. */ + +#include "dmub_abm.h" +#include "dmub_abm_cacp.h" +#include "dce_abm.h" +#include "dc.h" +#include "dc_dmub_srv.h" +#include "dmub/dmub_srv.h" +#include "core_types.h" + +#define CACP_LEVEL_NUM 4 + +void dmub_cacp_init(struct abm *abm, const char *src, unsigned int bytes, unsigned int panel_inst) +{ + union dmub_rb_cmd cmd; + struct dc_context *dc = abm->ctx; + uint8_t panel_mask = 0x01 << panel_inst; + struct dc_link *edp_links[MAX_NUM_EDP]; + unsigned int i, edp_num; + + // TODO: Optimize by only reading back final 4 bytes + dmub_srv_flush_buffer_mem(dc->dmub_srv->dmub, &dc->dmub_srv->dmub->scratch_mem_fb); + + // Copy iramtable into cw7 + memcpy(dc->dmub_srv->dmub->scratch_mem_fb.cpu_addr, (void *)src, bytes); + + memset(&cmd, 0, sizeof(cmd)); + // Fw will copy from cw7 to fw_state + cmd.cacp_init_config.header.type = DMUB_CMD__CACP; + cmd.cacp_init_config.header.sub_type = DMUB_CMD__CACP_INIT_CONFIG; + cmd.cacp_init_config.cacp_init_config_data.src.quad_part = dc->dmub_srv->dmub->scratch_mem_fb.gpu_addr; + cmd.cacp_init_config.cacp_init_config_data.bytes = (uint16_t)bytes; + cmd.cacp_init_config.cacp_init_config_data.panel_mask = panel_mask; + cmd.cacp_init_config.cacp_init_config_data.visual_confirm = + (dc->dc->debug.visual_confirm == VISUAL_CONFIRM_ABM) ? true : false; + + cmd.cacp_init_config.header.payload_bytes = sizeof(struct dmub_cmd_cacp_init_config_data); + + dc_get_edp_links(dc->dc, edp_links, &edp_num); + for (i = 0; i < edp_num; i++) { + if (panel_inst == i) + break; + } + + if (i < edp_num) { + cmd.cacp_init_config.cacp_init_config_data.strscl_valid = + (uint8_t)edp_links[panel_inst]->panel_config.cacp.strscl_valid; + cmd.cacp_init_config.cacp_init_config_data.mode = + edp_links[panel_inst]->panel_config.cacp.cacp_control_mode ? + DMUB_CMD_CACP_CONTROL_MODE_1 : DMUB_CMD_CACP_CONTROL_MODE_0; + for (int j = 0; j < CACP_LEVEL_NUM; j++) { + cmd.cacp_init_config.cacp_init_config_data.strscl_sdr[j] = + (uint8_t)edp_links[panel_inst]->panel_config.cacp.strscl_sdr[j]; + cmd.cacp_init_config.cacp_init_config_data.strscl_hdr[j] = + (uint8_t)edp_links[panel_inst]->panel_config.cacp.strscl_hdr[j]; + } + } + + dc_wake_and_execute_dmub_cmd(dc, &cmd, DM_DMUB_WAIT_TYPE_WAIT); +} + +bool dmub_cacp_set_level(struct abm *abm, unsigned int abm_level, unsigned char panel_mask) +{ + union dmub_rb_cmd cmd; + struct dc_context *dc = abm->ctx; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cacp_set_level.header.type = DMUB_CMD__CACP; + cmd.cacp_set_level.header.sub_type = DMUB_CMD__CACP_SET_LEVEL; + + cmd.cacp_set_level.cacp_set_level_data.level = abm_level; + cmd.cacp_set_level.cacp_set_level_data.version = DMUB_CMD_CACP_CONTROL_VERSION_1; + cmd.cacp_set_level.cacp_set_level_data.panel_mask = panel_mask; + + cmd.cacp_set_level.header.payload_bytes = sizeof(struct dmub_cmd_cacp_set_level_data); + + dc_wake_and_execute_dmub_cmd(dc, &cmd, DM_DMUB_WAIT_TYPE_WAIT); + + return true; +} + +bool dmub_cacp_set_pipe(struct abm *abm, unsigned int otg_inst, + unsigned int pipe_option, unsigned int panel_inst, unsigned int pwrseq_inst) +{ + union dmub_rb_cmd cmd; + struct dc_context *dc = abm->ctx; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cacp_set_pipe.header.type = DMUB_CMD__CACP; + cmd.cacp_set_pipe.header.sub_type = DMUB_CMD__CACP_SET_PIPE; + + cmd.cacp_set_pipe.cacp_set_pipe_data.otg_inst = (uint8_t)otg_inst; + cmd.cacp_set_pipe.cacp_set_pipe_data.panel_inst = (uint8_t)panel_inst; + cmd.cacp_set_pipe.cacp_set_pipe_data.set_pipe_option = (uint8_t)pipe_option; + cmd.cacp_set_pipe.cacp_set_pipe_data.pwrseq_inst = (uint8_t)pwrseq_inst; + cmd.cacp_set_pipe.header.payload_bytes = sizeof(struct dmub_cmd_cacp_set_pipe_data); + + dc_wake_and_execute_dmub_cmd(dc, &cmd, DM_DMUB_WAIT_TYPE_WAIT); + + return true; +} + +bool dmub_cacp_set_event(struct abm *abm, unsigned int full_screen, unsigned int trans_info, + unsigned int hdr_mode, unsigned int scaling_enable, unsigned int panel_inst) +{ + union dmub_rb_cmd cmd; + struct dc_context *dc = abm->ctx; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cacp_set_event.header.type = DMUB_CMD__CACP; + cmd.cacp_set_event.header.sub_type = DMUB_CMD__CACP_SET_EVENT; + + //TODO: + cmd.cacp_set_event.cacp_set_event_data.full_screen_mode = (uint8_t)full_screen; + cmd.cacp_set_event.cacp_set_event_data.trans_info = trans_info; + cmd.cacp_set_event.cacp_set_event_data.hdr_mode = (uint8_t)hdr_mode; + cmd.cacp_set_event.cacp_set_event_data.vb_scaling_enable = (uint8_t)scaling_enable; + cmd.cacp_set_event.cacp_set_event_data.panel_mask = (1<ctx; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cacp_pause.header.type = DMUB_CMD__CACP; + cmd.cacp_pause.header.sub_type = DMUB_CMD__CACP_PAUSE; + + cmd.cacp_pause.cacp_pause_data.panel_mask = (1<ctx; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cacp_set_backlight.header.type = DMUB_CMD__CACP; + cmd.cacp_set_backlight.header.sub_type = DMUB_CMD__CACP_SET_BACKLIGHT; + cmd.cacp_set_backlight.cacp_set_backlight_data.frame_ramp = frame_ramp; + cmd.cacp_set_backlight.cacp_set_backlight_data.backlight_user_level = backlight_pwm_u16_16; + cmd.cacp_set_backlight.cacp_set_backlight_data.version = DMUB_CMD_CACP_CONTROL_VERSION_1; + cmd.cacp_set_backlight.cacp_set_backlight_data.panel_mask = (0x01 << panel_inst); + cmd.cacp_set_backlight.header.payload_bytes = sizeof(struct dmub_cmd_cacp_set_backlight_data); + + dc_wake_and_execute_dmub_cmd(dc, &cmd, DM_DMUB_WAIT_TYPE_WAIT); + + return true; +} + +void dmub_cacp_enable_fractional_pwm(struct abm *abm, uint8_t panel_mask) +{ + union dmub_rb_cmd cmd; + struct dc_context *dc = abm->ctx; + uint32_t fractional_pwm = (dc->dc->config.disable_fractional_pwm == false) ? 1 : 0; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cacp_set_pwm_frac.header.type = DMUB_CMD__CACP; + cmd.cacp_set_pwm_frac.header.sub_type = DMUB_CMD__CACP_SET_PWM_FRAC; + cmd.cacp_set_pwm_frac.cacp_set_pwm_frac_data.fractional_pwm = fractional_pwm; + cmd.cacp_set_pwm_frac.cacp_set_pwm_frac_data.version = DMUB_CMD_CACP_CONTROL_VERSION_1; + cmd.cacp_set_pwm_frac.cacp_set_pwm_frac_data.panel_mask = panel_mask; + cmd.cacp_set_pwm_frac.header.payload_bytes = sizeof(struct dmub_cmd_cacp_set_pwm_frac_data); + + dc_wake_and_execute_dmub_cmd(dc, &cmd, DM_DMUB_WAIT_TYPE_WAIT); +} + +bool dmub_cacp_get_histogram( + struct dc_context *dc, + unsigned int panel_inst, + unsigned int *histogram, + enum dmub_abm_histogram_type histogram_type, + unsigned int size) +{ + union dmub_rb_cmd cmd; + + dmub_srv_flush_buffer_mem(dc->dmub_srv->dmub, &dc->dmub_srv->dmub->scratch_mem_fb); + + memset(&cmd, 0, sizeof(cmd)); + cmd.cacp_get_histogram.header.type = DMUB_CMD__CACP; + cmd.cacp_get_histogram.header.sub_type = DMUB_CMD__CACP_GET_HISTOGRAM; + + cmd.cacp_get_histogram.dest.quad_part = dc->dmub_srv->dmub->scratch_mem_fb.gpu_addr; + cmd.cacp_get_histogram.bytes = (uint16_t)size; + cmd.cacp_get_histogram.panel_inst = (uint8_t)panel_inst; + cmd.cacp_get_histogram.histogram_type = histogram_type; + cmd.cacp_get_histogram.header.payload_bytes = sizeof(struct dmub_rb_cmd_cacp_get_histogram); + + dc_wake_and_execute_dmub_cmd(dc, &cmd, DM_DMUB_WAIT_TYPE_WAIT); + + memcpy((void *)histogram, dc->dmub_srv->dmub->scratch_mem_fb.cpu_addr, size); + + return true; +} + diff --git a/drivers/gpu/drm/amd/display/dc/dce/dmub_abm_cacp.h b/drivers/gpu/drm/amd/display/dc/dce/dmub_abm_cacp.h new file mode 100644 index 000000000000..3b3b712c1242 --- /dev/null +++ b/drivers/gpu/drm/amd/display/dc/dce/dmub_abm_cacp.h @@ -0,0 +1,20 @@ +/* Copyright (C) 2022 Advanced Micro Devices, Inc. All rights reserved. */ + +#include "dmub_cmd.h" + +#ifndef __DMUB_ABM_CACP_H__ +#define __DMUB_ABM_CACP_H__ + +void dmub_cacp_init(struct abm *abm, const char *src, unsigned int bytes, unsigned int panel_inst); +bool dmub_cacp_set_level(struct abm *abm, unsigned int cacp_level, unsigned char panel_mask); +bool dmub_cacp_set_pipe(struct abm *abm, unsigned int otg_inst, unsigned int pipe_option, + unsigned int panel_inst, unsigned int pwrseq_inst); +bool dmub_cacp_set_event(struct abm *abm, unsigned int full_screen, unsigned int trans_info, unsigned int hdr_mode, + unsigned int scaling_enable, unsigned int panel_inst); +bool dmub_cacp_set_pause(struct abm *abm, bool pause, unsigned int panel_inst, unsigned int otg_inst); +bool dmub_cacp_set_backlight_level(struct abm *abm, unsigned int backlight_pwm_u16_16, unsigned int frame_ramp, + unsigned int panel_inst); +void dmub_cacp_enable_fractional_pwm(struct abm *abm, uint8_t panel_mask); +bool dmub_cacp_get_histogram(struct dc_context *dc, unsigned int panel_inst, unsigned int *histogram, + enum dmub_abm_histogram_type histogram_type, unsigned int size); +#endif diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn21/dcn21_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn21/dcn21_hwseq.c index 476c2112afec..2be68f8dd0fc 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn21/dcn21_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn21/dcn21_hwseq.c @@ -136,6 +136,26 @@ void dcn21_PLAT_58856_wa(struct dc_state *context, struct pipe_ctx *pipe_ctx) pipe_ctx->stream->dpms_off = true; } +bool dcn21_dmub_cacp_set_pipe(struct abm *abm, uint32_t otg_inst, + uint32_t option, uint32_t panel_inst, uint32_t pwrseq_inst) +{ + union dmub_rb_cmd cmd; + struct dc_context *dc = abm->ctx; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cacp_set_pipe.header.type = DMUB_CMD__CACP; + cmd.cacp_set_pipe.header.sub_type = DMUB_CMD__CACP_SET_PIPE; + cmd.cacp_set_pipe.cacp_set_pipe_data.otg_inst = (uint8_t)otg_inst; + cmd.cacp_set_pipe.cacp_set_pipe_data.pwrseq_inst = (uint8_t)pwrseq_inst; + cmd.cacp_set_pipe.cacp_set_pipe_data.set_pipe_option = (uint8_t)option; + cmd.cacp_set_pipe.cacp_set_pipe_data.panel_inst = (uint8_t)panel_inst; + cmd.cacp_set_pipe.header.payload_bytes = sizeof(struct dmub_cmd_cacp_set_pipe_data); + + dc_wake_and_execute_dmub_cmd(dc, &cmd, DM_DMUB_WAIT_TYPE_WAIT); + + return true; +} + bool dcn21_dmub_abm_set_pipe(struct abm *abm, uint32_t otg_inst, uint32_t option, uint32_t panel_inst, uint32_t pwrseq_inst) { @@ -181,10 +201,12 @@ void dcn21_set_abm_immediate_disable(struct pipe_ctx *pipe_ctx) uint32_t otg_inst = pipe_ctx->stream_res.tg->inst; struct panel_cntl *panel_cntl = pipe_ctx->stream->link->panel_cntl; struct dmcu *dmcu = pipe_ctx->stream->ctx->dc->res_pool->dmcu; + struct dc_link *link = pipe_ctx->stream->link; // make a short term w/a for an issue that backlight ramping unexpectedly paused in the middle, // will decouple backlight from ABM and redefine DMUB interface, then this w/a could be removed - if (pipe_ctx->stream->abm_level == 0 || pipe_ctx->stream->abm_level == ABM_LEVEL_IMMEDIATE_DISABLE) { + if ((pipe_ctx->stream->abm_level == 0 || pipe_ctx->stream->abm_level == ABM_LEVEL_IMMEDIATE_DISABLE) + && (link && !link->panel_config.cacp.cacp_supported)) { return; } @@ -197,6 +219,9 @@ void dcn21_set_abm_immediate_disable(struct pipe_ctx *pipe_ctx) if (abm->funcs && abm->funcs->set_pipe_ex) { abm->funcs->set_pipe_ex(abm, otg_inst, SET_ABM_PIPE_IMMEDIATELY_DISABLE, panel_cntl->inst, panel_cntl->pwrseq_inst); + } else if (link && link->panel_config.cacp.cacp_supported) { + dcn21_dmub_cacp_set_pipe(abm, otg_inst, SET_CACP_PIPE_IMMEDIATELY_DISABLE, + panel_cntl->inst, panel_cntl->pwrseq_inst); } else { dcn21_dmub_abm_set_pipe(abm, otg_inst, @@ -214,6 +239,7 @@ void dcn21_set_pipe(struct pipe_ctx *pipe_ctx) struct timing_generator *tg = pipe_ctx->stream_res.tg; struct panel_cntl *panel_cntl = pipe_ctx->stream->link->panel_cntl; struct dmcu *dmcu = pipe_ctx->stream->ctx->dc->res_pool->dmcu; + struct dc_link *link = pipe_ctx->stream->link; uint32_t otg_inst; if (!abm || !tg || !panel_cntl) @@ -233,6 +259,11 @@ void dcn21_set_pipe(struct pipe_ctx *pipe_ctx) panel_cntl->inst, panel_cntl->pwrseq_inst); } else { + + if (link && link->panel_config.cacp.cacp_supported) + dcn21_dmub_cacp_set_pipe(abm, otg_inst, SET_CACP_PIPE_NORMAL, + panel_cntl->inst, panel_cntl->pwrseq_inst); + else dcn21_dmub_abm_set_pipe(abm, otg_inst, SET_ABM_PIPE_NORMAL, panel_cntl->inst, @@ -250,6 +281,7 @@ bool dcn21_set_backlight_level(struct pipe_ctx *pipe_ctx, uint32_t otg_inst; uint32_t backlight_pwm_u16_16 = backlight_level_params->backlight_pwm_u16_16; uint32_t frame_ramp = backlight_level_params->frame_ramp; + struct dc_link *link = pipe_ctx->stream->link; if (!abm || !tg || !panel_cntl) return false; @@ -268,6 +300,12 @@ bool dcn21_set_backlight_level(struct pipe_ctx *pipe_ctx, panel_cntl->inst, panel_cntl->pwrseq_inst); } else { + if (link && link->panel_config.cacp.cacp_supported) + dcn21_dmub_cacp_set_pipe(abm, otg_inst, + SET_CACP_PIPE_NORMAL, + panel_cntl->inst, + panel_cntl->pwrseq_inst); + else dcn21_dmub_abm_set_pipe(abm, otg_inst, SET_ABM_PIPE_NORMAL, diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn21/dcn21_hwseq.h b/drivers/gpu/drm/amd/display/dc/hwss/dcn21/dcn21_hwseq.h index f72a27ac1bf1..aae13447cfa8 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn21/dcn21_hwseq.h +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn21/dcn21_hwseq.h @@ -47,6 +47,8 @@ void dcn21_optimize_pwr_state( void dcn21_PLAT_58856_wa(struct dc_state *context, struct pipe_ctx *pipe_ctx); +bool dcn21_dmub_cacp_set_pipe(struct abm *abm, uint32_t otg_inst, + uint32_t option, uint32_t panel_inst, uint32_t pwrseq_inst); bool dcn21_dmub_abm_set_pipe(struct abm *abm, uint32_t otg_inst, uint32_t option, uint32_t panel_inst, uint32_t pwrseq_inst); void dcn21_set_pipe(struct pipe_ctx *pipe_ctx); diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn31/dcn31_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn31/dcn31_hwseq.c index 43e4edfe9182..e72e10c276de 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn31/dcn31_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn31/dcn31_hwseq.c @@ -54,6 +54,7 @@ #include "dce/dce_i2c_hw.h" #include "dce/dmub_abm_lcd.h" #include "dio/dcn10/dcn10_dio.h" +#include "dce/dmub_abm_cacp.h" #define DC_LOGGER_INIT(logger) @@ -726,12 +727,19 @@ bool dcn31_set_backlight_level(struct pipe_ctx *pipe_ctx, struct timing_generator *tg = pipe_ctx->stream_res.tg; struct panel_cntl *panel_cntl = pipe_ctx->stream->link->panel_cntl; uint32_t otg_inst; + struct dc_link *link = pipe_ctx->stream->link; if (!abm || !tg || !panel_cntl) return false; otg_inst = tg->inst; + if (link && link->panel_config.cacp.cacp_supported) + dcn21_dmub_cacp_set_pipe(abm, otg_inst, + SET_CACP_PIPE_NORMAL, + panel_cntl->inst, + panel_cntl->pwrseq_inst); + else dcn21_dmub_abm_set_pipe(abm, otg_inst, SET_ABM_PIPE_NORMAL, diff --git a/drivers/gpu/drm/amd/display/dc/inc/hw/abm.h b/drivers/gpu/drm/amd/display/dc/inc/hw/abm.h index 3f0161d64675..6ae7cf109565 100644 --- a/drivers/gpu/drm/amd/display/dc/inc/hw/abm.h +++ b/drivers/gpu/drm/amd/display/dc/inc/hw/abm.h @@ -66,6 +66,9 @@ struct abm_funcs { unsigned int option, unsigned int panel_inst, unsigned int pwrseq_inst); + bool (*set_abm_event)(struct abm *abm, unsigned int full_screen, unsigned int trans_info, + unsigned int hdr_mode, unsigned int scaling_enable, unsigned int scaling_strength_map, + unsigned int panel_inst); }; #endif diff --git a/drivers/gpu/drm/amd/display/modules/inc/mod_power.h b/drivers/gpu/drm/amd/display/modules/inc/mod_power.h index f9814cf7bbdb..02bee3b1956d 100644 --- a/drivers/gpu/drm/amd/display/modules/inc/mod_power.h +++ b/drivers/gpu/drm/amd/display/modules/inc/mod_power.h @@ -328,6 +328,17 @@ bool mod_power_is_abm_active(struct mod_power *mod_power, const struct dc_link *link, unsigned int inst); +bool mod_power_is_abm_supported(struct mod_power *mod_power, + unsigned int inst); + +bool mod_power_abm_set_event(struct mod_power *mod_power, + unsigned int full_screen, unsigned int trans_info, + unsigned int hdr_mode, unsigned int scaling_enable, + unsigned int scaling_strength_map, unsigned int inst); + +bool mod_power_abm_set_strength(struct mod_power *mod_power, + unsigned int strength, + unsigned int inst); bool mod_power_set_psr_event(struct mod_power *mod_power, struct dc_stream_state *stream, bool set_event, diff --git a/drivers/gpu/drm/amd/display/modules/power/power_abm.c b/drivers/gpu/drm/amd/display/modules/power/power_abm.c index b9447cb7485b..5b972f04bf7a 100644 --- a/drivers/gpu/drm/amd/display/modules/power/power_abm.c +++ b/drivers/gpu/drm/amd/display/modules/power/power_abm.c @@ -1453,6 +1453,76 @@ bool mod_power_is_abm_active(struct mod_power *mod_power, return is_active; } +bool mod_power_is_abm_supported(struct mod_power *mod_power, + unsigned int inst) +{ + struct core_power *core_power = NULL; + struct dc *dc = NULL; + + if (mod_power == NULL) + return false; + + core_power = MOD_POWER_TO_CORE(mod_power); + dc = core_power->dc; + + // It's only implemented on dmcub. + if (dc->ctx->dmub_srv) { + if (!dmub_is_abm_supported(dc->res_pool, inst)) + return false; + } else + return false; + + return true; +} + +bool mod_power_abm_set_event(struct mod_power *mod_power, + unsigned int full_screen, unsigned int trans_info, + unsigned int hdr_mode, unsigned int scaling_enable, + unsigned int scaling_strength_map, unsigned int inst) +{ + struct core_power *core_power = NULL; + struct dc *dc = NULL; + + if (mod_power == NULL) + return false; + + core_power = MOD_POWER_TO_CORE(mod_power); + dc = core_power->dc; + + // It's only implemented on dmcub. + if (dc->ctx->dmub_srv) { + if (!dmub_set_abm_event(dc->res_pool, full_screen, trans_info, + hdr_mode, scaling_enable, scaling_strength_map, inst)) + return false; + } else + return false; + + return true; +} + +bool mod_power_abm_set_strength(struct mod_power *mod_power, + unsigned int strength, + unsigned int inst) +{ + struct core_power *core_power = NULL; + struct dc *dc = NULL; + + if (mod_power == NULL) + return false; + + core_power = MOD_POWER_TO_CORE(mod_power); + dc = core_power->dc; + + // It's only implemented on dmcub. + if (dc->ctx->dmub_srv) { + if (!dmub_set_abm_strength(dc->res_pool, strength, inst)) + return false; + } else + return false; + + return true; +} + static void fill_backlight_transform_table(struct dmcu_iram_parameters params, struct iram_table_v_2 *table) { @@ -1990,6 +2060,62 @@ bool dmub_init_abm_config(struct resource_pool *res_pool, return result; } +bool dmub_is_abm_supported(struct resource_pool *res_pool, unsigned int inst) +{ + + if (res_pool->abm == NULL && res_pool->multiple_abms[inst] == NULL) + return false; + + return true; +} + +bool dmub_set_abm_event(struct resource_pool *res_pool, + unsigned int full_screen, unsigned int trans_info, + unsigned int hdr_mode, unsigned int scaling_enable, unsigned int scaling_strength_map, + unsigned int inst) +{ + bool result = false; + + if (res_pool->abm == NULL && res_pool->multiple_abms[inst] == NULL) + return false; + + if (res_pool->multiple_abms[inst]) { + if (res_pool->multiple_abms[inst]->funcs->set_abm_event) + result = res_pool->multiple_abms[inst]->funcs->set_abm_event( + res_pool->multiple_abms[inst], full_screen, trans_info, + hdr_mode, scaling_enable, scaling_strength_map, inst); + } else { + if (res_pool->abm->funcs->set_abm_event) + result = res_pool->abm->funcs->set_abm_event( + res_pool->abm, full_screen, trans_info, + hdr_mode, scaling_enable, scaling_strength_map, inst); + } + + return result; +} + +bool dmub_set_abm_strength(struct resource_pool *res_pool, + unsigned int strength, + unsigned int inst) +{ + bool result = false; + + if (res_pool->abm == NULL && res_pool->multiple_abms[inst] == NULL) + return false; + + if (res_pool->multiple_abms[inst]) { + if (res_pool->multiple_abms[inst]->funcs->set_abm_level) + result = res_pool->multiple_abms[inst]->funcs->set_abm_level( + res_pool->multiple_abms[inst], strength); + } else { + if (res_pool->abm->funcs->set_abm_level) + result = res_pool->abm->funcs->set_abm_level( + res_pool->abm, strength); + } + + return result; +} + bool dmcu_load_iram(struct dmcu *dmcu, struct dmcu_iram_parameters params) { diff --git a/drivers/gpu/drm/amd/display/modules/power/power_helpers.h b/drivers/gpu/drm/amd/display/modules/power/power_helpers.h index 600da3e33126..548c8ff6ddb4 100644 --- a/drivers/gpu/drm/amd/display/modules/power/power_helpers.h +++ b/drivers/gpu/drm/amd/display/modules/power/power_helpers.h @@ -146,6 +146,16 @@ bool dmub_init_abm_config(struct resource_pool *res_pool, struct dmcu_iram_parameters params, unsigned int inst); +bool dmub_is_abm_supported(struct resource_pool *res_pool, + unsigned int inst); +bool dmub_set_abm_event(struct resource_pool *res_pool, + unsigned int full_screen, unsigned int trans_info, + unsigned int hdr_mode, unsigned int scaling_enable, + unsigned int scaling_strength_map, unsigned int inst); +bool dmub_set_abm_strength(struct resource_pool *res_pool, + unsigned int strength, + unsigned int inst); + void init_replay_config(struct dc_link *link, struct replay_config *pr_config); void set_replay_coasting_vtotal(struct dc_link *link, enum replay_coasting_vtotal_type type, From 329baa1c5f62bdce36b77d463b5c23c62ee59e4a Mon Sep 17 00:00:00 2001 From: Gabe Teeger Date: Fri, 5 Jun 2026 16:21:29 -0400 Subject: [PATCH 095/577] drm/amd/display: Enable PSR and Replay on DCN4 variant [Part 2] [Why] Enable PSR and Panel Replay on a DCN4 variant for display power savings. [How] Enable PSR and Replay in the DCN4 variant panel config defaults. Fixes: 68737239e891 ("drm/amd/display: Enable PSR and Replay on DCN4 variant and fix AUX instance") Reviewed-by: George Zhang Signed-off-by: Gabe Teeger Signed-off-by: Matthew Stewart Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c index 60cbaf4f6fdf..330e82b0974b 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c @@ -1016,9 +1016,9 @@ static struct hubp *dcn42b_hubp_create( } static const struct dc_panel_config dcn42b_panel_config_defaults = { .psr = { - .disable_psr = true, + .disable_psr = false, .disallow_psrsu = true, - .disallow_replay = true, + .disallow_replay = false, }, .ilr = { .optimize_edp_link_rate = true, From c52d32e6e7932c17ad9f202f6c86f5898a405b7c Mon Sep 17 00:00:00 2001 From: Marco Crivellari Date: Tue, 7 Jul 2026 11:38:12 +0200 Subject: [PATCH 096/577] drm/amdgpu: Change system_unbound_wq with system_dfl_wq MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit system_wq (per-CPU) and system_unbound_wq (unbound) are the older workqueue name, replaced by system_{percpu|dfl}_wq. The new workqueues have been introduced by: 128ea9f6ccfb ("workqueue: Add system_percpu_wq and system_dfl_wq") Usage of older workqueues will now trigger a pr_warn_once() because they are marked as deprecated as per commit: 64d8eae3f895 ("workqueue: Add warnings and fallback if system_{unbound}_wq is used") So change the used workqueue with the newer, keeping the same behavior. Reviewed-by: Timur Kristóf Suggested-by: Tejun Heo Signed-off-by: Marco Crivellari Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_irq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_irq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_irq.c index 53be764968e4..95cceed4e971 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_irq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_irq.c @@ -545,7 +545,7 @@ void amdgpu_irq_delegate(struct amdgpu_device *adev, unsigned int num_dw) { amdgpu_ih_ring_write(adev, &adev->irq.ih_soft, entry->iv_entry, num_dw); - queue_work(system_unbound_wq, &adev->irq.ih_soft_work); + queue_work(system_dfl_wq, &adev->irq.ih_soft_work); } /** From b3f2f83e84b88a04efbfb63f7b9e0ac20ab64103 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Thu, 18 Jun 2026 16:39:46 -0400 Subject: [PATCH 097/577] drm/amdgpu/gfx12.1: Add ip dump support Add support for dumping IP register state. v2: fixes suggested by Mukul Reviewed-by: Mukul Joshi Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v12_1.c | 277 +++++++++++++++++++++++++ 1 file changed, 277 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v12_1.c b/drivers/gpu/drm/amd/amdgpu/gfx_v12_1.c index b19a4956c222..1f7e243d2ad9 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v12_1.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v12_1.c @@ -69,6 +69,127 @@ MODULE_FIRMWARE("amdgpu/gc_12_1_0_rlc_1.bin"); (SH_MEM_ALIGNMENT_MODE_UNALIGNED_GFX12_1_0 << SH_MEM_CONFIG__ALIGNMENT_MODE__SHIFT) | \ (3 << SH_MEM_CONFIG__INITIAL_INST_PREFETCH__SHIFT)) + +static const struct amdgpu_hwip_reg_entry gc_reg_list_12_1[] = { + SOC15_REG_ENTRY_STR(GC, 0, regGRBM_STATUS), + SOC15_REG_ENTRY_STR(GC, 0, regGRBM_STATUS2), + SOC15_REG_ENTRY_STR(GC, 0, regGRBM_STATUS3), + SOC15_REG_ENTRY_STR(GC, 0, regCP_STALLED_STAT1), + SOC15_REG_ENTRY_STR(GC, 0, regCP_STALLED_STAT2), + SOC15_REG_ENTRY_STR(GC, 0, regCP_STALLED_STAT3), + SOC15_REG_ENTRY_STR(GC, 0, regCP_CPC_STALLED_STAT1), + SOC15_REG_ENTRY_STR(GC, 0, regCP_CPF_STALLED_STAT1), + SOC15_REG_ENTRY_STR(GC, 0, regCP_BUSY_STAT), + SOC15_REG_ENTRY_STR(GC, 0, regCP_CPC_BUSY_STAT), + SOC15_REG_ENTRY_STR(GC, 0, regCP_CPF_BUSY_STAT), + SOC15_REG_ENTRY_STR(GC, 0, regCP_CPC_BUSY_STAT2), + SOC15_REG_ENTRY_STR(GC, 0, regCP_CPF_BUSY_STAT2), + SOC15_REG_ENTRY_STR(GC, 0, regCP_CPF_STATUS), + SOC15_REG_ENTRY_STR(GC, 0, regCP_GFX_ERROR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_GFX_HPD_STATUS0), + SOC15_REG_ENTRY_STR(GC, 0, regCP_RB_BASE), + SOC15_REG_ENTRY_STR(GC, 0, regCP_RB_RPTR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_RB_WPTR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_RB0_BASE), + SOC15_REG_ENTRY_STR(GC, 0, regCP_RB0_RPTR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_RB0_WPTR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_IB1_CMD_BUFSZ), + SOC15_REG_ENTRY_STR(GC, 0, regCP_IB2_CMD_BUFSZ), + SOC15_REG_ENTRY_STR(GC, 0, regCP_IB1_BASE_LO), + SOC15_REG_ENTRY_STR(GC, 0, regCP_IB1_BASE_HI), + SOC15_REG_ENTRY_STR(GC, 0, regCP_IB1_BUFSZ), + SOC15_REG_ENTRY_STR(GC, 0, regCP_IB2_BASE_LO), + SOC15_REG_ENTRY_STR(GC, 0, regCP_IB2_BASE_HI), + SOC15_REG_ENTRY_STR(GC, 0, regCP_IB2_BUFSZ), + SOC15_REG_ENTRY_STR(GC, 0, regCPF_UTCL1_STATUS), + SOC15_REG_ENTRY_STR(GC, 0, regCPC_UTCL1_STATUS), + SOC15_REG_ENTRY_STR(GC, 0, regCPG_UTCL1_STATUS), + SOC15_REG_ENTRY_STR(GC, 0, regIA_UTCL1_STATUS), + SOC15_REG_ENTRY_STR(GC, 0, regIA_UTCL1_STATUS_2), + SOC15_REG_ENTRY_STR(GC, 0, regPA_CL_CNTL_STATUS), + SOC15_REG_ENTRY_STR(GC, 0, regRMI_UTCL1_STATUS), + SOC15_REG_ENTRY_STR(GC, 0, regSQC_CACHES), + SOC15_REG_ENTRY_STR(GC, 0, regSQG_STATUS), + SOC15_REG_ENTRY_STR(GC, 0, regWD_UTCL1_STATUS), + SOC15_REG_ENTRY_STR(GC, 0, regGCVM_L2_PROTECTION_FAULT_CNTL2), + SOC15_REG_ENTRY_STR(GC, 0, regGCVM_L2_PROTECTION_FAULT_STATUS_LO32), + SOC15_REG_ENTRY_STR(GC, 0, regGCVM_L2_PROTECTION_FAULT_STATUS_HI32), + SOC15_REG_ENTRY_STR(GC, 0, regCP_DEBUG), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MEC_CNTL), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MES_CNTL), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MES_INSTR_PNTR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_ME_INSTR_PNTR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_PFP_INSTR_PNTR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_CPC_STATUS), + SOC15_REG_ENTRY_STR(GC, 0, regCP_GFX_RS64_INSTR_PNTR0), + SOC15_REG_ENTRY_STR(GC, 0, regCP_GFX_RS64_INSTR_PNTR1), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MEC_RS64_INSTR_PNTR), + /* cp header registers */ + SOC15_REG_ENTRY_STR(GC, 0, regCP_MES_HEADER_DUMP), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MES_HEADER_DUMP), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MES_HEADER_DUMP), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MES_HEADER_DUMP), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MES_HEADER_DUMP), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MES_HEADER_DUMP), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MES_HEADER_DUMP), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MES_HEADER_DUMP), + /* SE status registers */ + SOC15_REG_ENTRY_STR(GC, 0, regGRBM_STATUS_SE0), + SOC15_REG_ENTRY_STR(GC, 0, regGRBM_STATUS_SE1), +}; + +static const struct amdgpu_hwip_reg_entry gc_cp_reg_list_12_1[] = { + /* compute registers */ + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_VMID), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_PERSISTENT_STATE), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_PIPE_PRIORITY), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_QUEUE_PRIORITY), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_QUANTUM), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_PQ_BASE), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_PQ_BASE_HI), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_PQ_RPTR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_PQ_WPTR_POLL_ADDR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_PQ_WPTR_POLL_ADDR_HI), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_PQ_DOORBELL_CONTROL), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_PQ_CONTROL), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_IB_BASE_ADDR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_IB_BASE_ADDR_HI), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_IB_RPTR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_IB_CONTROL), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_DEQUEUE_REQUEST), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_EOP_BASE_ADDR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_EOP_BASE_ADDR_HI), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_EOP_CONTROL), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_EOP_RPTR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_EOP_WPTR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_EOP_EVENTS), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_CTX_SAVE_BASE_ADDR_LO), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_CTX_SAVE_BASE_ADDR_HI), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_CTX_SAVE_CONTROL), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_CNTL_STACK_OFFSET), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_CNTL_STACK_SIZE), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_WG_STATE_OFFSET), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_CTX_SAVE_SIZE), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_GDS_RESOURCE_STATE), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_ERROR), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_EOP_WPTR_MEM), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_PQ_WPTR_LO), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_PQ_WPTR_HI), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_SUSPEND_CNTL_STACK_OFFSET), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_SUSPEND_CNTL_STACK_DW_CNT), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_SUSPEND_WG_STATE_OFFSET), + SOC15_REG_ENTRY_STR(GC, 0, regCP_HQD_DEQUEUE_STATUS), + /* cp header registers */ + SOC15_REG_ENTRY_STR(GC, 0, regCP_MEC_ME1_HEADER_DUMP), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MEC_ME1_HEADER_DUMP), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MEC_ME1_HEADER_DUMP), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MEC_ME1_HEADER_DUMP), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MEC_ME1_HEADER_DUMP), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MEC_ME1_HEADER_DUMP), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MEC_ME1_HEADER_DUMP), + SOC15_REG_ENTRY_STR(GC, 0, regCP_MEC_ME1_HEADER_DUMP), +}; + static void gfx_v12_1_xcc_disable_gpa_mode(struct amdgpu_device *adev, int xcc_id); static void gfx_v12_1_set_ring_funcs(struct amdgpu_device *adev); static void gfx_v12_1_set_irq_funcs(struct amdgpu_device *adev); @@ -1149,6 +1270,155 @@ static int gfx_v12_1_rlc_backdoor_autoload_enable(struct amdgpu_device *adev) return 0; } +static void gfx_v12_1_alloc_ip_dump(struct amdgpu_device *adev) +{ + uint32_t reg_count = ARRAY_SIZE(gc_reg_list_12_1); + uint32_t *ptr, inst, num_xcc; + + num_xcc = NUM_XCC(adev->gfx.xcc_mask); + + ptr = kcalloc(reg_count * num_xcc, sizeof(uint32_t), GFP_KERNEL); + if (!ptr) { + DRM_ERROR("Failed to allocate memory for GFX IP Dump\n"); + adev->gfx.ip_dump_core = NULL; + } else { + adev->gfx.ip_dump_core = ptr; + } + + /* Allocate memory for compute queue registers for all the instances */ + reg_count = ARRAY_SIZE(gc_cp_reg_list_12_1); + inst = adev->gfx.mec.num_mec * adev->gfx.mec.num_pipe_per_mec * + adev->gfx.mec.num_queue_per_pipe; + + ptr = kcalloc(reg_count * inst * num_xcc, sizeof(uint32_t), GFP_KERNEL); + if (!ptr) { + DRM_ERROR("Failed to allocate memory for Compute Queues IP Dump\n"); + adev->gfx.ip_dump_compute_queues = NULL; + } else { + adev->gfx.ip_dump_compute_queues = ptr; + } +} + +static void gfx_v12_1_ip_print(struct amdgpu_ip_block *ip_block, + struct drm_printer *p) +{ + struct amdgpu_device *adev = ip_block->adev; + uint32_t i, j, k; + uint32_t xcc_id, xcc_offset, inst_offset; + uint32_t num_xcc, reg, num_inst; + uint32_t reg_count = ARRAY_SIZE(gc_reg_list_12_1); + + if (!adev->gfx.ip_dump_core) + return; + + num_xcc = NUM_XCC(adev->gfx.xcc_mask); + drm_printf(p, "Number of Instances:%d\n", num_xcc); + for (xcc_id = 0; xcc_id < num_xcc; xcc_id++) { + xcc_offset = xcc_id * reg_count; + drm_printf(p, "\nInstance id:%d\n", xcc_id); + for (i = 0; i < reg_count; i++) + drm_printf(p, "%-50s \t 0x%08x\n", + gc_reg_list_12_1[i].reg_name, + adev->gfx.ip_dump_core[xcc_offset + i]); + } + + /* print compute queue registers for all instances */ + if (!adev->gfx.ip_dump_compute_queues) + return; + + reg_count = ARRAY_SIZE(gc_cp_reg_list_12_1); + drm_printf(p, "\nnum_xcc: %d num_mec: %d num_pipe: %d num_queue: %d\n", + num_xcc, + adev->gfx.mec.num_mec, + adev->gfx.mec.num_pipe_per_mec, + adev->gfx.mec.num_queue_per_pipe); + + num_inst = adev->gfx.mec.num_mec * adev->gfx.mec.num_pipe_per_mec * + adev->gfx.mec.num_queue_per_pipe; + for (xcc_id = 0; xcc_id < num_xcc; xcc_id++) { + xcc_offset = xcc_id * reg_count * num_inst; + inst_offset = 0; + for (i = 0; i < adev->gfx.mec.num_mec; i++) { + for (j = 0; j < adev->gfx.mec.num_pipe_per_mec; j++) { + for (k = 0; k < adev->gfx.mec.num_queue_per_pipe; k++) { + drm_printf(p, + "\nxcc:%d mec:%d, pipe:%d, queue:%d\n", + xcc_id, i, j, k); + for (reg = 0; reg < reg_count; reg++) { + drm_printf(p, + "%-50s \t 0x%08x\n", + gc_cp_reg_list_12_1[reg].reg_name, + adev->gfx.ip_dump_compute_queues + [xcc_offset + inst_offset + + reg]); + } + inst_offset += reg_count; + } + } + } + } +} + +static void gfx_v12_1_ip_dump(struct amdgpu_ip_block *ip_block) +{ + struct amdgpu_device *adev = ip_block->adev; + uint32_t i, j, k; + uint32_t num_xcc, reg, num_inst; + uint32_t xcc_id, xcc_offset, inst_offset; + uint32_t reg_count = ARRAY_SIZE(gc_reg_list_12_1); + + if (!adev->gfx.ip_dump_core) + return; + + num_xcc = NUM_XCC(adev->gfx.xcc_mask); + + amdgpu_gfx_off_ctrl(adev, false); + for (xcc_id = 0; xcc_id < num_xcc; xcc_id++) { + xcc_offset = xcc_id * reg_count; + for (i = 0; i < reg_count; i++) + adev->gfx.ip_dump_core[xcc_offset + i] = + RREG32(SOC15_REG_ENTRY_OFFSET_INST(gc_reg_list_12_1[i], + GET_INST(GC, xcc_id))); + } + amdgpu_gfx_off_ctrl(adev, true); + + /* dump compute queue registers for all instances */ + if (!adev->gfx.ip_dump_compute_queues) + return; + + num_inst = adev->gfx.mec.num_mec * adev->gfx.mec.num_pipe_per_mec * + adev->gfx.mec.num_queue_per_pipe; + reg_count = ARRAY_SIZE(gc_cp_reg_list_12_1); + amdgpu_gfx_off_ctrl(adev, false); + mutex_lock(&adev->srbm_mutex); + for (xcc_id = 0; xcc_id < num_xcc; xcc_id++) { + xcc_offset = xcc_id * reg_count * num_inst; + inst_offset = 0; + for (i = 0; i < adev->gfx.mec.num_mec; i++) { + for (j = 0; j < adev->gfx.mec.num_pipe_per_mec; j++) { + for (k = 0; k < adev->gfx.mec.num_queue_per_pipe; k++) { + /* ME0 is for GFX so start from 1 for CP */ + soc_v1_0_grbm_select(adev, 1 + i, j, k, 0, + GET_INST(GC, xcc_id)); + + for (reg = 0; reg < reg_count; reg++) { + adev->gfx.ip_dump_compute_queues + [xcc_offset + + inst_offset + reg] = + RREG32(SOC15_REG_ENTRY_OFFSET_INST( + gc_cp_reg_list_12_1[reg], + GET_INST(GC, xcc_id))); + } + inst_offset += reg_count; + } + } + } + } + soc_v1_0_grbm_select(adev, 0, 0, 0, 0, 0); + mutex_unlock(&adev->srbm_mutex); + amdgpu_gfx_off_ctrl(adev, true); +} + static int gfx_v12_1_sw_init(struct amdgpu_ip_block *ip_block) { uint16_t major_ver, minor_ver; @@ -1287,6 +1557,8 @@ static int gfx_v12_1_sw_init(struct amdgpu_ip_block *ip_block) if (r) return r; + gfx_v12_1_alloc_ip_dump(adev); + mutex_init(&adev->gfx.mec.reset_mutex); return 0; @@ -1326,6 +1598,9 @@ static int gfx_v12_1_sw_fini(struct amdgpu_ip_block *ip_block) gfx_v12_1_free_microcode(adev); amdgpu_gfx_sysfs_fini(adev); + kfree(adev->gfx.ip_dump_core); + kfree(adev->gfx.ip_dump_compute_queues); + return 0; } @@ -3912,6 +4187,8 @@ static const struct amd_ip_funcs gfx_v12_1_ip_funcs = { .set_clockgating_state = gfx_v12_1_set_clockgating_state, .set_powergating_state = gfx_v12_1_set_powergating_state, .get_clockgating_state = gfx_v12_1_get_clockgating_state, + .dump_ip_state = gfx_v12_1_ip_dump, + .print_ip_state = gfx_v12_1_ip_print, }; static const struct amdgpu_ring_funcs gfx_v12_1_ring_funcs_compute = { From c8a1066ccc9130edcbd2f092231cb71e283abdf5 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Tue, 7 Jul 2026 13:53:26 -0400 Subject: [PATCH 098/577] drm/amdgpu/gfx9: fix IP dump alloc ordering If gfx sysfs init fails, we may leak the ip dump allocations. Reviewed-by: Mukul Joshi Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c index 556e5cb3f4cd..1cad6dc9476e 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v9_0.c @@ -2442,12 +2442,12 @@ static int gfx_v9_0_sw_init(struct amdgpu_ip_block *ip_block) return -EINVAL; } - gfx_v9_0_alloc_ip_dump(adev); - r = amdgpu_gfx_sysfs_init(adev); if (r) return r; + gfx_v9_0_alloc_ip_dump(adev); + return 0; } From c2b218866214bce2a0a3518fc03d976aacfba98e Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Tue, 7 Jul 2026 13:55:01 -0400 Subject: [PATCH 099/577] drm/amdgpu/gfx10: fix IP dump alloc ordering If gfx sysfs init fails, we may leak the ip dump allocations. Reviewed-by: Mukul Joshi Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c index 55342962422b..a998ce77da40 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c @@ -4988,12 +4988,12 @@ static int gfx_v10_0_sw_init(struct amdgpu_ip_block *ip_block) gfx_v10_0_gpu_early_init(adev); - gfx_v10_0_alloc_ip_dump(adev); - r = amdgpu_gfx_sysfs_init(adev); if (r) return r; + gfx_v10_0_alloc_ip_dump(adev); + return 0; } From 601f7a11802ce16207f518b558384bbe16450029 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Tue, 7 Jul 2026 13:55:14 -0400 Subject: [PATCH 100/577] drm/amdgpu/gfx11: fix IP dump alloc ordering If gfx sysfs init fails, we may leak the ip dump allocations. Reviewed-by: Mukul Joshi Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c index 2ef6bc818aba..0cbbdc3694f4 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c @@ -1920,12 +1920,12 @@ static int gfx_v11_0_sw_init(struct amdgpu_ip_block *ip_block) return -EINVAL; } - gfx_v11_0_alloc_ip_dump(adev); - r = amdgpu_gfx_sysfs_init(adev); if (r) return r; + gfx_v11_0_alloc_ip_dump(adev); + adev->gfx.me.use_mmio_for_reset = false; adev->gfx.mec.use_mmio_for_reset = true; From 4ef372319332843c4c99b18d4a943a2122f0ad67 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Tue, 7 Jul 2026 13:55:25 -0400 Subject: [PATCH 101/577] drm/amdgpu/gfx12: fix IP dump alloc ordering If gfx sysfs init fails, we may leak the ip dump allocations. Reviewed-by: Mukul Joshi Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c index 6fedb6df8db9..b3887c5262a0 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c @@ -1597,12 +1597,12 @@ static int gfx_v12_0_sw_init(struct amdgpu_ip_block *ip_block) if (r) return r; - gfx_v12_0_alloc_ip_dump(adev); - r = amdgpu_gfx_sysfs_init(adev); if (r) return r; + gfx_v12_0_alloc_ip_dump(adev); + adev->gfx.me.use_mmio_for_reset = false; adev->gfx.mec.use_mmio_for_reset = true; From 6cefc59d36667b0ed6bea849e2a77e57a69a3139 Mon Sep 17 00:00:00 2001 From: Gabe Teeger Date: Mon, 8 Jun 2026 15:39:04 -0400 Subject: [PATCH 102/577] drm/amd/display: Enable IPS support for DCN4 Variant [Why] DCN4 variant had IPS completely disabled via ips_support = false and dc->config.disable_ips = DMUB_IPS_DISABLE_ALL. Enabling IPS for D3 allows DMUB to manage idle power savings when the display subsystem is inactive, reducing platform power without affecting active display operation. [How] In DCN4 variant resource construct: - Set dc->caps.ips_support = true - Remove dc->config.disable_ips = DMUB_IPS_DISABLE_ALL override This enables DMUB to dynamically manage IPS entry/exit during D3 power state transitions. Also enable zstate by default. Reviewed-by: Ovidiu (Ovi) Bunea Signed-off-by: Gabe Teeger Signed-off-by: Matthew Stewart Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c index 330e82b0974b..1bc2452b271f 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c @@ -1988,11 +1988,10 @@ static bool dcn42b_resource_construct( dc->caps.dmcub_support = true; dc->caps.is_apu = true; dc->caps.seamless_odm = true; - dc->caps.zstate_support = false; - dc->caps.ips_support = false; + dc->caps.zstate_support = true; + dc->caps.ips_support = true; dc->caps.max_v_total = (1 << 15) - 1; dc->caps.vtotal_limited_by_fp2 = true; - dc->config.disable_ips = DMUB_IPS_DISABLE_ALL; /* Color pipeline capabilities */ dc->caps.color.dpp.dcn_arch = 1; From b7d69145907cdefcbd39a70a31eefd30919af9f1 Mon Sep 17 00:00:00 2001 From: Matthew Stewart Date: Thu, 4 Jun 2026 11:36:09 -0400 Subject: [PATCH 103/577] drm/amd/display: Fix DCN42B null registers & register masks [why] DCN42B is missing some register masks, which are causing errors in dmesg. [how] Make DCN42B reuse the DCN42 register lists, and add the missing defines manually. Fixes: 64142f9d51af ("drm/amd/display: Fix DCN42 null registers & register masks") Reviewed-by: Ovidiu (Ovi) Bunea Signed-off-by: Matthew Stewart Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../dc/resource/dcn42b/dcn42b_resource.c | 20 ++- .../dc/resource/dcn42b/dcn42b_resource.h | 116 +----------------- 2 files changed, 20 insertions(+), 116 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c index 1bc2452b271f..18a7fd9136af 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c @@ -22,6 +22,7 @@ #include "dcn35/dcn35_resource.h" #include "dcn321/dcn321_resource.h" #include "dcn401/dcn401_resource.h" +#include "dcn42/dcn42_resource.h" #include "dcn42/dcn42_resource_fpu.h" #include "dcn10/dcn10_ipp.h" @@ -116,6 +117,23 @@ #define regAPG9_APG_DBG_GEN_CONTROL 0x38ae #define regAPG9_APG_DBG_GEN_CONTROL_BASE_IDX 2 +#define regHUBP0_HUBPREQ_DEBUG_DB 0x05f8 +#define regHUBP0_HUBPREQ_DEBUG_DB_BASE_IDX 2 +#define regHUBP0_HUBPREQ_DEBUG 0x05f9 +#define regHUBP0_HUBPREQ_DEBUG_BASE_IDX 2 +#define regHUBP1_HUBPREQ_DEBUG_DB 0x06d4 +#define regHUBP1_HUBPREQ_DEBUG_DB_BASE_IDX 2 +#define regHUBP1_HUBPREQ_DEBUG 0x06d5 +#define regHUBP1_HUBPREQ_DEBUG_BASE_IDX 2 +#define regHUBP2_HUBPREQ_DEBUG_DB 0x07b0 +#define regHUBP2_HUBPREQ_DEBUG_DB_BASE_IDX 2 +#define regHUBP2_HUBPREQ_DEBUG 0x07b1 +#define regHUBP2_HUBPREQ_DEBUG_BASE_IDX 2 +#define regHUBP3_HUBPREQ_DEBUG_DB 0x088c +#define regHUBP3_HUBPREQ_DEBUG_DB_BASE_IDX 2 +#define regHUBP3_HUBPREQ_DEBUG 0x088d +#define regHUBP3_HUBPREQ_DEBUG_BASE_IDX 2 + enum dcn401_clk_src_array_id { DCN401_CLK_SRC_PLL0, DCN401_CLK_SRC_PLL1, @@ -461,7 +479,7 @@ static const struct dcn_optc_mask optc_mask = { OPTC_COMMON_MASK_SH_LIST_DCN42B(_MASK)}; #define hubp_regs_init(id) \ - HUBP_REG_LIST_DCN42B_RI(id) + HUBP_REG_LIST_DCN42_RI(id) static struct dcn_hubp2_registers hubp_regs[4]; diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.h b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.h index 2da3e3c8304a..2824a0e1acc9 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.h +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.h @@ -344,7 +344,6 @@ * DCCG_SRII(PHASE, DP_DTO, 3), * DCCG_SRII(MODULO, DP_DTO, 3), * SR(DSCCLK3_DTO_PARAM), - * SR(HDMISTREAMCLK_CNTL), * SR(SYMCLKD_CLOCK_ENABLE), * SR(SYMCLKE_CLOCK_ENABLE) */ @@ -360,6 +359,7 @@ SR(PHYBSYMCLK_CLOCK_CNTL), \ SR(PHYCSYMCLK_CLOCK_CNTL), \ SR(DPSTREAMCLK_CNTL), \ + SR(HDMISTREAMCLK_CNTL), \ SR(SYMCLK32_SE_CNTL), \ SR(SYMCLK32_LE_CNTL), \ DCCG_SRII(PIXEL_RATE_CNTL, OTG, 0), \ @@ -542,120 +542,6 @@ SRI_ARR(DC_ABM1_ACE_OFFSET_SLOPE_DATA, ABM, id), \ SRI_ARR(DC_ABM1_ACE_PWL_CNTL, ABM, id) -/* HUBP */ -/* Not in DCN42B: HUBPREQ_DEBUG_DB and HUBPREQ_DEBUG */ -#define HUBP_REG_LIST_DCN42B_RI(id) \ - SRI_ARR(DCN_DMDATA_VM_CNTL, HUBPREQ, id), \ - SRI_ARR(FLIP_PARAMETERS_3, HUBPREQ, id), \ - SRI_ARR(FLIP_PARAMETERS_4, HUBPREQ, id), \ - SRI_ARR(FLIP_PARAMETERS_5, HUBPREQ, id), \ - SRI_ARR(FLIP_PARAMETERS_6, HUBPREQ, id), \ - SRI_ARR(VBLANK_PARAMETERS_5, HUBPREQ, id), \ - SRI_ARR(VBLANK_PARAMETERS_6, HUBPREQ, id), \ - HUBP_REG_LIST_DCN_VM_RI(id), \ - SRI_ARR(PREFETCH_SETTINGS, HUBPREQ, id), \ - SRI_ARR(PREFETCH_SETTINGS_C, HUBPREQ, id), \ - SRI_ARR(DCN_VM_SYSTEM_APERTURE_LOW_ADDR, HUBPREQ, id), \ - SRI_ARR(DCN_VM_SYSTEM_APERTURE_HIGH_ADDR, HUBPREQ, id), \ - SRI_ARR(CURSOR_SETTINGS, HUBPREQ, id), \ - SRI_ARR(CURSOR_SURFACE_ADDRESS_HIGH, CURSOR0_, id), \ - SRI_ARR(CURSOR_SURFACE_ADDRESS, CURSOR0_, id), \ - SRI_ARR(CURSOR_SIZE, CURSOR0_, id), \ - SRI_ARR(CURSOR_CONTROL, CURSOR0_, id), \ - SRI_ARR(CURSOR_POSITION, CURSOR0_, id), \ - SRI_ARR(CURSOR_HOT_SPOT, CURSOR0_, id), \ - SRI_ARR(CURSOR_DST_OFFSET, CURSOR0_, id), \ - SRI_ARR(DMDATA_ADDRESS_HIGH, CURSOR0_, id), \ - SRI_ARR(DMDATA_ADDRESS_LOW, CURSOR0_, id), \ - SRI_ARR(DMDATA_CNTL, CURSOR0_, id), \ - SRI_ARR(DMDATA_SW_CNTL, CURSOR0_, id), \ - SRI_ARR(DMDATA_QOS_CNTL, CURSOR0_, id), \ - SRI_ARR(DMDATA_SW_DATA, CURSOR0_, id), \ - SRI_ARR(DMDATA_STATUS, CURSOR0_, id), \ - SRI_ARR(FLIP_PARAMETERS_0, HUBPREQ, id), \ - SRI_ARR(FLIP_PARAMETERS_1, HUBPREQ, id), \ - SRI_ARR(FLIP_PARAMETERS_2, HUBPREQ, id), \ - SRI_ARR(DCN_CUR1_TTU_CNTL0, HUBPREQ, id), \ - SRI_ARR(DCN_CUR1_TTU_CNTL1, HUBPREQ, id), \ - SRI_ARR(DCSURF_FLIP_CONTROL2, HUBPREQ, id), \ - SRI_ARR(VMID_SETTINGS_0, HUBPREQ, id), \ - SRI_ARR(DCHUBP_CNTL, HUBP, id), \ - SRI_ARR(DCSURF_ADDR_CONFIG, HUBP, id), \ - SRI_ARR(DCSURF_TILING_CONFIG, HUBP, id), \ - SRI_ARR(DCSURF_SURFACE_PITCH, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_PITCH_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_CONFIG, HUBP, id), \ - SRI_ARR(DCSURF_FLIP_CONTROL, HUBPREQ, id), \ - SRI_ARR(DCSURF_PRI_VIEWPORT_DIMENSION, HUBP, id), \ - SRI_ARR(DCSURF_PRI_VIEWPORT_START, HUBP, id), \ - SRI_ARR(DCSURF_SEC_VIEWPORT_DIMENSION, HUBP, id), \ - SRI_ARR(DCSURF_SEC_VIEWPORT_START, HUBP, id), \ - SRI_ARR(DCSURF_PRI_VIEWPORT_DIMENSION_C, HUBP, id), \ - SRI_ARR(DCSURF_PRI_VIEWPORT_START_C, HUBP, id), \ - SRI_ARR(DCSURF_SEC_VIEWPORT_DIMENSION_C, HUBP, id), \ - SRI_ARR(DCSURF_SEC_VIEWPORT_START_C, HUBP, id), \ - SRI_ARR(DCSURF_PRIMARY_SURFACE_ADDRESS_HIGH, HUBPREQ, id), \ - SRI_ARR(DCSURF_PRIMARY_SURFACE_ADDRESS, HUBPREQ, id), \ - SRI_ARR(DCSURF_SECONDARY_SURFACE_ADDRESS_HIGH, HUBPREQ, id), \ - SRI_ARR(DCSURF_SECONDARY_SURFACE_ADDRESS, HUBPREQ, id), \ - SRI_ARR(DCSURF_PRIMARY_META_SURFACE_ADDRESS_HIGH, HUBPREQ, id), \ - SRI_ARR(DCSURF_PRIMARY_META_SURFACE_ADDRESS, HUBPREQ, id), \ - SRI_ARR(DCSURF_SECONDARY_META_SURFACE_ADDRESS_HIGH, HUBPREQ, id), \ - SRI_ARR(DCSURF_SECONDARY_META_SURFACE_ADDRESS, HUBPREQ, id), \ - SRI_ARR(DCSURF_PRIMARY_SURFACE_ADDRESS_HIGH_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_PRIMARY_SURFACE_ADDRESS_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_SECONDARY_SURFACE_ADDRESS_HIGH_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_SECONDARY_SURFACE_ADDRESS_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_PRIMARY_META_SURFACE_ADDRESS_HIGH_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_PRIMARY_META_SURFACE_ADDRESS_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_SECONDARY_META_SURFACE_ADDRESS_HIGH_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_SECONDARY_META_SURFACE_ADDRESS_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_INUSE, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_INUSE_HIGH, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_INUSE_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_INUSE_HIGH_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_EARLIEST_INUSE, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_EARLIEST_INUSE_HIGH, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_EARLIEST_INUSE_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_EARLIEST_INUSE_HIGH_C, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_CONTROL, HUBPREQ, id), \ - SRI_ARR(DCSURF_SURFACE_FLIP_INTERRUPT, HUBPREQ, id), \ - SRI_ARR(HUBPRET_CONTROL, HUBPRET, id), \ - SRI_ARR(HUBPRET_READ_LINE_STATUS, HUBPRET, id), \ - SRI_ARR(DCN_EXPANSION_MODE, HUBPREQ, id), \ - SRI_ARR(DCHUBP_REQ_SIZE_CONFIG, HUBP, id), \ - SRI_ARR(DCHUBP_REQ_SIZE_CONFIG_C, HUBP, id), \ - SRI_ARR(BLANK_OFFSET_0, HUBPREQ, id), \ - SRI_ARR(BLANK_OFFSET_1, HUBPREQ, id), \ - SRI_ARR(DST_DIMENSIONS, HUBPREQ, id), \ - SRI_ARR(DST_AFTER_SCALER, HUBPREQ, id), \ - SRI_ARR(VBLANK_PARAMETERS_0, HUBPREQ, id), \ - SRI_ARR(REF_FREQ_TO_PIX_FREQ, HUBPREQ, id), \ - SRI_ARR(VBLANK_PARAMETERS_1, HUBPREQ, id), \ - SRI_ARR(VBLANK_PARAMETERS_3, HUBPREQ, id), \ - SRI_ARR(NOM_PARAMETERS_4, HUBPREQ, id), \ - SRI_ARR(NOM_PARAMETERS_5, HUBPREQ, id), \ - SRI_ARR(PER_LINE_DELIVERY_PRE, HUBPREQ, id), \ - SRI_ARR(PER_LINE_DELIVERY, HUBPREQ, id), \ - SRI_ARR(VBLANK_PARAMETERS_2, HUBPREQ, id), \ - SRI_ARR(VBLANK_PARAMETERS_4, HUBPREQ, id), \ - SRI_ARR(NOM_PARAMETERS_6, HUBPREQ, id), \ - SRI_ARR(NOM_PARAMETERS_7, HUBPREQ, id), \ - SRI_ARR(DCN_TTU_QOS_WM, HUBPREQ, id), \ - SRI_ARR(DCN_GLOBAL_TTU_CNTL, HUBPREQ, id), \ - SRI_ARR(DCN_SURF0_TTU_CNTL0, HUBPREQ, id), \ - SRI_ARR(DCN_SURF0_TTU_CNTL1, HUBPREQ, id), \ - SRI_ARR(DCN_SURF1_TTU_CNTL0, HUBPREQ, id), \ - SRI_ARR(DCN_SURF1_TTU_CNTL1, HUBPREQ, id), \ - SRI_ARR(DCN_CUR0_TTU_CNTL0, HUBPREQ, id), \ - SRI_ARR(DCN_CUR0_TTU_CNTL1, HUBPREQ, id), \ - SRI_ARR(HUBP_CLK_CNTL, HUBP, id), \ - SRI_ARR(HUBPRET_READ_LINE_VALUE, HUBPRET, id), \ - SRI_ARR(DCHUBP_MALL_CONFIG, HUBP, id), \ - SRI_ARR(DCHUBP_VMPG_CONFIG, HUBP, id), \ - SRI_ARR(UCLK_PSTATE_FORCE, HUBPREQ, id), \ - SRI_ARR(HUBP_3DLUT_DLG_PARAM, CURSOR0_, id), \ - HUBP_3DLUT_FL_REG_LIST_DCN401(id) struct dcn42b_resource_pool { struct resource_pool base; }; From 219fb1f4c5b91a58b2db55ff2fe2d06412e39888 Mon Sep 17 00:00:00 2001 From: Gabe Teeger Date: Wed, 10 Jun 2026 11:42:50 -0400 Subject: [PATCH 104/577] drm/amd/display: Enable zstate support and fix seamless boot [Why & How] Port seamless boot fix from DCN42 to DCN42B and enable zstate support for DCN42B. Reviewed-by: Ovidiu (Ovi) Bunea Signed-off-by: Gabe Teeger Signed-off-by: Matthew Stewart Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c | 8 ++++---- .../drm/amd/display/dc/resource/dcn42b/dcn42b_resource.h | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c index 18a7fd9136af..172836dfd679 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c @@ -253,10 +253,10 @@ static struct bios_registers bios_regs; static struct dce110_clk_src_regs clk_src_regs[5]; static const struct dce110_clk_src_shift cs_shift = { - CS_COMMON_MASK_SH_LIST_DCN3_2(__SHIFT) + CS_COMMON_MASK_SH_LIST_DCN4_0_1(__SHIFT) }; static const struct dce110_clk_src_mask cs_mask = { - CS_COMMON_MASK_SH_LIST_DCN3_2(_MASK) + CS_COMMON_MASK_SH_LIST_DCN4_0_1(_MASK) }; #define abm_regs_init(id) \ ABM_DCN42B_REG_LIST_RI(id) @@ -801,7 +801,7 @@ static const struct dc_debug_options debug_defaults_drv = { } }, .seamless_boot_odm_combine = DML_FAIL_SOURCE_PIXEL_FORMAT, - .enable_z9_disable_interface = false, /* Allow support for the PMFW interface for disable Z9*/ + .enable_z9_disable_interface = true, /* Allow support for the PMFW interface for disable Z9*/ .minimum_z8_residency_time = 1, /* Always allow when other conditions are met */ .support_eDP1_5 = true, .use_max_lb = true, @@ -823,7 +823,7 @@ static const struct dc_debug_options debug_defaults_drv = { .disable_timeout = true, .min_disp_clk_khz = 50000, .static_screen_wait_frames = 2, - .disable_z10 = true, + .disable_z10 = false, .ignore_pg = true, .disable_stutter_for_wm_program = true, .min_deep_sleep_dcfclk_khz = 8000, diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.h b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.h index 2824a0e1acc9..1a6e0e7be0fa 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.h +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.h @@ -518,7 +518,8 @@ SRII_ARR_2(MODULO, DP_DTO, 2, index), \ SRII_ARR_2(PIXEL_RATE_CNTL, OTG, 0, index), \ SRII_ARR_2(PIXEL_RATE_CNTL, OTG, 1, index), \ - SRII_ARR_2(PIXEL_RATE_CNTL, OTG, 2, index) + SRII_ARR_2(PIXEL_RATE_CNTL, OTG, 2, index), \ + SR_ARR(OTG_PIXEL_RATE_DIV, index) /* ABM */ #define ABM_DCN42B_REG_LIST_RI(id) \ From ffa0cfa9d616aeea2adf182bda8e4dabc95f4531 Mon Sep 17 00:00:00 2001 From: Matthew Stewart Date: Wed, 24 Jun 2026 16:34:03 -0400 Subject: [PATCH 105/577] drm/amd/display: Add passive dongle handling in force_to_use_aux case [why] Need special handling for passive HDMI dongles with I2C over AUX. Reviewed-by: Aurabindo Pillai Signed-off-by: Matthew Stewart Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../drm/amd/display/dc/link/link_detection.c | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/dc/link/link_detection.c b/drivers/gpu/drm/amd/display/dc/link/link_detection.c index 281a7c5acaca..2abe9d3a68d5 100644 --- a/drivers/gpu/drm/amd/display/dc/link/link_detection.c +++ b/drivers/gpu/drm/amd/display/dc/link/link_detection.c @@ -497,6 +497,12 @@ static void query_hdcp_capability(enum signal_type signal, struct dc_link *link) msg22.data = link->hdcp_caps.rx_caps.raw; msg22.length = sizeof(link->hdcp_caps.rx_caps.raw); msg22.msg_id = HDCP_MESSAGE_ID_RX_CAPS; + if (link->force_to_use_aux && (signal == SIGNAL_TYPE_HDMI_TYPE_A)) { + // case with passive dongle with i2c over aux + msg22.data = &link->hdcp_caps.rx_caps.fields.version; + msg22.length = sizeof(link->hdcp_caps.rx_caps.fields.version); + msg22.msg_id = HDCP_MESSAGE_ID_HDCP2VERSION; + } } else { msg22.data = &link->hdcp_caps.rx_caps.fields.version; msg22.length = sizeof(link->hdcp_caps.rx_caps.fields.version); @@ -597,7 +603,24 @@ static bool detect_dp(struct dc_link *link, if (sink_caps->transaction_type == DDC_TRANSACTION_TYPE_I2C_OVER_AUX) { sink_caps->signal = SIGNAL_TYPE_DISPLAY_PORT; if (!detect_dp_sink_caps(link)) { - return false; + if (link->force_to_use_aux) { + sink_caps->signal = dp_passive_dongle_detection(link->ddc, sink_caps, audio_support); + link->dpcd_caps.dongle_type = sink_caps->dongle_type; + link->dpcd_caps.is_dongle_type_one = sink_caps->is_dongle_type_one; + link->dpcd_caps.dpcd_rev.raw = 0; + /* Type 1 dongles do not work with I2C over Aux and also some of + * Type 2 dongles do not support I2C over Aux well, so for these + * cases when native Aux transactons fails and I2C over Aux fails + * report that nothing is connected, as we can't tell is it bad + * DP sink or bad passive dongle. + */ + if (sink_caps->dongle_type == DISPLAY_DONGLE_NONE) + return false; + else + return true; + } else { + return false; + } } if (is_dp_branch_device(link)) From c96d79332efa760fd18d9be90b9162fb6acfac89 Mon Sep 17 00:00:00 2001 From: Srinivasan Shanmugam Date: Mon, 22 Jun 2026 14:58:52 +0530 Subject: [PATCH 106/577] drm/amd/display: Check dc_link before applying DSC policy apply_dsc_policy_for_stream() decides whether Display Stream Compression (DSC) should be enabled for a display stream based on display capabilities, link bandwidth and user overrides. Smatch reports that apply_dsc_policy_for_stream() dereferences aconnector->dc_link before checking whether it is NULL. Add an early NULL check for aconnector->dc_link before the first dereference and remove the later redundant NULL check in the HDMI_FRL path. Fixes: 0e967e086e75 ("drm/amd/display: Extract connector and encoder code to amdgpu_dm_connector") Reported-by: Dan Carpenter Cc: Roman Li Cc: Alex Hung Cc: Tom Chung Cc: Aurabindo Pillai Reviewed-by: George Zhang Signed-off-by: Srinivasan Shanmugam Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index 57be01c60f7d..ab14f811a85d 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c @@ -1258,6 +1258,9 @@ static void apply_dsc_policy_for_stream(struct amdgpu_dm_connector *aconnector, drm_connector->display_info.max_dsc_bpp; struct dc_dsc_config_options dsc_options = {0}; + if (!aconnector->dc_link) + return; + dc_dsc_get_default_config_option(dc, &dsc_options); dsc_options.max_target_bpp_limit_override_x16 = max_dsc_target_bpp_limit_override * 16; @@ -1319,7 +1322,7 @@ static void apply_dsc_policy_for_stream(struct amdgpu_dm_connector *aconnector, (dsc_caps->is_frl == 1) ? "HDMI FRL RX" : "DP-HDMI PCON"); } } - } else if (aconnector->dc_link && sink->sink_signal == SIGNAL_TYPE_HDMI_FRL) { + } else if (sink->sink_signal == SIGNAL_TYPE_HDMI_FRL) { struct dc_dsc_policy dsc_policy = {0}; frl_verified_link_cap = dc_link_get_frl_link_cap(stream->link); From 2a5810782369b2128394e783d30728393c7bd62d Mon Sep 17 00:00:00 2001 From: James Lin Date: Mon, 22 Jun 2026 13:38:02 +0800 Subject: [PATCH 107/577] drm/amd/display: program dither on all OPP heads under ODM combine [why] The IGT test kms_plane_alpha_blend@coverage-vs-premult-vs-constant failed on DCN with ODM 2:1 combine enabled. The test compares the CRC of a coverage-blended plane against a premultiplied-blended plane and expects them to be identical. With ODM combine the whole-frame CRC mismatched (left segment matched, right segment differed). Root cause: dc_stream_set_dither_option() iterated the pipe_ctx array and broke out at the first pipe matching the stream (the OTG master / OPP instance 0), then programmed bit-depth reduction (dither) on that single OPP only. Under ODM combine there is more than one OPP head, so the secondary ODM segment (OPP instance 1) was never reprogrammed. When CRC capture requested dither off, only the left OPP got updated while the right OPP kept its previous dither setting, producing a different CRC for the right segment and thus a whole-frame CRC mismatch. [how] Use resource_get_otg_master_for_stream() and resource_get_opp_heads_for_otg_master() to retrieve every OPP head of the stream, then loop over all of them and call transform_set_pixel_storage_depth() and opp_program_bit_depth_reduction() on each. This keeps all OPP heads in sync under ODM combine, so dither is applied (or cleared) identically across every segment and the CRCs match. Reviewed-by: Wayne Lin Signed-off-by: James Lin Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/core/dc.c | 53 +++++++++++++++--------- 1 file changed, 34 insertions(+), 19 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/core/dc.c b/drivers/gpu/drm/amd/display/dc/core/dc.c index 28339e4b6d67..ec5d6ddeefbe 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc.c @@ -769,22 +769,23 @@ void dc_stream_set_dither_option(struct dc_stream_state *stream, { struct bit_depth_reduction_params params; struct dc_link *link = stream->link; - struct pipe_ctx *pipes = NULL; + struct resource_context *res_ctx = &link->dc->current_state->res_ctx; + struct pipe_ctx *otg_master; + struct pipe_ctx *opp_heads[MAX_PIPES]; + int opp_cnt; int i; - for (i = 0; i < MAX_PIPES; i++) { - if (link->dc->current_state->res_ctx.pipe_ctx[i].stream == - stream) { - pipes = &link->dc->current_state->res_ctx.pipe_ctx[i]; - break; - } - } - - if (!pipes) + otg_master = resource_get_otg_master_for_stream(res_ctx, stream); + if (!otg_master) return; if (option > DITHER_OPTION_MAX) return; + opp_cnt = resource_get_opp_heads_for_otg_master(otg_master, res_ctx, opp_heads); + + if (opp_cnt == 0) + return; + dc_exit_ips_for_hw_access(stream->ctx->dc); stream->dither_option = option; @@ -793,16 +794,30 @@ void dc_stream_set_dither_option(struct dc_stream_state *stream, resource_build_bit_depth_reduction_params(stream, ¶ms); stream->bit_depth_params = params; - if (pipes->plane_res.xfm && - pipes->plane_res.xfm->funcs->transform_set_pixel_storage_depth) { - pipes->plane_res.xfm->funcs->transform_set_pixel_storage_depth( - pipes->plane_res.xfm, - pipes->plane_res.scl_data.lb_params.depth, - &stream->bit_depth_params); - } + /* + * Program bit-depth reduction (dither) on every OPP head of the + * stream. Under ODM combine there is more than one OPP head and they + * must all be kept in sync, otherwise (e.g. when CRC capture requests + * dither off) a secondary ODM segment can keep dither enabled and + * produce a different CRC than the primary segment. + */ + for (i = 0; i < opp_cnt; i++) { + struct pipe_ctx *opp_head = opp_heads[i]; - pipes->stream_res.opp->funcs-> - opp_program_bit_depth_reduction(pipes->stream_res.opp, ¶ms); + if (opp_head->plane_res.xfm && + opp_head->plane_res.xfm->funcs->transform_set_pixel_storage_depth) { + opp_head->plane_res.xfm->funcs->transform_set_pixel_storage_depth( + opp_head->plane_res.xfm, + opp_head->plane_res.scl_data.lb_params.depth, + &stream->bit_depth_params); + } + + if (opp_head->stream_res.opp && + opp_head->stream_res.opp->funcs->opp_program_bit_depth_reduction) { + opp_head->stream_res.opp->funcs->opp_program_bit_depth_reduction( + opp_head->stream_res.opp, ¶ms); + } + } } bool dc_stream_set_gamut_remap(struct dc *dc, const struct dc_stream_state *stream) From 8fa813b7fd1eccbed13e126166cf764f1f11a7d3 Mon Sep 17 00:00:00 2001 From: Dominik Kaszewski Date: Fri, 19 Jun 2026 11:22:07 +0200 Subject: [PATCH 108/577] drm/amd/display: Remove sink usage from DPMS [Why] stream->sink is optional and can be null, so should always be checked before dereference. Additionally, most of its usage in DPMS sequences is for stream->sink->link, which can be replaced with stream->link, as the two should always be the same. [How] * Replace stream->sink->link in DPMS on/off * Add assert to USB4 BW allocation where sink is required * Avoid inconsistencies in resource access, e.g. don't repeat stream->link after it was already saved to a local variable * Pull out effective VPG calculation to helper getter * Formatting fixes Reviewed-by: Nicholas Kazlauskas Signed-off-by: Dominik Kaszewski Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/dc/link/link_dpms.c | 180 +++++++++--------- 1 file changed, 91 insertions(+), 89 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/link/link_dpms.c b/drivers/gpu/drm/amd/display/dc/link/link_dpms.c index 42e7f0e9b3ce..479a8fbfdc5d 100644 --- a/drivers/gpu/drm/amd/display/dc/link/link_dpms.c +++ b/drivers/gpu/drm/amd/display/dc/link/link_dpms.c @@ -2296,6 +2296,8 @@ static enum dc_status enable_link( static bool allocate_usb4_bandwidth_for_stream(struct dc_stream_state *stream, int stream_bw) { + ASSERT(stream->sink); + struct dc_link *link = stream->sink->link; int req_bw = stream_bw; @@ -2342,6 +2344,8 @@ static bool allocate_usb4_bandwidth_for_stream(struct dc_stream_state *stream, i static bool allocate_usb4_bandwidth(struct dc_stream_state *stream) { + ASSERT(stream->sink); + bool ret; int bw = dc_bandwidth_in_kbps_from_timing(&stream->timing, @@ -2361,38 +2365,43 @@ static bool deallocate_usb4_bandwidth(struct dc_stream_state *stream) return ret; } +static struct vpg *get_vpg(struct pipe_ctx *pipe_ctx) +{ + if (dc_is_hdmi_frl_signal(pipe_ctx->stream->signal)) + return pipe_ctx->stream_res.hpo_frl_stream_enc->vpg; + else if (dp_is_128b_132b_signal(pipe_ctx)) + return pipe_ctx->stream_res.hpo_dp_stream_enc->vpg; + else + return pipe_ctx->stream_res.stream_enc->vpg; +} + void link_set_dpms_off(struct pipe_ctx *pipe_ctx) { - struct dc *dc = pipe_ctx->stream->ctx->dc; - struct dc_stream_state *stream = pipe_ctx->stream; - struct dc_link *link = stream->sink->link; - struct vpg *vpg = pipe_ctx->stream_res.stream_enc->vpg; - enum dp_panel_mode panel_mode_dp = dp_get_panel_mode(link); - DC_LOGGER_INIT(pipe_ctx->stream->ctx->logger); + struct dc_stream_state *stream = pipe_ctx->stream; + struct dc *dc = stream->ctx->dc; + struct dc_link *link = stream->link; + struct vpg *vpg = get_vpg(pipe_ctx); + enum dp_panel_mode panel_mode_dp = dp_get_panel_mode(link); ASSERT(is_master_pipe_for_link(link, pipe_ctx)); - if (dp_is_128b_132b_signal(pipe_ctx)) - vpg = pipe_ctx->stream_res.hpo_dp_stream_enc->vpg; - if (dc_is_hdmi_frl_signal(pipe_ctx->stream->signal)) - vpg = pipe_ctx->stream_res.hpo_frl_stream_enc->vpg; - if (dc_is_virtual_signal(pipe_ctx->stream->signal)) + if (dc_is_virtual_signal(stream->signal)) return; - if (pipe_ctx->stream->sink) { - if (pipe_ctx->stream->sink->sink_signal != SIGNAL_TYPE_VIRTUAL && - pipe_ctx->stream->sink->sink_signal != SIGNAL_TYPE_NONE) { + if (stream->sink) { + if (stream->sink->sink_signal != SIGNAL_TYPE_VIRTUAL && + stream->sink->sink_signal != SIGNAL_TYPE_NONE) { DC_LOG_DC("%s pipe_ctx dispname=%s signal=%x link=%d sink_count=%d\n", __func__, - pipe_ctx->stream->sink->edid_caps.display_name, - pipe_ctx->stream->signal, link->link_index, link->sink_count); + stream->sink->edid_caps.display_name, + stream->signal, link->link_index, link->sink_count); } } link_wait_for_unlocked(link); - if (!pipe_ctx->stream->sink->edid_caps.panel_patch.skip_avmute) { - if (dc_is_hdmi_signal(pipe_ctx->stream->signal)) + if (stream->sink && !stream->sink->edid_caps.panel_patch.skip_avmute) { + if (dc_is_hdmi_signal(stream->signal)) set_avmute(pipe_ctx, true); } @@ -2402,15 +2411,15 @@ void link_set_dpms_off(struct pipe_ctx *pipe_ctx) dc->hwss.blank_stream(pipe_ctx); if (pipe_ctx->link_config.dp_tunnel_settings.should_use_dp_bw_allocation) - deallocate_usb4_bandwidth(pipe_ctx->stream); + deallocate_usb4_bandwidth(stream); - if (pipe_ctx->stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST) + if (stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST) deallocate_mst_payload(pipe_ctx); - else if (dc_is_dp_sst_signal(pipe_ctx->stream->signal) && + else if (dc_is_dp_sst_signal(stream->signal) && dp_is_128b_132b_signal(pipe_ctx)) update_sst_payload(pipe_ctx, false); - if (dc_is_hdmi_signal(pipe_ctx->stream->signal)) { + if (dc_is_hdmi_signal(stream->signal)) { struct ext_hdmi_settings settings = {0}; enum engine_id eng_id = pipe_ctx->stream_res.stream_enc->id; @@ -2433,7 +2442,7 @@ void link_set_dpms_off(struct pipe_ctx *pipe_ctx) } } - if (pipe_ctx->stream->signal == SIGNAL_TYPE_DISPLAY_PORT && + if (stream->signal == SIGNAL_TYPE_DISPLAY_PORT && !dp_is_128b_132b_signal(pipe_ctx)) { /* In DP1.x SST mode, our encoder will go to TPS1 @@ -2443,18 +2452,18 @@ void link_set_dpms_off(struct pipe_ctx *pipe_ctx) * state machine. * In DP2 or MST mode, our encoder will stay video active */ - disable_link(pipe_ctx->stream->link, &pipe_ctx->link_res, pipe_ctx->stream->signal); + disable_link(link, &pipe_ctx->link_res, stream->signal); dc->hwss.disable_stream(pipe_ctx); } else { dc->hwss.disable_stream(pipe_ctx); - disable_link(pipe_ctx->stream->link, &pipe_ctx->link_res, pipe_ctx->stream->signal); + disable_link(link, &pipe_ctx->link_res, stream->signal); } edp_set_panel_assr(link, pipe_ctx, &panel_mode_dp, false); - if (pipe_ctx->stream->timing.flags.DSC) { - if (dc_is_dp_signal(pipe_ctx->stream->signal)) + if (stream->timing.flags.DSC) { + if (dc_is_dp_signal(stream->signal)) link_set_dsc_enable(pipe_ctx, false); - else if (dc_is_hdmi_frl_signal(pipe_ctx->stream->signal)) + else if (dc_is_hdmi_frl_signal(stream->signal)) link_set_dsc_on_stream(pipe_ctx, false); } if (dp_is_128b_132b_signal(pipe_ctx)) { @@ -2468,7 +2477,7 @@ void link_set_dpms_off(struct pipe_ctx *pipe_ctx) /* for psp not exist case */ if (link->connector_signal == SIGNAL_TYPE_EDP && dc->debug.psp_disabled_wa) { /* reset internal save state to default since eDP is off */ - enum dp_panel_mode panel_mode = dp_get_panel_mode(pipe_ctx->stream->link); + enum dp_panel_mode panel_mode = dp_get_panel_mode(link); /* since current psp not loaded, we need to reset it to default */ link->panel_mode = panel_mode; } @@ -2478,62 +2487,57 @@ void link_set_dpms_on( struct dc_state *state, struct pipe_ctx *pipe_ctx) { - struct dc *dc = pipe_ctx->stream->ctx->dc; + DC_LOGGER_INIT(pipe_ctx->stream->ctx->logger); struct dc_stream_state *stream = pipe_ctx->stream; - struct dc_link *link = stream->sink->link; + struct dc *dc = stream->ctx->dc; + struct dc_link *link = stream->link; enum dc_status status; struct link_encoder *link_enc = pipe_ctx->link_res.dio_link_enc; enum otg_out_mux_dest otg_out_dest = OUT_MUX_DIO; - struct vpg *vpg = pipe_ctx->stream_res.stream_enc->vpg; + struct vpg *vpg = get_vpg(pipe_ctx); const struct link_hwss *link_hwss = get_link_hwss(link, &pipe_ctx->link_res); bool apply_edp_fast_boot_optimization = - pipe_ctx->stream->apply_edp_fast_boot_optimization; - - DC_LOGGER_INIT(pipe_ctx->stream->ctx->logger); + stream->apply_edp_fast_boot_optimization; ASSERT(is_master_pipe_for_link(link, pipe_ctx)); - if (dp_is_128b_132b_signal(pipe_ctx)) - vpg = pipe_ctx->stream_res.hpo_dp_stream_enc->vpg; - if (dc_is_hdmi_frl_signal(pipe_ctx->stream->signal)) - vpg = pipe_ctx->stream_res.hpo_frl_stream_enc->vpg; - if (dc_is_virtual_signal(pipe_ctx->stream->signal)) + if (dc_is_virtual_signal(stream->signal)) return; - if (pipe_ctx->stream->sink) { - if (pipe_ctx->stream->sink->sink_signal != SIGNAL_TYPE_VIRTUAL && - pipe_ctx->stream->sink->sink_signal != SIGNAL_TYPE_NONE) { + if (stream->sink) { + if (stream->sink->sink_signal != SIGNAL_TYPE_VIRTUAL && + stream->sink->sink_signal != SIGNAL_TYPE_NONE) { DC_LOG_DC("%s pipe_ctx dispname=%s signal=%x link=%d sink_count=%d\n", __func__, - pipe_ctx->stream->sink->edid_caps.display_name, - pipe_ctx->stream->signal, + stream->sink->edid_caps.display_name, + stream->signal, link->link_index, link->sink_count); } } - link_wait_for_unlocked(stream->link); + link_wait_for_unlocked(link); if (!dc->config.unify_link_enc_assignment) link_enc = link_enc_cfg_get_link_enc(link); ASSERT(link_enc); - if (!dc_is_virtual_signal(pipe_ctx->stream->signal) - && !dc_is_hdmi_frl_signal(pipe_ctx->stream->signal) + if (!dc_is_virtual_signal(stream->signal) + && !dc_is_hdmi_frl_signal(stream->signal) && !dp_is_128b_132b_signal(pipe_ctx)) { if (link_enc) link_enc->funcs->setup( link_enc, - pipe_ctx->stream->signal); + stream->signal); } - pipe_ctx->stream->link->link_state_valid = true; + link->link_state_valid = true; - if (dc_is_hdmi_frl_signal(pipe_ctx->stream->signal)) - hdmi_frl_decide_link_settings(stream, &stream->link->frl_link_settings, &pipe_ctx->dsc_padding_params); + if (dc_is_hdmi_frl_signal(stream->signal)) + hdmi_frl_decide_link_settings(stream, &link->frl_link_settings, &pipe_ctx->dsc_padding_params); if (pipe_ctx->stream_res.tg->funcs->set_out_mux) { if (dp_is_128b_132b_signal(pipe_ctx)) otg_out_dest = OUT_MUX_HPO_DP; - else if (dc_is_hdmi_frl_signal(pipe_ctx->stream->signal)) + else if (dc_is_hdmi_frl_signal(stream->signal)) otg_out_dest = OUT_MUX_HPO_FRL; else otg_out_dest = OUT_MUX_DIO; @@ -2542,7 +2546,7 @@ void link_set_dpms_on( link_hwss->setup_stream_attribute(pipe_ctx); - pipe_ctx->stream->apply_edp_fast_boot_optimization = false; + stream->apply_edp_fast_boot_optimization = false; // Enable VPG before building infoframe if (vpg && vpg->funcs->vpg_poweron) @@ -2551,15 +2555,15 @@ void link_set_dpms_on( resource_build_info_frame(pipe_ctx); dc->hwss.update_info_frame(pipe_ctx); - if (dc_is_dp_signal(pipe_ctx->stream->signal)) + if (dc_is_dp_signal(stream->signal)) dp_trace_source_sequence(link, DPCD_SOURCE_SEQ_AFTER_UPDATE_INFO_FRAME); /* Do not touch link on seamless boot optimization. */ - if (pipe_ctx->stream->apply_seamless_boot_optimization) { - pipe_ctx->stream->dpms_off = false; + if (stream->apply_seamless_boot_optimization) { + stream->dpms_off = false; /* Still enable stream features & audio on seamless boot for DP external displays */ - if (pipe_ctx->stream->signal == SIGNAL_TYPE_DISPLAY_PORT) { + if (stream->signal == SIGNAL_TYPE_DISPLAY_PORT) { enable_stream_features(pipe_ctx); dc->hwss.enable_audio_stream(pipe_ctx); } @@ -2569,11 +2573,11 @@ void link_set_dpms_on( } /* eDP lit up by bios already, no need to enable again. */ - if (pipe_ctx->stream->signal == SIGNAL_TYPE_EDP && + if (stream->signal == SIGNAL_TYPE_EDP && apply_edp_fast_boot_optimization && - !pipe_ctx->stream->timing.flags.DSC && + !stream->timing.flags.DSC && !pipe_ctx->next_odm_pipe) { - pipe_ctx->stream->dpms_off = false; + stream->dpms_off = false; update_psp_stream_config(pipe_ctx, false); if (link->is_dds) { @@ -2586,7 +2590,7 @@ void link_set_dpms_on( return; } - if (pipe_ctx->stream->dpms_off) + if (stream->dpms_off) return; /* For Dp tunneling link, a pending HPD means that we have a race condition between processing @@ -2604,9 +2608,9 @@ void link_set_dpms_on( * will be automatically set at a later time when the video is enabled * (DP_VID_STREAM_EN = 1). */ - if (pipe_ctx->stream->timing.flags.DSC) { - if (dc_is_dp_signal(pipe_ctx->stream->signal) || - dc_is_virtual_signal(pipe_ctx->stream->signal)) + if (stream->timing.flags.DSC) { + if (dc_is_dp_signal(stream->signal) || + dc_is_virtual_signal(stream->signal)) link_set_dsc_enable(pipe_ctx, true); } @@ -2617,7 +2621,7 @@ void link_set_dpms_on( if (status != DC_OK) { DC_LOG_WARNING("enabling link %u failed: %d\n", - pipe_ctx->stream->link->link_index, + link->link_index, status); /* Abort stream enable *unless* the failure was due to @@ -2627,19 +2631,18 @@ void link_set_dpms_on( */ if ((status != DC_FAIL_DP_LINK_TRAINING && status != DC_FAIL_HDMI_FRL_LINK_TRAINING) || - pipe_ctx->stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST) { - if (false == stream->link->link_status.link_active) - disable_link(stream->link, &pipe_ctx->link_res, - pipe_ctx->stream->signal); + stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST) { + if (false == link->link_status.link_active) + disable_link(link, &pipe_ctx->link_res, + stream->signal); BREAK_TO_DEBUGGER(); return; } } - if (pipe_ctx->stream->timing.flags.DSC && - dc_is_hdmi_frl_signal(pipe_ctx->stream->signal)) - //TODO: bring HDMI FRL in line with DP - link_set_dsc_on_stream(pipe_ctx, true); + if (stream->timing.flags.DSC && dc_is_hdmi_frl_signal(stream->signal)) + //TODO: bring HDMI FRL in line with DP + link_set_dsc_on_stream(pipe_ctx, true); /* turn off otg test pattern if enable */ if (pipe_ctx->stream_res.tg->funcs->set_test_pattern) @@ -2651,37 +2654,37 @@ void link_set_dpms_on( * as a workaround for the incorrect value being applied * from transmitter control. */ - if (!(dc_is_virtual_signal(pipe_ctx->stream->signal) || - dc_is_hdmi_frl_signal(pipe_ctx->stream->signal) || + if (!(dc_is_virtual_signal(stream->signal) || + dc_is_hdmi_frl_signal(stream->signal) || dp_is_128b_132b_signal(pipe_ctx))) { if (link_enc) link_enc->funcs->setup( link_enc, - pipe_ctx->stream->signal); + stream->signal); } dc->hwss.enable_stream(pipe_ctx); /* Set DPS PPS SDP (AKA "info frames") */ - if (pipe_ctx->stream->timing.flags.DSC) { - if (dc_is_dp_signal(pipe_ctx->stream->signal) || - dc_is_virtual_signal(pipe_ctx->stream->signal)) { + if (stream->timing.flags.DSC) { + if (dc_is_dp_signal(stream->signal) || + dc_is_virtual_signal(stream->signal)) { dp_set_dsc_on_rx(pipe_ctx, true); link_set_dsc_pps_packet(pipe_ctx, true, true); } } - if (dc_is_dp_signal(pipe_ctx->stream->signal)) + if (dc_is_dp_signal(stream->signal)) dp_set_hblank_reduction_on_rx(pipe_ctx); if (pipe_ctx->link_config.dp_tunnel_settings.should_use_dp_bw_allocation) - allocate_usb4_bandwidth(pipe_ctx->stream); + allocate_usb4_bandwidth(stream); - if (pipe_ctx->stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST) + if (stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST) allocate_mst_payload(pipe_ctx); - else if (dc_is_dp_sst_signal(pipe_ctx->stream->signal) && + else if (dc_is_dp_sst_signal(stream->signal) && dp_is_128b_132b_signal(pipe_ctx)) update_sst_payload(pipe_ctx, true); @@ -2690,23 +2693,22 @@ void link_set_dpms_on( * training and stream unblank resolves the corruption issue. * This is workaround. */ - if (pipe_ctx->stream->signal == SIGNAL_TYPE_EDP && + if (stream->signal == SIGNAL_TYPE_EDP && link->is_display_mux_present) msleep(20); dc->hwss.unblank_stream(pipe_ctx, - &pipe_ctx->stream->link->cur_link_settings); + &link->cur_link_settings); if (stream->sink_patches.delay_ignore_msa > 0) msleep(stream->sink_patches.delay_ignore_msa); - if (dc_is_dp_signal(pipe_ctx->stream->signal)) + if (dc_is_dp_signal(stream->signal)) enable_stream_features(pipe_ctx); update_psp_stream_config(pipe_ctx, false); dc->hwss.enable_audio_stream(pipe_ctx); - if (dc_is_hdmi_signal(pipe_ctx->stream->signal)) { + if (dc_is_hdmi_signal(stream->signal)) set_avmute(pipe_ctx, false); - } } From 560cb7437c877329e74f2ae660f1b84992c54ea5 Mon Sep 17 00:00:00 2001 From: Chenyu Chen Date: Mon, 22 Jun 2026 16:25:09 +0800 Subject: [PATCH 109/577] drm/amd/display: Fix dead-code panel type detection from DID [Why] In amdgpu_dm_set_panel_type() the DID fallback branch tested and wrote link->panel_type, while the rest of the function tracked the result in the local variable panel_type. The final assignment unconditionally overwrites link->panel_type from panel_type, so the value derived from DID was always discarded, making the DID branch dead code. [How] Use the local panel_type variable in the DID fallback branch so that the DID result participates in the source priority (VSDB -> DPCD -> DID -> vendor luminance heuristic -> LCD default) and is preserved by the final assignment. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Alex Hung Signed-off-by: Chenyu Chen Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index ab14f811a85d..bbcd65e1a7c6 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c @@ -420,11 +420,11 @@ static void amdgpu_dm_set_panel_type(struct amdgpu_dm_connector *aconnector) } /* If VSDB and DPCD didn't determine panel type, check DID */ - if (link->panel_type == PANEL_TYPE_NONE) { + if (panel_type == PANEL_TYPE_NONE) { if (display_info->panel_type == DRM_MODE_PANEL_TYPE_LCD) - link->panel_type = PANEL_TYPE_LCD; + panel_type = PANEL_TYPE_LCD; else if (display_info->panel_type == DRM_MODE_PANEL_TYPE_OLED) - link->panel_type = PANEL_TYPE_OLED; + panel_type = PANEL_TYPE_OLED; } if (panel_type == PANEL_TYPE_NONE) { From 9dfd0da5e3caecee5dac3954997ae5acc8ad737e Mon Sep 17 00:00:00 2001 From: Chenyu Chen Date: Mon, 22 Jun 2026 16:31:53 +0800 Subject: [PATCH 110/577] drm/amd/display: Add KUnit tests for CACP on Linux [Why] The CACP enablement on Linux added panel-type detection and CACP capability logic that was not covered by KUnit tests. [How] Export amdgpu_dm_set_panel_type() and amdgpu_dm_update_cacp_caps() for KUnit via STATIC_IFN_KUNIT/EXPORT_IF_KUNIT and add unit tests covering: - amdgpu_dm_update_cacp_caps(): IP version gating (including the 3.1.6 exclusion), eDP/LVDS signal handling, non-eDP signals, and OLED vs LCD panel types. - amdgpu_dm_set_panel_type(): VSDB OLED/MINILED, DPCD oled/miniled bits, the DID path (OLED and LCD), the vendor luminance heuristic, and the LCD default. Update the should_create_sysfs backlight tests to reflect the new OLED/CACP behavior (OLED with/without CACP and LCD eDP panels). Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Alex Hung Signed-off-by: Chenyu Chen Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../display/amdgpu_dm/amdgpu_dm_connector.c | 6 +- .../display/amdgpu_dm/amdgpu_dm_connector.h | 2 + .../tests/amdgpu_dm_backlight_test.c | 43 ++- .../tests/amdgpu_dm_connector_test.c | 351 ++++++++++++++++++ 4 files changed, 392 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index bbcd65e1a7c6..f77af83ade39 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c @@ -392,7 +392,7 @@ amdgpu_dm_find_first_crtc_matching_connector(struct drm_atomic_commit *state, return NULL; } -static void amdgpu_dm_set_panel_type(struct amdgpu_dm_connector *aconnector) +STATIC_IFN_KUNIT void amdgpu_dm_set_panel_type(struct amdgpu_dm_connector *aconnector) { struct drm_connector *connector = &aconnector->base; struct drm_display_info *display_info = &connector->display_info; @@ -459,8 +459,9 @@ static void amdgpu_dm_set_panel_type(struct amdgpu_dm_connector *aconnector) drm_dbg_kms(aconnector->base.dev, "Panel type: %d\n", link->panel_type); } +EXPORT_IF_KUNIT(amdgpu_dm_set_panel_type); -static void amdgpu_dm_update_cacp_caps(struct amdgpu_dm_connector *aconnector) +STATIC_IFN_KUNIT void amdgpu_dm_update_cacp_caps(struct amdgpu_dm_connector *aconnector) { struct amdgpu_device *adev = drm_to_adev(aconnector->base.dev); struct dc_link *link = aconnector->dc_link; @@ -485,6 +486,7 @@ static void amdgpu_dm_update_cacp_caps(struct amdgpu_dm_connector *aconnector) drm_dbg_kms(aconnector->base.dev, "cacp_supported: %d\n", link->panel_config.cacp.cacp_supported); } +EXPORT_IF_KUNIT(amdgpu_dm_update_cacp_caps); DEFINE_FREE(sink_release, struct dc_sink *, if (_T) dc_sink_release(_T)) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h index c5b8b13f8f06..23843ac99642 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h @@ -158,5 +158,7 @@ enum dc_aspect_ratio get_aspect_ratio(const struct drm_display_mode *mode_in); void decide_crtc_timing_for_drm_display_mode(struct drm_display_mode *drm_mode, const struct drm_display_mode *native_mode, bool scale_enabled); +void amdgpu_dm_set_panel_type(struct amdgpu_dm_connector *aconnector); +void amdgpu_dm_update_cacp_caps(struct amdgpu_dm_connector *aconnector); #endif #endif /* __AMDGPU_DM_CONNECTOR_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c index 53df4b668f2e..adb896022a27 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c @@ -1034,10 +1034,13 @@ static void dm_test_should_create_sysfs_no_backlight_index(struct kunit *test) } /** - * dm_test_should_create_sysfs_aux_backlight - Test AUX backlight disables sysfs + * dm_test_should_create_sysfs_oled_no_cacp - Test OLED without CACP disables sysfs * @test: The KUnit test context + * + * A non-LCD panel that does not support CACP must not expose the sysfs + * backlight interface. */ -static void dm_test_should_create_sysfs_aux_backlight(struct kunit *test) +static void dm_test_should_create_sysfs_oled_no_cacp(struct kunit *test) { struct dm_backlight_connector_fixture fixture = {}; int saved_abm_level = amdgpu_dm_get_abm_level_param(); @@ -1045,7 +1048,8 @@ static void dm_test_should_create_sysfs_aux_backlight(struct kunit *test) amdgpu_dm_set_abm_level_param(-1); setup_test_connector(test, &fixture, 0, SIGNAL_TYPE_EDP); fixture.aconnector->base.connector_type = DRM_MODE_CONNECTOR_eDP; - fixture.adev->dm.backlight_caps[0].aux_support = true; + fixture.link->panel_type = PANEL_TYPE_OLED; + fixture.link->panel_config.cacp.cacp_supported = false; KUNIT_EXPECT_FALSE(test, amdgpu_dm_should_create_sysfs(fixture.aconnector)); @@ -1053,10 +1057,33 @@ static void dm_test_should_create_sysfs_aux_backlight(struct kunit *test) } /** - * dm_test_should_create_sysfs_pwm_backlight - Test PWM backlight enables sysfs + * dm_test_should_create_sysfs_oled_cacp - Test OLED with CACP enables sysfs + * @test: The KUnit test context + * + * An OLED panel that supports CACP must expose the sysfs backlight + * interface so the ABM/CACP level can be controlled. + */ +static void dm_test_should_create_sysfs_oled_cacp(struct kunit *test) +{ + struct dm_backlight_connector_fixture fixture = {}; + int saved_abm_level = amdgpu_dm_get_abm_level_param(); + + amdgpu_dm_set_abm_level_param(-1); + setup_test_connector(test, &fixture, 0, SIGNAL_TYPE_EDP); + fixture.aconnector->base.connector_type = DRM_MODE_CONNECTOR_eDP; + fixture.link->panel_type = PANEL_TYPE_OLED; + fixture.link->panel_config.cacp.cacp_supported = true; + + KUNIT_EXPECT_TRUE(test, amdgpu_dm_should_create_sysfs(fixture.aconnector)); + + amdgpu_dm_set_abm_level_param(saved_abm_level); +} + +/** + * dm_test_should_create_sysfs_lcd_panel - Test LCD eDP panel enables sysfs * @test: The KUnit test context */ -static void dm_test_should_create_sysfs_pwm_backlight(struct kunit *test) +static void dm_test_should_create_sysfs_lcd_panel(struct kunit *test) { struct dm_backlight_connector_fixture fixture = {}; int saved_abm_level = amdgpu_dm_get_abm_level_param(); @@ -1065,7 +1092,6 @@ static void dm_test_should_create_sysfs_pwm_backlight(struct kunit *test) setup_test_connector(test, &fixture, 0, SIGNAL_TYPE_EDP); fixture.aconnector->base.connector_type = DRM_MODE_CONNECTOR_eDP; fixture.link->panel_type = PANEL_TYPE_LCD; - fixture.adev->dm.backlight_caps[0].aux_support = false; KUNIT_EXPECT_TRUE(test, amdgpu_dm_should_create_sysfs(fixture.aconnector)); @@ -1225,8 +1251,9 @@ static struct kunit_case dm_backlight_test_cases[] = { KUNIT_CASE(dm_test_should_create_sysfs_abm_forced), KUNIT_CASE(dm_test_should_create_sysfs_non_edp), KUNIT_CASE(dm_test_should_create_sysfs_no_backlight_index), - KUNIT_CASE(dm_test_should_create_sysfs_aux_backlight), - KUNIT_CASE(dm_test_should_create_sysfs_pwm_backlight), + KUNIT_CASE(dm_test_should_create_sysfs_oled_no_cacp), + KUNIT_CASE(dm_test_should_create_sysfs_oled_cacp), + KUNIT_CASE(dm_test_should_create_sysfs_lcd_panel), /* amdgpu_dm_setup_backlight_device */ KUNIT_CASE(dm_test_setup_backlight_device_non_edp), KUNIT_CASE(dm_test_setup_backlight_device_connection_none), diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c index aa451064b30c..45210c64a000 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c @@ -2015,6 +2015,341 @@ static void dm_test_is_freesync_video_mode_no_match(struct kunit *test) KUNIT_EXPECT_FALSE(test, amdgpu_dm_is_freesync_video_mode(&candidate, aconnector)); } +/* Tests for amdgpu_dm_update_cacp_caps() */ + +struct dm_cacp_fixture { + struct amdgpu_device *adev; + struct amdgpu_dm_connector *aconnector; + struct dc_link *link; +}; + +static void setup_cacp_fixture(struct kunit *test, + struct dm_cacp_fixture *fixture, + enum signal_type signal, + enum dc_panel_type panel_type) +{ + fixture->adev = kunit_kzalloc(test, sizeof(*fixture->adev), GFP_KERNEL); + fixture->aconnector = kunit_kzalloc(test, sizeof(*fixture->aconnector), + GFP_KERNEL); + fixture->link = kunit_kzalloc(test, sizeof(*fixture->link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fixture->adev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fixture->aconnector); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fixture->link); + + fixture->aconnector->dc_link = fixture->link; + fixture->aconnector->base.dev = &fixture->adev->ddev; + fixture->link->connector_signal = signal; + fixture->link->panel_type = panel_type; +} + +/** + * dm_test_cacp_caps_unsupported_ip - Test CACP disabled on old DCE IP + * @test: The KUnit test context + * + * A DCE IP version below 3.1.4 does not support CACP, so cacp_supported + * must remain false regardless of signal or panel type. + */ +static void dm_test_cacp_caps_unsupported_ip(struct kunit *test) +{ + struct dm_cacp_fixture fixture = {}; + + setup_cacp_fixture(test, &fixture, SIGNAL_TYPE_EDP, PANEL_TYPE_OLED); + fixture.adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 1, 2); + + amdgpu_dm_update_cacp_caps(fixture.aconnector); + + KUNIT_EXPECT_FALSE(test, fixture.link->panel_config.cacp.cacp_supported); +} + +/** + * dm_test_cacp_caps_excluded_ip_316 - Test CACP disabled on DCE IP 3.1.6 + * @test: The KUnit test context + * + * DCE IP version 3.1.6 is explicitly excluded from CACP support. + */ +static void dm_test_cacp_caps_excluded_ip_316(struct kunit *test) +{ + struct dm_cacp_fixture fixture = {}; + + setup_cacp_fixture(test, &fixture, SIGNAL_TYPE_EDP, PANEL_TYPE_OLED); + fixture.adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 1, 6); + + amdgpu_dm_update_cacp_caps(fixture.aconnector); + + KUNIT_EXPECT_FALSE(test, fixture.link->panel_config.cacp.cacp_supported); +} + +/** + * dm_test_cacp_caps_edp_oled_supported - Test CACP enabled on eDP OLED + * @test: The KUnit test context + * + * A supported DCE IP version on an eDP OLED panel must enable CACP. + */ +static void dm_test_cacp_caps_edp_oled_supported(struct kunit *test) +{ + struct dm_cacp_fixture fixture = {}; + + setup_cacp_fixture(test, &fixture, SIGNAL_TYPE_EDP, PANEL_TYPE_OLED); + fixture.adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 1, 4); + + amdgpu_dm_update_cacp_caps(fixture.aconnector); + + KUNIT_EXPECT_TRUE(test, fixture.link->panel_config.cacp.cacp_supported); +} + +/** + * dm_test_cacp_caps_lvds_oled_supported - Test CACP enabled on LVDS OLED + * @test: The KUnit test context + * + * LVDS is an accepted connector signal for CACP support. + */ +static void dm_test_cacp_caps_lvds_oled_supported(struct kunit *test) +{ + struct dm_cacp_fixture fixture = {}; + + setup_cacp_fixture(test, &fixture, SIGNAL_TYPE_LVDS, PANEL_TYPE_OLED); + fixture.adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 5, 0); + + amdgpu_dm_update_cacp_caps(fixture.aconnector); + + KUNIT_EXPECT_TRUE(test, fixture.link->panel_config.cacp.cacp_supported); +} + +/** + * dm_test_cacp_caps_non_edp_signal - Test CACP disabled on non-eDP/LVDS signal + * @test: The KUnit test context + * + * External DisplayPort is neither eDP nor LVDS, so CACP must be disabled. + */ +static void dm_test_cacp_caps_non_edp_signal(struct kunit *test) +{ + struct dm_cacp_fixture fixture = {}; + + setup_cacp_fixture(test, &fixture, SIGNAL_TYPE_DISPLAY_PORT, + PANEL_TYPE_OLED); + fixture.adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 1, 4); + + amdgpu_dm_update_cacp_caps(fixture.aconnector); + + KUNIT_EXPECT_FALSE(test, fixture.link->panel_config.cacp.cacp_supported); +} + +/** + * dm_test_cacp_caps_lcd_panel - Test CACP disabled on LCD panel + * @test: The KUnit test context + * + * Plain LCD panels do not benefit from CACP, so it must be disabled even + * on a supported IP version and eDP signal. + */ +static void dm_test_cacp_caps_lcd_panel(struct kunit *test) +{ + struct dm_cacp_fixture fixture = {}; + + setup_cacp_fixture(test, &fixture, SIGNAL_TYPE_EDP, PANEL_TYPE_LCD); + fixture.adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 1, 4); + + amdgpu_dm_update_cacp_caps(fixture.aconnector); + + KUNIT_EXPECT_FALSE(test, fixture.link->panel_config.cacp.cacp_supported); +} + +/* Tests for amdgpu_dm_set_panel_type() */ + +struct dm_panel_type_fixture { + struct amdgpu_device *adev; + struct drm_device *drm; + struct amdgpu_dm_connector *aconnector; + struct dc_link *link; + struct dc_sink *sink; +}; + +static void setup_panel_type_fixture(struct kunit *test, + struct dm_panel_type_fixture *fixture) +{ + struct device *dev; + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + fixture->drm = __drm_kunit_helper_alloc_drm_device(test, dev, + sizeof(*fixture->adev), + offsetof(struct amdgpu_device, ddev), + DRIVER_MODESET); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fixture->drm); + fixture->adev = drm_to_adev(fixture->drm); + + fixture->aconnector = kunit_kzalloc(test, sizeof(*fixture->aconnector), + GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fixture->aconnector); + fixture->link = kunit_kzalloc(test, sizeof(*fixture->link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fixture->link); + fixture->sink = kunit_kzalloc(test, sizeof(*fixture->sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fixture->sink); + + fixture->aconnector->dc_link = fixture->link; + drmm_connector_init(fixture->drm, &fixture->aconnector->base, + &dm_test_connector_funcs, DRM_MODE_CONNECTOR_eDP, + NULL); +} + +/** + * dm_test_set_panel_type_vsdb_oled - Test VSDB OLED maps to PANEL_TYPE_OLED + * @test: The KUnit test context + */ +static void dm_test_set_panel_type_vsdb_oled(struct kunit *test) +{ + struct dm_panel_type_fixture fixture = {}; + + setup_panel_type_fixture(test, &fixture); + fixture.aconnector->base.display_info.amd_vsdb.panel_type = + AMD_VSDB_PANEL_TYPE_OLED; + + amdgpu_dm_set_panel_type(fixture.aconnector); + + KUNIT_EXPECT_EQ(test, (int)fixture.link->panel_type, + (int)PANEL_TYPE_OLED); +} + +/** + * dm_test_set_panel_type_vsdb_miniled - Test VSDB MINILED maps to PANEL_TYPE_MINILED + * @test: The KUnit test context + */ +static void dm_test_set_panel_type_vsdb_miniled(struct kunit *test) +{ + struct dm_panel_type_fixture fixture = {}; + + setup_panel_type_fixture(test, &fixture); + fixture.aconnector->base.display_info.amd_vsdb.panel_type = + AMD_VSDB_PANEL_TYPE_MINILED; + + amdgpu_dm_set_panel_type(fixture.aconnector); + + KUNIT_EXPECT_EQ(test, (int)fixture.link->panel_type, + (int)PANEL_TYPE_MINILED); +} + +/** + * dm_test_set_panel_type_dpcd_oled - Test DPCD oled bit maps to PANEL_TYPE_OLED + * @test: The KUnit test context + */ +static void dm_test_set_panel_type_dpcd_oled(struct kunit *test) +{ + struct dm_panel_type_fixture fixture = {}; + + setup_panel_type_fixture(test, &fixture); + fixture.link->dpcd_sink_ext_caps.bits.oled = 1; + + amdgpu_dm_set_panel_type(fixture.aconnector); + + KUNIT_EXPECT_EQ(test, (int)fixture.link->panel_type, + (int)PANEL_TYPE_OLED); +} + +/** + * dm_test_set_panel_type_dpcd_miniled - Test DPCD miniled bit maps to PANEL_TYPE_MINILED + * @test: The KUnit test context + */ +static void dm_test_set_panel_type_dpcd_miniled(struct kunit *test) +{ + struct dm_panel_type_fixture fixture = {}; + + setup_panel_type_fixture(test, &fixture); + fixture.link->dpcd_sink_ext_caps.bits.miniled = 1; + + amdgpu_dm_set_panel_type(fixture.aconnector); + + KUNIT_EXPECT_EQ(test, (int)fixture.link->panel_type, + (int)PANEL_TYPE_MINILED); +} + +/** + * dm_test_set_panel_type_did_oled - Test DID OLED maps to PANEL_TYPE_OLED + * @test: The KUnit test context + * + * When VSDB and DPCD do not identify the panel, a DID panel type of + * DRM_MODE_PANEL_TYPE_OLED must map to PANEL_TYPE_OLED. + */ +static void dm_test_set_panel_type_did_oled(struct kunit *test) +{ + struct dm_panel_type_fixture fixture = {}; + + setup_panel_type_fixture(test, &fixture); + fixture.aconnector->base.display_info.panel_type = + DRM_MODE_PANEL_TYPE_OLED; + + amdgpu_dm_set_panel_type(fixture.aconnector); + + KUNIT_EXPECT_EQ(test, (int)fixture.link->panel_type, + (int)PANEL_TYPE_OLED); +} + +/** + * dm_test_set_panel_type_did_lcd - Test DID LCD maps to PANEL_TYPE_LCD + * @test: The KUnit test context + * + * When VSDB and DPCD do not identify the panel, a DID panel type of + * DRM_MODE_PANEL_TYPE_LCD must map to PANEL_TYPE_LCD. + */ +static void dm_test_set_panel_type_did_lcd(struct kunit *test) +{ + struct dm_panel_type_fixture fixture = {}; + + setup_panel_type_fixture(test, &fixture); + fixture.aconnector->base.display_info.panel_type = + DRM_MODE_PANEL_TYPE_LCD; + + amdgpu_dm_set_panel_type(fixture.aconnector); + + KUNIT_EXPECT_EQ(test, (int)fixture.link->panel_type, + (int)PANEL_TYPE_LCD); +} + +/** + * dm_test_set_panel_type_vendor_lum_heuristic - Test vendor luminance heuristic maps to MINILED + * @test: The KUnit test context + * + * A panel from the specific vendor whose first luminance range is at least + * 1.5x the second is treated as a mini-LED panel. + */ +static void dm_test_set_panel_type_vendor_lum_heuristic(struct kunit *test) +{ + struct dm_panel_type_fixture fixture = {}; + struct drm_amd_vsdb_info *vsdb; + + setup_panel_type_fixture(test, &fixture); + fixture.link->local_sink = fixture.sink; + fixture.sink->edid_caps.manufacturer_id = DDC_MANUFACTURERNAME_SAMSUNG; + + vsdb = &fixture.aconnector->base.display_info.amd_vsdb; + vsdb->version = 1; + vsdb->luminance_range1.max_luminance = 3000; + vsdb->luminance_range2.max_luminance = 1000; + + amdgpu_dm_set_panel_type(fixture.aconnector); + + KUNIT_EXPECT_EQ(test, (int)fixture.link->panel_type, + (int)PANEL_TYPE_MINILED); +} + +/** + * dm_test_set_panel_type_defaults_to_lcd - Test undetermined panel defaults to LCD + * @test: The KUnit test context + * + * When no source identifies the panel, the type now defaults to + * PANEL_TYPE_LCD instead of remaining PANEL_TYPE_NONE. + */ +static void dm_test_set_panel_type_defaults_to_lcd(struct kunit *test) +{ + struct dm_panel_type_fixture fixture = {}; + + setup_panel_type_fixture(test, &fixture); + + amdgpu_dm_set_panel_type(fixture.aconnector); + + KUNIT_EXPECT_EQ(test, (int)fixture.link->panel_type, + (int)PANEL_TYPE_LCD); +} + static struct kunit_case amdgpu_dm_connector_tests[] = { /* get_subconnector_type */ KUNIT_CASE(dm_test_subconnector_type_none), @@ -2143,6 +2478,22 @@ static struct kunit_case amdgpu_dm_connector_tests[] = { KUNIT_CASE(dm_test_is_freesync_video_mode_null_mode), KUNIT_CASE(dm_test_is_freesync_video_mode_match), KUNIT_CASE(dm_test_is_freesync_video_mode_no_match), + /* amdgpu_dm_update_cacp_caps */ + KUNIT_CASE(dm_test_cacp_caps_unsupported_ip), + KUNIT_CASE(dm_test_cacp_caps_excluded_ip_316), + KUNIT_CASE(dm_test_cacp_caps_edp_oled_supported), + KUNIT_CASE(dm_test_cacp_caps_lvds_oled_supported), + KUNIT_CASE(dm_test_cacp_caps_non_edp_signal), + KUNIT_CASE(dm_test_cacp_caps_lcd_panel), + /* amdgpu_dm_set_panel_type */ + KUNIT_CASE(dm_test_set_panel_type_vsdb_oled), + KUNIT_CASE(dm_test_set_panel_type_vsdb_miniled), + KUNIT_CASE(dm_test_set_panel_type_dpcd_oled), + KUNIT_CASE(dm_test_set_panel_type_dpcd_miniled), + KUNIT_CASE(dm_test_set_panel_type_did_oled), + KUNIT_CASE(dm_test_set_panel_type_did_lcd), + KUNIT_CASE(dm_test_set_panel_type_vendor_lum_heuristic), + KUNIT_CASE(dm_test_set_panel_type_defaults_to_lcd), {} }; From 78d36c5b53e5c7c3cb7ffb31e0ff6251fd1dcacb Mon Sep 17 00:00:00 2001 From: Austin Zheng Date: Tue, 9 Jun 2026 19:01:13 -0400 Subject: [PATCH 111/577] drm/amd/display: Add Debug Option To Enable Per-DPM De-rate Usage [Why] DML has been updated to use per-DPM derates when provided but per-DPM de-rates have not been finalized. Need to validate to see what values should be stored in the bounding box. [How] Add debug options to set custom derates per DPM (starting at DPM0) and their values Each entry in the custom derate expects the derates to be stored in the following format: bits 0-7: dram_derate_percent_pixel bits 8-15: fclk_derate_percent bits 16-23: dcfclk_derate_percent bits 24-31 are unused. e.g. Using the value 0x414020 will set the following derates for DPM0 DPM0: 0x20, 0x40, 0x41 for dram, fclk, and dcfclk respectively Note that global derate value will be used if the per-DPM derate is 0. Reviewed-by: Dillon Varone Signed-off-by: Austin Zheng Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/dc.h | 2 ++ .../dcn401/dcn401_soc_and_ip_translator.c | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h index 2b59a0e9ca38..c78fdfc0ef4b 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -1289,6 +1289,8 @@ struct dc_debug_options { bool enable_replay_esd_recovery; uint8_t iommu_mismatch_temp_wka; bool disable_dynamic_expansion_for_test_pattern; + uint32_t dml21_custom_derate_num_dpms; + uint32_t dml21_custom_derate_at_dpm[DML2_MAX_NUM_DPM_LVL]; }; diff --git a/drivers/gpu/drm/amd/display/dc/soc_and_ip_translator/dcn401/dcn401_soc_and_ip_translator.c b/drivers/gpu/drm/amd/display/dc/soc_and_ip_translator/dcn401/dcn401_soc_and_ip_translator.c index 89f7ccd7f81f..0c8e652c3532 100644 --- a/drivers/gpu/drm/amd/display/dc/soc_and_ip_translator/dcn401/dcn401_soc_and_ip_translator.c +++ b/drivers/gpu/drm/amd/display/dc/soc_and_ip_translator/dcn401/dcn401_soc_and_ip_translator.c @@ -269,6 +269,22 @@ void dcn401_update_soc_bb_with_values_from_software_policy(struct dml2_soc_bb *s if (dc->bb_overrides.sr_enter_plus_exit_z8_time_ns) soc_bb->power_management_parameters.z8_stutter_enter_plus_exit_latency_us = dc->bb_overrides.sr_enter_plus_exit_z8_time_ns / 1000.0; + + /* Override per-dpm derates based on a custom derate table. + * Global derate value will be used for derates that aren't populated + * 3 derates for a single DPM level: + * bits 0-7: dram_derate_percent_pixel + * bits 8-15: fclk_derate_percent + * bits 16-23: dcfclk_derate_percent + */ + for (unsigned int i = 0; i < dc->debug.dml21_custom_derate_num_dpms; i++) { + soc_bb->qos_parameters.derate_table_per_dpm.system_active_derates_per_dpm.dram_derate_percent_pixel[i] + = dc->debug.dml21_custom_derate_at_dpm[i] & 0xFF; + soc_bb->qos_parameters.derate_table_per_dpm.system_active_derates_per_dpm.fclk_derate_percent[i] + = (dc->debug.dml21_custom_derate_at_dpm[i] >> 8) & 0xFF; + soc_bb->qos_parameters.derate_table_per_dpm.system_active_derates_per_dpm.dcfclk_derate_percent[i] + = (dc->debug.dml21_custom_derate_at_dpm[i] >> 16) & 0xFF; + } } static void apply_soc_bb_updates(struct dml2_soc_bb *soc_bb, const struct dc *dc, const struct dml2_configuration_options *config) From d52de56938c38e70b4b77250912ee9e816cc145e Mon Sep 17 00:00:00 2001 From: Dmytro Laktyushkin Date: Thu, 4 Jun 2026 09:26:22 -0400 Subject: [PATCH 112/577] drm/amd/display: update memclk clock table read for dcn42 Change memclk table to match dcfclk size to avoid fine grained mapping in dml2.1 Reviewed-by: Charlene Liu Signed-off-by: Dmytro Laktyushkin Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../amd/display/dc/clk_mgr/dcn42/dcn42_clk_mgr.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42/dcn42_clk_mgr.c b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42/dcn42_clk_mgr.c index d01e5969a670..3baac7fa313a 100644 --- a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42/dcn42_clk_mgr.c +++ b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42/dcn42_clk_mgr.c @@ -999,15 +999,21 @@ void dcn42_get_smu_clocks(struct clk_mgr_internal *clk_mgr_int) clk_mgr_base->bw_params->clk_table.num_entries_per_clk.num_fclk_levels = dpm_clks->NumFclkLevelsEnabled; clk_mgr_base->bw_params->clk_table.num_entries = dpm_clks->NumFclkLevelsEnabled; - /* Memory Pstate table is in reverse order*/ + /* Memory Pstate table is in reverse order for dcn42. Proper way to map this is for pmfw to provide an fclk indexed uclk key since we consume fclk matched pairs.*/ ASSERT(dpm_clks->NumMemPstatesEnabled <= NUM_MEM_PSTATE_LEVELS); if (dpm_clks->NumMemPstatesEnabled > NUM_MEM_PSTATE_LEVELS) dpm_clks->NumMemPstatesEnabled = NUM_MEM_PSTATE_LEVELS; - for (i = 0; i < dpm_clks->NumMemPstatesEnabled; i++) { - clk_mgr_base->bw_params->clk_table.entries[dpm_clks->NumMemPstatesEnabled - 1 - i].memclk_mhz = dpm_clks->MemPstateTable[i].MemClk; - clk_mgr_base->bw_params->clk_table.entries[dpm_clks->NumMemPstatesEnabled - 1 - i].wck_ratio = dcn42_convert_wck_ratio(dpm_clks->MemPstateTable[i].WckRatio) ; + for (i = 0; i < dpm_clks->NumDcfClkLevelsEnabled; i++) { + if (i < dpm_clks->NumMemPstatesEnabled) { + clk_mgr_base->bw_params->clk_table.entries[dpm_clks->NumMemPstatesEnabled - 1 - i].memclk_mhz = dpm_clks->MemPstateTable[i].MemClk; + clk_mgr_base->bw_params->clk_table.entries[dpm_clks->NumMemPstatesEnabled - 1 - i].wck_ratio = dcn42_convert_wck_ratio(dpm_clks->MemPstateTable[i].WckRatio); + } else { + clk_mgr_base->bw_params->clk_table.entries[i].memclk_mhz = dpm_clks->MemPstateTable[0].MemClk; + clk_mgr_base->bw_params->clk_table.entries[i].wck_ratio = dcn42_convert_wck_ratio(dpm_clks->MemPstateTable[0].WckRatio); + } } - clk_mgr_base->bw_params->clk_table.num_entries_per_clk.num_memclk_levels = dpm_clks->NumMemPstatesEnabled; + + clk_mgr_base->bw_params->clk_table.num_entries_per_clk.num_memclk_levels = dpm_clks->NumDcfClkLevelsEnabled; /* DTBCLK*/ clk_mgr_base->bw_params->clk_table.entries[0].dtbclk_mhz = 600; /* Fixed on platform */ From 5a22cc7c724dc4ced818b2eae1db7433f467a761 Mon Sep 17 00:00:00 2001 From: Bhuvanachandra Pinninti Date: Mon, 22 Jun 2026 16:12:07 +0530 Subject: [PATCH 113/577] drm/amd/display: Fix dc_stream_remove_writeback dropping wrong writeback entries. [why] The matching entry was disabled but still copied during compaction, so a removed pipe could survive and overwrite a valid entry, leaving num_wb_info wrong. [how] Skip every entry matching dwb_pipe_inst and compact only survivors. Covered by test_dc_stream_writeback_drc_and_remove.And added new test cases for coverage. Reviewed-by: Ilya Bakoulin Signed-off-by: Bhuvanachandra Pinninti Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/dc/core/dc_stream.c | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_stream.c b/drivers/gpu/drm/amd/display/dc/core/dc_stream.c index 7666cdc78f4e..dbc12640b01c 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc_stream.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc_stream.c @@ -720,20 +720,17 @@ bool dc_stream_remove_writeback(struct dc *dc, return false; } - /* remove writeback info for disabled writeback pipes from stream */ + /* remove writeback info for the requested writeback pipe from stream */ for (i = 0, j = 0; i < stream->num_wb_info; i++) { - if (stream->writeback_info[i].wb_enabled) { + /* drop every entry that targets the pipe being removed */ + if (stream->writeback_info[i].dwb_pipe_inst == dwb_pipe_inst) + continue; - if (stream->writeback_info[i].dwb_pipe_inst == dwb_pipe_inst) - stream->writeback_info[i].wb_enabled = false; - - /* trim the array */ - if (j < i) { - memcpy(&stream->writeback_info[j], &stream->writeback_info[i], - sizeof(struct dc_writeback_info)); - j++; - } - } + /* keep this entry, compacting it down when earlier entries were removed */ + if (j != i) + memcpy(&stream->writeback_info[j], &stream->writeback_info[i], + sizeof(struct dc_writeback_info)); + j++; } stream->num_wb_info = j; From 3b1fdd64133268aeccf8be488f52ff24998bab52 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Thu, 18 Jun 2026 21:12:21 -0600 Subject: [PATCH 114/577] drm/amd/display: Add KUnit tests for ism Add KUnit coverage for the following ISM functions: - dm_ism_next_state(): state transitions for running, busy, hysteresis-wait, optimized-idle, and aborted states - dm_ism_get_sso_delay(): null stream, zero frames, 1080p60 3-frame, and 4k60 1-frame calculations - dm_ism_get_idle_allow_delay(): null stream, zero filter/entry/delay frames, short-idle filtering, wrap-around, old history cutoff, mixed durations, and entry count exceeding history size - amdgpu_dm_ism_init(): initial state setup - amdgpu_dm_ism_fini(): cleanup after init - dm_ism_set_last_idle_ts(): timestamp update - dm_ism_insert_record(): basic insert and wrap-around - dm_ism_trigger_event(): valid and invalid transitions Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c | 4 + .../amdgpu_dm/tests/amdgpu_dm_ism_test.c | 353 ++++++++++++++++++ 2 files changed, 357 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c index 32391b56097e..a9575bf25fc2 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_ism.c @@ -502,6 +502,7 @@ void amdgpu_dm_ism_commit_event(struct amdgpu_dm_ism *ism, } while (next_event < DM_ISM_NUM_EVENTS); } +EXPORT_IF_KUNIT(amdgpu_dm_ism_commit_event); static void dm_ism_delayed_work_func(struct work_struct *work) @@ -568,6 +569,7 @@ void amdgpu_dm_ism_disable(struct amdgpu_display_manager *dm) disable_delayed_work_sync(&ism->sso_delayed_work); } } +EXPORT_IF_KUNIT(amdgpu_dm_ism_disable); /** * amdgpu_dm_ism_force_full_power - Force every CRTC's ISM FSM to FULL_POWER @@ -603,6 +605,7 @@ void amdgpu_dm_ism_force_full_power(struct amdgpu_display_manager *dm) DM_ISM_EVENT_EXIT_IDLE_REQUESTED); } } +EXPORT_IF_KUNIT(amdgpu_dm_ism_force_full_power); /** * amdgpu_dm_ism_enable - enable the ISM @@ -626,6 +629,7 @@ void amdgpu_dm_ism_enable(struct amdgpu_display_manager *dm) enable_delayed_work(&ism->sso_delayed_work); } } +EXPORT_IF_KUNIT(amdgpu_dm_ism_enable); void amdgpu_dm_ism_init(struct amdgpu_dm_ism *ism, struct amdgpu_dm_ism_config *config) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_ism_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_ism_test.c index 7dfb3b351d20..6394b967ac3a 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_ism_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_ism_test.c @@ -8,6 +8,9 @@ #include #include "dc.h" +#include "amdgpu.h" +#include "amdgpu_mode.h" +#include "amdgpu_dm.h" #include "amdgpu_dm_ism.h" #include "amdgpu_dm_kunit_test_helpers.h" @@ -848,6 +851,345 @@ static void dm_test_dispatch_next_event_no_action_state(struct kunit *test) KUNIT_EXPECT_EQ(test, (int)result, (int)DM_ISM_NUM_EVENTS); } +/* + * Helper: allocate an amdgpu_crtc (which embeds the ISM) wired up to a + * minimally-populated amdgpu_device so that the container_of()/drm_to_adev() + * lookups inside the ISM event machinery resolve correctly. + * + * The amdgpu_device is large, so it must be heap-allocated via kunit_kzalloc. + * acrtc->base.dev points at the embedded &adev->ddev, which is what + * drm_to_adev() expects (it is a container_of of ddev). + */ +static struct amdgpu_crtc *alloc_test_acrtc(struct kunit *test, + struct amdgpu_device **adev_out) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc; + struct dc *dc; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc); + + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, acrtc); + + adev->dm.dc = dc; + adev->dm.ddev = &adev->ddev; + mutex_init(&adev->dm.dc_lock); + + acrtc->base.dev = &adev->ddev; + + if (adev_out) + *adev_out = adev; + + return acrtc; +} + +/* + * Helper: register an already-allocated amdgpu_crtc into the DRM device's + * crtc_list so that drm_for_each_crtc() iterates it. Only the list linkage is + * required for the ISM enable/disable/force-full-power helpers. + */ +static void register_test_acrtc(struct amdgpu_device *adev, + struct amdgpu_crtc *acrtc) +{ + INIT_LIST_HEAD(&adev->ddev.mode_config.crtc_list); + INIT_LIST_HEAD(&acrtc->base.head); + list_add_tail(&acrtc->base.head, &adev->ddev.mode_config.crtc_list); +} + +/* ===== Tests for amdgpu_dm_ism_commit_event ===== */ + +/** + * dm_test_ism_commit_event_no_state - commit_event returns early without a crtc state + * @test: KUnit test context + * + * When the CRTC has no atomic state (base.state == NULL) the function takes + * the NO_STATE early-return path and the FSM is left untouched. + */ +static void dm_test_ism_commit_event_no_state(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc = alloc_test_acrtc(test, &adev); + struct amdgpu_dm_ism_config config = { 0 }; + + amdgpu_dm_ism_init(&acrtc->ism, &config); + acrtc->base.state = NULL; + + guard(mutex)(&adev->dm.dc_lock); + amdgpu_dm_ism_commit_event(&acrtc->ism, + DM_ISM_EVENT_BEGIN_CURSOR_UPDATE); + + KUNIT_EXPECT_EQ(test, (int)acrtc->ism.current_state, + (int)DM_ISM_STATE_FULL_POWER_RUNNING); + + amdgpu_dm_ism_fini(&acrtc->ism); +} + +/** + * dm_test_ism_commit_event_cursor_transition - cursor begin/end drive FSM without DC work + * @test: KUnit test context + * + * BEGIN_CURSOR_UPDATE then END_CURSOR_UPDATE from FULL_POWER_RUNNING traverse + * the FULL_POWER_BUSY state. Neither transition reaches the idle-optimization + * commit path, so no DC hardware access occurs and the FSM returns to + * FULL_POWER_RUNNING. + */ +static void dm_test_ism_commit_event_cursor_transition(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc = alloc_test_acrtc(test, &adev); + struct dm_crtc_state *dm_state; + struct amdgpu_dm_ism_config config = { 0 }; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + + amdgpu_dm_ism_init(&acrtc->ism, &config); + acrtc->base.state = &dm_state->base; + + guard(mutex)(&adev->dm.dc_lock); + amdgpu_dm_ism_commit_event(&acrtc->ism, + DM_ISM_EVENT_BEGIN_CURSOR_UPDATE); + KUNIT_EXPECT_EQ(test, (int)acrtc->ism.current_state, + (int)DM_ISM_STATE_FULL_POWER_BUSY); + + amdgpu_dm_ism_commit_event(&acrtc->ism, + DM_ISM_EVENT_END_CURSOR_UPDATE); + KUNIT_EXPECT_EQ(test, (int)acrtc->ism.current_state, + (int)DM_ISM_STATE_FULL_POWER_RUNNING); + + amdgpu_dm_ism_fini(&acrtc->ism); +} + +/** + * dm_test_ism_commit_event_invalid_event - invalid event leaves FSM unchanged + * @test: KUnit test context + * + * EXIT_IDLE_REQUESTED is not a valid event from FULL_POWER_RUNNING, so the + * FSM does not transition and no power-state dispatch occurs. + */ +static void dm_test_ism_commit_event_invalid_event(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc = alloc_test_acrtc(test, &adev); + struct dm_crtc_state *dm_state; + struct amdgpu_dm_ism_config config = { 0 }; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + + amdgpu_dm_ism_init(&acrtc->ism, &config); + acrtc->base.state = &dm_state->base; + + guard(mutex)(&adev->dm.dc_lock); + amdgpu_dm_ism_commit_event(&acrtc->ism, + DM_ISM_EVENT_EXIT_IDLE_REQUESTED); + + KUNIT_EXPECT_EQ(test, (int)acrtc->ism.current_state, + (int)DM_ISM_STATE_FULL_POWER_RUNNING); + + amdgpu_dm_ism_fini(&acrtc->ism); +} + +/* ===== Tests for amdgpu_dm_ism_force_full_power ===== */ + +/** + * dm_test_ism_force_full_power - force-full-power sends EXIT_IDLE to every CRTC + * @test: KUnit test context + * + * From the initial FULL_POWER_RUNNING state EXIT_IDLE_REQUESTED is a no-op, so + * the FSM remains in FULL_POWER_RUNNING and no DC work is scheduled. + */ +static void dm_test_ism_force_full_power(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc = alloc_test_acrtc(test, &adev); + struct dm_crtc_state *dm_state; + struct amdgpu_dm_ism_config config = { 0 }; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + + amdgpu_dm_ism_init(&acrtc->ism, &config); + acrtc->base.state = &dm_state->base; + register_test_acrtc(adev, acrtc); + + guard(mutex)(&adev->dm.dc_lock); + amdgpu_dm_ism_force_full_power(&adev->dm); + + KUNIT_EXPECT_EQ(test, (int)acrtc->ism.current_state, + (int)DM_ISM_STATE_FULL_POWER_RUNNING); + + amdgpu_dm_ism_fini(&acrtc->ism); +} + +/* ===== Tests for amdgpu_dm_ism_disable / amdgpu_dm_ism_enable ===== */ + +/** + * dm_test_ism_disable_enable_cycle - disable then enable quiesces and re-arms work + * @test: KUnit test context + * + * Walks every CRTC's ISM, disabling its delayed work (synchronously) and then + * re-enabling it. With no work ever scheduled both calls complete without + * touching the FSM state. disable must be called without dc_lock held. + */ +static void dm_test_ism_disable_enable_cycle(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc = alloc_test_acrtc(test, &adev); + struct amdgpu_dm_ism_config config = { 0 }; + + amdgpu_dm_ism_init(&acrtc->ism, &config); + register_test_acrtc(adev, acrtc); + + amdgpu_dm_ism_disable(&adev->dm); + amdgpu_dm_ism_enable(&adev->dm); + + KUNIT_EXPECT_EQ(test, (int)acrtc->ism.current_state, + (int)DM_ISM_STATE_FULL_POWER_RUNNING); + + amdgpu_dm_ism_fini(&acrtc->ism); +} + +/* ===== Tests for dm_ism_dispatch_power_state (via commit_event) ===== */ + +/* + * Build a config + history that makes dm_ism_get_idle_allow_delay() return a + * non-zero hysteresis delay. A non-zero delay keeps the FSM parked in + * HYSTERESIS_WAITING (the dispatcher returns DM_ISM_NUM_EVENTS instead of an + * immediate follow-up event), preventing the cascade into the DC-dependent + * OPTIMIZED_IDLE / *_SSO states. + */ +static void setup_idle_delay_history(struct amdgpu_dm_ism *ism, + struct dc_stream_state *stream) +{ + uint64_t one_frame_ns; + + stream->timing.v_total = 1125; + stream->timing.h_total = 2200; + stream->timing.pix_clk_100hz = 1485000; + + one_frame_ns = div64_u64((uint64_t)1125 * 2200 * 10000000ULL, 1485000); + + for (int i = 0; i < 8; i++) { + ism->records[i].duration_ns = one_frame_ns; + ism->records[i].timestamp_ns = 0; + } + ism->next_record_idx = 8; +} + +/** + * dm_test_ism_dispatch_hysteresis_schedule_and_cancel - cover the HYSTERESIS_WAITING dispatch arms + * @test: KUnit test context + * + * ENTER_IDLE_REQUESTED moves FULL_POWER_RUNNING -> HYSTERESIS_WAITING. The + * current-state arm of dm_ism_dispatch_power_state() then records the idle + * timestamp, computes a (non-zero) idle-allow delay and schedules the delayed + * worker. A subsequent BEGIN_CURSOR_UPDATE (-> HYSTERESIS_BUSY) exercises the + * previous-state arm that cancels that pending worker. Neither arm reaches the + * DC-dependent idle-optimization commit path. + */ +static void dm_test_ism_dispatch_hysteresis_schedule_and_cancel(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc = alloc_test_acrtc(test, &adev); + struct dm_crtc_state *dm_state; + struct dc_stream_state *stream; + struct amdgpu_dm_ism_config config = { + .filter_num_frames = 5, + .filter_entry_count = 3, + .activation_num_delay_frames = 10, + .filter_history_size = 8, + .filter_old_history_threshold = 0, + .sso_num_frames = 0, + }; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + stream = dm_kunit_alloc_stream(test, NULL); + + amdgpu_dm_ism_init(&acrtc->ism, &config); + setup_idle_delay_history(&acrtc->ism, stream); + dm_state->stream = stream; + acrtc->base.state = &dm_state->base; + + scoped_guard(mutex, &adev->dm.dc_lock) { + /* Enter HYSTERESIS_WAITING: schedules the idle-allow worker. */ + amdgpu_dm_ism_commit_event(&acrtc->ism, + DM_ISM_EVENT_ENTER_IDLE_REQUESTED); + KUNIT_EXPECT_EQ(test, (int)acrtc->ism.current_state, + (int)DM_ISM_STATE_HYSTERESIS_WAITING); + + /* Cursor update cancels the pending worker (prev-state arm). */ + amdgpu_dm_ism_commit_event(&acrtc->ism, + DM_ISM_EVENT_BEGIN_CURSOR_UPDATE); + KUNIT_EXPECT_EQ(test, (int)acrtc->ism.current_state, + (int)DM_ISM_STATE_HYSTERESIS_BUSY); + } + + amdgpu_dm_ism_fini(&acrtc->ism); +} + +/** + * dm_test_ism_dispatch_optimized_idle_defers_sso - cover OPTIMIZED_IDLE dispatch without DC + * @test: KUnit test context + * + * With sso_num_frames < filter_num_frames the OPTIMIZED_IDLE current-state arm + * skips the idle-optimization commit and only schedules the SSO worker. Driving + * HYSTERESIS_WAITING -> OPTIMIZED_IDLE via TIMER_ELAPSED exercises that arm + * (get_sso_delay + the skip branch + mod_delayed_work) without any DC access. + */ +static void dm_test_ism_dispatch_optimized_idle_defers_sso(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc = alloc_test_acrtc(test, &adev); + struct dm_crtc_state *dm_state; + struct dc_stream_state *stream; + struct amdgpu_dm_ism_config config = { + .filter_num_frames = 5, + .filter_entry_count = 3, + .activation_num_delay_frames = 10, + .filter_history_size = 8, + .filter_old_history_threshold = 0, + .sso_num_frames = 2, + }; + + dm_state = kunit_kzalloc(test, sizeof(*dm_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_state); + stream = dm_kunit_alloc_stream(test, NULL); + + amdgpu_dm_ism_init(&acrtc->ism, &config); + setup_idle_delay_history(&acrtc->ism, stream); + dm_state->stream = stream; + acrtc->base.state = &dm_state->base; + + scoped_guard(mutex, &adev->dm.dc_lock) { + amdgpu_dm_ism_commit_event(&acrtc->ism, + DM_ISM_EVENT_ENTER_IDLE_REQUESTED); + KUNIT_EXPECT_EQ(test, (int)acrtc->ism.current_state, + (int)DM_ISM_STATE_HYSTERESIS_WAITING); + + /* + * Timer fires: HYSTERESIS_WAITING -> OPTIMIZED_IDLE. sso_delay + * is non-zero and sso_num_frames < filter_num_frames, so the + * commit is deferred to the SSO worker and the FSM parks here. + */ + amdgpu_dm_ism_commit_event(&acrtc->ism, + DM_ISM_EVENT_TIMER_ELAPSED); + KUNIT_EXPECT_EQ(test, (int)acrtc->ism.current_state, + (int)DM_ISM_STATE_OPTIMIZED_IDLE); + + /* Cancel the scheduled SSO worker while still holding dc_lock. */ + cancel_delayed_work(&acrtc->ism.sso_delayed_work); + } + + amdgpu_dm_ism_fini(&acrtc->ism); +} + static struct kunit_case dm_ism_test_cases[] = { /* dm_ism_next_state — FULL_POWER_RUNNING */ KUNIT_CASE(dm_test_ism_next_state_running_enter_idle), @@ -910,6 +1252,17 @@ static struct kunit_case dm_ism_test_cases[] = { KUNIT_CASE(dm_test_dispatch_next_event_opt_idle_with_sso_delay), KUNIT_CASE(dm_test_dispatch_next_event_timer_aborted), KUNIT_CASE(dm_test_dispatch_next_event_no_action_state), + /* amdgpu_dm_ism_commit_event */ + KUNIT_CASE(dm_test_ism_commit_event_no_state), + KUNIT_CASE(dm_test_ism_commit_event_cursor_transition), + KUNIT_CASE(dm_test_ism_commit_event_invalid_event), + /* amdgpu_dm_ism_force_full_power */ + KUNIT_CASE(dm_test_ism_force_full_power), + /* amdgpu_dm_ism_disable / amdgpu_dm_ism_enable */ + KUNIT_CASE(dm_test_ism_disable_enable_cycle), + /* dm_ism_dispatch_power_state (via commit_event) */ + KUNIT_CASE(dm_test_ism_dispatch_hysteresis_schedule_and_cancel), + KUNIT_CASE(dm_test_ism_dispatch_optimized_idle_defers_sso), {} }; From b6f466c509ad2f390b3fc91cd0de4783554f5f98 Mon Sep 17 00:00:00 2001 From: Charlene Liu Date: Mon, 22 Jun 2026 16:29:25 -0400 Subject: [PATCH 115/577] drm/amd/display: add null pointer access check [why] need to add null pointer access check Reviewed-by: Gabe Teeger Signed-off-by: Charlene Liu Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../drm/amd/display/dc/dio/dcn10/dcn10_link_encoder.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dio/dcn10/dcn10_link_encoder.c b/drivers/gpu/drm/amd/display/dc/dio/dcn10/dcn10_link_encoder.c index 4b36c01a9e7a..00e82d627d91 100644 --- a/drivers/gpu/drm/amd/display/dc/dio/dcn10/dcn10_link_encoder.c +++ b/drivers/gpu/drm/amd/display/dc/dio/dcn10/dcn10_link_encoder.c @@ -1189,8 +1189,9 @@ void dcn10_link_encoder_dp_set_phy_pattern( set_dp_phy_pattern_prbs7(enc10); break; case DP_TEST_PATTERN_80BIT_CUSTOM: - set_dp_phy_pattern_80bit_custom( - enc10, param->custom_pattern); + if (param) + set_dp_phy_pattern_80bit_custom( + enc10, param->custom_pattern); break; case DP_TEST_PATTERN_CP2520_1: set_dp_phy_pattern_hbr2_compliance_cp2520_2(enc10, 1); @@ -1202,8 +1203,9 @@ void dcn10_link_encoder_dp_set_phy_pattern( set_dp_phy_pattern_hbr2_compliance_cp2520_2(enc10, 3); break; case DP_TEST_PATTERN_VIDEO_MODE: { - set_dp_phy_pattern_passthrough_mode( - enc10, param->dp_panel_mode); + if (param) + set_dp_phy_pattern_passthrough_mode( + enc10, param->dp_panel_mode); break; } From fd651b9a3be9e7aba98ac2afacbf10b541d9ba00 Mon Sep 17 00:00:00 2001 From: Matthew Stewart Date: Wed, 24 Jun 2026 14:59:20 -0400 Subject: [PATCH 116/577] drm/amd/display: Add DCN42B VID_CRC_CONTROL and HBLANK_CONTROL registers [why] These registers are needed by our existing code. [how] Add the missing register defines to dcn42b_resource.c. Remove DCN42B variant of HP_DP_STREAM_ENC_REG_LIST_RI as it is now the same as the DCN42B one, when these registers are included. Reviewed-by: Aurabindo Pillai Signed-off-by: Matthew Stewart Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../dc/resource/dcn42b/dcn42b_resource.c | 15 ++++++- .../dc/resource/dcn42b/dcn42b_resource.h | 40 ------------------- 2 files changed, 14 insertions(+), 41 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c index 172836dfd679..c555be6e541d 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c @@ -134,6 +134,19 @@ #define regHUBP3_HUBPREQ_DEBUG 0x088d #define regHUBP3_HUBPREQ_DEBUG_BASE_IDX 2 +#define regDP_SYM32_ENC0_DP_SYM32_ENC_VID_CRC_CONTROL 0x3687 +#define regDP_SYM32_ENC0_DP_SYM32_ENC_VID_CRC_CONTROL_BASE_IDX 2 +#define regDP_SYM32_ENC1_DP_SYM32_ENC_VID_CRC_CONTROL 0x375b +#define regDP_SYM32_ENC1_DP_SYM32_ENC_VID_CRC_CONTROL_BASE_IDX 2 +#define regDP_SYM32_ENC2_DP_SYM32_ENC_VID_CRC_CONTROL 0x382f +#define regDP_SYM32_ENC2_DP_SYM32_ENC_VID_CRC_CONTROL_BASE_IDX 2 +#define regDP_SYM32_ENC0_DP_SYM32_ENC_HBLANK_CONTROL 0x366b +#define regDP_SYM32_ENC0_DP_SYM32_ENC_HBLANK_CONTROL_BASE_IDX 2 +#define regDP_SYM32_ENC1_DP_SYM32_ENC_HBLANK_CONTROL 0x373f +#define regDP_SYM32_ENC1_DP_SYM32_ENC_HBLANK_CONTROL_BASE_IDX 2 +#define regDP_SYM32_ENC2_DP_SYM32_ENC_HBLANK_CONTROL 0x3813 +#define regDP_SYM32_ENC2_DP_SYM32_ENC_HBLANK_CONTROL_BASE_IDX 2 + enum dcn401_clk_src_array_id { DCN401_CLK_SRC_PLL0, DCN401_CLK_SRC_PLL1, @@ -351,7 +364,7 @@ static const struct dcn10_link_enc_mask le_mask = { LINK_ENCODER_MASK_SH_LIST_DCN42B(_MASK)}; #define hpo_dp_stream_encoder_reg_init(id) \ - DCN42B_HPO_DP_STREAM_ENC_REG_LIST_RI(id) + DCN42_HPO_DP_STREAM_ENC_REG_LIST_RI(id) static struct dcn31_hpo_dp_stream_encoder_registers hpo_dp_stream_enc_regs[4]; diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.h b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.h index 1a6e0e7be0fa..484ba1455468 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.h +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.h @@ -254,46 +254,6 @@ SRI_ARR(STREAM_MAPPER_CONTROL, DIG, id),\ SRI_ARR(DIG_FE_AUDIO_CNTL, DIG, id) -/* HPO DP stream encoder */ -/* Not in DCN42B: - * SRI_ARR(DP_SYM32_ENC_VID_CRC_CONTROL, DP_SYM32_ENC, id), - * SRI_ARR(DP_SYM32_ENC_HBLANK_CONTROL, DP_SYM32_ENC, id), - */ -#define DCN42B_HPO_DP_STREAM_ENC_REG_LIST_RI(id) \ - SR_ARR(DP_STREAM_MAPPER_CONTROL0, id), \ - SR_ARR(DP_STREAM_MAPPER_CONTROL1, id), \ - SR_ARR(DP_STREAM_MAPPER_CONTROL2, id), \ - SR_ARR(DP_STREAM_MAPPER_CONTROL3, id), \ - SRI_ARR(DP_STREAM_ENC_CLOCK_CONTROL, DP_STREAM_ENC, id), \ - SRI_ARR(DP_STREAM_ENC_INPUT_MUX_CONTROL, DP_STREAM_ENC, id), \ - SRI_ARR(DP_STREAM_ENC_AUDIO_CONTROL, DP_STREAM_ENC, id), \ - SRI_ARR(DP_STREAM_ENC_CLOCK_RAMP_ADJUSTER_FIFO_STATUS_CONTROL0, DP_STREAM_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_CONTROL, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_PIXEL_FORMAT, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_PIXEL_FORMAT_DOUBLE_BUFFER_CONTROL, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_MSA0, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_MSA1, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_MSA2, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_MSA3, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_MSA4, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_MSA5, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_MSA6, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_MSA7, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_MSA8, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_MSA_CONTROL, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_MSA_DOUBLE_BUFFER_CONTROL, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_FIFO_CONTROL, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_STREAM_CONTROL, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_VID_VBID_CONTROL, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_SDP_CONTROL, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_SDP_GSP_CONTROL0, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_SDP_GSP_CONTROL2, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_SDP_GSP_CONTROL3, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_SDP_GSP_CONTROL5, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_SDP_GSP_CONTROL11, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_SDP_METADATA_PACKET_CONTROL, DP_SYM32_ENC, id), \ - SRI_ARR(DP_SYM32_ENC_SDP_AUDIO_CONTROL0, DP_SYM32_ENC, id) - /*HPO DP link encoder regs */ #define DCN42B_HPO_DP_LINK_ENC_REG_LIST_RI(id) \ SRI_ARR(DP_LINK_ENC_CLOCK_CONTROL, DP_LINK_ENC, id), \ From cee2f77c774f256676422483e4cad2c38e10d68b Mon Sep 17 00:00:00 2001 From: James Lin Date: Wed, 24 Jun 2026 15:36:03 +0800 Subject: [PATCH 117/577] drm/amd/display: Add MALL status readback support for DCN 4.0.1 [why] The IGT test igt@amdgpu/amd_mall@static-screen always fails on DCN 4.0.1 because debugfs reports "mall enabled: no" even when MALL is actually active. This is because the DCN 4.0.1 hubbub function table is missing the .get_mall_en callback, so capabilities_show() always sees mall_in_use as false. [how] Reuse hubbub32_get_mall_en() for DCN 4.0.1 since the DCHUBBUB_ARB_MALL_CNTL register layout is identical to DCN 3.2: - Register the DCHUBBUB_ARB_MALL_CNTL register offset in dcn401_resource.h - Add MALL_PREFETCH_COMPLETE and MALL_IN_USE mask/shift definitions in dcn401_hubbub.h - Wire up .get_mall_en = hubbub32_get_mall_en in hubbub4_01_funcs Reviewed-by: ChiaHsuan (Tom) Chung Signed-off-by: James Lin Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/hubbub/dcn401/dcn401_hubbub.c | 3 ++- drivers/gpu/drm/amd/display/dc/hubbub/dcn401/dcn401_hubbub.h | 4 +++- .../gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.h | 3 ++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/hubbub/dcn401/dcn401_hubbub.c b/drivers/gpu/drm/amd/display/dc/hubbub/dcn401/dcn401_hubbub.c index bf30a1bb61b7..e9ff20c02b1e 100644 --- a/drivers/gpu/drm/amd/display/dc/hubbub/dcn401/dcn401_hubbub.c +++ b/drivers/gpu/drm/amd/display/dc/hubbub/dcn401/dcn401_hubbub.c @@ -1250,7 +1250,8 @@ static const struct hubbub_funcs hubbub4_01_funcs = { .program_compbuf_segments = dcn401_program_compbuf_segments, .wait_for_det_update = dcn401_wait_for_det_update, .program_arbiter = dcn401_program_arbiter, - .hubbub_read_reg_state = hubbub3_read_reg_state + .hubbub_read_reg_state = hubbub3_read_reg_state, + .get_mall_en = hubbub32_get_mall_en }; void hubbub401_construct(struct dcn20_hubbub *hubbub2, diff --git a/drivers/gpu/drm/amd/display/dc/hubbub/dcn401/dcn401_hubbub.h b/drivers/gpu/drm/amd/display/dc/hubbub/dcn401/dcn401_hubbub.h index f48715544429..346bead81504 100644 --- a/drivers/gpu/drm/amd/display/dc/hubbub/dcn401/dcn401_hubbub.h +++ b/drivers/gpu/drm/amd/display/dc/hubbub/dcn401/dcn401_hubbub.h @@ -133,7 +133,9 @@ HUBBUB_SF(DCHUBBUB_CTRL_STATUS, ROB_OVERFLOW_STATUS, mask_sh),\ HUBBUB_SF(DCHUBBUB_CTRL_STATUS, ROB_OVERFLOW_CLEAR, mask_sh),\ HUBBUB_SF(DCHUBBUB_CTRL_STATUS, DCHUBBUB_HW_DEBUG, mask_sh),\ - HUBBUB_SF(DCHUBBUB_CTRL_STATUS, CSTATE_SWATH_CHK_GOOD_MODE, mask_sh) + HUBBUB_SF(DCHUBBUB_CTRL_STATUS, CSTATE_SWATH_CHK_GOOD_MODE, mask_sh),\ + HUBBUB_SF(DCHUBBUB_ARB_MALL_CNTL, MALL_PREFETCH_COMPLETE, mask_sh),\ + HUBBUB_SF(DCHUBBUB_ARB_MALL_CNTL, MALL_IN_USE, mask_sh) bool hubbub401_program_urgent_watermarks( struct hubbub *hubbub, diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.h b/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.h index 47f82b818262..ce96fe1ddb7c 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.h +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.h @@ -622,7 +622,8 @@ int dcn401_get_power_profile(const struct dc_state *context); SR(DCHUBBUB_MEM_PWR_MODE_CTRL), \ SR(DCHUBBUB_TIMEOUT_DETECTION_CTRL1), \ SR(DCHUBBUB_TIMEOUT_DETECTION_CTRL2), \ - SR(DCHUBBUB_CTRL_STATUS) + SR(DCHUBBUB_CTRL_STATUS), \ + SR(DCHUBBUB_ARB_MALL_CNTL) /* DCCG */ From 5a3fb8427ebc39e95597626da3991a4fd79deef8 Mon Sep 17 00:00:00 2001 From: Dominik Kaszewski Date: Fri, 19 Jun 2026 13:50:39 +0200 Subject: [PATCH 118/577] drm/amd/display: Fix indenting of dpms on [Why & How] Cleanup for future refactors. Reviewed-by: Wenjing Liu Signed-off-by: Dominik Kaszewski Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/dc/link/link_dpms.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/link/link_dpms.c b/drivers/gpu/drm/amd/display/dc/link/link_dpms.c index 479a8fbfdc5d..335ae952ef60 100644 --- a/drivers/gpu/drm/amd/display/dc/link/link_dpms.c +++ b/drivers/gpu/drm/amd/display/dc/link/link_dpms.c @@ -2610,13 +2610,14 @@ void link_set_dpms_on( */ if (stream->timing.flags.DSC) { if (dc_is_dp_signal(stream->signal) || - dc_is_virtual_signal(stream->signal)) + dc_is_virtual_signal(stream->signal)) link_set_dsc_enable(pipe_ctx, true); } if (link->replay_settings.config.replay_supported && !dc_is_embedded_signal(link->connector_signal)) dp_setup_replay(link, stream); + // TODO: Split DPMS-on into 3 functions at this point status = enable_link(state, pipe_ctx); if (status != DC_OK) { @@ -2629,9 +2630,9 @@ void link_set_dpms_on( * show the stream anyway. But MST displays can't proceed * without link training. */ - if ((status != DC_FAIL_DP_LINK_TRAINING && - status != DC_FAIL_HDMI_FRL_LINK_TRAINING) || - stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST) { + if ((status != DC_FAIL_DP_LINK_TRAINING && + status != DC_FAIL_HDMI_FRL_LINK_TRAINING) || + stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST) { if (false == link->link_status.link_active) disable_link(link, &pipe_ctx->link_res, stream->signal); @@ -2639,6 +2640,7 @@ void link_set_dpms_on( return; } } + // TODO: Split DPMS-on into 3 functions at this point if (stream->timing.flags.DSC && dc_is_hdmi_frl_signal(stream->signal)) //TODO: bring HDMI FRL in line with DP @@ -2657,13 +2659,11 @@ void link_set_dpms_on( if (!(dc_is_virtual_signal(stream->signal) || dc_is_hdmi_frl_signal(stream->signal) || dp_is_128b_132b_signal(pipe_ctx))) { - - if (link_enc) - link_enc->funcs->setup( + if (link_enc) + link_enc->funcs->setup( link_enc, stream->signal); - - } + } dc->hwss.enable_stream(pipe_ctx); From 5b7e4ad090f2700bfad37f43e4d3553b71be3591 Mon Sep 17 00:00:00 2001 From: Charlene Liu Date: Wed, 24 Jun 2026 18:54:43 -0400 Subject: [PATCH 119/577] drm/amd/display: ensure dtbclk clk_src selected before hdmistream_clk_en [why] correct a sequence issue by switching to dcn35's dccg sequence: to make sure select dtbclk src first before programming hdmistream_clk_en. Reviewed-by: Leo Chen Signed-off-by: Charlene Liu Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/dc/dccg/dcn35/dcn35_dccg.c | 2 +- drivers/gpu/drm/amd/display/dc/dccg/dcn35/dcn35_dccg.h | 5 ++++- drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.c | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dccg/dcn35/dcn35_dccg.c b/drivers/gpu/drm/amd/display/dc/dccg/dcn35/dcn35_dccg.c index 483cd9ab7eb7..617f504c8e6b 100644 --- a/drivers/gpu/drm/amd/display/dc/dccg/dcn35/dcn35_dccg.c +++ b/drivers/gpu/drm/amd/display/dc/dccg/dcn35/dcn35_dccg.c @@ -1654,7 +1654,7 @@ void dccg35_set_dpstreamclk_root_clock_gating(struct dccg *dccg, int dp_hpo_inst -static void dccg35_set_hdmistreamclk( +void dccg35_set_hdmistreamclk( struct dccg *dccg, enum streamclk_source src, uint32_t otg_inst) diff --git a/drivers/gpu/drm/amd/display/dc/dccg/dcn35/dcn35_dccg.h b/drivers/gpu/drm/amd/display/dc/dccg/dcn35/dcn35_dccg.h index 554700287c1a..58f4c1e998b3 100644 --- a/drivers/gpu/drm/amd/display/dc/dccg/dcn35/dcn35_dccg.h +++ b/drivers/gpu/drm/amd/display/dc/dccg/dcn35/dcn35_dccg.h @@ -269,5 +269,8 @@ void dccg35_disable_dscclk(struct dccg *dccg, int inst); void dccg35_enable_symclk_se(struct dccg *dccg, uint32_t stream_enc_inst, uint32_t link_enc_inst); void dccg35_disable_symclk_se(struct dccg *dccg, uint32_t stream_enc_inst, uint32_t link_enc_inst); - +void dccg35_set_hdmistreamclk( + struct dccg *dccg, + enum streamclk_source src, + uint32_t otg_inst); #endif //__DCN35_DCCG_H__ diff --git a/drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.c b/drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.c index 616a896f0782..3b03b152da22 100644 --- a/drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.c +++ b/drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.c @@ -313,7 +313,7 @@ static void dccg42_init(struct dccg *dccg) static const struct dccg_funcs dccg42_funcs = { .enable_hdmicharclk = dccg401_enable_hdmicharclk, .disable_hdmicharclk = dccg42_disable_hdmicharclk, - .set_hdmistreamclk = dccg401_set_hdmistreamclk, + .set_hdmistreamclk = dccg35_set_hdmistreamclk, .set_hdmistreamclk_root_clock_gating = dccg35_set_hdmistreamclk_root_clock_gating, .update_dpp_dto = dccg35_update_dpp_dto, .dpp_root_clock_control = dccg35_dpp_root_clock_control, From acf97c57d091f0df2315310f82ffd6ccff500594 Mon Sep 17 00:00:00 2001 From: James Lin Date: Fri, 26 Jun 2026 11:21:00 +0800 Subject: [PATCH 120/577] drm/amd/display: Re-evaluate cursor mode on plane position/size change [Why] dm_crtc_get_cursor_mode() only re-evaluates the required cursor mode (native vs overlay) when a top plane changes its scale, pixel format, enable state, or zpos/color pipeline. It does not re-evaluate when a plane changes only its destination rectangle (crtc_x, crtc_y, crtc_w, crtc_h) at a constant scale. A pure move/resize can create or remove a hole under the cursor, which changes whether the native cursor is valid. When a primary plane shrinks and no longer covers the cursor region, the cursor mode stays NATIVE and the cursor is not rendered over the uncovered area, so it becomes invisible there. This is caught by igt@amdgpu/amd_cursor_overlay@non-full, where the test CRC was a constant black value across all cursor positions instead of tracking the reference. [How] In the per-plane loop of dm_crtc_get_cursor_mode(), set consider_mode_change when any of crtc_x, crtc_y, crtc_w or crtc_h differs between the old and new plane state, so a plane move/resize forces re-evaluation of the cursor mode. The driver then correctly promotes the cursor to OVERLAY mode when the primary stops covering the cursor region. Reviewed-by: ChiaHsuan (Tom) Chung Signed-off-by: James Lin Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index 67b825cbb88f..74bb5845b6be 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -6649,6 +6649,20 @@ static int dm_crtc_get_cursor_mode(struct amdgpu_device *adev, break; } + /* + * A plane moving or resizing (without a scale change) changes how + * much of the CRTC it covers. This can create/remove holes under + * the cursor and thus flip the required cursor mode (native vs + * overlay), so the destination rect must be re-evaluated too. + */ + if (old_plane_state->crtc_x != plane_state->crtc_x || + old_plane_state->crtc_y != plane_state->crtc_y || + old_plane_state->crtc_w != plane_state->crtc_w || + old_plane_state->crtc_h != plane_state->crtc_h) { + consider_mode_change = true; + break; + } + if (dm_plane_color_pipeline_active(state, plane, true) != dm_plane_color_pipeline_active(state, plane, false)) { consider_mode_change = true; From 50dd2baefb4cfb0d1a83b14d6a94ea212eb8f134 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Wed, 24 Jun 2026 23:34:18 -0600 Subject: [PATCH 121/577] drm/amd/display: Test EDID and ACPI/VBIOS readers [WHAT] Add KUnit coverage for the EDID parsing helpers edid_extract_panel_id, apply_edid_quirks and dm_helpers_parse_edid_caps, together with the ACPI/VBIOS EDID readers dm_helpers_probe_acpi_edid, dm_helpers_read_acpi_edid and dm_helpers_read_vbios_hardcoded_edid. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 27 +- .../amd/display/amdgpu_dm/amdgpu_dm_helpers.h | 19 + .../amdgpu_dm/tests/amdgpu_dm_helpers_test.c | 727 ++++++++++++++++++ 3 files changed, 769 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c index 71e2627f9a9d..8f905ad8aaa1 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c @@ -98,7 +98,7 @@ STATIC_IFN_KUNIT u32 edid_extract_panel_id(struct edid *edid) } EXPORT_IF_KUNIT(edid_extract_panel_id); -static void apply_edid_quirks(struct dc_link *link, struct edid *edid, +STATIC_IFN_KUNIT void apply_edid_quirks(struct dc_link *link, struct edid *edid, struct dc_edid_caps *edid_caps) { struct amdgpu_dm_connector *aconnector = link->priv; @@ -141,6 +141,7 @@ static void apply_edid_quirks(struct dc_link *link, struct edid *edid, return; } } +EXPORT_IF_KUNIT(apply_edid_quirks); /** * dm_helpers_parse_edid_caps() - Parse edid caps @@ -237,6 +238,7 @@ enum dc_edid_status dm_helpers_parse_edid_caps( return result; } +EXPORT_IF_KUNIT(dm_helpers_parse_edid_caps); static void fill_dc_mst_payload_table_from_drm(struct dc_link *link, @@ -979,6 +981,20 @@ bool dm_helpers_dp_write_dsc_enable( return ret; } +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +uint dm_helpers_get_dc_debug_mask(void) +{ + return amdgpu_dc_debug_mask; +} +EXPORT_IF_KUNIT(dm_helpers_get_dc_debug_mask); + +void dm_helpers_set_dc_debug_mask(uint debug_mask) +{ + amdgpu_dc_debug_mask = debug_mask; +} +EXPORT_IF_KUNIT(dm_helpers_set_dc_debug_mask); +#endif + bool dm_helpers_dp_write_hblank_reduction(struct dc_context *ctx, const struct dc_stream_state *stream) { // TODO @@ -1002,7 +1018,7 @@ bool dm_helpers_is_dp_sink_present(struct dc_link *link) return dp_sink_present; } -static int +STATIC_IFN_KUNIT int dm_helpers_probe_acpi_edid(void *data, u8 *buf, unsigned int block, size_t len) { struct drm_connector *connector = data; @@ -1040,8 +1056,9 @@ cleanup: return r; } +EXPORT_IF_KUNIT(dm_helpers_probe_acpi_edid); -static const struct drm_edid * +STATIC_IFN_KUNIT const struct drm_edid * dm_helpers_read_acpi_edid(struct amdgpu_dm_connector *aconnector) { struct drm_connector *connector = &aconnector->base; @@ -1062,8 +1079,9 @@ dm_helpers_read_acpi_edid(struct amdgpu_dm_connector *aconnector) return drm_edid_read_custom(connector, dm_helpers_probe_acpi_edid, connector); } +EXPORT_IF_KUNIT(dm_helpers_read_acpi_edid); -static const struct drm_edid * +STATIC_IFN_KUNIT const struct drm_edid * dm_helpers_read_vbios_hardcoded_edid(struct dc_link *link, struct amdgpu_dm_connector *aconnector) { struct dc_bios *bios = link->ctx->dc_bios; @@ -1101,6 +1119,7 @@ dm_helpers_read_vbios_hardcoded_edid(struct dc_link *link, struct amdgpu_dm_conn return edid; } +EXPORT_IF_KUNIT(dm_helpers_read_vbios_hardcoded_edid); STATIC_IFN_KUNIT uint8_t get_max_frl_rate(uint8_t max_lanes, uint8_t max_rate_per_lane) { diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h index 2ac9762895ec..e5cbcd425847 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h @@ -9,12 +9,31 @@ #if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) #include +struct amdgpu_dm_connector; +struct dc_link; +struct dc_context; +struct dc_stream_state; +struct dc_edid_caps; +struct dc_dp_mst_stream_allocation_table; +struct drm_dp_aux; +struct drm_dp_mst_atomic_payload; +struct drm_dp_mst_topology_mgr; +struct drm_dp_mst_topology_state; + /* Exported for KUnit testing */ u32 edid_extract_panel_id(struct edid *edid); +void apply_edid_quirks(struct dc_link *link, struct edid *edid, + struct dc_edid_caps *edid_caps); uint8_t get_max_frl_rate(uint8_t max_lanes, uint8_t max_rate_per_lane); bool dm_is_freesync_pcon_whitelist(const uint32_t branch_dev_id); extern const uint32_t dm_freesync_pcon_whitelist[]; uint32_t dm_freesync_pcon_whitelist_count(void); +uint dm_helpers_get_dc_debug_mask(void); +void dm_helpers_set_dc_debug_mask(uint debug_mask); +int dm_helpers_probe_acpi_edid(void *data, u8 *buf, unsigned int block, size_t len); +const struct drm_edid *dm_helpers_read_acpi_edid(struct amdgpu_dm_connector *aconnector); +const struct drm_edid *dm_helpers_read_vbios_hardcoded_edid(struct dc_link *link, + struct amdgpu_dm_connector *aconnector); #endif /* CONFIG_DRM_AMD_DC_KUNIT_TEST */ #endif /* __AMDGPU_DM_HELPERS_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c index 33014a2d2222..3766d8d211e4 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c @@ -8,13 +8,18 @@ #include #include #include +#include #include "dc.h" +#include "core_types.h" #include "amdgpu.h" #include "amdgpu_mode.h" #include "amdgpu_dm.h" +#include "amdgpu_dm_mst_types.h" +#include "dc_bios_types.h" #include "dm_helpers.h" #include "ddc_service_types.h" +#include "dmub_cmd.h" #include "amdgpu_dm_helpers.h" #include "amdgpu_dm_kunit_test_helpers.h" @@ -61,6 +66,701 @@ static void dm_test_edid_extract_panel_id_zeros(struct kunit *test) KUNIT_EXPECT_EQ(test, edid_extract_panel_id(edid), 0U); } +/* Tests for apply_edid_quirks() */ + +/* + * Build an EDID whose extracted panel id equals @panel_id. Inverse of + * edid_extract_panel_id(): mfg_id holds the top 16 bits, prod_code the + * low 16 bits (prod_code[0] | prod_code[1] << 8). + */ +static struct edid *dm_test_edid_with_panel_id(struct kunit *test, u32 panel_id) +{ + struct edid *edid; + + edid = kunit_kzalloc(test, sizeof(*edid), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, edid); + + edid->mfg_id[0] = (panel_id >> 24) & 0xff; + edid->mfg_id[1] = (panel_id >> 16) & 0xff; + edid->prod_code[0] = panel_id & 0xff; + edid->prod_code[1] = (panel_id >> 8) & 0xff; + + return edid; +} + +/* + * Wire a connector-backed link so apply_edid_quirks() can resolve + * link->priv->base.dev for its drm_dbg_driver() calls. + */ +static struct dc_link *dm_test_quirk_link(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct amdgpu_device *adev; + struct dc_link *link; + + adev = dm_kunit_alloc_adev(test); + link = dm_kunit_alloc_link(test); + aconnector = dm_kunit_alloc_connector(test, adev, NULL); + link->priv = aconnector; + + return link; +} + +/** + * dm_test_apply_edid_quirks_dpcd_poweroff_delay - Test GBT 0x3215 delay quirk + * @test: The KUnit test context + */ +static void dm_test_apply_edid_quirks_dpcd_poweroff_delay(struct kunit *test) +{ + struct dc_edid_caps edid_caps = {0}; + struct dc_link *link = dm_test_quirk_link(test); + struct edid *edid = dm_test_edid_with_panel_id(test, + drm_edid_encode_panel_id('G', 'B', 'T', 0x3215)); + + apply_edid_quirks(link, edid, &edid_caps); + + KUNIT_EXPECT_EQ(test, edid_caps.panel_patch.wait_after_dpcd_poweroff_ms, 10000U); +} + +/** + * dm_test_apply_edid_quirks_disable_fams - Test SAM panel FAMS-disable quirk + * @test: The KUnit test context + */ +static void dm_test_apply_edid_quirks_disable_fams(struct kunit *test) +{ + struct dc_edid_caps edid_caps = {0}; + struct dc_link *link = dm_test_quirk_link(test); + struct edid *edid = dm_test_edid_with_panel_id(test, + drm_edid_encode_panel_id('S', 'A', 'M', 0x0E5E)); + + apply_edid_quirks(link, edid, &edid_caps); + + KUNIT_EXPECT_TRUE(test, edid_caps.panel_patch.disable_fams); +} + +/** + * dm_test_apply_edid_quirks_remove_sink_ext_caps - Test AUO 0x317 clear quirk + * @test: The KUnit test context + */ +static void dm_test_apply_edid_quirks_remove_sink_ext_caps(struct kunit *test) +{ + struct dc_edid_caps edid_caps = {0}; + struct dc_link *link = dm_test_quirk_link(test); + struct edid *edid = dm_test_edid_with_panel_id(test, + drm_edid_encode_panel_id('A', 'U', 'O', 0xA7AB)); + + apply_edid_quirks(link, edid, &edid_caps); + + KUNIT_EXPECT_TRUE(test, edid_caps.panel_patch.remove_sink_ext_caps); +} + +/** + * dm_test_apply_edid_quirks_disable_colorimetry - Test SDC VSC-disable quirk + * @test: The KUnit test context + */ +static void dm_test_apply_edid_quirks_disable_colorimetry(struct kunit *test) +{ + struct dc_edid_caps edid_caps = {0}; + struct dc_link *link = dm_test_quirk_link(test); + struct edid *edid = dm_test_edid_with_panel_id(test, + drm_edid_encode_panel_id('S', 'D', 'C', 0x4154)); + + apply_edid_quirks(link, edid, &edid_caps); + + KUNIT_EXPECT_TRUE(test, edid_caps.panel_patch.disable_colorimetry); +} + +/** + * dm_test_apply_edid_quirks_skip_phy_ssc - Test DEL 0x4147 PHY SSC quirk + * @test: The KUnit test context + */ +static void dm_test_apply_edid_quirks_skip_phy_ssc(struct kunit *test) +{ + struct dc_edid_caps edid_caps = {0}; + struct dc_link *link = dm_test_quirk_link(test); + struct edid *edid = dm_test_edid_with_panel_id(test, + drm_edid_encode_panel_id('D', 'E', 'L', 0x4147)); + + apply_edid_quirks(link, edid, &edid_caps); + + KUNIT_EXPECT_TRUE(test, link->wa_flags.skip_phy_ssc_reduction); +} + +/** + * dm_test_apply_edid_quirks_unknown_noop - Test unknown panel id is a no-op + * @test: The KUnit test context + */ +static void dm_test_apply_edid_quirks_unknown_noop(struct kunit *test) +{ + struct dc_edid_caps edid_caps = {0}; + struct dc_link *link = dm_test_quirk_link(test); + struct edid *edid = dm_test_edid_with_panel_id(test, + drm_edid_encode_panel_id('X', 'Y', 'Z', 0x0000)); + + apply_edid_quirks(link, edid, &edid_caps); + + /* default: branch leaves every quirk field untouched */ + KUNIT_EXPECT_EQ(test, edid_caps.panel_patch.wait_after_dpcd_poweroff_ms, 0U); + KUNIT_EXPECT_FALSE(test, edid_caps.panel_patch.disable_fams); + KUNIT_EXPECT_FALSE(test, edid_caps.panel_patch.remove_sink_ext_caps); + KUNIT_EXPECT_FALSE(test, edid_caps.panel_patch.disable_colorimetry); + KUNIT_EXPECT_FALSE(test, link->wa_flags.skip_phy_ssc_reduction); +} + +/* Tests for dm_helpers_parse_edid_caps() */ + +/* + * Build a minimal valid base EDID block into @dc_edid. When @good_checksum is + * false the final checksum byte is corrupted so drm_edid_is_valid() fails. + * + * manufacturer_id = 0xAC10, product_id = 0x1234, serial = 0x12345678, + * week = 10, year (raw) = 30, digital input, no CEA extension. + */ +static void dm_test_fill_base_edid(struct dc_edid *dc_edid, bool good_checksum) +{ + static const u8 header[8] = { + 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00 + }; + u8 *raw = dc_edid->raw_edid; + int sum = 0; + int i; + + memset(raw, 0, EDID_LENGTH); + memcpy(raw, header, sizeof(header)); + + raw[8] = 0x10; raw[9] = 0xAC; /* mfg_id -> 0xAC10 */ + raw[10] = 0x34; raw[11] = 0x12; /* prod_code -> 0x1234 */ + raw[12] = 0x78; raw[13] = 0x56; /* serial -> 0x12345678 */ + raw[14] = 0x34; raw[15] = 0x12; + raw[16] = 10; /* mfg_week */ + raw[17] = 30; /* mfg_year (raw) */ + raw[18] = 1; /* version */ + raw[19] = 4; /* revision */ + raw[20] = DRM_EDID_INPUT_DIGITAL; /* digital input */ + raw[126] = 0; /* no extensions */ + + for (i = 0; i < EDID_LENGTH - 1; i++) + sum += raw[i]; + raw[127] = (256 - (sum % 256)) % 256; + if (!good_checksum) + raw[127] ^= 0xFF; /* corrupt checksum */ + + dc_edid->length = EDID_LENGTH; +} + +/** + * dm_test_parse_edid_caps_null_edid - Test NULL edid returns EDID_BAD_INPUT + * @test: The KUnit test context + */ +static void dm_test_parse_edid_caps_null_edid(struct kunit *test) +{ + struct dc_edid_caps edid_caps = {0}; + struct dc_link *link = dm_test_quirk_link(test); + + KUNIT_EXPECT_EQ(test, dm_helpers_parse_edid_caps(link, NULL, &edid_caps), EDID_BAD_INPUT); +} + +/** + * dm_test_parse_edid_caps_null_caps - Test NULL edid_caps returns EDID_BAD_INPUT + * @test: The KUnit test context + */ +static void dm_test_parse_edid_caps_null_caps(struct kunit *test) +{ + struct dc_link *link = dm_test_quirk_link(test); + struct dc_edid *dc_edid; + + dc_edid = kunit_kzalloc(test, sizeof(*dc_edid), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc_edid); + dm_test_fill_base_edid(dc_edid, true); + + KUNIT_EXPECT_EQ(test, dm_helpers_parse_edid_caps(link, dc_edid, NULL), EDID_BAD_INPUT); +} + +/** + * dm_test_parse_edid_caps_valid - Test field extraction from a valid EDID + * @test: The KUnit test context + */ +static void dm_test_parse_edid_caps_valid(struct kunit *test) +{ + struct dc_link *link = dm_test_quirk_link(test); + struct dc_edid_caps *edid_caps; + struct dc_edid *dc_edid; + + dc_edid = kunit_kzalloc(test, sizeof(*dc_edid), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc_edid); + edid_caps = kunit_kzalloc(test, sizeof(*edid_caps), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, edid_caps); + + dm_test_fill_base_edid(dc_edid, true); + + KUNIT_EXPECT_EQ(test, dm_helpers_parse_edid_caps(link, dc_edid, edid_caps), EDID_OK); + KUNIT_EXPECT_EQ(test, edid_caps->manufacturer_id, 0xAC10); + KUNIT_EXPECT_EQ(test, edid_caps->product_id, 0x1234); + KUNIT_EXPECT_EQ(test, edid_caps->serial_number, 0x12345678U); + KUNIT_EXPECT_EQ(test, edid_caps->manufacture_week, 10); + KUNIT_EXPECT_EQ(test, edid_caps->manufacture_year, 30); + KUNIT_EXPECT_FALSE(test, edid_caps->analog); +} + +/** + * dm_test_parse_edid_caps_bad_checksum - Test bad checksum still parses fields + * @test: The KUnit test context + */ +static void dm_test_parse_edid_caps_bad_checksum(struct kunit *test) +{ + struct dc_link *link = dm_test_quirk_link(test); + struct dc_edid_caps *edid_caps; + struct dc_edid *dc_edid; + + dc_edid = kunit_kzalloc(test, sizeof(*dc_edid), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc_edid); + edid_caps = kunit_kzalloc(test, sizeof(*edid_caps), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, edid_caps); + + dm_test_fill_base_edid(dc_edid, false); + + /* Invalid checksum -> EDID_BAD_CHECKSUM but fields are still parsed */ + KUNIT_EXPECT_EQ(test, + dm_helpers_parse_edid_caps(link, dc_edid, edid_caps), + EDID_BAD_CHECKSUM); + KUNIT_EXPECT_EQ(test, edid_caps->manufacturer_id, 0xAC10); + KUNIT_EXPECT_EQ(test, edid_caps->product_id, 0x1234); +} + +/** + * dm_test_parse_edid_caps_hdmi_frl - Test HDMI/FRL branch via connector info + * @test: The KUnit test context + * + * Drives the edid_caps->edid_hdmi path by marking the connector as HDMI and + * providing a fake dc with FRL enabled, so populate_hdmi_info_from_connector() + * is exercised inside dm_helpers_parse_edid_caps(). + */ +static void dm_test_parse_edid_caps_hdmi_frl(struct kunit *test) +{ + struct dc_link *link = dm_test_quirk_link(test); + struct amdgpu_dm_connector *aconnector = link->priv; + struct drm_connector *connector = &aconnector->base; + struct dc_edid_caps *edid_caps; + struct dc_edid *dc_edid; + struct dc *dc; + + dc_edid = kunit_kzalloc(test, sizeof(*dc_edid), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc_edid); + edid_caps = kunit_kzalloc(test, sizeof(*edid_caps), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, edid_caps); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc); + + link->dc = dc; + dc->config.enable_frl = true; + + dm_test_fill_base_edid(dc_edid, true); + + /* Drive the HDMI/FRL branch */ + connector->display_info.is_hdmi = true; + connector->display_info.hdmi.scdc.supported = true; + connector->display_info.hdmi.max_lanes = 4; + connector->display_info.hdmi.max_frl_rate_per_lane = 12; + + KUNIT_EXPECT_EQ(test, dm_helpers_parse_edid_caps(link, dc_edid, edid_caps), EDID_OK); + KUNIT_EXPECT_TRUE(test, edid_caps->edid_hdmi); + KUNIT_EXPECT_TRUE(test, edid_caps->scdc_present); + /* max_lanes 4 + max_frl_rate_per_lane 12 -> rate index 6 */ + KUNIT_EXPECT_EQ(test, edid_caps->max_frl_rate, 6); +} + +/** + * dm_test_parse_edid_caps_hdmi_frl_dsc - Test HDMI FRL DSC sub-branch + * @test: The KUnit test context + * + * Sets the connector's HDMI DSC capability so populate_hdmi_info_from_connector() + * reports frl_dsc_support, exercising the frl_dsc_support log path. + */ +static void dm_test_parse_edid_caps_hdmi_frl_dsc(struct kunit *test) +{ + struct dc_link *link = dm_test_quirk_link(test); + struct amdgpu_dm_connector *aconnector = link->priv; + struct drm_connector *connector = &aconnector->base; + struct dc_edid_caps *edid_caps; + struct dc_edid *dc_edid; + struct dc *dc; + + dc_edid = kunit_kzalloc(test, sizeof(*dc_edid), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc_edid); + edid_caps = kunit_kzalloc(test, sizeof(*edid_caps), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, edid_caps); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc); + + link->dc = dc; + dc->config.enable_frl = true; + + dm_test_fill_base_edid(dc_edid, true); + + connector->display_info.is_hdmi = true; + /* Drive the frl_dsc_support sub-branch */ + connector->display_info.hdmi.dsc_cap.v_1p2 = true; + connector->display_info.hdmi.dsc_cap.bpc_supported = 10; + + KUNIT_EXPECT_EQ(test, dm_helpers_parse_edid_caps(link, dc_edid, edid_caps), EDID_OK); + KUNIT_EXPECT_TRUE(test, edid_caps->frl_dsc_support); + KUNIT_EXPECT_TRUE(test, edid_caps->frl_dsc_10bpc); +} + +/* + * Build a 2-block EDID: a valid base block plus a CTA-861 extension block + * carrying one LPCM Short Audio Descriptor and, when @with_speaker is set, a + * Speaker Allocation Data Block, so the audio/speaker parsing code is + * exercised. + */ +static void dm_test_fill_cea_edid(struct dc_edid *dc_edid, bool with_speaker) +{ + u8 *raw = dc_edid->raw_edid; + u8 *ext = raw + EDID_LENGTH; + u8 dtd_offset; + int sum = 0; + int i; + + /* Base block, flagged as having one extension */ + dm_test_fill_base_edid(dc_edid, true); + raw[126] = 1; + for (i = 0; i < EDID_LENGTH - 1; i++) + sum += raw[i]; + raw[127] = (256 - (sum % 256)) % 256; + + /* Audio DB spans [4,7]; optional Speaker Alloc DB spans [8,11] */ + dtd_offset = with_speaker ? 12 : 8; + + /* CTA-861 extension block */ + memset(ext, 0, EDID_LENGTH); + ext[0] = 0x02; /* CTA extension tag */ + ext[1] = 0x03; /* revision 3 */ + ext[2] = dtd_offset; /* DTD offset; end of data blocks */ + ext[3] = 0x00; /* no native DTDs / feature flags */ + /* Audio Data Block: tag 1, length 3, one LPCM SAD */ + ext[4] = (1 << 5) | 3; + ext[5] = (1 << 3) | 1; /* format 1 (LPCM), 2 channels */ + ext[6] = 0x07; /* 32 / 44.1 / 48 kHz */ + ext[7] = 0x07; /* 16 / 20 / 24-bit */ + if (with_speaker) { + /* Speaker Allocation Data Block: tag 4, length 3 */ + ext[8] = (4 << 5) | 3; + ext[9] = 0x01; /* front left / front right */ + } + + sum = 0; + for (i = 0; i < EDID_LENGTH - 1; i++) + sum += ext[i]; + ext[127] = (256 - (sum % 256)) % 256; + + dc_edid->length = 2 * EDID_LENGTH; +} + +/** + * dm_test_parse_edid_caps_cea_audio - Test CEA audio/speaker block parsing + * @test: The KUnit test context + */ +static void dm_test_parse_edid_caps_cea_audio(struct kunit *test) +{ + struct dc_link *link = dm_test_quirk_link(test); + struct dc_edid_caps *edid_caps; + struct dc_edid *dc_edid; + + dc_edid = kunit_kzalloc(test, sizeof(*dc_edid), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc_edid); + edid_caps = kunit_kzalloc(test, sizeof(*edid_caps), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, edid_caps); + + dm_test_fill_cea_edid(dc_edid, true); + + KUNIT_EXPECT_EQ(test, dm_helpers_parse_edid_caps(link, dc_edid, edid_caps), EDID_OK); + KUNIT_EXPECT_EQ(test, edid_caps->audio_mode_count, 1); + KUNIT_EXPECT_EQ(test, edid_caps->audio_modes[0].format_code, 1); + KUNIT_EXPECT_EQ(test, edid_caps->audio_modes[0].channel_count, 2); + KUNIT_EXPECT_EQ(test, edid_caps->audio_modes[0].sample_rate, 0x07); + KUNIT_EXPECT_EQ(test, edid_caps->audio_modes[0].sample_size, 0x07); + KUNIT_EXPECT_EQ(test, edid_caps->speaker_flags, 0x01); +} + +/** + * dm_test_parse_edid_caps_cea_no_speaker - Test default speaker flags path + * @test: The KUnit test context + * + * Audio data block present but no Speaker Allocation Data Block, so + * speaker_flags falls back to DEFAULT_SPEAKER_LOCATION. + */ +static void dm_test_parse_edid_caps_cea_no_speaker(struct kunit *test) +{ + struct dc_link *link = dm_test_quirk_link(test); + struct dc_edid_caps *edid_caps; + struct dc_edid *dc_edid; + + dc_edid = kunit_kzalloc(test, sizeof(*dc_edid), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc_edid); + edid_caps = kunit_kzalloc(test, sizeof(*edid_caps), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, edid_caps); + + dm_test_fill_cea_edid(dc_edid, false); + + KUNIT_EXPECT_EQ(test, dm_helpers_parse_edid_caps(link, dc_edid, edid_caps), EDID_OK); + KUNIT_EXPECT_EQ(test, edid_caps->audio_mode_count, 1); + KUNIT_EXPECT_EQ(test, edid_caps->speaker_flags, DEFAULT_SPEAKER_LOCATION); +} + +/* Tests for ACPI and VBIOS EDID readers */ + +/** + * dm_test_probe_acpi_edid_no_companion - Test ACPI probe without companion + * @test: The KUnit test context + */ +static void dm_test_probe_acpi_edid_no_companion(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct amdgpu_device *adev; + u8 buf[EDID_LENGTH] = {0}; + + adev = dm_kunit_alloc_adev(test); + aconnector = dm_kunit_alloc_connector(test, adev, NULL); + + KUNIT_EXPECT_EQ(test, + dm_helpers_probe_acpi_edid(&aconnector->base, buf, 0, sizeof(buf)), + -ENODEV); +} + +/** + * dm_test_read_acpi_edid_debug_mask_disabled - Test debug mask early return + * @test: The KUnit test context + */ +static void dm_test_read_acpi_edid_debug_mask_disabled(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct amdgpu_device *adev; + uint old_debug_mask; + + adev = dm_kunit_alloc_adev(test); + aconnector = dm_kunit_alloc_connector(test, adev, NULL); + old_debug_mask = dm_helpers_get_dc_debug_mask(); + + dm_helpers_set_dc_debug_mask(old_debug_mask | DC_DISABLE_ACPI_EDID); + KUNIT_EXPECT_NULL(test, dm_helpers_read_acpi_edid(aconnector)); + dm_helpers_set_dc_debug_mask(old_debug_mask); +} + +/** + * dm_test_read_acpi_edid_non_panel_connector - Test non-panel connector skip + * @test: The KUnit test context + */ +static void dm_test_read_acpi_edid_non_panel_connector(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct amdgpu_device *adev; + + adev = dm_kunit_alloc_adev(test); + aconnector = dm_kunit_alloc_connector(test, adev, NULL); + aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA; + + KUNIT_EXPECT_NULL(test, dm_helpers_read_acpi_edid(aconnector)); +} + +/** + * dm_test_read_acpi_edid_force_off - Test forced-off connector skip + * @test: The KUnit test context + */ +static void dm_test_read_acpi_edid_force_off(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct amdgpu_device *adev; + + adev = dm_kunit_alloc_adev(test); + aconnector = dm_kunit_alloc_connector(test, adev, NULL); + aconnector->base.connector_type = DRM_MODE_CONNECTOR_eDP; + aconnector->base.force = DRM_FORCE_OFF; + + KUNIT_EXPECT_NULL(test, dm_helpers_read_acpi_edid(aconnector)); +} + +struct dm_test_vbios_edid { + struct dc_bios bios; + enum bp_result result; + const u8 *fake_edid; + u16 fake_edid_size; + u16 width_mm; + u16 height_mm; +}; + +static enum bp_result dm_test_get_embedded_panel_info(struct dc_bios *bios, + struct embedded_panel_info *info) +{ + struct dm_test_vbios_edid *fixture; + + fixture = container_of(bios, struct dm_test_vbios_edid, bios); + if (fixture->result != BP_RESULT_OK) + return fixture->result; + + info->fake_edid = fixture->fake_edid; + info->fake_edid_size = fixture->fake_edid_size; + info->panel_width_mm = fixture->width_mm; + info->panel_height_mm = fixture->height_mm; + + return BP_RESULT_OK; +} + +static const struct dc_vbios_funcs dm_test_vbios_edid_funcs = { + .get_embedded_panel_info = dm_test_get_embedded_panel_info, +}; + +static void dm_test_setup_vbios_link(struct kunit *test, + struct dc_link **link_out, + struct amdgpu_dm_connector **aconnector_out, + struct dm_test_vbios_edid **fixture_out) +{ + struct dm_test_vbios_edid *fixture; + struct amdgpu_dm_connector *aconnector; + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_link *link; + + adev = dm_kunit_alloc_adev(test); + link = dm_kunit_alloc_link(test); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + fixture = kunit_kzalloc(test, sizeof(*fixture), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, fixture); + aconnector = dm_kunit_alloc_connector(test, adev, link); + + fixture->bios.funcs = &dm_test_vbios_edid_funcs; + fixture->result = BP_RESULT_OK; + ctx->dc_bios = &fixture->bios; + link->ctx = ctx; + link->priv = aconnector; + link->connector_signal = SIGNAL_TYPE_EDP; + + *link_out = link; + *aconnector_out = aconnector; + *fixture_out = fixture; +} + +/** + * dm_test_read_vbios_edid_non_embedded - Test non-embedded signal skip + * @test: The KUnit test context + */ +static void dm_test_read_vbios_edid_non_embedded(struct kunit *test) +{ + struct dm_test_vbios_edid *fixture; + struct amdgpu_dm_connector *aconnector; + struct dc_link *link; + + dm_test_setup_vbios_link(test, &link, &aconnector, &fixture); + link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT; + + KUNIT_EXPECT_NULL(test, + dm_helpers_read_vbios_hardcoded_edid(link, aconnector)); +} + +/** + * dm_test_read_vbios_edid_missing_callback - Test missing callback skip + * @test: The KUnit test context + */ +static void dm_test_read_vbios_edid_missing_callback(struct kunit *test) +{ + static const struct dc_vbios_funcs empty_funcs; + struct dm_test_vbios_edid *fixture; + struct amdgpu_dm_connector *aconnector; + struct dc_link *link; + + dm_test_setup_vbios_link(test, &link, &aconnector, &fixture); + fixture->bios.funcs = &empty_funcs; + + KUNIT_EXPECT_NULL(test, + dm_helpers_read_vbios_hardcoded_edid(link, aconnector)); +} + +/** + * dm_test_read_vbios_edid_callback_error - Test callback failure skip + * @test: The KUnit test context + */ +static void dm_test_read_vbios_edid_callback_error(struct kunit *test) +{ + struct dm_test_vbios_edid *fixture; + struct amdgpu_dm_connector *aconnector; + struct dc_link *link; + + dm_test_setup_vbios_link(test, &link, &aconnector, &fixture); + fixture->result = BP_RESULT_BADINPUT; + + KUNIT_EXPECT_NULL(test, + dm_helpers_read_vbios_hardcoded_edid(link, aconnector)); +} + +/** + * dm_test_read_vbios_edid_missing_fake_edid - Test missing fake EDID skip + * @test: The KUnit test context + */ +static void dm_test_read_vbios_edid_missing_fake_edid(struct kunit *test) +{ + struct dm_test_vbios_edid *fixture; + struct amdgpu_dm_connector *aconnector; + struct dc_link *link; + + dm_test_setup_vbios_link(test, &link, &aconnector, &fixture); + fixture->fake_edid = NULL; + fixture->fake_edid_size = 0; + + KUNIT_EXPECT_NULL(test, + dm_helpers_read_vbios_hardcoded_edid(link, aconnector)); +} + +/** + * dm_test_read_vbios_edid_invalid_fake_edid - Test invalid fake EDID skip + * @test: The KUnit test context + */ +static void dm_test_read_vbios_edid_invalid_fake_edid(struct kunit *test) +{ + struct dm_test_vbios_edid *fixture; + struct amdgpu_dm_connector *aconnector; + struct dc_edid *dc_edid; + struct dc_link *link; + + dm_test_setup_vbios_link(test, &link, &aconnector, &fixture); + dc_edid = kunit_kzalloc(test, sizeof(*dc_edid), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc_edid); + dm_test_fill_base_edid(dc_edid, false); + fixture->fake_edid = dc_edid->raw_edid; + fixture->fake_edid_size = EDID_LENGTH; + + KUNIT_EXPECT_NULL(test, + dm_helpers_read_vbios_hardcoded_edid(link, aconnector)); +} + +/** + * dm_test_read_vbios_edid_valid - Test valid fake EDID updates display size + * @test: The KUnit test context + */ +static void dm_test_read_vbios_edid_valid(struct kunit *test) +{ + struct dm_test_vbios_edid *fixture; + struct amdgpu_dm_connector *aconnector; + const struct drm_edid *edid; + struct dc_edid *dc_edid; + struct dc_link *link; + + dm_test_setup_vbios_link(test, &link, &aconnector, &fixture); + dc_edid = kunit_kzalloc(test, sizeof(*dc_edid), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc_edid); + dm_test_fill_base_edid(dc_edid, true); + fixture->fake_edid = dc_edid->raw_edid; + fixture->fake_edid_size = EDID_LENGTH; + fixture->width_mm = 301; + fixture->height_mm = 201; + + edid = dm_helpers_read_vbios_hardcoded_edid(link, aconnector); + + KUNIT_ASSERT_NOT_NULL(test, edid); + KUNIT_EXPECT_EQ(test, aconnector->base.display_info.width_mm, 301U); + KUNIT_EXPECT_EQ(test, aconnector->base.display_info.height_mm, 201U); + drm_edid_free(edid); +} + /* Tests for dm_is_freesync_pcon_whitelist() */ /** @@ -581,6 +1281,33 @@ static struct kunit_case amdgpu_dm_helpers_test_cases[] = { /* edid_extract_panel_id */ KUNIT_CASE(dm_test_edid_extract_panel_id_basic), KUNIT_CASE(dm_test_edid_extract_panel_id_zeros), + /* apply_edid_quirks */ + KUNIT_CASE(dm_test_apply_edid_quirks_dpcd_poweroff_delay), + KUNIT_CASE(dm_test_apply_edid_quirks_disable_fams), + KUNIT_CASE(dm_test_apply_edid_quirks_remove_sink_ext_caps), + KUNIT_CASE(dm_test_apply_edid_quirks_disable_colorimetry), + KUNIT_CASE(dm_test_apply_edid_quirks_skip_phy_ssc), + KUNIT_CASE(dm_test_apply_edid_quirks_unknown_noop), + /* dm_helpers_parse_edid_caps */ + KUNIT_CASE(dm_test_parse_edid_caps_null_edid), + KUNIT_CASE(dm_test_parse_edid_caps_null_caps), + KUNIT_CASE(dm_test_parse_edid_caps_valid), + KUNIT_CASE(dm_test_parse_edid_caps_bad_checksum), + KUNIT_CASE(dm_test_parse_edid_caps_hdmi_frl), + KUNIT_CASE(dm_test_parse_edid_caps_hdmi_frl_dsc), + KUNIT_CASE(dm_test_parse_edid_caps_cea_audio), + KUNIT_CASE(dm_test_parse_edid_caps_cea_no_speaker), + /* ACPI / VBIOS / local EDID readers */ + KUNIT_CASE(dm_test_probe_acpi_edid_no_companion), + KUNIT_CASE(dm_test_read_acpi_edid_debug_mask_disabled), + KUNIT_CASE(dm_test_read_acpi_edid_non_panel_connector), + KUNIT_CASE(dm_test_read_acpi_edid_force_off), + KUNIT_CASE(dm_test_read_vbios_edid_non_embedded), + KUNIT_CASE(dm_test_read_vbios_edid_missing_callback), + KUNIT_CASE(dm_test_read_vbios_edid_callback_error), + KUNIT_CASE(dm_test_read_vbios_edid_missing_fake_edid), + KUNIT_CASE(dm_test_read_vbios_edid_invalid_fake_edid), + KUNIT_CASE(dm_test_read_vbios_edid_valid), /* dm_is_freesync_pcon_whitelist */ KUNIT_CASE(dm_test_freesync_pcon_whitelist_all_known), KUNIT_CASE(dm_test_freesync_pcon_whitelist_not_in_list), From 7af4219735d90c6a935c74406fb33e0c4cac9ecc Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Wed, 24 Jun 2026 23:34:18 -0600 Subject: [PATCH 122/577] drm/amd/display: Test DPCD AUX and Synaptics helpers [WHAT] Add KUnit coverage for DTN logging, DPCD read/write, fused IO and the Synaptics DSC workaround helpers execute_synaptics_rc_command, apply_synaptics_fifo_reset_wa, write_dsc_enable_synaptics_non_virtual_dpcd_mst and dm_helpers_dp_write_dsc_enable. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 11 +- .../amd/display/amdgpu_dm/amdgpu_dm_helpers.h | 13 + .../amdgpu_dm/tests/amdgpu_dm_helpers_test.c | 485 ++++++++++++++++++ 3 files changed, 506 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c index 8f905ad8aaa1..169f83a3c901 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c @@ -719,8 +719,9 @@ bool dm_helpers_execute_fused_io( return amdgpu_dm_execute_fused_io(dev, link, commands, count, timeout_us); } +EXPORT_IF_KUNIT(dm_helpers_execute_fused_io); -static bool execute_synaptics_rc_command(struct drm_dp_aux *aux, +STATIC_IFN_KUNIT bool execute_synaptics_rc_command(struct drm_dp_aux *aux, bool is_write_cmd, unsigned char cmd, unsigned int length, @@ -792,8 +793,9 @@ err: DRM_ERROR("%s: write cmd ..., err = %d\n", __func__, ret); return false; } +EXPORT_IF_KUNIT(execute_synaptics_rc_command); -static void apply_synaptics_fifo_reset_wa(struct drm_dp_aux *aux) +STATIC_IFN_KUNIT void apply_synaptics_fifo_reset_wa(struct drm_dp_aux *aux) { unsigned char data[16] = {0}; @@ -857,11 +859,12 @@ static void apply_synaptics_fifo_reset_wa(struct drm_dp_aux *aux) drm_dbg_dp(aux->drm_dev, "Done\n"); } +EXPORT_IF_KUNIT(apply_synaptics_fifo_reset_wa); /* MST Dock */ static const uint8_t SYNAPTICS_DEVICE_ID[] = "SYNA"; -static uint8_t write_dsc_enable_synaptics_non_virtual_dpcd_mst( +STATIC_IFN_KUNIT uint8_t write_dsc_enable_synaptics_non_virtual_dpcd_mst( struct drm_dp_aux *aux, const struct dc_stream_state *stream, bool enable) @@ -897,6 +900,7 @@ static uint8_t write_dsc_enable_synaptics_non_virtual_dpcd_mst( return ret; } +EXPORT_IF_KUNIT(write_dsc_enable_synaptics_non_virtual_dpcd_mst); bool dm_helpers_dp_write_dsc_enable( struct dc_context *ctx, @@ -980,6 +984,7 @@ bool dm_helpers_dp_write_dsc_enable( return ret; } +EXPORT_IF_KUNIT(dm_helpers_dp_write_dsc_enable); #if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) uint dm_helpers_get_dc_debug_mask(void) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h index e5cbcd425847..343650b50707 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h @@ -28,12 +28,25 @@ uint8_t get_max_frl_rate(uint8_t max_lanes, uint8_t max_rate_per_lane); bool dm_is_freesync_pcon_whitelist(const uint32_t branch_dev_id); extern const uint32_t dm_freesync_pcon_whitelist[]; uint32_t dm_freesync_pcon_whitelist_count(void); +bool dm_helpers_dp_write_dsc_enable(struct dc_context *ctx, + const struct dc_stream_state *stream, + bool enable); uint dm_helpers_get_dc_debug_mask(void); void dm_helpers_set_dc_debug_mask(uint debug_mask); int dm_helpers_probe_acpi_edid(void *data, u8 *buf, unsigned int block, size_t len); const struct drm_edid *dm_helpers_read_acpi_edid(struct amdgpu_dm_connector *aconnector); const struct drm_edid *dm_helpers_read_vbios_hardcoded_edid(struct dc_link *link, struct amdgpu_dm_connector *aconnector); +bool execute_synaptics_rc_command(struct drm_dp_aux *aux, + bool is_write_cmd, + unsigned char cmd, + unsigned int length, + unsigned int offset, + unsigned char *data); +void apply_synaptics_fifo_reset_wa(struct drm_dp_aux *aux); +uint8_t write_dsc_enable_synaptics_non_virtual_dpcd_mst(struct drm_dp_aux *aux, + const struct dc_stream_state *stream, + bool enable); #endif /* CONFIG_DRM_AMD_DC_KUNIT_TEST */ #endif /* __AMDGPU_DM_HELPERS_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c index 3766d8d211e4..eed941a889e9 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c @@ -1211,6 +1211,475 @@ static void dm_test_dp_write_dpcd_null_priv(struct kunit *test) dm_helpers_dp_write_dpcd(NULL, link, 0, &data, sizeof(data))); } +/* + * Stub AUX transfer that ACKs every transaction (zero-filling reads), so + * drm_dp_dpcd_read()/drm_dp_dpcd_write() report the full transfer size. + */ +static ssize_t dm_test_dpcd_ack_transfer(struct drm_dp_aux *aux, + struct drm_dp_aux_msg *msg) +{ + if ((msg->request & ~DP_AUX_I2C_MOT) == DP_AUX_NATIVE_READ) + memset(msg->buffer, 0, msg->size); + msg->reply = DP_AUX_NATIVE_REPLY_ACK; + return msg->size; +} + +/* + * Wire a connector-backed link with a working AUX channel so the DPCD + * read/write helpers can complete a real transaction. + */ +static struct dc_link *dm_test_dpcd_link(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct amdgpu_device *adev; + struct dc_link *link; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_NOT_NULL(test, adev); + + link = dm_kunit_alloc_link(test); + aconnector = dm_kunit_alloc_connector(test, adev, NULL); + + aconnector->dm_dp_aux.aux.drm_dev = &adev->ddev; + aconnector->dm_dp_aux.aux.transfer = dm_test_dpcd_ack_transfer; + drm_dp_aux_init(&aconnector->dm_dp_aux.aux); + + link->priv = aconnector; + + return link; +} + +/** + * dm_test_dp_read_dpcd_success - Test DPCD read returns true on ACKed transfer + * @test: The KUnit test context + */ +static void dm_test_dp_read_dpcd_success(struct kunit *test) +{ + struct dc_link *link = dm_test_dpcd_link(test); + uint8_t data = 0; + + KUNIT_EXPECT_TRUE(test, + dm_helpers_dp_read_dpcd(NULL, link, 0, &data, sizeof(data))); +} + +/** + * dm_test_dp_write_dpcd_success - Test DPCD write returns true on ACKed transfer + * @test: The KUnit test context + */ +static void dm_test_dp_write_dpcd_success(struct kunit *test) +{ + struct dc_link *link = dm_test_dpcd_link(test); + uint8_t data = 0; + + KUNIT_EXPECT_TRUE(test, + dm_helpers_dp_write_dpcd(NULL, link, 0, &data, sizeof(data))); +} + +/* Tests for dm_helpers_execute_fused_io() */ + +/** + * dm_test_execute_fused_io_null_dmub_srv - Test fused IO fails without DMUB service + * @test: The KUnit test context + */ +static void dm_test_execute_fused_io_null_dmub_srv(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_link *link; + union dmub_rb_cmd *commands; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_NOT_NULL(test, adev); + mutex_init(&adev->dm.dpia_aux_lock); + spin_lock_init(&adev->dm.dmub_lock); + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + commands = kunit_kzalloc(test, sizeof(*commands), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, commands); + + ctx->driver_context = adev; + link->ctx = ctx; + commands[0].fused_io.request.u.aux.ddc_line = 0; + + KUNIT_EXPECT_FALSE(test, dm_helpers_execute_fused_io(ctx, link, commands, 1, 1)); +} + +struct dm_test_synaptics_aux { + struct drm_dp_aux aux; + u32 fail_address; + u32 last_dpcd_write_address; + u8 rc_result; + u8 dpcd_read_value; + u8 dpcd_write_value; + u8 last_rc_data[16]; + u8 read_rc_data[16]; + u8 last_rc_command; + u8 rc_commands[32]; + u8 dsc_enable_values[8]; + u32 last_rc_offset; + u32 last_rc_length; + unsigned int rc_data_writes; + unsigned int rc_data_reads; + unsigned int rc_command_reads; + unsigned int rc_result_reads; + unsigned int rc_command_count; + unsigned int downspread_reads; + unsigned int downspread_writes; + unsigned int dsc_enable_writes; +}; + +static ssize_t dm_test_synaptics_aux_transfer(struct drm_dp_aux *aux, + struct drm_dp_aux_msg *msg) +{ + struct dm_test_synaptics_aux *fixture; + u8 request; + u8 *buffer; + size_t copy_size; + unsigned int index; + + fixture = container_of(aux, struct dm_test_synaptics_aux, aux); + request = msg->request & ~DP_AUX_I2C_MOT; + buffer = msg->buffer; + + if (fixture->fail_address == msg->address) + return -EIO; + + if (request == DP_AUX_NATIVE_WRITE) { + switch (msg->address) { + case DP_DOWNSPREAD_CTRL: + fixture->last_dpcd_write_address = msg->address; + if (msg->size) + fixture->dpcd_write_value = buffer[0]; + fixture->downspread_writes++; + break; + case SYNAPTICS_RC_DATA: + copy_size = min_t(size_t, msg->size, sizeof(fixture->last_rc_data)); + memset(fixture->last_rc_data, 0, sizeof(fixture->last_rc_data)); + memcpy(fixture->last_rc_data, buffer, copy_size); + fixture->rc_data_writes++; + break; + case SYNAPTICS_RC_OFFSET: + if (msg->size >= 4) + fixture->last_rc_offset = buffer[0] | buffer[1] << 8 | + buffer[2] << 16 | buffer[3] << 24; + break; + case SYNAPTICS_RC_LENGTH: + if (msg->size >= 2) + fixture->last_rc_length = buffer[0] | buffer[1] << 8; + break; + case SYNAPTICS_RC_COMMAND: + fixture->last_rc_command = buffer[0]; + if (fixture->rc_command_count < ARRAY_SIZE(fixture->rc_commands)) { + fixture->rc_commands[fixture->rc_command_count] = buffer[0] & 0x7f; + fixture->rc_command_count++; + } + break; + case DP_DSC_ENABLE: + if (fixture->dsc_enable_writes < ARRAY_SIZE(fixture->dsc_enable_values)) { + fixture->dsc_enable_values[fixture->dsc_enable_writes] = buffer[0]; + fixture->dsc_enable_writes++; + } + break; + } + msg->reply = DP_AUX_NATIVE_REPLY_ACK; + return msg->size; + } + + if (request == DP_AUX_NATIVE_READ) { + memset(buffer, 0, msg->size); + switch (msg->address) { + case DP_DOWNSPREAD_CTRL: + if (msg->size) + buffer[0] = fixture->dpcd_read_value; + fixture->downspread_reads++; + break; + case SYNAPTICS_RC_COMMAND: + if (msg->size) + buffer[0] = fixture->last_rc_command & 0x7f; + fixture->rc_command_reads++; + break; + case SYNAPTICS_RC_RESULT: + if (msg->size) + buffer[0] = fixture->rc_result; + fixture->rc_result_reads++; + break; + case SYNAPTICS_RC_DATA: + copy_size = min_t(size_t, msg->size, sizeof(fixture->read_rc_data)); + for (index = 0; index < copy_size; index++) + buffer[index] = fixture->read_rc_data[index]; + fixture->rc_data_reads++; + break; + } + msg->reply = DP_AUX_NATIVE_REPLY_ACK; + return msg->size; + } + + msg->reply = DP_AUX_NATIVE_REPLY_ACK; + return msg->size; +} + +static struct dm_test_synaptics_aux *dm_test_alloc_synaptics_aux_with_dev(struct kunit *test, + struct drm_device *drm_dev) +{ + struct dm_test_synaptics_aux *fixture; + unsigned int index; + + fixture = kunit_kzalloc(test, sizeof(*fixture), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, fixture); + + for (index = 0; index < ARRAY_SIZE(fixture->read_rc_data); index++) + fixture->read_rc_data[index] = 0x03; + + fixture->rc_result = 0; + fixture->aux.drm_dev = drm_dev; + fixture->aux.transfer = dm_test_synaptics_aux_transfer; + drm_dp_aux_init(&fixture->aux); + + return fixture; +} + +static struct dm_test_synaptics_aux *dm_test_alloc_synaptics_aux(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_NOT_NULL(test, adev); + + return dm_test_alloc_synaptics_aux_with_dev(test, &adev->ddev); +} + +static void dm_test_expect_synaptics_commands(struct kunit *test, + struct dm_test_synaptics_aux *fixture, + const u8 *expected_commands, + unsigned int expected_count) +{ + unsigned int index; + + KUNIT_ASSERT_EQ(test, fixture->rc_command_count, expected_count); + + for (index = 0; index < expected_count; index++) + KUNIT_EXPECT_EQ(test, fixture->rc_commands[index], expected_commands[index]); +} + +static void dm_test_setup_synaptics_stream(struct dc_stream_state *stream, + struct dc_link *link) +{ + stream->link = link; + stream->signal = SIGNAL_TYPE_DISPLAY_PORT_MST; + link->dpcd_caps.branch_dev_id = DP_BRANCH_DEVICE_ID_90CC24; + link->dpcd_caps.dpcd_rev.raw = DP_DPCD_REV_14; + link->dpcd_caps.sink_count.bits.SINK_COUNT = 2; + memcpy(link->dpcd_caps.branch_dev_name, "SYNA", 4); +} + +/** + * dm_test_execute_synaptics_rc_command_write_success - Test RC write success + * @test: The KUnit test context + */ +static void dm_test_execute_synaptics_rc_command_write_success(struct kunit *test) +{ + struct dm_test_synaptics_aux *fixture; + u8 data[5] = { 'P', 'R', 'I', 'U', 'S' }; + + fixture = dm_test_alloc_synaptics_aux(test); + + KUNIT_EXPECT_TRUE(test, execute_synaptics_rc_command(&fixture->aux, true, + 0x01, sizeof(data), 0x123456, + data)); + KUNIT_EXPECT_EQ(test, memcmp(fixture->last_rc_data, data, sizeof(data)), 0); + KUNIT_EXPECT_EQ(test, fixture->last_rc_offset, 0x123456U); + KUNIT_EXPECT_EQ(test, fixture->last_rc_length, (u32)sizeof(data)); + KUNIT_EXPECT_EQ(test, fixture->last_rc_command, (u8)0x81); + KUNIT_EXPECT_EQ(test, fixture->rc_command_reads, 1U); + KUNIT_EXPECT_EQ(test, fixture->rc_result_reads, 1U); +} + +/** + * dm_test_execute_synaptics_rc_command_read_success - Test RC read success + * @test: The KUnit test context + */ +static void dm_test_execute_synaptics_rc_command_read_success(struct kunit *test) +{ + struct dm_test_synaptics_aux *fixture; + u8 data[4] = { 0 }; + u8 expected[4] = { 0xa5, 0x5a, 0xc3, 0x3c }; + + fixture = dm_test_alloc_synaptics_aux(test); + memcpy(fixture->read_rc_data, expected, sizeof(expected)); + + KUNIT_EXPECT_TRUE(test, execute_synaptics_rc_command(&fixture->aux, false, + 0x31, sizeof(data), 0x220998, + data)); + KUNIT_EXPECT_EQ(test, memcmp(data, expected, sizeof(expected)), 0); + KUNIT_EXPECT_EQ(test, fixture->rc_data_writes, 0U); + KUNIT_EXPECT_EQ(test, fixture->rc_data_reads, 1U); + KUNIT_EXPECT_EQ(test, fixture->last_rc_offset, 0x220998U); + KUNIT_EXPECT_EQ(test, fixture->last_rc_length, (u32)sizeof(data)); +} + +/** + * dm_test_execute_synaptics_rc_command_write_fail - Test RC write failure + * @test: The KUnit test context + */ +static void dm_test_execute_synaptics_rc_command_write_fail(struct kunit *test) +{ + struct dm_test_synaptics_aux *fixture; + u8 data = 0; + + fixture = dm_test_alloc_synaptics_aux(test); + fixture->fail_address = SYNAPTICS_RC_LENGTH; + + KUNIT_EXPECT_FALSE(test, execute_synaptics_rc_command(&fixture->aux, true, + 0x01, sizeof(data), 0, &data)); + KUNIT_EXPECT_EQ(test, fixture->rc_command_count, 0U); +} + +/** + * dm_test_apply_synaptics_fifo_reset_wa_full - Test full FIFO reset sequence + * @test: The KUnit test context + */ +static void dm_test_apply_synaptics_fifo_reset_wa_full(struct kunit *test) +{ + static const u8 expected_commands[] = { + 0x01, 0x31, 0x21, 0x31, 0x21, 0x31, 0x21, + 0x31, 0x21, 0x31, 0x31, 0x21, 0x02, + }; + struct dm_test_synaptics_aux *fixture; + + fixture = dm_test_alloc_synaptics_aux(test); + + apply_synaptics_fifo_reset_wa(&fixture->aux); + + dm_test_expect_synaptics_commands(test, fixture, expected_commands, + ARRAY_SIZE(expected_commands)); + KUNIT_EXPECT_EQ(test, fixture->rc_result_reads, (unsigned int)ARRAY_SIZE(expected_commands)); +} + +/** + * dm_test_apply_synaptics_fifo_reset_wa_first_fail - Test FIFO reset early exit + * @test: The KUnit test context + */ +static void dm_test_apply_synaptics_fifo_reset_wa_first_fail(struct kunit *test) +{ + struct dm_test_synaptics_aux *fixture; + + fixture = dm_test_alloc_synaptics_aux(test); + fixture->rc_result = 0xff; + + apply_synaptics_fifo_reset_wa(&fixture->aux); + + KUNIT_EXPECT_EQ(test, fixture->rc_command_count, 1U); + KUNIT_EXPECT_EQ(test, fixture->rc_commands[0], (u8)0x01); +} + +static void dm_test_write_dsc_enable_synaptics(struct kunit *test, + bool link_active, + bool enable, + bool synaptics_branch, + unsigned int expected_dsc_writes, + unsigned int expected_rc_commands) +{ + struct dm_test_synaptics_aux *fixture; + struct dc_stream_state *stream; + struct dc_link *link; + u8 ret; + + fixture = dm_test_alloc_synaptics_aux(test); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + link = dm_kunit_alloc_link(test); + + dm_test_setup_synaptics_stream(stream, link); + link->link_status.link_active = link_active; + if (!synaptics_branch) + memcpy(link->dpcd_caps.branch_dev_name, "ABCD", 4); + + ret = write_dsc_enable_synaptics_non_virtual_dpcd_mst(&fixture->aux, stream, enable); + + KUNIT_EXPECT_EQ(test, ret, expected_dsc_writes ? 1 : 0); + KUNIT_EXPECT_EQ(test, fixture->dsc_enable_writes, expected_dsc_writes); + KUNIT_EXPECT_EQ(test, fixture->rc_command_count, expected_rc_commands); + if (expected_dsc_writes) + KUNIT_EXPECT_EQ(test, fixture->dsc_enable_values[0], enable ? 1 : 0); +} + +/** + * dm_test_write_dsc_enable_synaptics_enable_inactive - Test enable plus FIFO reset + * @test: The KUnit test context + */ +static void dm_test_write_dsc_enable_synaptics_enable_inactive(struct kunit *test) +{ + dm_test_write_dsc_enable_synaptics(test, false, true, true, 1, 13); +} + +/** + * dm_test_write_dsc_enable_synaptics_enable_active - Test enable skips FIFO reset + * @test: The KUnit test context + */ +static void dm_test_write_dsc_enable_synaptics_enable_active(struct kunit *test) +{ + dm_test_write_dsc_enable_synaptics(test, true, true, true, 1, 0); +} + +/** + * dm_test_write_dsc_enable_synaptics_disable_inactive - Test inactive disable writes DPCD + * @test: The KUnit test context + */ +static void dm_test_write_dsc_enable_synaptics_disable_inactive(struct kunit *test) +{ + dm_test_write_dsc_enable_synaptics(test, false, false, true, 1, 0); +} + +/** + * dm_test_write_dsc_enable_synaptics_disable_active - Test active disable skips DPCD + * @test: The KUnit test context + */ +static void dm_test_write_dsc_enable_synaptics_disable_active(struct kunit *test) +{ + dm_test_write_dsc_enable_synaptics(test, true, false, true, 0, 0); +} + +/** + * dm_test_write_dsc_enable_synaptics_enable_non_synaptics - Test non-Synaptics enable + * @test: The KUnit test context + */ +static void dm_test_write_dsc_enable_synaptics_enable_non_synaptics(struct kunit *test) +{ + dm_test_write_dsc_enable_synaptics(test, false, true, false, 1, 0); +} + +/** + * dm_test_dp_write_dsc_enable_routes_synaptics - Test public DSC helper workaround route + * @test: The KUnit test context + */ +static void dm_test_dp_write_dsc_enable_routes_synaptics(struct kunit *test) +{ + struct dm_test_synaptics_aux *fixture; + struct amdgpu_dm_connector *aconnector; + struct dc_stream_state *stream; + struct dc_link *link; + + fixture = dm_test_alloc_synaptics_aux(test); + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + link = dm_kunit_alloc_link(test); + + dm_test_setup_synaptics_stream(stream, link); + stream->dm_stream_context = aconnector; + aconnector->dc_link = link; + aconnector->dsc_aux = &fixture->aux; + + KUNIT_EXPECT_TRUE(test, dm_helpers_dp_write_dsc_enable(NULL, stream, true)); + KUNIT_EXPECT_EQ(test, fixture->dsc_enable_writes, 1U); + KUNIT_EXPECT_EQ(test, fixture->dsc_enable_values[0], (u8)1); + KUNIT_EXPECT_EQ(test, fixture->rc_command_count, 13U); +} + /* Tests for dm_helpers_dp_mst_start_top_mgr() / dm_helpers_dp_mst_stop_top_mgr() */ /** @@ -1341,6 +1810,22 @@ static struct kunit_case amdgpu_dm_helpers_test_cases[] = { /* dm_helpers_dp_read_dpcd / dm_helpers_dp_write_dpcd */ KUNIT_CASE(dm_test_dp_read_dpcd_null_priv), KUNIT_CASE(dm_test_dp_write_dpcd_null_priv), + KUNIT_CASE(dm_test_dp_read_dpcd_success), + KUNIT_CASE(dm_test_dp_write_dpcd_success), + /* dm_helpers_execute_fused_io */ + KUNIT_CASE(dm_test_execute_fused_io_null_dmub_srv), + /* Synaptics RC/FIFO/DSC helpers */ + KUNIT_CASE(dm_test_execute_synaptics_rc_command_write_success), + KUNIT_CASE(dm_test_execute_synaptics_rc_command_read_success), + KUNIT_CASE(dm_test_execute_synaptics_rc_command_write_fail), + KUNIT_CASE(dm_test_apply_synaptics_fifo_reset_wa_full), + KUNIT_CASE(dm_test_apply_synaptics_fifo_reset_wa_first_fail), + KUNIT_CASE(dm_test_write_dsc_enable_synaptics_enable_inactive), + KUNIT_CASE(dm_test_write_dsc_enable_synaptics_enable_active), + KUNIT_CASE(dm_test_write_dsc_enable_synaptics_disable_inactive), + KUNIT_CASE(dm_test_write_dsc_enable_synaptics_disable_active), + KUNIT_CASE(dm_test_write_dsc_enable_synaptics_enable_non_synaptics), + KUNIT_CASE(dm_test_dp_write_dsc_enable_routes_synaptics), /* dm_helpers_dp_mst_start_top_mgr / dm_helpers_dp_mst_stop_top_mgr */ KUNIT_CASE(dm_test_mst_start_top_mgr_null_priv), KUNIT_CASE(dm_test_mst_stop_top_mgr_null_priv), From 0984a22c9ffc241bf8190d64bf3d26d6f098e73c Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Wed, 24 Jun 2026 23:34:18 -0600 Subject: [PATCH 123/577] drm/amd/display: Test MST start/stop and panel settings [WHAT] Add KUnit coverage for dm_helpers_dp_mst_start_top_mgr and dm_helpers_dp_mst_stop_top_mgr, dm_helpers_dp_write_hblank_reduction, get_dsc_max_slices, dm_helpers_init_panel_settings, dm_helpers_override_panel_settings and fill_dc_mst_payload_table_from_drm. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 8 +- .../amd/display/amdgpu_dm/amdgpu_dm_helpers.h | 5 + .../amdgpu_dm/tests/amdgpu_dm_helpers_test.c | 295 ++++++++++++++++++ 3 files changed, 306 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c index 169f83a3c901..f7ba7eab69e2 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c @@ -240,7 +240,7 @@ enum dc_edid_status dm_helpers_parse_edid_caps( } EXPORT_IF_KUNIT(dm_helpers_parse_edid_caps); -static void +STATIC_IFN_KUNIT void fill_dc_mst_payload_table_from_drm(struct dc_link *link, bool enable, struct drm_dp_mst_atomic_payload *target_payload, @@ -290,6 +290,7 @@ fill_dc_mst_payload_table_from_drm(struct dc_link *link, /* Overwrite the old table */ *table = new_table; } +EXPORT_IF_KUNIT(fill_dc_mst_payload_table_from_drm); void dm_helpers_dp_update_branch_info( struct dc_context *ctx, @@ -1149,7 +1150,7 @@ STATIC_IFN_KUNIT uint8_t get_max_frl_rate(uint8_t max_lanes, uint8_t max_rate_pe } EXPORT_IF_KUNIT(get_max_frl_rate); -static uint8_t get_dsc_max_slices(uint8_t max_slices, int clk_per_slice) +STATIC_IFN_KUNIT uint8_t get_dsc_max_slices(uint8_t max_slices, int clk_per_slice) { uint8_t dsc_max_slices; @@ -1172,6 +1173,7 @@ static uint8_t get_dsc_max_slices(uint8_t max_slices, int clk_per_slice) return dsc_max_slices; } +EXPORT_IF_KUNIT(get_dsc_max_slices); void populate_hdmi_info_from_connector(bool enable_frl, struct drm_hdmi_info *hdmi, struct dc_edid_caps *edid_caps) { @@ -1354,6 +1356,7 @@ void dm_helpers_init_panel_settings( panel_config->dsc.disable_dsc_edp = false; panel_config->dsc.force_dsc_edp_policy = 0; } +EXPORT_IF_KUNIT(dm_helpers_init_panel_settings); void dm_helpers_override_panel_settings( struct dc_context *ctx, @@ -1371,6 +1374,7 @@ void dm_helpers_override_panel_settings( link->panel_config.psr.disallow_replay = true; } } +EXPORT_IF_KUNIT(dm_helpers_override_panel_settings); void *dm_helpers_allocate_gpu_mem( struct dc_context *ctx, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h index 343650b50707..e256e1e12422 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h @@ -25,9 +25,14 @@ u32 edid_extract_panel_id(struct edid *edid); void apply_edid_quirks(struct dc_link *link, struct edid *edid, struct dc_edid_caps *edid_caps); uint8_t get_max_frl_rate(uint8_t max_lanes, uint8_t max_rate_per_lane); +uint8_t get_dsc_max_slices(uint8_t max_slices, int clk_per_slice); bool dm_is_freesync_pcon_whitelist(const uint32_t branch_dev_id); extern const uint32_t dm_freesync_pcon_whitelist[]; uint32_t dm_freesync_pcon_whitelist_count(void); +void fill_dc_mst_payload_table_from_drm(struct dc_link *link, + bool enable, + struct drm_dp_mst_atomic_payload *target_payload, + struct dc_dp_mst_stream_allocation_table *table); bool dm_helpers_dp_write_dsc_enable(struct dc_context *ctx, const struct dc_stream_state *stream, bool enable); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c index eed941a889e9..599de8240bee 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c @@ -1746,6 +1746,282 @@ static void dm_test_dp_write_hblank_reduction_false(struct kunit *test) KUNIT_EXPECT_FALSE(test, dm_helpers_dp_write_hblank_reduction(NULL, NULL)); } +/* Tests for get_dsc_max_slices() */ + +/** + * dm_test_get_dsc_max_slices_1_340 - Test 1 slice at 340 MHz + * @test: The KUnit test context + */ +static void dm_test_get_dsc_max_slices_1_340(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, get_dsc_max_slices(1, 340), 1); +} + +/** + * dm_test_get_dsc_max_slices_2_340 - Test 2 slices at 340 MHz + * @test: The KUnit test context + */ +static void dm_test_get_dsc_max_slices_2_340(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, get_dsc_max_slices(2, 340), 2); +} + +/** + * dm_test_get_dsc_max_slices_4_340 - Test 4 slices at 340 MHz + * @test: The KUnit test context + */ +static void dm_test_get_dsc_max_slices_4_340(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, get_dsc_max_slices(4, 340), 3); +} + +/** + * dm_test_get_dsc_max_slices_8_340 - Test 8 slices at 340 MHz + * @test: The KUnit test context + */ +static void dm_test_get_dsc_max_slices_8_340(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, get_dsc_max_slices(8, 340), 4); +} + +/** + * dm_test_get_dsc_max_slices_8_400 - Test 8 slices at 400 MHz + * @test: The KUnit test context + */ +static void dm_test_get_dsc_max_slices_8_400(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, get_dsc_max_slices(8, 400), 5); +} + +/** + * dm_test_get_dsc_max_slices_12_400 - Test 12 slices at 400 MHz + * @test: The KUnit test context + */ +static void dm_test_get_dsc_max_slices_12_400(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, get_dsc_max_slices(12, 400), 6); +} + +/** + * dm_test_get_dsc_max_slices_16_400 - Test 16 slices at 400 MHz + * @test: The KUnit test context + */ +static void dm_test_get_dsc_max_slices_16_400(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, get_dsc_max_slices(16, 400), 7); +} + +/** + * dm_test_get_dsc_max_slices_unknown - Test unknown combination returns 0 + * @test: The KUnit test context + */ +static void dm_test_get_dsc_max_slices_unknown(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, get_dsc_max_slices(3, 340), 0); + KUNIT_EXPECT_EQ(test, get_dsc_max_slices(1, 400), 0); +} + +/* Tests for dm_helpers_init_panel_settings() */ + +/** + * dm_test_init_panel_settings_pps - Test panel power sequence settings init + * @test: The KUnit test context + */ +static void dm_test_init_panel_settings_pps(struct kunit *test) +{ + struct dc_panel_config panel_config = {0}; + struct dc_sink *sink; + + sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, sink); + + sink->edid_caps.panel_patch.extra_t3_ms = 100; + sink->edid_caps.panel_patch.extra_t7_ms = 200; + sink->edid_caps.panel_patch.extra_delay_backlight_off = 50; + sink->edid_caps.panel_patch.extra_t12_ms = 300; + + dm_helpers_init_panel_settings(NULL, &panel_config, sink); + + KUNIT_EXPECT_EQ(test, panel_config.pps.extra_t3_ms, 100U); + KUNIT_EXPECT_EQ(test, panel_config.pps.extra_t7_ms, 200U); + KUNIT_EXPECT_EQ(test, panel_config.pps.extra_delay_backlight_off, 50U); + KUNIT_EXPECT_EQ(test, panel_config.pps.extra_post_t7_ms, 0U); + KUNIT_EXPECT_EQ(test, panel_config.pps.extra_pre_t11_ms, 0U); + KUNIT_EXPECT_EQ(test, panel_config.pps.extra_t12_ms, 300U); + KUNIT_EXPECT_EQ(test, panel_config.pps.extra_post_OUI_ms, 0U); +} + +/** + * dm_test_init_panel_settings_dsc - Test DSC defaults in panel settings init + * @test: The KUnit test context + */ +static void dm_test_init_panel_settings_dsc(struct kunit *test) +{ + struct dc_panel_config panel_config; + struct dc_sink *sink; + + memset(&panel_config, 0xFF, sizeof(panel_config)); + + sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, sink); + + dm_helpers_init_panel_settings(NULL, &panel_config, sink); + + KUNIT_EXPECT_FALSE(test, panel_config.dsc.disable_dsc_edp); + KUNIT_EXPECT_EQ(test, panel_config.dsc.force_dsc_edp_policy, 0U); +} + +/* Tests for dm_helpers_override_panel_settings() */ + +/** + * dm_test_override_panel_settings_debug_mask_disables_dsc - Test DSC mask + * @test: The KUnit test context + */ +static void dm_test_override_panel_settings_debug_mask_disables_dsc(struct kunit *test) +{ + struct dc_context *ctx; + struct dc_link *link; + struct dc *dc; + uint old_debug_mask; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc); + link = dm_kunit_alloc_link(test); + ctx->dc = dc; + link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT; + + old_debug_mask = dm_helpers_get_dc_debug_mask(); + dm_helpers_set_dc_debug_mask(old_debug_mask | DC_DISABLE_DSC); + dm_helpers_override_panel_settings(ctx, link); + dm_helpers_set_dc_debug_mask(old_debug_mask); + + KUNIT_EXPECT_TRUE(test, link->panel_config.dsc.disable_dsc_edp); +} + +/** + * dm_test_override_panel_settings_second_edp_disables_psr - Test eDP index 1 + * @test: The KUnit test context + */ +static void dm_test_override_panel_settings_second_edp_disables_psr(struct kunit *test) +{ + struct dc_context *ctx; + struct dc_link *first_link; + struct dc_link *second_link; + struct dc *dc; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc); + first_link = dm_kunit_alloc_link(test); + second_link = dm_kunit_alloc_link(test); + + ctx->dc = dc; + dc->link_count = 2; + dc->links[0] = first_link; + dc->links[1] = second_link; + first_link->connector_signal = SIGNAL_TYPE_EDP; + second_link->connector_signal = SIGNAL_TYPE_EDP; + + dm_helpers_override_panel_settings(ctx, second_link); + + KUNIT_EXPECT_TRUE(test, second_link->panel_config.psr.disable_psr); + KUNIT_EXPECT_TRUE(test, second_link->panel_config.psr.disallow_psrsu); + KUNIT_EXPECT_TRUE(test, second_link->panel_config.psr.disallow_replay); +} + +/* Tests for fill_dc_mst_payload_table_from_drm() */ + +/** + * dm_test_fill_mst_payload_table_enable - Test payload table fill on enable + * @test: The KUnit test context + */ +static void dm_test_fill_mst_payload_table_enable(struct kunit *test) +{ + struct dc_link *link; + struct drm_dp_mst_atomic_payload payload = {0}; + struct dc_dp_mst_stream_allocation_table table = {0}; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + /* Pre-existing allocation in the link table */ + link->mst_stream_alloc_table.stream_count = 1; + link->mst_stream_alloc_table.stream_allocations[0].vcp_id = 1; + link->mst_stream_alloc_table.stream_allocations[0].slot_count = 4; + + /* New payload to add */ + payload.vcpi = 2; + payload.time_slots = 8; + + fill_dc_mst_payload_table_from_drm(link, true, &payload, &table); + + /* Should contain both the pre-existing and new allocation */ + KUNIT_EXPECT_EQ(test, table.stream_count, 2); + KUNIT_EXPECT_EQ(test, table.stream_allocations[0].vcp_id, 1); + KUNIT_EXPECT_EQ(test, table.stream_allocations[0].slot_count, 4); + KUNIT_EXPECT_EQ(test, table.stream_allocations[1].vcp_id, 2); + KUNIT_EXPECT_EQ(test, table.stream_allocations[1].slot_count, 8); +} + +/** + * dm_test_fill_mst_payload_table_disable - Test payload table fill on disable + * @test: The KUnit test context + */ +static void dm_test_fill_mst_payload_table_disable(struct kunit *test) +{ + struct dc_link *link; + struct drm_dp_mst_atomic_payload payload = {0}; + struct dc_dp_mst_stream_allocation_table table = {0}; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + /* Two existing allocations in the link table */ + link->mst_stream_alloc_table.stream_count = 2; + link->mst_stream_alloc_table.stream_allocations[0].vcp_id = 1; + link->mst_stream_alloc_table.stream_allocations[0].slot_count = 4; + link->mst_stream_alloc_table.stream_allocations[1].vcp_id = 2; + link->mst_stream_alloc_table.stream_allocations[1].slot_count = 8; + + /* Remove vcp_id 1 */ + payload.vcpi = 1; + payload.time_slots = 4; + + fill_dc_mst_payload_table_from_drm(link, false, &payload, &table); + + /* Only vcp_id 2 should remain */ + KUNIT_EXPECT_EQ(test, table.stream_count, 1); + KUNIT_EXPECT_EQ(test, table.stream_allocations[0].vcp_id, 2); + KUNIT_EXPECT_EQ(test, table.stream_allocations[0].slot_count, 8); +} + +/** + * dm_test_fill_mst_payload_table_empty - Test payload table fill when empty + * @test: The KUnit test context + */ +static void dm_test_fill_mst_payload_table_empty(struct kunit *test) +{ + struct dc_link *link; + struct drm_dp_mst_atomic_payload payload = {0}; + struct dc_dp_mst_stream_allocation_table table = {0}; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + /* Enable on an empty table */ + payload.vcpi = 5; + payload.time_slots = 12; + + fill_dc_mst_payload_table_from_drm(link, true, &payload, &table); + + KUNIT_EXPECT_EQ(test, table.stream_count, 1); + KUNIT_EXPECT_EQ(test, table.stream_allocations[0].vcp_id, 5); + KUNIT_EXPECT_EQ(test, table.stream_allocations[0].slot_count, 12); +} + static struct kunit_case amdgpu_dm_helpers_test_cases[] = { /* edid_extract_panel_id */ KUNIT_CASE(dm_test_edid_extract_panel_id_basic), @@ -1832,6 +2108,25 @@ static struct kunit_case amdgpu_dm_helpers_test_cases[] = { KUNIT_CASE(dm_test_mst_start_top_mgr_boot), /* dm_helpers_dp_write_hblank_reduction */ KUNIT_CASE(dm_test_dp_write_hblank_reduction_false), + /* get_dsc_max_slices */ + KUNIT_CASE(dm_test_get_dsc_max_slices_1_340), + KUNIT_CASE(dm_test_get_dsc_max_slices_2_340), + KUNIT_CASE(dm_test_get_dsc_max_slices_4_340), + KUNIT_CASE(dm_test_get_dsc_max_slices_8_340), + KUNIT_CASE(dm_test_get_dsc_max_slices_8_400), + KUNIT_CASE(dm_test_get_dsc_max_slices_12_400), + KUNIT_CASE(dm_test_get_dsc_max_slices_16_400), + KUNIT_CASE(dm_test_get_dsc_max_slices_unknown), + /* dm_helpers_init_panel_settings */ + KUNIT_CASE(dm_test_init_panel_settings_pps), + KUNIT_CASE(dm_test_init_panel_settings_dsc), + /* dm_helpers_override_panel_settings */ + KUNIT_CASE(dm_test_override_panel_settings_debug_mask_disables_dsc), + KUNIT_CASE(dm_test_override_panel_settings_second_edp_disables_psr), + /* fill_dc_mst_payload_table_from_drm */ + KUNIT_CASE(dm_test_fill_mst_payload_table_enable), + KUNIT_CASE(dm_test_fill_mst_payload_table_disable), + KUNIT_CASE(dm_test_fill_mst_payload_table_empty), {} }; From f5e5e134dcf010d2ae45a579fe7470bfa357a2c2 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Wed, 24 Jun 2026 23:34:18 -0600 Subject: [PATCH 124/577] drm/amd/display: Test I2C, stubs and MST early returns [WHAT] Add KUnit coverage for dm_helpers_submit_i2c, dm_helper_dmub_aux_transfer_sync, the empty stub helpers, the MST null-connector early returns, dm_helpers_dmub_outbox_interrupt_control, dm_helpers_mst_enable_stream_features and dm_helpers_enable_periodic_detection. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 18 + .../amdgpu_dm/tests/amdgpu_dm_helpers_test.c | 541 ++++++++++++++++++ 2 files changed, 559 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c index f7ba7eab69e2..ff23aee54c16 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c @@ -296,6 +296,7 @@ void dm_helpers_dp_update_branch_info( struct dc_context *ctx, const struct dc_link *link) {} +EXPORT_IF_KUNIT(dm_helpers_dp_update_branch_info); static void dm_helpers_construct_old_payload( struct drm_dp_mst_topology_mgr *mgr, @@ -380,6 +381,7 @@ bool dm_helpers_dp_mst_write_payload_allocation_table( return true; } +EXPORT_IF_KUNIT(dm_helpers_dp_mst_write_payload_allocation_table); /* * poll pending down reply @@ -388,6 +390,7 @@ void dm_helpers_dp_mst_poll_pending_down_reply( struct dc_context *ctx, const struct dc_link *link) {} +EXPORT_IF_KUNIT(dm_helpers_dp_mst_poll_pending_down_reply); /* * Clear payload allocation table before enable MST DP link. @@ -396,6 +399,7 @@ void dm_helpers_dp_mst_clear_payload_allocation_table( struct dc_context *ctx, const struct dc_link *link) {} +EXPORT_IF_KUNIT(dm_helpers_dp_mst_clear_payload_allocation_table); /* * Polls for ACT (allocation change trigger) handled and sends @@ -426,6 +430,7 @@ enum act_return_status dm_helpers_dp_mst_poll_for_allocation_change_trigger( return ACT_SUCCESS; } +EXPORT_IF_KUNIT(dm_helpers_dp_mst_poll_for_allocation_change_trigger); void dm_helpers_dp_mst_send_payload_allocation( struct dc_context *ctx, @@ -460,6 +465,7 @@ void dm_helpers_dp_mst_send_payload_allocation( clr_flag, false); } } +EXPORT_IF_KUNIT(dm_helpers_dp_mst_send_payload_allocation); void dm_helpers_dp_mst_update_mst_mgr_for_deallocation( struct dc_context *ctx, @@ -488,6 +494,7 @@ void dm_helpers_dp_mst_update_mst_mgr_for_deallocation( amdgpu_dm_set_mst_status(&aconnector->mst_status, set_flag, true); amdgpu_dm_set_mst_status(&aconnector->mst_status, clr_flag, false); } +EXPORT_IF_KUNIT(dm_helpers_dp_mst_update_mst_mgr_for_deallocation); void dm_dtn_log_begin(struct dc_context *ctx, struct dc_log_buffer_ctx *log_ctx) @@ -707,6 +714,7 @@ bool dm_helpers_submit_i2c( return result; } +EXPORT_IF_KUNIT(dm_helpers_submit_i2c); bool dm_helpers_execute_fused_io( struct dc_context *ctx, @@ -1023,6 +1031,7 @@ bool dm_helpers_is_dp_sink_present(struct dc_link *link) mutex_unlock(&aconnector->dm_dp_aux.aux.hw_mutex); return dp_sink_present; } +EXPORT_IF_KUNIT(dm_helpers_is_dp_sink_present); STATIC_IFN_KUNIT int dm_helpers_probe_acpi_edid(void *data, u8 *buf, unsigned int block, size_t len) @@ -1312,6 +1321,7 @@ int dm_helper_dmub_aux_transfer_sync( return amdgpu_dm_process_dmub_aux_transfer_sync(ctx, link->link_index, payload, operation_result); } +EXPORT_IF_KUNIT(dm_helper_dmub_aux_transfer_sync); int dm_helpers_dmub_set_config_sync(struct dc_context *ctx, const struct dc_link *link, @@ -1326,18 +1336,21 @@ void dm_set_dcn_clocks(struct dc_context *ctx, struct dc_clocks *clks) { /* TODO: something */ } +EXPORT_IF_KUNIT(dm_set_dcn_clocks); void dm_helpers_dmu_timeout(struct dc_context *ctx) { // TODO: //amdgpu_device_gpu_recover(dc_context->driver-context, NULL); } +EXPORT_IF_KUNIT(dm_helpers_dmu_timeout); void dm_helpers_smu_timeout(struct dc_context *ctx, unsigned int msg_id, unsigned int param, unsigned int timeout_us) { // TODO: //amdgpu_device_gpu_recover(dc_context->driver-context, NULL); } +EXPORT_IF_KUNIT(dm_helpers_smu_timeout); void dm_helpers_init_panel_settings( struct dc_context *ctx, @@ -1410,6 +1423,7 @@ bool dm_helpers_dmub_outbox_interrupt_control(struct dc_context *ctx, bool enabl enable ? "en" : "dis", ret); return ret; } +EXPORT_IF_KUNIT(dm_helpers_dmub_outbox_interrupt_control); void dm_helpers_mst_enable_stream_features(const struct dc_stream_state *stream) { @@ -1435,6 +1449,7 @@ void dm_helpers_mst_enable_stream_features(const struct dc_stream_state *stream) &new_downspread.raw, sizeof(new_downspread)); } +EXPORT_IF_KUNIT(dm_helpers_mst_enable_stream_features); bool dm_helpers_dp_handle_test_pattern_request( struct dc_context *ctx, @@ -1578,6 +1593,7 @@ void dm_set_phyd32clk(struct dc_context *ctx, int freq_khz) { // TODO } +EXPORT_IF_KUNIT(dm_set_phyd32clk); void dm_helpers_enable_periodic_detection(struct dc_context *ctx, bool enable) { @@ -1589,6 +1605,7 @@ void dm_helpers_enable_periodic_detection(struct dc_context *ctx, bool enable) schedule_work(&adev->dm.idle_workqueue->work); } } +EXPORT_IF_KUNIT(dm_helpers_enable_periodic_detection); void dm_helpers_dp_mst_update_branch_bandwidth( struct dc_context *ctx, @@ -1596,6 +1613,7 @@ void dm_helpers_dp_mst_update_branch_bandwidth( { // TODO } +EXPORT_IF_KUNIT(dm_helpers_dp_mst_update_branch_bandwidth); STATIC_IFN_KUNIT const uint32_t dm_freesync_pcon_whitelist[] = { DP_BRANCH_DEVICE_ID_0060AD, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c index 599de8240bee..29feb8c3940b 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c @@ -1421,6 +1421,14 @@ static ssize_t dm_test_synaptics_aux_transfer(struct drm_dp_aux *aux, return msg->size; } +static struct dm_test_synaptics_aux *dm_test_current_aux_recorder; + +static ssize_t dm_test_current_aux_transfer(struct drm_dp_aux *aux, + struct drm_dp_aux_msg *msg) +{ + return dm_test_synaptics_aux_transfer(&dm_test_current_aux_recorder->aux, msg); +} + static struct dm_test_synaptics_aux *dm_test_alloc_synaptics_aux_with_dev(struct kunit *test, struct drm_device *drm_dev) { @@ -2022,6 +2030,507 @@ static void dm_test_fill_mst_payload_table_empty(struct kunit *test) KUNIT_EXPECT_EQ(test, table.stream_allocations[0].slot_count, 12); } +/* Tests for dm_helpers_submit_i2c() */ + +/** + * dm_test_submit_i2c_null_priv - Test i2c submit returns false without connector + * @test: The KUnit test context + */ +static void dm_test_submit_i2c_null_priv(struct kunit *test) +{ + struct dc_link *link; + struct i2c_command cmd = {0}; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + KUNIT_EXPECT_FALSE(test, dm_helpers_submit_i2c(NULL, link, &cmd)); +} + +struct dm_test_i2c_adapter { + struct i2c_adapter base; + struct kunit *test; + struct i2c_payload *expected_payloads; + int expected_num; + int ret; + int calls; +}; + +static void dm_test_i2c_lock_bus(struct i2c_adapter *adapter, + unsigned int flags) +{ + rt_mutex_lock(&adapter->bus_lock); +} + +static int dm_test_i2c_trylock_bus(struct i2c_adapter *adapter, + unsigned int flags) +{ + return rt_mutex_trylock(&adapter->bus_lock); +} + +static void dm_test_i2c_unlock_bus(struct i2c_adapter *adapter, + unsigned int flags) +{ + rt_mutex_unlock(&adapter->bus_lock); +} + +static const struct i2c_lock_operations dm_test_i2c_lock_ops = { + .lock_bus = dm_test_i2c_lock_bus, + .trylock_bus = dm_test_i2c_trylock_bus, + .unlock_bus = dm_test_i2c_unlock_bus, +}; + +static int dm_test_i2c_master_xfer(struct i2c_adapter *adapter, + struct i2c_msg *msgs, + int num) +{ + struct dm_test_i2c_adapter *fake; + int i; + + fake = container_of(adapter, struct dm_test_i2c_adapter, base); + fake->calls++; + + KUNIT_EXPECT_EQ(fake->test, num, fake->expected_num); + + for (i = 0; i < num; i++) { + KUNIT_EXPECT_EQ(fake->test, msgs[i].flags, + fake->expected_payloads[i].write ? 0 : I2C_M_RD); + KUNIT_EXPECT_EQ(fake->test, msgs[i].addr, + (u16)fake->expected_payloads[i].address); + KUNIT_EXPECT_EQ(fake->test, msgs[i].len, + (u16)fake->expected_payloads[i].length); + KUNIT_EXPECT_PTR_EQ(fake->test, msgs[i].buf, + fake->expected_payloads[i].data); + } + + return fake->ret; +} + +static const struct i2c_algorithm dm_test_i2c_algorithm = { + .master_xfer = dm_test_i2c_master_xfer, +}; + +static void dm_test_submit_i2c_transfer(struct kunit *test, + bool full_transfer) +{ + struct amdgpu_dm_connector *aconnector; + struct dm_test_i2c_adapter *fake; + struct dc_link *link; + u8 write_data[2] = { 0x12, 0x34 }; + u8 read_data[3] = { 0 }; + struct i2c_payload payloads[] = { + { true, 0x50, sizeof(write_data), write_data }, + { false, 0x51, sizeof(read_data), read_data }, + }; + struct i2c_command cmd = { + .payloads = payloads, + .number_of_payloads = ARRAY_SIZE(payloads), + }; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + fake = kunit_kzalloc(test, sizeof(*fake), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, fake); + + fake->test = test; + fake->expected_payloads = payloads; + fake->expected_num = ARRAY_SIZE(payloads); + fake->ret = full_transfer ? ARRAY_SIZE(payloads) : 1; + fake->base.algo = &dm_test_i2c_algorithm; + fake->base.lock_ops = &dm_test_i2c_lock_ops; + rt_mutex_init(&fake->base.bus_lock); + rt_mutex_init(&fake->base.mux_lock); + + aconnector->i2c = (struct amdgpu_i2c_adapter *)fake; + link->priv = aconnector; + + KUNIT_EXPECT_EQ(test, dm_helpers_submit_i2c(NULL, link, &cmd), full_transfer); + KUNIT_EXPECT_EQ(test, fake->calls, 1); +} + +/** + * dm_test_submit_i2c_success - Test payloads are mapped to i2c_msg array + * @test: The KUnit test context + */ +static void dm_test_submit_i2c_success(struct kunit *test) +{ + dm_test_submit_i2c_transfer(test, true); +} + +/** + * dm_test_submit_i2c_partial_transfer - Test short i2c transfer returns false + * @test: The KUnit test context + */ +static void dm_test_submit_i2c_partial_transfer(struct kunit *test) +{ + dm_test_submit_i2c_transfer(test, false); +} + +/* Tests for dm_helper_dmub_aux_transfer_sync() */ + +/** + * dm_test_dmub_aux_transfer_sync_hpd_discon - Test aux transfer with HPD disconnected + * @test: The KUnit test context + */ +static void dm_test_dmub_aux_transfer_sync_hpd_discon(struct kunit *test) +{ + struct dc_link *link; + struct dc_context *ctx; + struct aux_payload payload = {0}; + enum aux_return_code_type result = AUX_RET_SUCCESS; + int ret; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + link->hpd_status = false; + + ret = dm_helper_dmub_aux_transfer_sync(ctx, link, &payload, &result); + + KUNIT_EXPECT_EQ(test, ret, -1); + KUNIT_EXPECT_EQ(test, (int)result, (int)AUX_RET_ERROR_HPD_DISCON); +} + +/* Tests for empty stub functions (must not crash) */ + +/** + * dm_test_dp_update_branch_info_no_crash - Test empty stub does not crash + * @test: The KUnit test context + */ +static void dm_test_dp_update_branch_info_no_crash(struct kunit *test) +{ + dm_helpers_dp_update_branch_info(NULL, NULL); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_mst_poll_pending_down_reply_no_crash - Test empty stub does not crash + * @test: The KUnit test context + */ +static void dm_test_mst_poll_pending_down_reply_no_crash(struct kunit *test) +{ + dm_helpers_dp_mst_poll_pending_down_reply(NULL, NULL); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_mst_clear_payload_alloc_table_no_crash - Test empty stub does not crash + * @test: The KUnit test context + */ +static void dm_test_mst_clear_payload_alloc_table_no_crash(struct kunit *test) +{ + dm_helpers_dp_mst_clear_payload_allocation_table(NULL, NULL); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_set_dcn_clocks_no_crash - Test empty stub does not crash + * @test: The KUnit test context + */ +static void dm_test_set_dcn_clocks_no_crash(struct kunit *test) +{ + dm_set_dcn_clocks(NULL, NULL); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_dmu_timeout_no_crash - Test empty stub does not crash + * @test: The KUnit test context + */ +static void dm_test_dmu_timeout_no_crash(struct kunit *test) +{ + dm_helpers_dmu_timeout(NULL); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_smu_timeout_no_crash - Test empty stub does not crash + * @test: The KUnit test context + */ +static void dm_test_smu_timeout_no_crash(struct kunit *test) +{ + dm_helpers_smu_timeout(NULL, 0, 0, 0); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_set_phyd32clk_no_crash - Test empty stub does not crash + * @test: The KUnit test context + */ +static void dm_test_set_phyd32clk_no_crash(struct kunit *test) +{ + dm_set_phyd32clk(NULL, 0); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_mst_update_branch_bandwidth_no_crash - Test empty stub does not crash + * @test: The KUnit test context + */ +static void dm_test_mst_update_branch_bandwidth_no_crash(struct kunit *test) +{ + dm_helpers_dp_mst_update_branch_bandwidth(NULL, NULL); + KUNIT_EXPECT_TRUE(test, true); +} + +/* Tests for MST functions null-connector early returns */ + +/** + * dm_test_mst_write_payload_alloc_table_null_ctx - Test null connector returns false + * @test: The KUnit test context + */ +static void dm_test_mst_write_payload_alloc_table_null_ctx(struct kunit *test) +{ + struct dc_stream_state *stream; + + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + /* dm_stream_context is NULL → aconnector is NULL → return false */ + KUNIT_EXPECT_FALSE(test, + dm_helpers_dp_mst_write_payload_allocation_table(NULL, stream, NULL, true)); +} + +/** + * dm_test_mst_poll_for_act_null_ctx - Test null connector returns ACT_FAILED + * @test: The KUnit test context + */ +static void dm_test_mst_poll_for_act_null_ctx(struct kunit *test) +{ + struct dc_stream_state *stream; + + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + KUNIT_EXPECT_EQ(test, + (int)dm_helpers_dp_mst_poll_for_allocation_change_trigger(NULL, stream), + (int)ACT_FAILED); +} + +/** + * dm_test_mst_send_payload_alloc_null_ctx - Test null connector does not crash + * @test: The KUnit test context + */ +static void dm_test_mst_send_payload_alloc_null_ctx(struct kunit *test) +{ + struct dc_stream_state *stream; + + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + /* Should early-return without crash */ + dm_helpers_dp_mst_send_payload_allocation(NULL, stream); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_mst_update_mgr_dealloc_null_ctx - Test null connector does not crash + * @test: The KUnit test context + */ +static void dm_test_mst_update_mgr_dealloc_null_ctx(struct kunit *test) +{ + struct dc_stream_state *stream; + + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + dm_helpers_dp_mst_update_mst_mgr_for_deallocation(NULL, stream); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_is_dp_sink_present_null_priv - Test null connector returns true + * @test: The KUnit test context + */ +static void dm_test_is_dp_sink_present_null_priv(struct kunit *test) +{ + struct dc_link *link; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + /* NULL priv → DRM_ERROR + return true */ + KUNIT_EXPECT_TRUE(test, dm_helpers_is_dp_sink_present(link)); +} + +/* Tests for dm_helpers_dmub_outbox_interrupt_control() */ + +/** + * dm_test_dmub_outbox_interrupt_control_null_dc - Test outbox irq control with NULL dc + * @test: The KUnit test context + * + * dc_interrupt_set() is NULL-safe and returns false when dc is NULL, so the + * helper returns false without touching real interrupt hardware. + */ +static void dm_test_dmub_outbox_interrupt_control_null_dc(struct kunit *test) +{ + struct dc_context *ctx; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + /* ctx->dc is NULL → dc_interrupt_set returns false */ + KUNIT_EXPECT_FALSE(test, dm_helpers_dmub_outbox_interrupt_control(ctx, true)); + KUNIT_EXPECT_FALSE(test, dm_helpers_dmub_outbox_interrupt_control(ctx, false)); +} + +/* Tests for dm_helpers_mst_enable_stream_features() */ + +/** + * dm_test_mst_enable_stream_features_aux_disabled - Test early return when aux disabled + * @test: The KUnit test context + */ +static void dm_test_mst_enable_stream_features_aux_disabled(struct kunit *test) +{ + struct dc_stream_state *stream; + struct dc_link *link; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + stream->link = link; + link->aux_access_disabled = true; + + /* aux_access_disabled → early return without DPCD access */ + dm_helpers_mst_enable_stream_features(stream); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_mst_enable_stream_features_writes_downspread - Test MSA ignore write + * @test: The KUnit test context + */ +static void dm_test_mst_enable_stream_features_writes_downspread(struct kunit *test) +{ + struct dm_test_synaptics_aux *fixture; + struct amdgpu_dm_connector *aconnector; + struct dc_stream_state *stream; + struct amdgpu_device *adev; + struct dc_link *link; + + adev = dm_kunit_alloc_adev(test); + fixture = dm_test_alloc_synaptics_aux_with_dev(test, &adev->ddev); + aconnector = dm_kunit_alloc_connector(test, adev, NULL); + link = dm_kunit_alloc_link(test); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + dm_test_current_aux_recorder = fixture; + aconnector->dm_dp_aux.aux.drm_dev = &adev->ddev; + aconnector->dm_dp_aux.aux.transfer = dm_test_current_aux_transfer; + drm_dp_aux_init(&aconnector->dm_dp_aux.aux); + link->priv = aconnector; + stream->link = link; + stream->ignore_msa_timing_param = true; + + dm_helpers_mst_enable_stream_features(stream); + + KUNIT_EXPECT_EQ(test, fixture->downspread_reads, 1U); + KUNIT_EXPECT_EQ(test, fixture->downspread_writes, 1U); + KUNIT_EXPECT_EQ(test, fixture->last_dpcd_write_address, + DP_DOWNSPREAD_CTRL); + KUNIT_EXPECT_EQ(test, fixture->dpcd_write_value, (u8)BIT(7)); +} + +/* Tests for dm_helpers_enable_periodic_detection() */ + +struct dm_test_idle_work { + struct idle_workqueue base; + bool ran; +}; + +static void dm_test_idle_work_func(struct work_struct *work) +{ + struct dm_test_idle_work *idle_work; + + idle_work = container_of(work, struct dm_test_idle_work, base.work); + idle_work->ran = true; +} + +/** + * dm_test_enable_periodic_detection_no_workqueue - Test no-op without idle workqueue + * @test: The KUnit test context + */ +static void dm_test_enable_periodic_detection_no_workqueue(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_context *ctx; + + adev = dm_kunit_alloc_adev(test); + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + ctx->driver_context = adev; + + /* adev->dm.idle_workqueue is NULL → no-op, no crash */ + dm_helpers_enable_periodic_detection(ctx, true); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_enable_periodic_detection_updates_enable - Test idle work enable flag + * @test: The KUnit test context + * + * Keeps idle_workqueue->running set so the helper only updates the enable flag + * and does not queue the idle worker. + */ +static void dm_test_enable_periodic_detection_updates_enable(struct kunit *test) +{ + struct idle_workqueue *idle_work; + struct amdgpu_device *adev; + struct dc_context *ctx; + + adev = dm_kunit_alloc_adev(test); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + idle_work = kunit_kzalloc(test, sizeof(*idle_work), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, idle_work); + + ctx->driver_context = adev; + adev->dm.idle_workqueue = idle_work; + idle_work->running = true; + + dm_helpers_enable_periodic_detection(ctx, true); + KUNIT_EXPECT_TRUE(test, idle_work->enable); + + dm_helpers_enable_periodic_detection(ctx, false); + KUNIT_EXPECT_FALSE(test, idle_work->enable); +} + +/** + * dm_test_enable_periodic_detection_schedules_work - Test headless schedule path + * @test: The KUnit test context + */ +static void dm_test_enable_periodic_detection_schedules_work(struct kunit *test) +{ + struct dm_test_idle_work *idle_work; + struct amdgpu_device *adev; + struct dc_context *ctx; + + adev = dm_kunit_alloc_adev(test); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + idle_work = kunit_kzalloc(test, sizeof(*idle_work), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, idle_work); + + ctx->driver_context = adev; + adev->dm.ddev = &adev->ddev; + adev->dm.idle_workqueue = &idle_work->base; + INIT_WORK(&idle_work->base.work, dm_test_idle_work_func); + + dm_helpers_enable_periodic_detection(ctx, true); + flush_work(&idle_work->base.work); + + KUNIT_EXPECT_TRUE(test, idle_work->base.enable); + KUNIT_EXPECT_TRUE(test, idle_work->ran); +} + static struct kunit_case amdgpu_dm_helpers_test_cases[] = { /* edid_extract_panel_id */ KUNIT_CASE(dm_test_edid_extract_panel_id_basic), @@ -2127,6 +2636,38 @@ static struct kunit_case amdgpu_dm_helpers_test_cases[] = { KUNIT_CASE(dm_test_fill_mst_payload_table_enable), KUNIT_CASE(dm_test_fill_mst_payload_table_disable), KUNIT_CASE(dm_test_fill_mst_payload_table_empty), + /* dm_helpers_submit_i2c */ + KUNIT_CASE(dm_test_submit_i2c_null_priv), + KUNIT_CASE(dm_test_submit_i2c_success), + KUNIT_CASE(dm_test_submit_i2c_partial_transfer), + /* dm_helper_dmub_aux_transfer_sync */ + KUNIT_CASE(dm_test_dmub_aux_transfer_sync_hpd_discon), + /* Empty stub functions */ + KUNIT_CASE(dm_test_dp_update_branch_info_no_crash), + KUNIT_CASE(dm_test_mst_poll_pending_down_reply_no_crash), + KUNIT_CASE(dm_test_mst_clear_payload_alloc_table_no_crash), + KUNIT_CASE(dm_test_set_dcn_clocks_no_crash), + KUNIT_CASE(dm_test_dmu_timeout_no_crash), + KUNIT_CASE(dm_test_smu_timeout_no_crash), + KUNIT_CASE(dm_test_set_phyd32clk_no_crash), + KUNIT_CASE(dm_test_mst_update_branch_bandwidth_no_crash), + /* MST null-connector early returns */ + KUNIT_CASE(dm_test_mst_write_payload_alloc_table_null_ctx), + KUNIT_CASE(dm_test_mst_poll_for_act_null_ctx), + /* dm_helpers_dp_mst_poll_for_allocation_change_trigger success/fail */ + KUNIT_CASE(dm_test_mst_send_payload_alloc_null_ctx), + KUNIT_CASE(dm_test_mst_update_mgr_dealloc_null_ctx), + /* dm_helpers_is_dp_sink_present */ + KUNIT_CASE(dm_test_is_dp_sink_present_null_priv), + /* dm_helpers_dmub_outbox_interrupt_control */ + KUNIT_CASE(dm_test_dmub_outbox_interrupt_control_null_dc), + /* dm_helpers_mst_enable_stream_features */ + KUNIT_CASE(dm_test_mst_enable_stream_features_aux_disabled), + KUNIT_CASE(dm_test_mst_enable_stream_features_writes_downspread), + /* dm_helpers_enable_periodic_detection */ + KUNIT_CASE(dm_test_enable_periodic_detection_no_workqueue), + KUNIT_CASE(dm_test_enable_periodic_detection_updates_enable), + KUNIT_CASE(dm_test_enable_periodic_detection_schedules_work), {} }; From a30c4e979bba8da348a29edd77f103b89f0d717b Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Wed, 24 Jun 2026 23:34:18 -0600 Subject: [PATCH 125/577] drm/amd/display: Test MCCS FreeSync VCP helpers [WHAT] Add KUnit coverage for dm_helpers_read_mccs_caps and dm_helpers_mccs_vcp_set, including the DP/HDMI/legacy-PCON selection, the i2c VCP request and set packets and the retry-failure paths. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 2 + .../amdgpu_dm/tests/amdgpu_dm_helpers_test.c | 419 ++++++++++++++++++ 2 files changed, 421 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c index ff23aee54c16..a1612b1ae666 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c @@ -1800,6 +1800,7 @@ void dm_helpers_read_mccs_caps(struct dc_context *ctx, struct dc_link *link, } } } +EXPORT_IF_KUNIT(dm_helpers_read_mccs_caps); static int mccs_operation_vcp_set(unsigned int vcp_code, struct dc_link *link, uint16_t value) { @@ -1874,4 +1875,5 @@ void dm_helpers_mccs_vcp_set(struct dc_context *ctx, struct dc_link *link, drm_dbg_driver(dev, "%s: Failed to set VCP code %d", __func__, sink->edid_caps.freesync_vcp_code); } +EXPORT_IF_KUNIT(dm_helpers_mccs_vcp_set); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c index 29feb8c3940b..bde697092032 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c @@ -2110,6 +2110,83 @@ static const struct i2c_algorithm dm_test_i2c_algorithm = { .master_xfer = dm_test_i2c_master_xfer, }; +struct dm_test_mccs_i2c_adapter { + struct i2c_adapter base; + u8 write_data[16]; + u8 read_reply[11]; + int write_ret; + int read_ret; + unsigned int write_len; + unsigned int writes; + unsigned int reads; +}; + +static int dm_test_mccs_i2c_master_xfer(struct i2c_adapter *adapter, + struct i2c_msg *msgs, + int num) +{ + struct dm_test_mccs_i2c_adapter *fake; + struct i2c_msg *msg = msgs; + size_t copy_len; + + fake = container_of(adapter, struct dm_test_mccs_i2c_adapter, base); + + if (num != 1) + return 0; + + if (msg->flags & I2C_M_RD) { + fake->reads++; + if (fake->read_ret != 1) + return fake->read_ret; + + copy_len = min_t(size_t, msg->len, sizeof(fake->read_reply)); + memcpy(msg->buf, fake->read_reply, copy_len); + return 1; + } + + fake->writes++; + if (fake->write_ret != 1) + return fake->write_ret; + + copy_len = min_t(size_t, msg->len, sizeof(fake->write_data)); + memcpy(fake->write_data, msg->buf, copy_len); + fake->write_len = copy_len; + + return 1; +} + +static const struct i2c_algorithm dm_test_mccs_i2c_algorithm = { + .master_xfer = dm_test_mccs_i2c_master_xfer, +}; + +static struct dm_test_mccs_i2c_adapter *dm_test_alloc_mccs_i2c(struct kunit *test) +{ + struct dm_test_mccs_i2c_adapter *fake; + + fake = kunit_kzalloc(test, sizeof(*fake), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, fake); + + fake->base.algo = &dm_test_mccs_i2c_algorithm; + fake->base.lock_ops = &dm_test_i2c_lock_ops; + fake->write_ret = 1; + fake->read_ret = 1; + rt_mutex_init(&fake->base.bus_lock); + rt_mutex_init(&fake->base.mux_lock); + + return fake; +} + +static u8 dm_test_mccs_checksum(const u8 *data, unsigned int len) +{ + u8 checksum = 0x6e; + unsigned int i; + + for (i = 0; i < len; i++) + checksum ^= data[i]; + + return checksum; +} + static void dm_test_submit_i2c_transfer(struct kunit *test, bool full_transfer) { @@ -2531,6 +2608,334 @@ static void dm_test_enable_periodic_detection_schedules_work(struct kunit *test) KUNIT_EXPECT_TRUE(test, idle_work->ran); } +/* Tests for dm_helpers_read_mccs_caps() */ + +/** + * dm_test_read_mccs_caps_null_ctx - Test early return with NULL context + * @test: The KUnit test context + */ +static void dm_test_read_mccs_caps_null_ctx(struct kunit *test) +{ + /* NULL ctx → early return, no crash */ + dm_helpers_read_mccs_caps(NULL, NULL, NULL); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_read_mccs_caps_null_link - Test early return with NULL link + * @test: The KUnit test context + */ +static void dm_test_read_mccs_caps_null_link(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_sink *sink; + + adev = dm_kunit_alloc_adev(test); + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + ctx->driver_context = adev; + sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, sink); + + /* link is NULL → drm_dbg_driver + return */ + dm_helpers_read_mccs_caps(ctx, NULL, sink); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_read_mccs_caps_no_vcp_code - Test no-vcp-code path clears freesync support + * @test: The KUnit test context + * + * With freesync_vcp_code == 0 the i2c/MCCS path is skipped entirely and the + * function only clears sink->mccs_caps.freesync_supported. + */ +static void dm_test_read_mccs_caps_no_vcp_code(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_link *link; + struct dc_sink *sink; + + adev = dm_kunit_alloc_adev(test); + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + ctx->driver_context = adev; + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, sink); + + sink->edid_caps.freesync_vcp_code = 0; + sink->mccs_caps.freesync_supported = true; /* should be cleared */ + + dm_helpers_read_mccs_caps(ctx, link, sink); + + KUNIT_EXPECT_FALSE(test, sink->mccs_caps.freesync_supported); +} + +/* + * Allocate and wire the adev/ctx/link/sink/connector/i2c objects shared by the + * MCCS read/set tests so each test only configures the fields it exercises. + */ +struct dm_test_mccs_fixture { + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_link *link; + struct dc_sink *sink; + struct amdgpu_dm_connector *aconnector; + struct dm_test_mccs_i2c_adapter *fake; +}; + +static struct dm_test_mccs_fixture dm_test_alloc_mccs_fixture(struct kunit *test) +{ + struct dm_test_mccs_fixture fixture; + + fixture.adev = dm_kunit_alloc_adev(test); + KUNIT_ASSERT_NOT_NULL(test, fixture.adev); + fixture.ctx = kunit_kzalloc(test, sizeof(*fixture.ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, fixture.ctx); + fixture.link = kunit_kzalloc(test, sizeof(*fixture.link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, fixture.link); + fixture.sink = kunit_kzalloc(test, sizeof(*fixture.sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, fixture.sink); + fixture.aconnector = kunit_kzalloc(test, sizeof(*fixture.aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, fixture.aconnector); + fixture.fake = dm_test_alloc_mccs_i2c(test); + + fixture.ctx->driver_context = fixture.adev; + fixture.aconnector->i2c = (struct amdgpu_i2c_adapter *)fixture.fake; + fixture.link->priv = fixture.aconnector; + + return fixture; +} + +/** + * dm_test_read_mccs_caps_i2c_vcp_request - Test MCCS VCP request packet + * @test: The KUnit test context + */ +static void dm_test_read_mccs_caps_i2c_vcp_request(struct kunit *test) +{ + static const u8 expected_prefix[] = { 0x51, 0x82, 0x01, 0xe3 }; + struct dm_test_mccs_fixture fixture = dm_test_alloc_mccs_fixture(test); + struct dm_test_mccs_i2c_adapter *fake = fixture.fake; + struct dc_context *ctx = fixture.ctx; + struct dc_link *link = fixture.link; + struct dc_sink *sink = fixture.sink; + + link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT; + link->dpcd_caps.dpcd_rev.raw = DP_DPCD_REV_14; + link->dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_HDMI_CONVERTER; + link->dpcd_caps.branch_dev_id = DP_BRANCH_DEVICE_ID_0060AD; + link->dpcd_caps.adaptive_sync_caps.dp_adap_sync_caps.bits.ADAPTIVE_SYNC_SDP_SUPPORT = 1; + sink->edid_caps.freesync_vcp_code = 0xe3; + fake->read_reply[1] = 0x82; + fake->read_reply[9] = 0x01; + + dm_helpers_read_mccs_caps(ctx, link, sink); + + KUNIT_EXPECT_TRUE(test, sink->mccs_caps.freesync_supported); + KUNIT_EXPECT_EQ(test, fake->writes, 1U); + KUNIT_EXPECT_EQ(test, fake->reads, 1U); + KUNIT_EXPECT_EQ(test, fake->write_len, (unsigned int)sizeof(expected_prefix) + 1); + KUNIT_EXPECT_EQ(test, memcmp(fake->write_data, expected_prefix, + sizeof(expected_prefix)), 0); + KUNIT_EXPECT_EQ(test, fake->write_data[4], + dm_test_mccs_checksum(expected_prefix, sizeof(expected_prefix))); +} + +/** + * dm_test_read_mccs_caps_hdmi_vcp_request - Test local HDMI MCCS path + * @test: The KUnit test context + */ +static void dm_test_read_mccs_caps_hdmi_vcp_request(struct kunit *test) +{ + struct dm_test_mccs_fixture fixture = dm_test_alloc_mccs_fixture(test); + struct dm_test_mccs_i2c_adapter *fake = fixture.fake; + struct dc_context *ctx = fixture.ctx; + struct dc_link *link = fixture.link; + struct dc_sink *sink = fixture.sink; + + link->connector_signal = SIGNAL_TYPE_HDMI_TYPE_A; + sink->edid_caps.freesync_vcp_code = 0xe3; + fake->read_reply[1] = 0x82; + fake->read_reply[9] = 0x01; + + dm_helpers_read_mccs_caps(ctx, link, sink); + + KUNIT_EXPECT_TRUE(test, sink->mccs_caps.freesync_supported); + KUNIT_EXPECT_EQ(test, fake->writes, 1U); + KUNIT_EXPECT_EQ(test, fake->reads, 1U); +} + +/** + * dm_test_read_mccs_caps_legacy_pcon_vcp_request - Test legacy PCON path + * @test: The KUnit test context + */ +static void dm_test_read_mccs_caps_legacy_pcon_vcp_request(struct kunit *test) +{ + struct dm_test_mccs_fixture fixture = dm_test_alloc_mccs_fixture(test); + struct dm_test_mccs_i2c_adapter *fake = fixture.fake; + struct dc_context *ctx = fixture.ctx; + struct dc_link *link = fixture.link; + struct dc_sink *sink = fixture.sink; + + link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT; + link->dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_DVI_CONVERTER; + sink->edid_caps.freesync_vcp_code = 0xe3; + fake->read_reply[1] = 0x82; + fake->read_reply[9] = 0x01; + + dm_helpers_read_mccs_caps(ctx, link, sink); + + KUNIT_EXPECT_TRUE(test, sink->mccs_caps.freesync_supported); + KUNIT_EXPECT_EQ(test, fake->writes, 1U); + KUNIT_EXPECT_EQ(test, fake->reads, 1U); +} + +/** + * dm_test_read_mccs_caps_i2c_failure - Test VCP request retry failure + * @test: The KUnit test context + */ +static void dm_test_read_mccs_caps_i2c_failure(struct kunit *test) +{ + struct dm_test_mccs_fixture fixture = dm_test_alloc_mccs_fixture(test); + struct dm_test_mccs_i2c_adapter *fake = fixture.fake; + struct dc_context *ctx = fixture.ctx; + struct dc_link *link = fixture.link; + struct dc_sink *sink = fixture.sink; + + fixture.aconnector->base.dev = &fixture.adev->ddev; + link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT; + link->dpcd_caps.dpcd_rev.raw = DP_DPCD_REV_14; + link->dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_HDMI_CONVERTER; + link->dpcd_caps.branch_dev_id = DP_BRANCH_DEVICE_ID_0060AD; + link->dpcd_caps.adaptive_sync_caps.dp_adap_sync_caps.bits.ADAPTIVE_SYNC_SDP_SUPPORT = 1; + sink->edid_caps.freesync_vcp_code = 0xe3; + fake->write_ret = 0; + + dm_helpers_read_mccs_caps(ctx, link, sink); + + KUNIT_EXPECT_FALSE(test, sink->mccs_caps.freesync_supported); + KUNIT_EXPECT_EQ(test, fake->writes, 5U); + KUNIT_EXPECT_EQ(test, fake->reads, 0U); +} + +/* Tests for dm_helpers_mccs_vcp_set() */ + +/** + * dm_test_mccs_vcp_set_null_ctx - Test early return with NULL context + * @test: The KUnit test context + */ +static void dm_test_mccs_vcp_set_null_ctx(struct kunit *test) +{ + /* NULL ctx → early return, no crash */ + dm_helpers_mccs_vcp_set(NULL, NULL, NULL); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_mccs_vcp_set_not_supported - Test early return when freesync unsupported + * @test: The KUnit test context + */ +static void dm_test_mccs_vcp_set_not_supported(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_link *link; + struct dc_sink *sink; + + adev = dm_kunit_alloc_adev(test); + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + ctx->driver_context = adev; + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, sink); + + sink->mccs_caps.freesync_supported = false; + + /* freesync not supported → early return without i2c */ + dm_helpers_mccs_vcp_set(ctx, link, sink); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_mccs_vcp_set_null_link - Test early return with NULL link + * @test: The KUnit test context + */ +static void dm_test_mccs_vcp_set_null_link(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_context *ctx; + struct dc_sink *sink; + + adev = dm_kunit_alloc_adev(test); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + sink = kunit_kzalloc(test, sizeof(*sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, sink); + + ctx->driver_context = adev; + dm_helpers_mccs_vcp_set(ctx, NULL, sink); + KUNIT_EXPECT_TRUE(test, true); +} + +/** + * dm_test_mccs_vcp_set_i2c_packet - Test MCCS VCP set packet + * @test: The KUnit test context + */ +static void dm_test_mccs_vcp_set_i2c_packet(struct kunit *test) +{ + static const u8 expected_prefix[] = { + 0x51, 0x84, 0x03, 0xe3, 0x01, 0x01, + }; + struct dm_test_mccs_fixture fixture = dm_test_alloc_mccs_fixture(test); + struct dm_test_mccs_i2c_adapter *fake = fixture.fake; + struct dc_context *ctx = fixture.ctx; + struct dc_link *link = fixture.link; + struct dc_sink *sink = fixture.sink; + + sink->mccs_caps.freesync_supported = true; + sink->edid_caps.freesync_vcp_code = 0xe3; + + dm_helpers_mccs_vcp_set(ctx, link, sink); + + KUNIT_EXPECT_EQ(test, fake->writes, 1U); + KUNIT_EXPECT_EQ(test, fake->reads, 0U); + KUNIT_EXPECT_EQ(test, fake->write_len, (unsigned int)sizeof(expected_prefix) + 1); + KUNIT_EXPECT_EQ(test, memcmp(fake->write_data, expected_prefix, + sizeof(expected_prefix)), 0); + KUNIT_EXPECT_EQ(test, fake->write_data[6], + dm_test_mccs_checksum(expected_prefix, sizeof(expected_prefix))); +} + +/** + * dm_test_mccs_vcp_set_i2c_failure - Test VCP set retry failure path + * @test: The KUnit test context + */ +static void dm_test_mccs_vcp_set_i2c_failure(struct kunit *test) +{ + struct dm_test_mccs_fixture fixture = dm_test_alloc_mccs_fixture(test); + struct dm_test_mccs_i2c_adapter *fake = fixture.fake; + struct dc_context *ctx = fixture.ctx; + struct dc_link *link = fixture.link; + struct dc_sink *sink = fixture.sink; + + sink->mccs_caps.freesync_supported = true; + sink->edid_caps.freesync_vcp_code = 0xe3; + fake->write_ret = 0; + + dm_helpers_mccs_vcp_set(ctx, link, sink); + + KUNIT_EXPECT_EQ(test, fake->writes, 5U); + KUNIT_EXPECT_EQ(test, fake->reads, 0U); +} + static struct kunit_case amdgpu_dm_helpers_test_cases[] = { /* edid_extract_panel_id */ KUNIT_CASE(dm_test_edid_extract_panel_id_basic), @@ -2668,6 +3073,20 @@ static struct kunit_case amdgpu_dm_helpers_test_cases[] = { KUNIT_CASE(dm_test_enable_periodic_detection_no_workqueue), KUNIT_CASE(dm_test_enable_periodic_detection_updates_enable), KUNIT_CASE(dm_test_enable_periodic_detection_schedules_work), + /* dm_helpers_read_mccs_caps */ + KUNIT_CASE(dm_test_read_mccs_caps_null_ctx), + KUNIT_CASE(dm_test_read_mccs_caps_null_link), + KUNIT_CASE(dm_test_read_mccs_caps_no_vcp_code), + KUNIT_CASE(dm_test_read_mccs_caps_i2c_vcp_request), + KUNIT_CASE(dm_test_read_mccs_caps_hdmi_vcp_request), + KUNIT_CASE(dm_test_read_mccs_caps_legacy_pcon_vcp_request), + KUNIT_CASE(dm_test_read_mccs_caps_i2c_failure), + /* dm_helpers_mccs_vcp_set */ + KUNIT_CASE(dm_test_mccs_vcp_set_null_ctx), + KUNIT_CASE(dm_test_mccs_vcp_set_not_supported), + KUNIT_CASE(dm_test_mccs_vcp_set_null_link), + KUNIT_CASE(dm_test_mccs_vcp_set_i2c_packet), + KUNIT_CASE(dm_test_mccs_vcp_set_i2c_failure), {} }; From 319f7e13423ae3f486b9aea82f9ad2d6af0ee608 Mon Sep 17 00:00:00 2001 From: Yongqiang Sun Date: Mon, 6 Jul 2026 15:15:07 -0400 Subject: [PATCH 126/577] drm/amdkfd: fix 32-bit overflow in CWSR total size calculation total_cwsr_size was computed in 32-bit before being used as a BO/SVM allocation size. With large ctx_save_restore_area_size and debug_memory_size multiplied by the XCC count, the product can wrap, yielding an undersized CWSR save area that firmware later overruns. Promote total_cwsr_size to u64 and use check_add_overflow()/ check_mul_overflow() in both kfd_queue_acquire_buffers() and kfd_queue_release_buffers(). Signed-off-by: Yongqiang Sun Reviewed-by: Philip Yang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_queue.c | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_queue.c b/drivers/gpu/drm/amd/amdkfd/kfd_queue.c index 28354a4e5dd5..98a5512b701b 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_queue.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_queue.c @@ -23,6 +23,7 @@ */ #include +#include #include "kfd_priv.h" #include "kfd_topology.h" #include "kfd_svm.h" @@ -235,7 +236,7 @@ int kfd_queue_acquire_buffers(struct kfd_process_device *pdd, struct queue_prope struct kfd_topology_device *topo_dev; u64 expected_queue_size; struct amdgpu_vm *vm; - u32 total_cwsr_size; + u64 total_cwsr_size; int err; topo_dev = kfd_topology_device_by_id(pdd->dev->id); @@ -308,8 +309,14 @@ int kfd_queue_acquire_buffers(struct kfd_process_device *pdd, struct queue_prope goto out_err_unreserve; } - total_cwsr_size = (properties->ctx_save_restore_area_size + - topo_dev->node_props.debug_memory_size) * NUM_XCC(pdd->dev->xcc_mask); + total_cwsr_size = (u64)properties->ctx_save_restore_area_size + + topo_dev->node_props.debug_memory_size; + if (check_mul_overflow(total_cwsr_size, + NUM_XCC(pdd->dev->xcc_mask), + &total_cwsr_size)) { + err = -EINVAL; + goto out_err_unreserve; + } total_cwsr_size = ALIGN(total_cwsr_size, PAGE_SIZE); err = kfd_queue_buffer_get(vm, (void *)properties->ctx_save_restore_area_address, @@ -344,7 +351,7 @@ out_err_release: int kfd_queue_release_buffers(struct kfd_process_device *pdd, struct queue_properties *properties) { struct kfd_topology_device *topo_dev; - u32 total_cwsr_size; + u64 total_cwsr_size; kfd_queue_buffer_put(&properties->wptr_bo); kfd_queue_buffer_put(&properties->rptr_bo); @@ -355,8 +362,12 @@ int kfd_queue_release_buffers(struct kfd_process_device *pdd, struct queue_prope topo_dev = kfd_topology_device_by_id(pdd->dev->id); if (!topo_dev) return -EINVAL; - total_cwsr_size = (properties->ctx_save_restore_area_size + - topo_dev->node_props.debug_memory_size) * NUM_XCC(pdd->dev->xcc_mask); + total_cwsr_size = (u64)properties->ctx_save_restore_area_size + + topo_dev->node_props.debug_memory_size; + if (check_mul_overflow(total_cwsr_size, + NUM_XCC(pdd->dev->xcc_mask), + &total_cwsr_size)) + return -EINVAL; total_cwsr_size = ALIGN(total_cwsr_size, PAGE_SIZE); kfd_queue_buffer_svm_put(pdd, properties->ctx_save_restore_area_address, total_cwsr_size); From 290b370657fe7373cb0f301355536bfc9900bcba Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Wed, 8 Jul 2026 15:27:51 +0800 Subject: [PATCH 127/577] drm/amd/display: move connector state dereference after NULL check amdgpu_dm_connector_atomic_check() checks whether the old or new connector state returned by the atomic helpers is NULL before using those pointers. However, new_con_state is already dereferenced while initializing crtc, before the NULL check is reached. If drm_atomic_get_new_connector_state() returns NULL, the function can dereference the NULL pointer before the WARN_ON() check can handle it. Declare crtc first and initialize it only after the NULL check has succeeded. Fixes: 1e5e8d672fec ("drm/amd/display: Avoid a NULL pointer dereference") Signed-off-by: Guangshuo Li Reviewed-by: Mario Limonciello Link: https://patch.msgid.link/20260708072751.724400-1-lgs201920130244@gmail.com (ML: adjust for movement to amdgpu_dm_connector.c) Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index f77af83ade39..dfce7f79dfe0 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c @@ -2369,7 +2369,7 @@ amdgpu_dm_connector_atomic_check(struct drm_connector *conn, drm_atomic_get_new_connector_state(state, conn); struct drm_connector_state *old_con_state = drm_atomic_get_old_connector_state(state, conn); - struct drm_crtc *crtc = new_con_state->crtc; + struct drm_crtc *crtc; struct drm_crtc_state *new_crtc_state; struct amdgpu_dm_connector *aconn = to_amdgpu_dm_connector(conn); int ret; @@ -2385,6 +2385,7 @@ amdgpu_dm_connector_atomic_check(struct drm_connector *conn, return ret; } + crtc = new_con_state->crtc; if (!crtc) return 0; From b60d1d91444bc9c8e3b99d815be9647e129d017f Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Thu, 9 Jul 2026 14:22:51 +0800 Subject: [PATCH 128/577] drm/amdgpu: use IP version check in sysfs creation conditional logic avoid sysfs node creation faults when performing NPS mode switching Signed-off-by: Ce Sun Reviewed-by: Tao Zhou Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c index fd2c0cc1b958..19067df24600 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c @@ -3777,8 +3777,11 @@ int amdgpu_ras_block_late_init(struct amdgpu_device *adev, } if (amdgpu_uniras_enabled(adev) || (ras_obj->hw_ops && - (ras_obj->hw_ops->query_ras_error_count || - ras_obj->hw_ops->query_ras_error_status))) { + (ras_obj->hw_ops->query_ras_error_count || + ras_obj->hw_ops->query_ras_error_status)) || + amdgpu_ip_version(adev, MP0_HWIP, 0) == IP_VERSION(13, 0, 14) || + amdgpu_ip_version(adev, MP0_HWIP, 0) == IP_VERSION(13, 0, 12) || + amdgpu_ip_version(adev, MP0_HWIP, 0) == IP_VERSION(13, 0, 6)) { r = amdgpu_ras_sysfs_create(adev, ras_block); if (r) goto interrupt; From a8880289fd67e85d155dbe6d625e3f6fcd94ff51 Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Tue, 7 Jul 2026 21:30:43 +0800 Subject: [PATCH 129/577] drm/amdgpu: drop debug_enable_ras_aca debug mask flag drop debug_enable_ras_aca debug mask flag Signed-off-by: Ce Sun Reviewed-by: Hawking Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu.h | 1 - drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c | 6 ------ drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_mgr.c | 3 +-- 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu.h b/drivers/gpu/drm/amd/amdgpu/amdgpu.h index 8339ab6f5d32..f5d65bd0ac25 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu.h @@ -926,7 +926,6 @@ struct amdgpu_device { bool debug_largebar; bool debug_disable_soft_recovery; bool debug_use_vram_fw_buf; - bool debug_enable_ras_aca; bool debug_exp_resets; bool debug_disable_gpu_ring_reset; bool debug_vm_userptr; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c index ad631ad31899..5362705143bc 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c @@ -139,7 +139,6 @@ enum AMDGPU_DEBUG_MASK { AMDGPU_DEBUG_LARGEBAR = BIT(1), AMDGPU_DEBUG_DISABLE_GPU_SOFT_RECOVERY = BIT(2), AMDGPU_DEBUG_USE_VRAM_FW_BUF = BIT(3), - AMDGPU_DEBUG_ENABLE_RAS_ACA = BIT(4), AMDGPU_DEBUG_ENABLE_EXP_RESETS = BIT(5), AMDGPU_DEBUG_DISABLE_GPU_RING_RESET = BIT(6), AMDGPU_DEBUG_SMU_POOL = BIT(7), @@ -2264,11 +2263,6 @@ static void amdgpu_init_debug_options(struct amdgpu_device *adev) adev->debug_use_vram_fw_buf = true; } - if (amdgpu_debug_mask & AMDGPU_DEBUG_ENABLE_RAS_ACA) { - pr_info("debug: enable RAS ACA\n"); - adev->debug_enable_ras_aca = true; - } - if (amdgpu_debug_mask & AMDGPU_DEBUG_ENABLE_EXP_RESETS) { pr_info("debug: enable experimental reset features\n"); adev->debug_exp_resets = true; diff --git a/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_mgr.c b/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_mgr.c index b62bbb5ea292..3b5da8515a20 100644 --- a/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_mgr.c +++ b/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_mgr.c @@ -332,8 +332,7 @@ static int amdgpu_ras_mgr_sw_init(struct amdgpu_ip_block *ip_block) if (amdgpu_ip_version(adev, MP0_HWIP, 0) == IP_VERSION(13, 0, 14) || amdgpu_ip_version(adev, MP0_HWIP, 0) == IP_VERSION(13, 0, 12) || - amdgpu_ip_version(adev, MP0_HWIP, 0) == IP_VERSION(13, 0, 6) || - adev->debug_enable_ras_aca) + amdgpu_ip_version(adev, MP0_HWIP, 0) == IP_VERSION(13, 0, 6)) con->uniras_enabled = true; else return 0; From bf65973e5f5e1d6f7398c43bb8bc78ec6c1f3116 Mon Sep 17 00:00:00 2001 From: "Stanley.Yang" Date: Fri, 26 Jun 2026 20:20:15 +0800 Subject: [PATCH 130/577] drm/amdgpu/ras: only check bad page for address-based UMC injection UMC error injection on MI300 series is dispatched by the RAS TA using the injection method; only the "coherent" methods are address based, the single-shot/persistent/ac-parity ones ignore the address. The debugfs control path validated the injection address against the bad page list for every UMC injection. On uniras (SMU v13+) devices the address is now validated by the ras_mgr inject handler, so the legacy debugfs bad page check only runs on the legacy RAS path; other ASICs keep injecting by address. In the ras_mgr handler an injection is treated as non address-based only when userspace passes the U64_MAX sentinel address and the method is a non-address method. In that case the address is cleared to 0 and the bad page / range validation is skipped; otherwise the injection address is validated as before. Signed-off-by: Stanley.Yang Reviewed-by: Hawking Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c | 10 ++- .../gpu/drm/amd/ras/ras_mgr/amdgpu_ras_cmd.c | 81 +++++++++++++++---- 2 files changed, 72 insertions(+), 19 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c index 19067df24600..de121539788d 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c @@ -596,8 +596,14 @@ static ssize_t amdgpu_ras_debugfs_ctrl_write(struct file *f, ret = amdgpu_ras_feature_enable(adev, &data.head, 1); break; case 2: - /* umc ce/ue error injection for a bad page is not allowed */ - if (data.head.block == AMDGPU_RAS_BLOCK__UMC) + /* + * UMC ce/ue error injection for a bad page is not allowed. For + * uniras (SMU v13+) devices the injection address is validated by + * the ras_mgr inject handler, so only run the legacy bad page + * check for the legacy RAS path. + */ + if (data.head.block == AMDGPU_RAS_BLOCK__UMC && + !amdgpu_uniras_enabled(adev)) ret = amdgpu_ras_check_bad_page(adev, data.inject.address); if (ret == -EINVAL) { dev_warn(adev->dev, "RAS WARN: input address 0x%llx is invalid.", diff --git a/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_cmd.c b/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_cmd.c index bfbfdffbfbe6..c2285fde8b3c 100644 --- a/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_cmd.c +++ b/drivers/gpu/drm/amd/ras/ras_mgr/amdgpu_ras_cmd.c @@ -82,6 +82,43 @@ static uint64_t local_addr_to_xgmi_global_addr(struct ras_core_context *ras_core return (addr + xgmi->physical_node_id * xgmi->node_segment_size); } +/* + * UMC error injection is dispatched by the RAS TA using the injection method + * carried in struct ras_cmd_inject_error_req. Only the "coherent" methods + * program an explicit injection address and are therefore address-based; the + * single-shot, persistent and ac-parity methods ignore the address. + * + * Keep these values in sync with the RAS TA. + */ +enum umc_inject_method { + UMC_METHOD_COHERENT = 0, + UMC_METHOD_SINGLE_SHOT = 1, + UMC_METHOD_PERSISTENT = 2, + UMC_METHOD_PERSISTENT_DISABLE = 3, + UMC_METHOD_COHERENT_NO_DETECTION = 4, + UMC_METHOD_COHERENT_WR = 5, + UMC_METHOD_SINGLE_SHOT_WR = 6, + UMC_METHOD_PERSISTENT_WR = 7, + UMC_METHOD_SINGLE_SHOT_CLEAN = 8, +}; + +/* + * Return true when @method does not program an explicit injection address. + * Only the coherent methods are address-based; every other method ignores the + * address, so userspace signals them by setting the address to U64_MAX. + */ +static bool amdgpu_ras_mgr_is_non_address_injection(u64 method) +{ + switch (method) { + case UMC_METHOD_COHERENT: + case UMC_METHOD_COHERENT_NO_DETECTION: + case UMC_METHOD_COHERENT_WR: + return false; + default: + return true; + } +} + static int amdgpu_ras_inject_error(struct ras_core_context *ras_core, struct ras_cmd_ctx *cmd, void *data) { @@ -91,25 +128,35 @@ static int amdgpu_ras_inject_error(struct ras_core_context *ras_core, int ret = RAS_CMD__ERROR_GENERIC; if (req->block_id == RAS_BLOCK_ID__UMC) { - if (amdgpu_ras_mgr_check_retired_addr(adev, req->address)) { - RAS_DEV_WARN(ras_core->dev, - "RAS WARN: inject: 0x%llx has already been marked as bad!\n", - req->address); - return RAS_CMD__ERROR_ACCESS_DENIED; - } - - if ((req->address >= adev->gmc.mc_vram_size && - adev->gmc.mc_vram_size) || - (req->address >= RAS_UMC_INJECT_ADDR_LIMIT)) { - RAS_DEV_WARN(adev, "RAS WARN: input address 0x%llx is invalid.", + /* + * Only address-based UMC injections carry an explicit injection + * address that has to be validated. A non address-based method + * ignores the address, and userspace flags such an injection by + * setting the address to U64_MAX. When both the sentinel and the + * method agree, clear the address so the RAS TA ignores it and + * skip the validation; otherwise validate the injection address. + */ + if (req->address == U64_MAX && amdgpu_ras_mgr_is_non_address_injection(req->method)) { + req->address = 0x0; + } else { + if (amdgpu_ras_mgr_check_retired_addr(adev, req->address)) { + RAS_DEV_WARN(ras_core->dev, + "RAS WARN: inject: 0x%llx has already been marked as bad!\n", req->address); - return RAS_CMD__ERROR_INVALID_INPUT_DATA; - } + return RAS_CMD__ERROR_ACCESS_DENIED; + } - /* Calculate XGMI relative offset */ - if (adev->gmc.xgmi.num_physical_nodes > 1 && - req->block_id != RAS_BLOCK_ID__GFX) { - req->address = local_addr_to_xgmi_global_addr(ras_core, req->address); + if ((req->address >= adev->gmc.mc_vram_size && + adev->gmc.mc_vram_size) || + (req->address >= RAS_UMC_INJECT_ADDR_LIMIT)) { + RAS_DEV_WARN(adev, "RAS WARN: input address 0x%llx is invalid.", + req->address); + return RAS_CMD__ERROR_INVALID_INPUT_DATA; + } + + /* Calculate XGMI relative offset */ + if (adev->gmc.xgmi.num_physical_nodes > 1) + req->address = local_addr_to_xgmi_global_addr(ras_core, req->address); } } From 94896bc5fb93d0b18832e5cf00fa5a19f1b5d749 Mon Sep 17 00:00:00 2001 From: Dibin Moolakadan Subrahmanian Date: Wed, 8 Jul 2026 14:37:12 +0530 Subject: [PATCH 131/577] 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 d91748f683693be386fa127b7ec25d2a366b88b9 Mon Sep 17 00:00:00 2001 From: "Jiri Slaby (SUSE)" Date: Wed, 8 Jul 2026 11:56:44 +0200 Subject: [PATCH 132/577] drm/imx: Switch to irq_domain_create_linear() irq_domain_add_linear() is going away as being obsolete now. Switch to the preferred irq_domain_create_linear(). That differs in the first parameter: It takes more generic struct fwnode_handle instead of struct device_node. Therefore, of_fwnode_handle() is added around the parameter. Note some of the users can likely use dev->fwnode directly instead of indirect of_fwnode_handle(dev->of_node). But dev->fwnode is not guaranteed to be set for all, so this has to be investigated on case to case basis (by people who can actually test with the HW). [ Liu Ying: Fix a complaint from 'checkpatch.pl --strict'. ] Signed-off-by: Jiri Slaby (SUSE) Cc: Thomas Gleixner Cc: Liu Ying Acked-by: Liu Ying Link: https://patch.msgid.link/20260708095645.385291-1-jirislaby@kernel.org Signed-off-by: Liu Ying --- drivers/gpu/drm/imx/dc/dc-ic.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/imx/dc/dc-ic.c b/drivers/gpu/drm/imx/dc/dc-ic.c index a270ae4030cd..4f3b1553cbba 100644 --- a/drivers/gpu/drm/imx/dc/dc-ic.c +++ b/drivers/gpu/drm/imx/dc/dc-ic.c @@ -174,8 +174,8 @@ static int dc_ic_probe(struct platform_device *pdev) regmap_write(data->regs, USERINTERRUPTMASK(i), 0xffffffff); } - data->domain = irq_domain_add_linear(dev->of_node, IRQ_COUNT, - &irq_generic_chip_ops, data); + data->domain = irq_domain_create_linear(of_fwnode_handle(dev->of_node), IRQ_COUNT, + &irq_generic_chip_ops, data); if (!data->domain) { dev_err(dev, "failed to create IRQ domain\n"); pm_runtime_put(dev); From 68952f1b00736f91867939f50d7b78c08126caad Mon Sep 17 00:00:00 2001 From: "Jiri Slaby (SUSE)" Date: Wed, 8 Jul 2026 11:56:45 +0200 Subject: [PATCH 133/577] drm/imx: include dc-drv.h to avoid sparse warning sparse (build with C=1) warns: drivers/gpu/drm/imx/dc/dc-ic.c:273:24: warning: symbol 'dc_ic_driver' was not declared. Should it be static? And it is right. Include dc-drv.h which declares the struct. This ensures the types of both match. Signed-off-by: Jiri Slaby (SUSE) Cc: Liu Ying Reviewed-by: Liu Ying Link: https://patch.msgid.link/20260708095645.385291-2-jirislaby@kernel.org Signed-off-by: Liu Ying --- drivers/gpu/drm/imx/dc/dc-ic.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/imx/dc/dc-ic.c b/drivers/gpu/drm/imx/dc/dc-ic.c index 4f3b1553cbba..fa87bf1f4508 100644 --- a/drivers/gpu/drm/imx/dc/dc-ic.c +++ b/drivers/gpu/drm/imx/dc/dc-ic.c @@ -14,6 +14,8 @@ #include #include +#include "dc-drv.h" + #define USERINTERRUPTMASK(n) (0x8 + 4 * (n)) #define INTERRUPTENABLE(n) (0x10 + 4 * (n)) #define INTERRUPTPRESET(n) (0x18 + 4 * (n)) From a3910673fec99f335285810df584f722f26f70b0 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 7 Jul 2026 17:11:08 +0200 Subject: [PATCH 134/577] drm/adp: mipi: Add atomic bridge state handlers The mipi bridge doesn't implement any enable or disable callbacks, but is still missing the atomic state handlers. In order to remove the legacy bridge callback support from the DRM bridge core, add them. Generated by the following Coccinelle script: @ is_bridge @ identifier funcs; @@ struct drm_bridge_funcs funcs = { ..., }; @ has_create_state depends on is_bridge @ identifier funcs, f; @@ struct drm_bridge_funcs funcs = { ..., .atomic_create_state = f, ..., }; @ update_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, ..., }; @ update_pre_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .pre_enable = f, + .atomic_pre_enable = f, ..., }; @ update_pre_enable_impl depends on update_pre_enable_struct @ identifier update_pre_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .enable = f, + .atomic_enable = f, ..., }; @ update_enable_impl depends on update_enable_struct @ identifier update_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .disable = f, + .atomic_disable = f, ..., }; @ update_disable_impl depends on update_disable_struct @ identifier update_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_post_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .post_disable = f, + .atomic_post_disable = f, ..., }; @ update_post_disable_impl depends on update_post_disable_struct @ identifier update_post_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } Reviewed-by: Joshua Peisach Reviewed-by: Luca Ceresoli Link: https://patch.msgid.link/20260707-drm-all-atomic-bridges-v2-1-21d03cbca446@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/adp/adp-mipi.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/gpu/drm/adp/adp-mipi.c b/drivers/gpu/drm/adp/adp-mipi.c index cba7d32150a9..232bb9539423 100644 --- a/drivers/gpu/drm/adp/adp-mipi.c +++ b/drivers/gpu/drm/adp/adp-mipi.c @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -222,6 +223,9 @@ static int adp_dsi_bridge_attach(struct drm_bridge *bridge, } static const struct drm_bridge_funcs adp_dsi_bridge_funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .attach = adp_dsi_bridge_attach, }; From ae2c6b34a2b0cbb36d1bf841ceae4866cac87d7b Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 7 Jul 2026 17:11:09 +0200 Subject: [PATCH 135/577] drm/bridge: analogix-anx6345: Switch to atomic bridge callbacks The analogix-anx6345 bridge uses the legacy non-atomic enable and disable bridge callbacks. In order to remove the legacy bridge callback support from the DRM bridge core, switch to their atomic counterparts and add the bridge atomic state handlers. Generated by the following Coccinelle script: @ is_bridge @ identifier funcs; @@ struct drm_bridge_funcs funcs = { ..., }; @ has_create_state depends on is_bridge @ identifier funcs, f; @@ struct drm_bridge_funcs funcs = { ..., .atomic_create_state = f, ..., }; @ update_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, ..., }; @ update_pre_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .pre_enable = f, + .atomic_pre_enable = f, ..., }; @ update_pre_enable_impl depends on update_pre_enable_struct @ identifier update_pre_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .enable = f, + .atomic_enable = f, ..., }; @ update_enable_impl depends on update_enable_struct @ identifier update_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .disable = f, + .atomic_disable = f, ..., }; @ update_disable_impl depends on update_disable_struct @ identifier update_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_post_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .post_disable = f, + .atomic_post_disable = f, ..., }; @ update_post_disable_impl depends on update_post_disable_struct @ identifier update_post_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } Reviewed-by: Luca Ceresoli Link: https://patch.msgid.link/20260707-drm-all-atomic-bridges-v2-2-21d03cbca446@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/bridge/analogix/analogix-anx6345.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c b/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c index f3fe47b12edc..eba5c6dcb5ad 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c +++ b/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c @@ -566,7 +566,8 @@ anx6345_bridge_mode_valid(struct drm_bridge *bridge, return MODE_OK; } -static void anx6345_bridge_disable(struct drm_bridge *bridge) +static void anx6345_bridge_disable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct anx6345 *anx6345 = bridge_to_anx6345(bridge); @@ -580,7 +581,8 @@ static void anx6345_bridge_disable(struct drm_bridge *bridge) anx6345_poweroff(anx6345); } -static void anx6345_bridge_enable(struct drm_bridge *bridge) +static void anx6345_bridge_enable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct anx6345 *anx6345 = bridge_to_anx6345(bridge); int err; @@ -600,11 +602,14 @@ static void anx6345_bridge_enable(struct drm_bridge *bridge) } static const struct drm_bridge_funcs anx6345_bridge_funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .attach = anx6345_bridge_attach, .detach = anx6345_bridge_detach, .mode_valid = anx6345_bridge_mode_valid, - .disable = anx6345_bridge_disable, - .enable = anx6345_bridge_enable, + .atomic_disable = anx6345_bridge_disable, + .atomic_enable = anx6345_bridge_enable, }; static void unregister_i2c_dummy_clients(struct anx6345 *anx6345) From e25f2a1d6e69135d17ef421843cbb5d82b114db3 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 7 Jul 2026 17:11:10 +0200 Subject: [PATCH 136/577] drm/bridge: analogix-anx78xx: Switch to atomic bridge callbacks The analogix-anx78xx bridge uses the legacy non-atomic enable and disable bridge callbacks. In order to remove the legacy bridge callback support from the DRM bridge core, switch to their atomic counterparts and add the bridge atomic state handlers. Generated by the following Coccinelle script: @ is_bridge @ identifier funcs; @@ struct drm_bridge_funcs funcs = { ..., }; @ has_create_state depends on is_bridge @ identifier funcs, f; @@ struct drm_bridge_funcs funcs = { ..., .atomic_create_state = f, ..., }; @ update_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, ..., }; @ update_pre_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .pre_enable = f, + .atomic_pre_enable = f, ..., }; @ update_pre_enable_impl depends on update_pre_enable_struct @ identifier update_pre_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .enable = f, + .atomic_enable = f, ..., }; @ update_enable_impl depends on update_enable_struct @ identifier update_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .disable = f, + .atomic_disable = f, ..., }; @ update_disable_impl depends on update_disable_struct @ identifier update_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_post_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .post_disable = f, + .atomic_post_disable = f, ..., }; @ update_post_disable_impl depends on update_post_disable_struct @ identifier update_post_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } Reviewed-by: Luca Ceresoli Link: https://patch.msgid.link/20260707-drm-all-atomic-bridges-v2-3-21d03cbca446@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/bridge/analogix/analogix-anx78xx.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/bridge/analogix/analogix-anx78xx.c b/drivers/gpu/drm/bridge/analogix/analogix-anx78xx.c index ba0fc149a9e7..32186efa6f50 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix-anx78xx.c +++ b/drivers/gpu/drm/bridge/analogix/analogix-anx78xx.c @@ -937,7 +937,8 @@ anx78xx_bridge_mode_valid(struct drm_bridge *bridge, return MODE_OK; } -static void anx78xx_bridge_disable(struct drm_bridge *bridge) +static void anx78xx_bridge_disable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct anx78xx *anx78xx = bridge_to_anx78xx(bridge); @@ -975,7 +976,8 @@ unlock: mutex_unlock(&anx78xx->lock); } -static void anx78xx_bridge_enable(struct drm_bridge *bridge) +static void anx78xx_bridge_enable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct anx78xx *anx78xx = bridge_to_anx78xx(bridge); int err; @@ -992,12 +994,15 @@ static void anx78xx_bridge_enable(struct drm_bridge *bridge) } static const struct drm_bridge_funcs anx78xx_bridge_funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .attach = anx78xx_bridge_attach, .detach = anx78xx_bridge_detach, .mode_valid = anx78xx_bridge_mode_valid, - .disable = anx78xx_bridge_disable, + .atomic_disable = anx78xx_bridge_disable, .mode_set = anx78xx_bridge_mode_set, - .enable = anx78xx_bridge_enable, + .atomic_enable = anx78xx_bridge_enable, }; static irqreturn_t anx78xx_hpd_threaded_handler(int irq, void *data) From 0dcfc4c7f7a42170b0c3a952f0f56904258a724f Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 7 Jul 2026 17:11:11 +0200 Subject: [PATCH 137/577] drm/bridge: aux-bridge: Add atomic bridge state handlers The aux-bridge bridge doesn't implement any enable or disable callbacks, but is still missing the atomic state handlers. In order to remove the legacy bridge callback support from the DRM bridge core, add them. Generated by the following Coccinelle script: @ is_bridge @ identifier funcs; @@ struct drm_bridge_funcs funcs = { ..., }; @ has_create_state depends on is_bridge @ identifier funcs, f; @@ struct drm_bridge_funcs funcs = { ..., .atomic_create_state = f, ..., }; @ update_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, ..., }; @ update_pre_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .pre_enable = f, + .atomic_pre_enable = f, ..., }; @ update_pre_enable_impl depends on update_pre_enable_struct @ identifier update_pre_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .enable = f, + .atomic_enable = f, ..., }; @ update_enable_impl depends on update_enable_struct @ identifier update_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .disable = f, + .atomic_disable = f, ..., }; @ update_disable_impl depends on update_disable_struct @ identifier update_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_post_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .post_disable = f, + .atomic_post_disable = f, ..., }; @ update_post_disable_impl depends on update_post_disable_struct @ identifier update_post_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } Reviewed-by: Luca Ceresoli Link: https://patch.msgid.link/20260707-drm-all-atomic-bridges-v2-4-21d03cbca446@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/bridge/aux-bridge.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/gpu/drm/bridge/aux-bridge.c b/drivers/gpu/drm/bridge/aux-bridge.c index 1ed21a8713bf..169939d23310 100644 --- a/drivers/gpu/drm/bridge/aux-bridge.c +++ b/drivers/gpu/drm/bridge/aux-bridge.c @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -104,6 +105,9 @@ static int drm_aux_bridge_attach(struct drm_bridge *bridge, } static const struct drm_bridge_funcs drm_aux_bridge_funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .attach = drm_aux_bridge_attach, }; From dc015b8b282136771c42170ec7f541cd1e22efc8 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 7 Jul 2026 17:11:12 +0200 Subject: [PATCH 138/577] drm/bridge: aux-hpd-bridge: Add atomic bridge state handlers The aux-hpd-bridge bridge doesn't implement any enable or disable callbacks, but is still missing the atomic state handlers. In order to remove the legacy bridge callback support from the DRM bridge core, add them. Generated by the following Coccinelle script: @ is_bridge @ identifier funcs; @@ struct drm_bridge_funcs funcs = { ..., }; @ has_create_state depends on is_bridge @ identifier funcs, f; @@ struct drm_bridge_funcs funcs = { ..., .atomic_create_state = f, ..., }; @ update_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, ..., }; @ update_pre_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .pre_enable = f, + .atomic_pre_enable = f, ..., }; @ update_pre_enable_impl depends on update_pre_enable_struct @ identifier update_pre_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .enable = f, + .atomic_enable = f, ..., }; @ update_enable_impl depends on update_enable_struct @ identifier update_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .disable = f, + .atomic_disable = f, ..., }; @ update_disable_impl depends on update_disable_struct @ identifier update_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_post_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .post_disable = f, + .atomic_post_disable = f, ..., }; @ update_post_disable_impl depends on update_post_disable_struct @ identifier update_post_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } Reviewed-by: Luca Ceresoli Link: https://patch.msgid.link/20260707-drm-all-atomic-bridges-v2-5-21d03cbca446@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/bridge/aux-hpd-bridge.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/gpu/drm/bridge/aux-hpd-bridge.c b/drivers/gpu/drm/bridge/aux-hpd-bridge.c index f02a38a2638a..a2e525aa5788 100644 --- a/drivers/gpu/drm/bridge/aux-hpd-bridge.c +++ b/drivers/gpu/drm/bridge/aux-hpd-bridge.c @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -165,6 +166,9 @@ static int drm_aux_hpd_bridge_attach(struct drm_bridge *bridge, } static const struct drm_bridge_funcs drm_aux_hpd_bridge_funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .attach = drm_aux_hpd_bridge_attach, }; From 94775f06ea3c66a9023b2db4bd8211fc36301239 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 7 Jul 2026 17:11:13 +0200 Subject: [PATCH 139/577] drm/bridge: chrontel-ch7033: Switch to atomic bridge callbacks The chrontel-ch7033 bridge uses the legacy non-atomic enable and disable bridge callbacks. In order to remove the legacy bridge callback support from the DRM bridge core, switch to their atomic counterparts and add the bridge atomic state handlers. Generated by the following Coccinelle script: @ is_bridge @ identifier funcs; @@ struct drm_bridge_funcs funcs = { ..., }; @ has_create_state depends on is_bridge @ identifier funcs, f; @@ struct drm_bridge_funcs funcs = { ..., .atomic_create_state = f, ..., }; @ update_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, ..., }; @ update_pre_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .pre_enable = f, + .atomic_pre_enable = f, ..., }; @ update_pre_enable_impl depends on update_pre_enable_struct @ identifier update_pre_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .enable = f, + .atomic_enable = f, ..., }; @ update_enable_impl depends on update_enable_struct @ identifier update_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .disable = f, + .atomic_disable = f, ..., }; @ update_disable_impl depends on update_disable_struct @ identifier update_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_post_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .post_disable = f, + .atomic_post_disable = f, ..., }; @ update_post_disable_impl depends on update_post_disable_struct @ identifier update_post_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } Reviewed-by: Luca Ceresoli Link: https://patch.msgid.link/20260707-drm-all-atomic-bridges-v2-6-21d03cbca446@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/bridge/chrontel-ch7033.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/bridge/chrontel-ch7033.c b/drivers/gpu/drm/bridge/chrontel-ch7033.c index a237c65ebd69..92d464727e41 100644 --- a/drivers/gpu/drm/bridge/chrontel-ch7033.c +++ b/drivers/gpu/drm/bridge/chrontel-ch7033.c @@ -330,7 +330,8 @@ static enum drm_mode_status ch7033_bridge_mode_valid(struct drm_bridge *bridge, return MODE_OK; } -static void ch7033_bridge_disable(struct drm_bridge *bridge) +static void ch7033_bridge_disable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct ch7033_priv *priv = bridge_to_ch7033_priv(bridge); @@ -338,7 +339,8 @@ static void ch7033_bridge_disable(struct drm_bridge *bridge) regmap_update_bits(priv->regmap, 0x52, RESETDB, 0x00); } -static void ch7033_bridge_enable(struct drm_bridge *bridge) +static void ch7033_bridge_enable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct ch7033_priv *priv = bridge_to_ch7033_priv(bridge); @@ -514,11 +516,14 @@ static void ch7033_bridge_mode_set(struct drm_bridge *bridge, } static const struct drm_bridge_funcs ch7033_bridge_funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .attach = ch7033_bridge_attach, .detach = ch7033_bridge_detach, .mode_valid = ch7033_bridge_mode_valid, - .disable = ch7033_bridge_disable, - .enable = ch7033_bridge_enable, + .atomic_disable = ch7033_bridge_disable, + .atomic_enable = ch7033_bridge_enable, .mode_set = ch7033_bridge_mode_set, }; From 43b4eda100a44e0ee2c146466f41d4806215ab3a Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 7 Jul 2026 17:11:14 +0200 Subject: [PATCH 140/577] drm/bridge: cros-ec-anx7688: Add atomic bridge state handlers The cros-ec-anx7688 bridge doesn't implement any enable or disable callbacks, but is still missing the atomic state handlers. In order to remove the legacy bridge callback support from the DRM bridge core, add them. Generated by the following Coccinelle script: @ is_bridge @ identifier funcs; @@ struct drm_bridge_funcs funcs = { ..., }; @ has_create_state depends on is_bridge @ identifier funcs, f; @@ struct drm_bridge_funcs funcs = { ..., .atomic_create_state = f, ..., }; @ update_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, ..., }; @ update_pre_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .pre_enable = f, + .atomic_pre_enable = f, ..., }; @ update_pre_enable_impl depends on update_pre_enable_struct @ identifier update_pre_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .enable = f, + .atomic_enable = f, ..., }; @ update_enable_impl depends on update_enable_struct @ identifier update_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .disable = f, + .atomic_disable = f, ..., }; @ update_disable_impl depends on update_disable_struct @ identifier update_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_post_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .post_disable = f, + .atomic_post_disable = f, ..., }; @ update_post_disable_impl depends on update_post_disable_struct @ identifier update_post_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } Reviewed-by: Luca Ceresoli Link: https://patch.msgid.link/20260707-drm-all-atomic-bridges-v2-7-21d03cbca446@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/bridge/cros-ec-anx7688.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/gpu/drm/bridge/cros-ec-anx7688.c b/drivers/gpu/drm/bridge/cros-ec-anx7688.c index a35dae9b56e2..da16c793e3ba 100644 --- a/drivers/gpu/drm/bridge/cros-ec-anx7688.c +++ b/drivers/gpu/drm/bridge/cros-ec-anx7688.c @@ -5,6 +5,7 @@ * Copyright 2020 Google LLC */ +#include #include #include #include @@ -92,6 +93,9 @@ static bool cros_ec_anx7688_bridge_mode_fixup(struct drm_bridge *bridge, } static const struct drm_bridge_funcs cros_ec_anx7688_bridge_funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .mode_fixup = cros_ec_anx7688_bridge_mode_fixup, }; From 3c8c30fcd10425014bdcbafc5d75eaa6799da3f5 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 7 Jul 2026 17:11:15 +0200 Subject: [PATCH 141/577] drm/bridge: lontium-lt8713sx: Add atomic bridge state handlers The lontium-lt8713sx bridge doesn't implement any enable or disable callbacks, but is still missing the atomic state handlers. In order to remove the legacy bridge callback support from the DRM bridge core, add them. Generated by the following Coccinelle script: @ is_bridge @ identifier funcs; @@ struct drm_bridge_funcs funcs = { ..., }; @ has_create_state depends on is_bridge @ identifier funcs, f; @@ struct drm_bridge_funcs funcs = { ..., .atomic_create_state = f, ..., }; @ update_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, ..., }; @ update_pre_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .pre_enable = f, + .atomic_pre_enable = f, ..., }; @ update_pre_enable_impl depends on update_pre_enable_struct @ identifier update_pre_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .enable = f, + .atomic_enable = f, ..., }; @ update_enable_impl depends on update_enable_struct @ identifier update_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .disable = f, + .atomic_disable = f, ..., }; @ update_disable_impl depends on update_disable_struct @ identifier update_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_post_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .post_disable = f, + .atomic_post_disable = f, ..., }; @ update_post_disable_impl depends on update_post_disable_struct @ identifier update_post_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } Reviewed-by: Luca Ceresoli Link: https://patch.msgid.link/20260707-drm-all-atomic-bridges-v2-8-21d03cbca446@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/bridge/lontium-lt8713sx.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/gpu/drm/bridge/lontium-lt8713sx.c b/drivers/gpu/drm/bridge/lontium-lt8713sx.c index cee485adf5e5..4a6420b876da 100644 --- a/drivers/gpu/drm/bridge/lontium-lt8713sx.c +++ b/drivers/gpu/drm/bridge/lontium-lt8713sx.c @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -508,6 +509,9 @@ static const struct attribute_group *lt8713sx_attr_groups[] = { }; static const struct drm_bridge_funcs lt8713sx_bridge_funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .attach = lt8713sx_bridge_attach, }; From b68169fc055dd26d58f8856fbf0c737af0504ce6 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 7 Jul 2026 17:11:16 +0200 Subject: [PATCH 142/577] drm/bridge: lontium-lt8912b: Switch to atomic bridge callbacks The lontium-lt8912b bridge uses the legacy non-atomic enable bridge callback. In order to remove the legacy bridge callback support from the DRM bridge core, switch to their atomic counterparts and add the bridge atomic state handlers. Generated by the following Coccinelle script: @ is_bridge @ identifier funcs; @@ struct drm_bridge_funcs funcs = { ..., }; @ has_create_state depends on is_bridge @ identifier funcs, f; @@ struct drm_bridge_funcs funcs = { ..., .atomic_create_state = f, ..., }; @ update_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, ..., }; @ update_pre_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .pre_enable = f, + .atomic_pre_enable = f, ..., }; @ update_pre_enable_impl depends on update_pre_enable_struct @ identifier update_pre_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .enable = f, + .atomic_enable = f, ..., }; @ update_enable_impl depends on update_enable_struct @ identifier update_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .disable = f, + .atomic_disable = f, ..., }; @ update_disable_impl depends on update_disable_struct @ identifier update_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_post_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .post_disable = f, + .atomic_post_disable = f, ..., }; @ update_post_disable_impl depends on update_post_disable_struct @ identifier update_post_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } Acked-by: Francesco Dolcini Tested-by: Francesco Dolcini # Verdin AM62 Reviewed-by: Luca Ceresoli Link: https://patch.msgid.link/20260707-drm-all-atomic-bridges-v2-9-21d03cbca446@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/bridge/lontium-lt8912b.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/bridge/lontium-lt8912b.c b/drivers/gpu/drm/bridge/lontium-lt8912b.c index 729b12b67470..df5d07e7644c 100644 --- a/drivers/gpu/drm/bridge/lontium-lt8912b.c +++ b/drivers/gpu/drm/bridge/lontium-lt8912b.c @@ -457,7 +457,8 @@ static void lt8912_bridge_mode_set(struct drm_bridge *bridge, drm_display_mode_to_videomode(adj, <->mode); } -static void lt8912_bridge_enable(struct drm_bridge *bridge) +static void lt8912_bridge_enable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct lt8912 *lt = bridge_to_lt8912(bridge); @@ -634,11 +635,14 @@ static const struct drm_edid *lt8912_bridge_edid_read(struct drm_bridge *bridge, } static const struct drm_bridge_funcs lt8912_bridge_funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .attach = lt8912_bridge_attach, .detach = lt8912_bridge_detach, .mode_valid = lt8912_bridge_mode_valid, .mode_set = lt8912_bridge_mode_set, - .enable = lt8912_bridge_enable, + .atomic_enable = lt8912_bridge_enable, .detect = lt8912_bridge_detect, .edid_read = lt8912_bridge_edid_read, }; From 59a6bf37641e072d6289903bc0fdf1e042aede7b Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 7 Jul 2026 17:11:17 +0200 Subject: [PATCH 143/577] drm/bridge: lontium-lt9611uxc: Add atomic bridge state handlers The lontium-lt9611uxc bridge doesn't implement any enable or disable callbacks, but is still missing the atomic state handlers. In order to remove the legacy bridge callback support from the DRM bridge core, add them. Generated by the following Coccinelle script: @ is_bridge @ identifier funcs; @@ struct drm_bridge_funcs funcs = { ..., }; @ has_create_state depends on is_bridge @ identifier funcs, f; @@ struct drm_bridge_funcs funcs = { ..., .atomic_create_state = f, ..., }; @ update_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, ..., }; @ update_pre_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .pre_enable = f, + .atomic_pre_enable = f, ..., }; @ update_pre_enable_impl depends on update_pre_enable_struct @ identifier update_pre_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .enable = f, + .atomic_enable = f, ..., }; @ update_enable_impl depends on update_enable_struct @ identifier update_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .disable = f, + .atomic_disable = f, ..., }; @ update_disable_impl depends on update_disable_struct @ identifier update_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_post_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .post_disable = f, + .atomic_post_disable = f, ..., }; @ update_post_disable_impl depends on update_post_disable_struct @ identifier update_post_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } Reviewed-by: Luca Ceresoli Link: https://patch.msgid.link/20260707-drm-all-atomic-bridges-v2-10-21d03cbca446@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/bridge/lontium-lt9611uxc.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/bridge/lontium-lt9611uxc.c b/drivers/gpu/drm/bridge/lontium-lt9611uxc.c index 9427cc2358ae..6625ba23761c 100644 --- a/drivers/gpu/drm/bridge/lontium-lt9611uxc.c +++ b/drivers/gpu/drm/bridge/lontium-lt9611uxc.c @@ -465,6 +465,9 @@ static void lt9611uxc_hdmi_audio_shutdown(struct drm_bridge *bridge, } static const struct drm_bridge_funcs lt9611uxc_bridge_funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .attach = lt9611uxc_bridge_attach, .mode_valid = lt9611uxc_bridge_mode_valid, .mode_set = lt9611uxc_bridge_mode_set, From c47ab48c139e8b0bd8c81639c88128a1ffb6a2d4 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 7 Jul 2026 17:11:18 +0200 Subject: [PATCH 144/577] drm/bridge: lvds-codec: Switch to atomic bridge callbacks The lvds-codec bridge uses the legacy non-atomic enable and disable bridge callbacks. In order to remove the legacy bridge callback support from the DRM bridge core, switch to their atomic counterparts and add the bridge atomic state handlers. Generated by the following Coccinelle script: @ is_bridge @ identifier funcs; @@ struct drm_bridge_funcs funcs = { ..., }; @ has_create_state depends on is_bridge @ identifier funcs, f; @@ struct drm_bridge_funcs funcs = { ..., .atomic_create_state = f, ..., }; @ update_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, ..., }; @ update_pre_enable_struct depends on is_bridge @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .pre_enable = f, + .atomic_pre_enable = f, ..., }; @ update_pre_enable_impl depends on update_pre_enable_struct @ identifier update_pre_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_enable_struct depends on is_bridge @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .enable = f, + .atomic_enable = f, ..., }; @ update_enable_impl depends on update_enable_struct @ identifier update_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_disable_struct depends on is_bridge @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .disable = f, + .atomic_disable = f, ..., }; @ update_disable_impl depends on update_disable_struct @ identifier update_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_post_disable_struct depends on is_bridge @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .post_disable = f, + .atomic_post_disable = f, ..., }; @ update_post_disable_impl depends on update_post_disable_struct @ identifier update_post_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } Reviewed-by: Laurent Pinchart Reviewed-by: Luca Ceresoli Link: https://patch.msgid.link/20260707-drm-all-atomic-bridges-v2-11-21d03cbca446@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/bridge/lvds-codec.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/bridge/lvds-codec.c b/drivers/gpu/drm/bridge/lvds-codec.c index d1135dc3b99f..a82ea0c944eb 100644 --- a/drivers/gpu/drm/bridge/lvds-codec.c +++ b/drivers/gpu/drm/bridge/lvds-codec.c @@ -43,7 +43,8 @@ static int lvds_codec_attach(struct drm_bridge *bridge, bridge, flags); } -static void lvds_codec_enable(struct drm_bridge *bridge) +static void lvds_codec_enable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct lvds_codec *lvds_codec = to_lvds_codec(bridge); int ret; @@ -59,7 +60,8 @@ static void lvds_codec_enable(struct drm_bridge *bridge) gpiod_set_value_cansleep(lvds_codec->powerdown_gpio, 0); } -static void lvds_codec_disable(struct drm_bridge *bridge) +static void lvds_codec_disable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct lvds_codec *lvds_codec = to_lvds_codec(bridge); int ret; @@ -100,8 +102,8 @@ lvds_codec_atomic_get_input_bus_fmts(struct drm_bridge *bridge, static const struct drm_bridge_funcs funcs = { .attach = lvds_codec_attach, - .enable = lvds_codec_enable, - .disable = lvds_codec_disable, + .atomic_enable = lvds_codec_enable, + .atomic_disable = lvds_codec_disable, .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, .atomic_create_state = drm_atomic_helper_bridge_create_state, From 2ee765ca19e7e09ddd65770c26ef31d9e070fbfd Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 7 Jul 2026 17:11:19 +0200 Subject: [PATCH 145/577] drm/bridge: megachips-stdpxxxx-ge-b850v3-fw: Add atomic bridge state handlers The megachips-stdpxxxx-ge-b850v3-fw bridge doesn't implement any enable or disable callbacks, but is still missing the atomic state handlers. In order to remove the legacy bridge callback support from the DRM bridge core, add them. Generated by the following Coccinelle script: @ is_bridge @ identifier funcs; @@ struct drm_bridge_funcs funcs = { ..., }; @ has_create_state depends on is_bridge @ identifier funcs, f; @@ struct drm_bridge_funcs funcs = { ..., .atomic_create_state = f, ..., }; @ update_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, ..., }; @ update_pre_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .pre_enable = f, + .atomic_pre_enable = f, ..., }; @ update_pre_enable_impl depends on update_pre_enable_struct @ identifier update_pre_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .enable = f, + .atomic_enable = f, ..., }; @ update_enable_impl depends on update_enable_struct @ identifier update_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .disable = f, + .atomic_disable = f, ..., }; @ update_disable_impl depends on update_disable_struct @ identifier update_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_post_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .post_disable = f, + .atomic_post_disable = f, ..., }; @ update_post_disable_impl depends on update_post_disable_struct @ identifier update_post_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } Reviewed-by: Ian Ray Reviewed-by: Luca Ceresoli Link: https://patch.msgid.link/20260707-drm-all-atomic-bridges-v2-12-21d03cbca446@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c b/drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c index 2d02cc69f237..25ff0a0acc93 100644 --- a/drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c +++ b/drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c @@ -214,6 +214,9 @@ static int ge_b850v3_lvds_attach(struct drm_bridge *bridge, } static const struct drm_bridge_funcs ge_b850v3_lvds_funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .attach = ge_b850v3_lvds_attach, .detect = ge_b850v3_lvds_bridge_detect, .edid_read = ge_b850v3_lvds_edid_read, From f9ac341cf57aeb75af0791de8b33c88ce8766185 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 7 Jul 2026 17:11:20 +0200 Subject: [PATCH 146/577] drm/bridge: microchip-lvds: Add atomic bridge state handlers The microchip-lvds bridge doesn't implement any enable or disable callbacks, but is still missing the atomic state handlers. In order to remove the legacy bridge callback support from the DRM bridge core, add them. Generated by the following Coccinelle script: @ is_bridge @ identifier funcs; @@ struct drm_bridge_funcs funcs = { ..., }; @ has_create_state depends on is_bridge @ identifier funcs, f; @@ struct drm_bridge_funcs funcs = { ..., .atomic_create_state = f, ..., }; @ update_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, ..., }; @ update_pre_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .pre_enable = f, + .atomic_pre_enable = f, ..., }; @ update_pre_enable_impl depends on update_pre_enable_struct @ identifier update_pre_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .enable = f, + .atomic_enable = f, ..., }; @ update_enable_impl depends on update_enable_struct @ identifier update_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .disable = f, + .atomic_disable = f, ..., }; @ update_disable_impl depends on update_disable_struct @ identifier update_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_post_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .post_disable = f, + .atomic_post_disable = f, ..., }; @ update_post_disable_impl depends on update_post_disable_struct @ identifier update_post_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } Reviewed-by: Manikandan Muralidharan Reviewed-by: Luca Ceresoli Link: https://patch.msgid.link/20260707-drm-all-atomic-bridges-v2-13-21d03cbca446@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/bridge/microchip-lvds.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/bridge/microchip-lvds.c b/drivers/gpu/drm/bridge/microchip-lvds.c index 5fb8633f43c5..dd4ffc9f4df3 100644 --- a/drivers/gpu/drm/bridge/microchip-lvds.c +++ b/drivers/gpu/drm/bridge/microchip-lvds.c @@ -167,6 +167,9 @@ static void mchp_lvds_atomic_disable(struct drm_bridge *bridge, } static const struct drm_bridge_funcs mchp_lvds_bridge_funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .attach = mchp_lvds_attach, .atomic_enable = mchp_lvds_atomic_enable, .atomic_disable = mchp_lvds_atomic_disable, From b50791afed41825ec6839889756aa39f500b45ef Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 7 Jul 2026 17:11:21 +0200 Subject: [PATCH 147/577] drm/bridge: nxp-ptn3460: Switch to atomic bridge callbacks The nxp-ptn3460 bridge uses the legacy non-atomic pre_enable and disable bridge callbacks. In order to remove the legacy bridge callback support from the DRM bridge core, switch to their atomic counterparts and add the bridge atomic state handlers. Generated by the following Coccinelle script: @ is_bridge @ identifier funcs; @@ struct drm_bridge_funcs funcs = { ..., }; @ has_create_state depends on is_bridge @ identifier funcs, f; @@ struct drm_bridge_funcs funcs = { ..., .atomic_create_state = f, ..., }; @ update_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, ..., }; @ update_pre_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .pre_enable = f, + .atomic_pre_enable = f, ..., }; @ update_pre_enable_impl depends on update_pre_enable_struct @ identifier update_pre_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .enable = f, + .atomic_enable = f, ..., }; @ update_enable_impl depends on update_enable_struct @ identifier update_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .disable = f, + .atomic_disable = f, ..., }; @ update_disable_impl depends on update_disable_struct @ identifier update_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_post_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .post_disable = f, + .atomic_post_disable = f, ..., }; @ update_post_disable_impl depends on update_post_disable_struct @ identifier update_post_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } Reviewed-by: Luca Ceresoli Link: https://patch.msgid.link/20260707-drm-all-atomic-bridges-v2-14-21d03cbca446@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/bridge/nxp-ptn3460.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/bridge/nxp-ptn3460.c b/drivers/gpu/drm/bridge/nxp-ptn3460.c index 7acb11f16dc1..a9b4ac2defc9 100644 --- a/drivers/gpu/drm/bridge/nxp-ptn3460.c +++ b/drivers/gpu/drm/bridge/nxp-ptn3460.c @@ -111,7 +111,8 @@ static int ptn3460_select_edid(struct ptn3460_bridge *ptn_bridge) return 0; } -static void ptn3460_pre_enable(struct drm_bridge *bridge) +static void ptn3460_pre_enable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct ptn3460_bridge *ptn_bridge = bridge_to_ptn3460(bridge); int ret; @@ -139,7 +140,8 @@ static void ptn3460_pre_enable(struct drm_bridge *bridge) ptn_bridge->enabled = true; } -static void ptn3460_disable(struct drm_bridge *bridge) +static void ptn3460_disable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct ptn3460_bridge *ptn_bridge = bridge_to_ptn3460(bridge); @@ -163,7 +165,7 @@ static const struct drm_edid *ptn3460_edid_read(struct drm_bridge *bridge, int ret; power_off = !ptn_bridge->enabled; - ptn3460_pre_enable(&ptn_bridge->bridge); + ptn3460_pre_enable(&ptn_bridge->bridge, NULL); edid = kmalloc(EDID_LENGTH, GFP_KERNEL); if (!edid) { @@ -182,7 +184,7 @@ static const struct drm_edid *ptn3460_edid_read(struct drm_bridge *bridge, out: if (power_off) - ptn3460_disable(&ptn_bridge->bridge); + ptn3460_disable(&ptn_bridge->bridge, NULL); return drm_edid; } @@ -248,8 +250,11 @@ static int ptn3460_bridge_attach(struct drm_bridge *bridge, } static const struct drm_bridge_funcs ptn3460_bridge_funcs = { - .pre_enable = ptn3460_pre_enable, - .disable = ptn3460_disable, + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_pre_enable = ptn3460_pre_enable, + .atomic_disable = ptn3460_disable, .attach = ptn3460_bridge_attach, .edid_read = ptn3460_edid_read, }; From 92a31e7cd5961a6f91b7b14e4c28e3bbb262b368 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 7 Jul 2026 17:11:22 +0200 Subject: [PATCH 148/577] drm/bridge: of-display-mode-bridge: Add atomic bridge state handlers The of-display-mode-bridge bridge doesn't implement any enable or disable callbacks, but is still missing the atomic state handlers. In order to remove the legacy bridge callback support from the DRM bridge core, add them. Generated by the following Coccinelle script: @ is_bridge @ identifier funcs; @@ struct drm_bridge_funcs funcs = { ..., }; @ has_create_state depends on is_bridge @ identifier funcs, f; @@ struct drm_bridge_funcs funcs = { ..., .atomic_create_state = f, ..., }; @ update_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, ..., }; @ update_pre_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .pre_enable = f, + .atomic_pre_enable = f, ..., }; @ update_pre_enable_impl depends on update_pre_enable_struct @ identifier update_pre_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .enable = f, + .atomic_enable = f, ..., }; @ update_enable_impl depends on update_enable_struct @ identifier update_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .disable = f, + .atomic_disable = f, ..., }; @ update_disable_impl depends on update_disable_struct @ identifier update_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_post_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .post_disable = f, + .atomic_post_disable = f, ..., }; @ update_post_disable_impl depends on update_post_disable_struct @ identifier update_post_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } Reviewed-by: Luca Ceresoli Link: https://patch.msgid.link/20260707-drm-all-atomic-bridges-v2-15-21d03cbca446@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/bridge/of-display-mode-bridge.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/gpu/drm/bridge/of-display-mode-bridge.c b/drivers/gpu/drm/bridge/of-display-mode-bridge.c index e66dae168fd0..af3e727e91c9 100644 --- a/drivers/gpu/drm/bridge/of-display-mode-bridge.c +++ b/drivers/gpu/drm/bridge/of-display-mode-bridge.c @@ -9,6 +9,7 @@ #include +#include #include #include #include @@ -52,6 +53,9 @@ static int of_display_mode_bridge_get_modes(struct drm_bridge *bridge, } static const struct drm_bridge_funcs of_display_mode_bridge_funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .attach = of_display_mode_bridge_attach, .get_modes = of_display_mode_bridge_get_modes, }; From a1289e41ed6317650f60f2e8b3c1271ea13ba611 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 7 Jul 2026 17:11:23 +0200 Subject: [PATCH 149/577] drm/bridge: parade-ps8622: Switch to atomic bridge callbacks The parade-ps8622 bridge uses the legacy non-atomic pre_enable, disable and post_disable bridge callbacks. In order to remove the legacy bridge callback support from the DRM bridge core, switch to their atomic counterparts and add the bridge atomic state handlers. Generated by the following Coccinelle script: @ is_bridge @ identifier funcs; @@ struct drm_bridge_funcs funcs = { ..., }; @ has_create_state depends on is_bridge @ identifier funcs, f; @@ struct drm_bridge_funcs funcs = { ..., .atomic_create_state = f, ..., }; @ update_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, ..., }; @ update_pre_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .pre_enable = f, + .atomic_pre_enable = f, ..., }; @ update_pre_enable_impl depends on update_pre_enable_struct @ identifier update_pre_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .enable = f, + .atomic_enable = f, ..., }; @ update_enable_impl depends on update_enable_struct @ identifier update_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .disable = f, + .atomic_disable = f, ..., }; @ update_disable_impl depends on update_disable_struct @ identifier update_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_post_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .post_disable = f, + .atomic_post_disable = f, ..., }; @ update_post_disable_impl depends on update_post_disable_struct @ identifier update_post_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } Reviewed-by: Luca Ceresoli Link: https://patch.msgid.link/20260707-drm-all-atomic-bridges-v2-16-21d03cbca446@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/bridge/parade-ps8622.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/bridge/parade-ps8622.c b/drivers/gpu/drm/bridge/parade-ps8622.c index f879a1df077d..33844db753aa 100644 --- a/drivers/gpu/drm/bridge/parade-ps8622.c +++ b/drivers/gpu/drm/bridge/parade-ps8622.c @@ -336,7 +336,8 @@ static const struct backlight_ops ps8622_backlight_ops = { .update_status = ps8622_backlight_update, }; -static void ps8622_pre_enable(struct drm_bridge *bridge) +static void ps8622_pre_enable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct ps8622_bridge *ps8622 = bridge_to_ps8622(bridge); int ret; @@ -381,13 +382,15 @@ static void ps8622_pre_enable(struct drm_bridge *bridge) ps8622->enabled = true; } -static void ps8622_disable(struct drm_bridge *bridge) +static void ps8622_disable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { /* Delay after panel is disabled */ msleep(PS8622_PWMO_END_T12_MS); } -static void ps8622_post_disable(struct drm_bridge *bridge) +static void ps8622_post_disable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct ps8622_bridge *ps8622 = bridge_to_ps8622(bridge); @@ -428,9 +431,12 @@ static int ps8622_attach(struct drm_bridge *bridge, } static const struct drm_bridge_funcs ps8622_bridge_funcs = { - .pre_enable = ps8622_pre_enable, - .disable = ps8622_disable, - .post_disable = ps8622_post_disable, + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_pre_enable = ps8622_pre_enable, + .atomic_disable = ps8622_disable, + .atomic_post_disable = ps8622_post_disable, .attach = ps8622_attach, }; From 9acab3cbecf5d736287e754b17ea80988b4ecec4 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 7 Jul 2026 17:11:24 +0200 Subject: [PATCH 150/577] drm/bridge: sii9234: Add atomic bridge state handlers The sii9234 bridge doesn't implement any enable or disable callbacks, but is still missing the atomic state handlers. In order to remove the legacy bridge callback support from the DRM bridge core, add them. Generated by the following Coccinelle script: @ is_bridge @ identifier funcs; @@ struct drm_bridge_funcs funcs = { ..., }; @ has_create_state depends on is_bridge @ identifier funcs, f; @@ struct drm_bridge_funcs funcs = { ..., .atomic_create_state = f, ..., }; @ update_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, ..., }; @ update_pre_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .pre_enable = f, + .atomic_pre_enable = f, ..., }; @ update_pre_enable_impl depends on update_pre_enable_struct @ identifier update_pre_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .enable = f, + .atomic_enable = f, ..., }; @ update_enable_impl depends on update_enable_struct @ identifier update_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .disable = f, + .atomic_disable = f, ..., }; @ update_disable_impl depends on update_disable_struct @ identifier update_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_post_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .post_disable = f, + .atomic_post_disable = f, ..., }; @ update_post_disable_impl depends on update_post_disable_struct @ identifier update_post_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } Reviewed-by: Luca Ceresoli Link: https://patch.msgid.link/20260707-drm-all-atomic-bridges-v2-17-21d03cbca446@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/bridge/sii9234.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/gpu/drm/bridge/sii9234.c b/drivers/gpu/drm/bridge/sii9234.c index e43248e515b3..be67642ab7d3 100644 --- a/drivers/gpu/drm/bridge/sii9234.c +++ b/drivers/gpu/drm/bridge/sii9234.c @@ -13,6 +13,7 @@ * Dharam Kumar */ #include +#include #include #include #include @@ -874,6 +875,9 @@ static enum drm_mode_status sii9234_mode_valid(struct drm_bridge *bridge, } static const struct drm_bridge_funcs sii9234_bridge_funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .mode_valid = sii9234_mode_valid, }; From 22275d9f584c26a018167830a29a23adfebe2b75 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 7 Jul 2026 17:11:25 +0200 Subject: [PATCH 151/577] drm/bridge: sil-sii8620: Add atomic bridge state handlers The sil-sii8620 bridge doesn't implement any enable or disable callbacks, but is still missing the atomic state handlers. In order to remove the legacy bridge callback support from the DRM bridge core, add them. Generated by the following Coccinelle script: @ is_bridge @ identifier funcs; @@ struct drm_bridge_funcs funcs = { ..., }; @ has_create_state depends on is_bridge @ identifier funcs, f; @@ struct drm_bridge_funcs funcs = { ..., .atomic_create_state = f, ..., }; @ update_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, ..., }; @ update_pre_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .pre_enable = f, + .atomic_pre_enable = f, ..., }; @ update_pre_enable_impl depends on update_pre_enable_struct @ identifier update_pre_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .enable = f, + .atomic_enable = f, ..., }; @ update_enable_impl depends on update_enable_struct @ identifier update_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .disable = f, + .atomic_disable = f, ..., }; @ update_disable_impl depends on update_disable_struct @ identifier update_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_post_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .post_disable = f, + .atomic_post_disable = f, ..., }; @ update_post_disable_impl depends on update_post_disable_struct @ identifier update_post_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } Reviewed-by: Luca Ceresoli Link: https://patch.msgid.link/20260707-drm-all-atomic-bridges-v2-18-21d03cbca446@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/bridge/sil-sii8620.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/gpu/drm/bridge/sil-sii8620.c b/drivers/gpu/drm/bridge/sil-sii8620.c index 982306eb4f0a..22c81b38b41e 100644 --- a/drivers/gpu/drm/bridge/sil-sii8620.c +++ b/drivers/gpu/drm/bridge/sil-sii8620.c @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -2280,6 +2281,9 @@ static bool sii8620_mode_fixup(struct drm_bridge *bridge, } static const struct drm_bridge_funcs sii8620_bridge_funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .attach = sii8620_attach, .detach = sii8620_detach, .mode_fixup = sii8620_mode_fixup, From 0a01de65a741da622e06dd3c31289ce1053b1a77 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 7 Jul 2026 17:11:26 +0200 Subject: [PATCH 152/577] drm/bridge: simple-bridge: Switch to atomic bridge callbacks The simple-bridge bridge uses the legacy non-atomic enable and disable bridge callbacks. In order to remove the legacy bridge callback support from the DRM bridge core, switch to their atomic counterparts and add the bridge atomic state handlers. Generated by the following Coccinelle script: @ is_bridge @ identifier funcs; @@ struct drm_bridge_funcs funcs = { ..., }; @ has_create_state depends on is_bridge @ identifier funcs, f; @@ struct drm_bridge_funcs funcs = { ..., .atomic_create_state = f, ..., }; @ update_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, ..., }; @ update_pre_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .pre_enable = f, + .atomic_pre_enable = f, ..., }; @ update_pre_enable_impl depends on update_pre_enable_struct @ identifier update_pre_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .enable = f, + .atomic_enable = f, ..., }; @ update_enable_impl depends on update_enable_struct @ identifier update_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .disable = f, + .atomic_disable = f, ..., }; @ update_disable_impl depends on update_disable_struct @ identifier update_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_post_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .post_disable = f, + .atomic_post_disable = f, ..., }; @ update_post_disable_impl depends on update_post_disable_struct @ identifier update_post_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } Reviewed-by: Luca Ceresoli Link: https://patch.msgid.link/20260707-drm-all-atomic-bridges-v2-19-21d03cbca446@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/bridge/simple-bridge.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/bridge/simple-bridge.c b/drivers/gpu/drm/bridge/simple-bridge.c index cc13c98f9be6..3fba7002322e 100644 --- a/drivers/gpu/drm/bridge/simple-bridge.c +++ b/drivers/gpu/drm/bridge/simple-bridge.c @@ -132,7 +132,8 @@ static int simple_bridge_attach(struct drm_bridge *bridge, return 0; } -static void simple_bridge_enable(struct drm_bridge *bridge) +static void simple_bridge_enable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct simple_bridge *sbridge = drm_bridge_to_simple_bridge(bridge); int ret; @@ -146,7 +147,8 @@ static void simple_bridge_enable(struct drm_bridge *bridge) gpiod_set_value_cansleep(sbridge->enable, 1); } -static void simple_bridge_disable(struct drm_bridge *bridge) +static void simple_bridge_disable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct simple_bridge *sbridge = drm_bridge_to_simple_bridge(bridge); @@ -157,9 +159,12 @@ static void simple_bridge_disable(struct drm_bridge *bridge) } static const struct drm_bridge_funcs simple_bridge_bridge_funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .attach = simple_bridge_attach, - .enable = simple_bridge_enable, - .disable = simple_bridge_disable, + .atomic_enable = simple_bridge_enable, + .atomic_disable = simple_bridge_disable, }; static int simple_bridge_probe(struct platform_device *pdev) From f27a178ddc9963c5c6c0dd239940c358eabdf08d Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 7 Jul 2026 17:11:27 +0200 Subject: [PATCH 153/577] drm/bridge: tc358764: Switch to atomic bridge callbacks The tc358764 bridge uses the legacy non-atomic pre_enable and post_disable bridge callbacks. In order to remove the legacy bridge callback support from the DRM bridge core, switch to their atomic counterparts and add the bridge atomic state handlers. Generated by the following Coccinelle script: @ is_bridge @ identifier funcs; @@ struct drm_bridge_funcs funcs = { ..., }; @ has_create_state depends on is_bridge @ identifier funcs, f; @@ struct drm_bridge_funcs funcs = { ..., .atomic_create_state = f, ..., }; @ update_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, ..., }; @ update_pre_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .pre_enable = f, + .atomic_pre_enable = f, ..., }; @ update_pre_enable_impl depends on update_pre_enable_struct @ identifier update_pre_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .enable = f, + .atomic_enable = f, ..., }; @ update_enable_impl depends on update_enable_struct @ identifier update_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .disable = f, + .atomic_disable = f, ..., }; @ update_disable_impl depends on update_disable_struct @ identifier update_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_post_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .post_disable = f, + .atomic_post_disable = f, ..., }; @ update_post_disable_impl depends on update_post_disable_struct @ identifier update_post_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } Reviewed-by: Luca Ceresoli Link: https://patch.msgid.link/20260707-drm-all-atomic-bridges-v2-20-21d03cbca446@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/bridge/tc358764.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/bridge/tc358764.c b/drivers/gpu/drm/bridge/tc358764.c index 12b43245bb8f..f55a56cbd88f 100644 --- a/drivers/gpu/drm/bridge/tc358764.c +++ b/drivers/gpu/drm/bridge/tc358764.c @@ -266,7 +266,8 @@ static void tc358764_reset(struct tc358764 *ctx) usleep_range(1000, 2000); } -static void tc358764_post_disable(struct drm_bridge *bridge) +static void tc358764_post_disable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct tc358764 *ctx = bridge_to_tc358764(bridge); int ret; @@ -278,7 +279,8 @@ static void tc358764_post_disable(struct drm_bridge *bridge) dev_err(ctx->dev, "error disabling regulators (%d)\n", ret); } -static void tc358764_pre_enable(struct drm_bridge *bridge) +static void tc358764_pre_enable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct tc358764 *ctx = bridge_to_tc358764(bridge); int ret; @@ -303,8 +305,11 @@ static int tc358764_attach(struct drm_bridge *bridge, } static const struct drm_bridge_funcs tc358764_bridge_funcs = { - .post_disable = tc358764_post_disable, - .pre_enable = tc358764_pre_enable, + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_post_disable = tc358764_post_disable, + .atomic_pre_enable = tc358764_pre_enable, .attach = tc358764_attach, }; From ad0e737d5ce4b0218fcacd3b19a4794ed4324673 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 7 Jul 2026 17:11:28 +0200 Subject: [PATCH 154/577] drm/bridge: tda998x: Switch to atomic bridge callbacks The tda998x bridge uses the legacy non-atomic enable and disable bridge callbacks. In order to remove the legacy bridge callback support from the DRM bridge core, switch to their atomic counterparts and add the bridge atomic state handlers. Generated by the following Coccinelle script: @ is_bridge @ identifier funcs; @@ struct drm_bridge_funcs funcs = { ..., }; @ has_create_state depends on is_bridge @ identifier funcs, f; @@ struct drm_bridge_funcs funcs = { ..., .atomic_create_state = f, ..., }; @ update_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, ..., }; @ update_pre_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .pre_enable = f, + .atomic_pre_enable = f, ..., }; @ update_pre_enable_impl depends on update_pre_enable_struct @ identifier update_pre_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .enable = f, + .atomic_enable = f, ..., }; @ update_enable_impl depends on update_enable_struct @ identifier update_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .disable = f, + .atomic_disable = f, ..., }; @ update_disable_impl depends on update_disable_struct @ identifier update_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_post_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .post_disable = f, + .atomic_post_disable = f, ..., }; @ update_post_disable_impl depends on update_post_disable_struct @ identifier update_post_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } Reviewed-by: Luca Ceresoli Link: https://patch.msgid.link/20260707-drm-all-atomic-bridges-v2-21-21d03cbca446@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/bridge/tda998x_drv.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/bridge/tda998x_drv.c b/drivers/gpu/drm/bridge/tda998x_drv.c index 6c427bc75896..16c745591175 100644 --- a/drivers/gpu/drm/bridge/tda998x_drv.c +++ b/drivers/gpu/drm/bridge/tda998x_drv.c @@ -1410,7 +1410,8 @@ static enum drm_mode_status tda998x_bridge_mode_valid(struct drm_bridge *bridge, return MODE_OK; } -static void tda998x_bridge_enable(struct drm_bridge *bridge) +static void tda998x_bridge_enable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct tda998x_priv *priv = bridge_to_tda998x_priv(bridge); @@ -1428,7 +1429,8 @@ static void tda998x_bridge_enable(struct drm_bridge *bridge) } } -static void tda998x_bridge_disable(struct drm_bridge *bridge) +static void tda998x_bridge_disable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct tda998x_priv *priv = bridge_to_tda998x_priv(bridge); @@ -1745,12 +1747,15 @@ static void tda998x_bridge_hpd_disable(struct drm_bridge *bridge) } static const struct drm_bridge_funcs tda998x_bridge_funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .attach = tda998x_bridge_attach, .detach = tda998x_bridge_detach, .mode_valid = tda998x_bridge_mode_valid, - .disable = tda998x_bridge_disable, + .atomic_disable = tda998x_bridge_disable, .mode_set = tda998x_bridge_mode_set, - .enable = tda998x_bridge_enable, + .atomic_enable = tda998x_bridge_enable, .edid_read = tda998x_bridge_edid_read, .detect = tda998x_bridge_detect, .hpd_enable = tda998x_bridge_hpd_enable, From d2281093ace59d5a9293f9a0cef979c596e2d5ec Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 7 Jul 2026 17:11:29 +0200 Subject: [PATCH 155/577] drm/bridge: ti-tfp410: Switch to atomic bridge callbacks The ti-tfp410 bridge uses the legacy non-atomic enable and disable bridge callbacks. In order to remove the legacy bridge callback support from the DRM bridge core, switch to their atomic counterparts and add the bridge atomic state handlers. Generated by the following Coccinelle script: @ is_bridge @ identifier funcs; @@ struct drm_bridge_funcs funcs = { ..., }; @ has_create_state depends on is_bridge @ identifier funcs, f; @@ struct drm_bridge_funcs funcs = { ..., .atomic_create_state = f, ..., }; @ update_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, ..., }; @ update_pre_enable_struct depends on is_bridge @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .pre_enable = f, + .atomic_pre_enable = f, ..., }; @ update_pre_enable_impl depends on update_pre_enable_struct @ identifier update_pre_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_enable_struct depends on is_bridge @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .enable = f, + .atomic_enable = f, ..., }; @ update_enable_impl depends on update_enable_struct @ identifier update_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_disable_struct depends on is_bridge @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .disable = f, + .atomic_disable = f, ..., }; @ update_disable_impl depends on update_disable_struct @ identifier update_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_post_disable_struct depends on is_bridge @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .post_disable = f, + .atomic_post_disable = f, ..., }; @ update_post_disable_impl depends on update_post_disable_struct @ identifier update_post_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } Reviewed-by: Laurent Pinchart Reviewed-by: Luca Ceresoli Link: https://patch.msgid.link/20260707-drm-all-atomic-bridges-v2-22-21d03cbca446@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/bridge/ti-tfp410.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/bridge/ti-tfp410.c b/drivers/gpu/drm/bridge/ti-tfp410.c index 6f06f7b4e992..f0a14f52aaac 100644 --- a/drivers/gpu/drm/bridge/ti-tfp410.c +++ b/drivers/gpu/drm/bridge/ti-tfp410.c @@ -174,14 +174,16 @@ static void tfp410_detach(struct drm_bridge *bridge) } } -static void tfp410_enable(struct drm_bridge *bridge) +static void tfp410_enable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct tfp410 *dvi = drm_bridge_to_tfp410(bridge); gpiod_set_value_cansleep(dvi->powerdown, 0); } -static void tfp410_disable(struct drm_bridge *bridge) +static void tfp410_disable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct tfp410 *dvi = drm_bridge_to_tfp410(bridge); @@ -242,8 +244,8 @@ static int tfp410_atomic_check(struct drm_bridge *bridge, static const struct drm_bridge_funcs tfp410_bridge_funcs = { .attach = tfp410_attach, .detach = tfp410_detach, - .enable = tfp410_enable, - .disable = tfp410_disable, + .atomic_enable = tfp410_enable, + .atomic_disable = tfp410_disable, .mode_valid = tfp410_mode_valid, .atomic_create_state = drm_atomic_helper_bridge_create_state, .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, From cf083a5e46143566f8a923da29010870aa675ca1 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 7 Jul 2026 17:11:30 +0200 Subject: [PATCH 156/577] drm/bridge: ti-tpd12s015: Add atomic bridge state handlers The ti-tpd12s015 bridge doesn't implement any enable or disable callbacks, but is still missing the atomic state handlers. In order to remove the legacy bridge callback support from the DRM bridge core, add them. Generated by the following Coccinelle script: @ is_bridge @ identifier funcs; @@ struct drm_bridge_funcs funcs = { ..., }; @ has_create_state depends on is_bridge @ identifier funcs, f; @@ struct drm_bridge_funcs funcs = { ..., .atomic_create_state = f, ..., }; @ update_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, ..., }; @ update_pre_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .pre_enable = f, + .atomic_pre_enable = f, ..., }; @ update_pre_enable_impl depends on update_pre_enable_struct @ identifier update_pre_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .enable = f, + .atomic_enable = f, ..., }; @ update_enable_impl depends on update_enable_struct @ identifier update_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .disable = f, + .atomic_disable = f, ..., }; @ update_disable_impl depends on update_disable_struct @ identifier update_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_post_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .post_disable = f, + .atomic_post_disable = f, ..., }; @ update_post_disable_impl depends on update_post_disable_struct @ identifier update_post_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } Reviewed-by: Luca Ceresoli Link: https://patch.msgid.link/20260707-drm-all-atomic-bridges-v2-23-21d03cbca446@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/bridge/ti-tpd12s015.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/gpu/drm/bridge/ti-tpd12s015.c b/drivers/gpu/drm/bridge/ti-tpd12s015.c index 136e47ad1a10..e7b172ab7f55 100644 --- a/drivers/gpu/drm/bridge/ti-tpd12s015.c +++ b/drivers/gpu/drm/bridge/ti-tpd12s015.c @@ -19,6 +19,7 @@ #include #include +#include #include struct tpd12s015_device { @@ -96,6 +97,9 @@ static void tpd12s015_hpd_disable(struct drm_bridge *bridge) } static const struct drm_bridge_funcs tpd12s015_bridge_funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .attach = tpd12s015_attach, .detach = tpd12s015_detach, .detect = tpd12s015_bridge_detect, From 1e503f172cf98b5628c5a4130a23afca86ed3f9e Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 7 Jul 2026 17:11:31 +0200 Subject: [PATCH 157/577] drm/bridge: thc63lvd1024: Switch to atomic bridge callbacks The thc63lvd1024 bridge uses the legacy non-atomic enable and disable bridge callbacks. In order to remove the legacy bridge callback support from the DRM bridge core, switch to their atomic counterparts and add the bridge atomic state handlers. Generated by the following Coccinelle script: @ is_bridge @ identifier funcs; @@ struct drm_bridge_funcs funcs = { ..., }; @ has_create_state depends on is_bridge @ identifier funcs, f; @@ struct drm_bridge_funcs funcs = { ..., .atomic_create_state = f, ..., }; @ update_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, ..., }; @ update_pre_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .pre_enable = f, + .atomic_pre_enable = f, ..., }; @ update_pre_enable_impl depends on update_pre_enable_struct @ identifier update_pre_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .enable = f, + .atomic_enable = f, ..., }; @ update_enable_impl depends on update_enable_struct @ identifier update_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .disable = f, + .atomic_disable = f, ..., }; @ update_disable_impl depends on update_disable_struct @ identifier update_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_post_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .post_disable = f, + .atomic_post_disable = f, ..., }; @ update_post_disable_impl depends on update_post_disable_struct @ identifier update_post_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } Reviewed-by: Luca Ceresoli Link: https://patch.msgid.link/20260707-drm-all-atomic-bridges-v2-24-21d03cbca446@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/bridge/thc63lvd1024.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/bridge/thc63lvd1024.c b/drivers/gpu/drm/bridge/thc63lvd1024.c index c804222846c3..679b424ba0c3 100644 --- a/drivers/gpu/drm/bridge/thc63lvd1024.c +++ b/drivers/gpu/drm/bridge/thc63lvd1024.c @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -81,7 +82,8 @@ static enum drm_mode_status thc63_mode_valid(struct drm_bridge *bridge, return MODE_OK; } -static void thc63_enable(struct drm_bridge *bridge) +static void thc63_enable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct thc63_dev *thc63 = to_thc63(bridge); int ret; @@ -97,7 +99,8 @@ static void thc63_enable(struct drm_bridge *bridge) gpiod_set_value(thc63->oe, 1); } -static void thc63_disable(struct drm_bridge *bridge) +static void thc63_disable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct thc63_dev *thc63 = to_thc63(bridge); int ret; @@ -112,10 +115,13 @@ static void thc63_disable(struct drm_bridge *bridge) } static const struct drm_bridge_funcs thc63_bridge_func = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .attach = thc63_attach, .mode_valid = thc63_mode_valid, - .enable = thc63_enable, - .disable = thc63_disable, + .atomic_enable = thc63_enable, + .atomic_disable = thc63_disable, }; static int thc63_parse_dt(struct thc63_dev *thc63) From cc83959891cbb437dd8e1309c7d0bca287634bfa Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 7 Jul 2026 17:11:32 +0200 Subject: [PATCH 158/577] drm/bridge: waveshare-dsi: Switch to atomic bridge callbacks The waveshare-dsi bridge uses the legacy non-atomic enable and disable bridge callbacks. In order to remove the legacy bridge callback support from the DRM bridge core, switch to their atomic counterparts and add the bridge atomic state handlers. Generated by the following Coccinelle script: @ is_bridge @ identifier funcs; @@ struct drm_bridge_funcs funcs = { ..., }; @ has_create_state depends on is_bridge @ identifier funcs, f; @@ struct drm_bridge_funcs funcs = { ..., .atomic_create_state = f, ..., }; @ update_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, ..., }; @ update_pre_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .pre_enable = f, + .atomic_pre_enable = f, ..., }; @ update_pre_enable_impl depends on update_pre_enable_struct @ identifier update_pre_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .enable = f, + .atomic_enable = f, ..., }; @ update_enable_impl depends on update_enable_struct @ identifier update_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .disable = f, + .atomic_disable = f, ..., }; @ update_disable_impl depends on update_disable_struct @ identifier update_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_post_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .post_disable = f, + .atomic_post_disable = f, ..., }; @ update_post_disable_impl depends on update_post_disable_struct @ identifier update_post_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } Reviewed-by: Luca Ceresoli Link: https://patch.msgid.link/20260707-drm-all-atomic-bridges-v2-25-21d03cbca446@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/bridge/waveshare-dsi.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/bridge/waveshare-dsi.c b/drivers/gpu/drm/bridge/waveshare-dsi.c index ded57f298d64..9c9825c9b7c5 100644 --- a/drivers/gpu/drm/bridge/waveshare-dsi.c +++ b/drivers/gpu/drm/bridge/waveshare-dsi.c @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -92,7 +93,8 @@ static int ws_bridge_bridge_attach(struct drm_bridge *bridge, &ws->bridge, flags); } -static void ws_bridge_bridge_enable(struct drm_bridge *bridge) +static void ws_bridge_bridge_enable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct ws_bridge *ws = bridge_to_ws_bridge(bridge); @@ -100,7 +102,8 @@ static void ws_bridge_bridge_enable(struct drm_bridge *bridge) backlight_enable(ws->backlight); } -static void ws_bridge_bridge_disable(struct drm_bridge *bridge) +static void ws_bridge_bridge_disable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) { struct ws_bridge *ws = bridge_to_ws_bridge(bridge); @@ -109,8 +112,11 @@ static void ws_bridge_bridge_disable(struct drm_bridge *bridge) } static const struct drm_bridge_funcs ws_bridge_bridge_funcs = { - .enable = ws_bridge_bridge_enable, - .disable = ws_bridge_bridge_disable, + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_enable = ws_bridge_bridge_enable, + .atomic_disable = ws_bridge_bridge_disable, .attach = ws_bridge_bridge_attach, }; From d1fd3717bb135f972099df881d2921b0d452e5b4 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 7 Jul 2026 17:11:33 +0200 Subject: [PATCH 159/577] drm/exynos: mic: Switch to atomic bridge callbacks The mic bridge uses the legacy non-atomic pre_enable and post_disable bridge callbacks. In order to remove the legacy bridge callback support from the DRM bridge core, switch to their atomic counterparts and add the bridge atomic state handlers. Generated by the following Coccinelle script: @ is_bridge @ identifier funcs; @@ struct drm_bridge_funcs funcs = { ..., }; @ has_create_state depends on is_bridge @ identifier funcs, f; @@ struct drm_bridge_funcs funcs = { ..., .atomic_create_state = f, ..., }; @ update_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, ..., }; @ update_pre_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .pre_enable = f, + .atomic_pre_enable = f, ..., }; @ update_pre_enable_impl depends on update_pre_enable_struct @ identifier update_pre_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_enable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .enable = f, + .atomic_enable = f, ..., }; @ update_enable_impl depends on update_enable_struct @ identifier update_enable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .disable = f, + .atomic_disable = f, ..., }; @ update_disable_impl depends on update_disable_struct @ identifier update_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } @ update_post_disable_struct depends on (is_bridge && !has_create_state) @ identifier is_bridge.funcs; identifier f; @@ struct drm_bridge_funcs funcs = { ..., - .post_disable = f, + .atomic_post_disable = f, ..., }; @ update_post_disable_impl depends on update_post_disable_struct @ identifier update_post_disable_struct.f; identifier b; @@ -void f(struct drm_bridge *b) +void f(struct drm_bridge *b, struct drm_atomic_commit *commit) { ... } Reviewed-by: Luca Ceresoli Link: https://patch.msgid.link/20260707-drm-all-atomic-bridges-v2-26-21d03cbca446@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/exynos/exynos_drm_mic.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/exynos/exynos_drm_mic.c b/drivers/gpu/drm/exynos/exynos_drm_mic.c index e68c954ec3e6..3069f958137f 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_mic.c +++ b/drivers/gpu/drm/exynos/exynos_drm_mic.c @@ -21,6 +21,7 @@ #include