From 217da5aaf95e7c5a73b314095b709c40525c6b46 Mon Sep 17 00:00:00 2001 From: Nicolas Frattaroli Date: Fri, 4 Sep 2026 19:38:09 +0200 Subject: [PATCH 01/46] drm/panthor: Display priorities of panthor groups over debugfs Analogous to what was added in Commit d41c79838c47 ("drm/panfrost: Display list of device JM contexts over debugfs") for panfrost, add similar debugfs information for panthor. The group priority does not change over the lifetime of the group, so no effort to synchronise with the scheduler lock is being made. Signed-off-by: Nicolas Frattaroli Reviewed-by: Liviu Dudau Reviewed-by: Boris Brezillon Link: https://patch.msgid.link/20260904-panthor-sched-group-prio-v3-1-678afcca08b8@collabora.com Signed-off-by: Boris Brezillon --- drivers/gpu/drm/panthor/panthor_drv.c | 1 + drivers/gpu/drm/panthor/panthor_sched.c | 73 +++++++++++++++++++++++++ drivers/gpu/drm/panthor/panthor_sched.h | 5 ++ 3 files changed, 79 insertions(+) diff --git a/drivers/gpu/drm/panthor/panthor_drv.c b/drivers/gpu/drm/panthor/panthor_drv.c index 46a3080b0b20..8cdba0c1a14b 100644 --- a/drivers/gpu/drm/panthor/panthor_drv.c +++ b/drivers/gpu/drm/panthor/panthor_drv.c @@ -1769,6 +1769,7 @@ static void panthor_debugfs_init(struct drm_minor *minor) { panthor_mmu_debugfs_init(minor); panthor_gem_debugfs_init(minor); + panthor_sched_debugfs_init(minor); } #endif diff --git a/drivers/gpu/drm/panthor/panthor_sched.c b/drivers/gpu/drm/panthor/panthor_sched.c index 60b2417deb81..ac6aee554820 100644 --- a/drivers/gpu/drm/panthor/panthor_sched.c +++ b/drivers/gpu/drm/panthor/panthor_sched.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 or MIT /* Copyright 2023 Collabora ltd. */ +#include #include #include #include @@ -4175,3 +4176,75 @@ int panthor_sched_init(struct panthor_device *ptdev) ptdev->scheduler = sched; return 0; } + +#ifdef CONFIG_DEBUG_FS + +static const char * +panthor_sched_prio_str(enum panthor_csg_priority prio) +{ + switch (prio) { + case PANTHOR_CSG_PRIORITY_LOW: + return "LOW"; + case PANTHOR_CSG_PRIORITY_MEDIUM: + return "MEDIUM"; + case PANTHOR_CSG_PRIORITY_HIGH: + return "HIGH"; + case PANTHOR_CSG_PRIORITY_RT: + return "REAL-TIME"; + default: + return "UNKNOWN"; + } +} + +static int show_file_groups(struct drm_file *file, struct seq_file *m) +{ + struct panthor_file *pfile = file->driver_priv; + struct panthor_group *group; + unsigned long i; + + if (IS_ERR_OR_NULL(pfile->groups)) + return -ENOENT; + + xa_lock(&pfile->groups->xa); + xa_for_each_marked(&pfile->groups->xa, i, group, GROUP_REGISTERED) { + seq_printf(m, "client_id %8llu pid %8d command %s: group %2lu priority %s queue count %u\n", + file->client_id, group->task_info.pid, group->task_info.comm, i, + panthor_sched_prio_str(group->priority), group->queue_count); + } + xa_unlock(&pfile->groups->xa); + + return 0; +} + +static int show_each_file(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *)m->private; + struct drm_device *ddev = node->minor->dev; + int (*show)(struct drm_file *, struct seq_file *) = + node->info_ent->data; + struct drm_file *file; + int ret; + + scoped_cond_guard(mutex_intr, return -EINTR, &ddev->filelist_mutex) { + list_for_each_entry(file, &ddev->filelist, lhead) { + ret = show(file, m); + if (ret < 0) + return ret; + } + } + + return 0; +} + +static struct drm_info_list panthor_sched_debugfs_list[] = { + { "sched_groups", show_each_file, 0, show_file_groups }, +}; + +void panthor_sched_debugfs_init(struct drm_minor *minor) +{ + drm_debugfs_create_files(panthor_sched_debugfs_list, + ARRAY_SIZE(panthor_sched_debugfs_list), + minor->debugfs_root, minor); +} + +#endif /* CONFIG_DEBUG_FS */ diff --git a/drivers/gpu/drm/panthor/panthor_sched.h b/drivers/gpu/drm/panthor/panthor_sched.h index 9a8692de8ade..d87cbdba4d40 100644 --- a/drivers/gpu/drm/panthor/panthor_sched.h +++ b/drivers/gpu/drm/panthor/panthor_sched.h @@ -10,6 +10,7 @@ struct drm_file; struct drm_gem_object; struct drm_sched_job; struct drm_memory_stats; +struct drm_minor; struct drm_panthor_group_create; struct drm_panthor_queue_create; struct drm_panthor_group_get_state; @@ -55,4 +56,8 @@ void panthor_sched_report_fw_events(struct panthor_device *ptdev, u32 events); void panthor_fdinfo_gather_group_samples(struct panthor_file *pfile); +#ifdef CONFIG_DEBUG_FS +void panthor_sched_debugfs_init(struct drm_minor *minor); +#endif + #endif From 5eee433b5c97821979e7de58476dc245ee168d89 Mon Sep 17 00:00:00 2001 From: Lizhi Hou Date: Fri, 11 Sep 2026 09:39:07 -0700 Subject: [PATCH 02/46] accel/amdxdna: Fully initialize map structure before registering notifier The map structure is currently only partially initialized when the notifier is registered. Although holding mmap_write_lock() prevents the notifier callback from running before the remaining initialization is complete, the ordering can be confusing during code review. Fully initialize the map structure before registering the notifier so that the structure is completely constructed before it is published. Reviewed-by: Max Zhen Signed-off-by: Lizhi Hou Link: https://patch.msgid.link/20260911163907.1193852-1-lizhi.hou@amd.com --- drivers/accel/amdxdna/amdxdna_gem.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/accel/amdxdna/amdxdna_gem.c b/drivers/accel/amdxdna/amdxdna_gem.c index 5a1a2ee261b7..e861db6f9369 100644 --- a/drivers/accel/amdxdna/amdxdna_gem.c +++ b/drivers/accel/amdxdna/amdxdna_gem.c @@ -415,6 +415,15 @@ static int amdxdna_hmm_register(struct amdxdna_gem_obj *abo, goto free_map; } + mapp->range.notifier = &mapp->notifier; + mapp->range.start = vma->vm_start; + mapp->range.end = vma->vm_end; + mapp->range.default_flags = HMM_PFN_REQ_FAULT; + mapp->abo = abo; + kref_init(&mapp->refcnt); + + INIT_WORK(&mapp->hmm_unreg_work, amdxdna_hmm_unreg_work); + ret = mmu_interval_notifier_insert_locked(&mapp->notifier, current->mm, addr, @@ -425,15 +434,6 @@ static int amdxdna_hmm_register(struct amdxdna_gem_obj *abo, goto free_pfns; } - mapp->range.notifier = &mapp->notifier; - mapp->range.start = vma->vm_start; - mapp->range.end = vma->vm_end; - mapp->range.default_flags = HMM_PFN_REQ_FAULT; - mapp->abo = abo; - kref_init(&mapp->refcnt); - - INIT_WORK(&mapp->hmm_unreg_work, amdxdna_hmm_unreg_work); - down_write(&xdna->notifier_lock); if (list_empty(&abo->mem.umap_list)) abo->mem.uva = addr; From ac18f34f5927e420ae529be72aec86201a262d04 Mon Sep 17 00:00:00 2001 From: Lizhi Hou Date: Thu, 17 Sep 2026 08:48:24 -0700 Subject: [PATCH 03/46] accel/amdxdna: Fix race between unmap and free BO A userspace process can concurrently trigger an unmap, which queues hmm_unreg_work, and free BO. If amdxdna_hmm_unreg_work() executes list_del(&mapp->node) and is preempted before calling amdxdna_umap_put(), amdxdna_hmm_unregister_all() can fail to find the mapping in the list and return without calling cancel_work_sync(). This allows object destruction to proceed while the worker still holds a pointer to the BO in the umap structure. Moving the work item from struct amdxdna_umap to struct amdxdna_gem_obj. The worker no longer holds a pointer to an individual mapping, so there is no longer a window where the umap has been unlinked from umap_list but work is still pending against it. The work item now lives in the BO itself, so use a single cancel_work_sync() in the free path to drain the work. Fixes: 445d20910429 ("accel/amdxdna: Fix unexpected wait when flushing notifier_wq") Reviewed-by: Max Zhen Signed-off-by: Lizhi Hou Link: https://patch.msgid.link/20260917154824.1872220-1-lizhi.hou@amd.com --- drivers/accel/amdxdna/amdxdna_gem.c | 95 +++++++++++++---------------- drivers/accel/amdxdna/amdxdna_gem.h | 3 +- 2 files changed, 42 insertions(+), 56 deletions(-) diff --git a/drivers/accel/amdxdna/amdxdna_gem.c b/drivers/accel/amdxdna/amdxdna_gem.c index e861db6f9369..398d0a58b53a 100644 --- a/drivers/accel/amdxdna/amdxdna_gem.c +++ b/drivers/accel/amdxdna/amdxdna_gem.c @@ -164,30 +164,6 @@ void amdxdna_gem_heap_free(struct amdxdna_client *client, struct amdxdna_gem_obj mutex_unlock(&client->mm_lock); } -static struct amdxdna_gem_obj * -amdxdna_gem_create_obj(struct drm_device *dev, size_t size) -{ - struct amdxdna_gem_obj *abo; - - abo = kzalloc_obj(*abo); - if (!abo) - return ERR_PTR(-ENOMEM); - - abo->pinned = false; - abo->assigned_hwctx = AMDXDNA_INVALID_CTX_HANDLE; - mutex_init(&abo->lock); - - abo->mem.dma_addr = AMDXDNA_INVALID_ADDR; - abo->mem.uva = AMDXDNA_INVALID_ADDR; - abo->mem.size = size; - abo->open_ref = 0; - abo->internal = false; - INIT_LIST_HEAD(&abo->mem.umap_list); - xa_init_flags(&abo->heap_xa, XA_FLAGS_ALLOC); - - return abo; -} - static void amdxdna_gem_destroy_obj(struct amdxdna_gem_obj *abo) { @@ -278,10 +254,8 @@ static bool amdxdna_hmm_invalidate(struct mmu_interval_notifier *mni, if (range->event == MMU_NOTIFY_UNMAP) { down_write(&xdna->notifier_lock); - if (!mapp->unmapped) { - queue_work(xdna->notifier_wq, &mapp->hmm_unreg_work); - mapp->unmapped = true; - } + mapp->unmapped = true; + queue_work(xdna->notifier_wq, &abo->hmm_unreg_work); up_write(&xdna->notifier_lock); } @@ -311,13 +285,13 @@ static void amdxdna_hmm_unregister(struct amdxdna_gem_obj *abo, if (!compare_range(mapp, vma->vm_mm, vma->vm_start, vma->vm_end)) continue; - queue_work(xdna->notifier_wq, &mapp->hmm_unreg_work); mapp->unmapped = true; + queue_work(xdna->notifier_wq, &abo->hmm_unreg_work); } up_write(&xdna->notifier_lock); } -static void amdxdna_hmm_unregister_all(struct amdxdna_gem_obj *abo) +static void amdxdna_hmm_unreg_umaps(struct amdxdna_gem_obj *abo, bool force) { struct amdxdna_dev *xdna = to_xdna_dev(to_gobj(abo)->dev); struct amdxdna_umap *mapp, *tmp; @@ -325,16 +299,18 @@ static void amdxdna_hmm_unregister_all(struct amdxdna_gem_obj *abo) down_write(&xdna->notifier_lock); list_for_each_entry_safe(mapp, tmp, &abo->mem.umap_list, node) { + if (!force && !mapp->unmapped) + continue; + mapp->unmapped = true; - mapp->cleanup = true; list_move(&mapp->node, &dead); } + if (list_empty(&abo->mem.umap_list)) + abo->mem.uva = AMDXDNA_INVALID_ADDR; up_write(&xdna->notifier_lock); - list_for_each_entry_safe(mapp, tmp, &dead, node) { - cancel_work_sync(&mapp->hmm_unreg_work); + list_for_each_entry_safe(mapp, tmp, &dead, node) amdxdna_umap_put(mapp); - } } static void amdxdna_umap_release(struct kref *ref) @@ -353,24 +329,10 @@ void amdxdna_umap_put(struct amdxdna_umap *mapp) static void amdxdna_hmm_unreg_work(struct work_struct *work) { - struct amdxdna_umap *mapp = container_of(work, struct amdxdna_umap, - hmm_unreg_work); - struct amdxdna_gem_obj *abo = mapp->abo; - struct amdxdna_dev *xdna; + struct amdxdna_gem_obj *abo = container_of(work, struct amdxdna_gem_obj, + hmm_unreg_work); - xdna = to_xdna_dev(to_gobj(mapp->abo)->dev); - down_write(&xdna->notifier_lock); - if (mapp->cleanup) { - up_write(&xdna->notifier_lock); - return; - } - - list_del(&mapp->node); - if (list_empty(&abo->mem.umap_list)) - abo->mem.uva = AMDXDNA_INVALID_ADDR; - up_write(&xdna->notifier_lock); - - amdxdna_umap_put(mapp); + amdxdna_hmm_unreg_umaps(abo, false); } static int amdxdna_hmm_register(struct amdxdna_gem_obj *abo, @@ -422,8 +384,6 @@ static int amdxdna_hmm_register(struct amdxdna_gem_obj *abo, mapp->abo = abo; kref_init(&mapp->refcnt); - INIT_WORK(&mapp->hmm_unreg_work, amdxdna_hmm_unreg_work); - ret = mmu_interval_notifier_insert_locked(&mapp->notifier, current->mm, addr, @@ -449,6 +409,31 @@ free_map: return ret; } +static struct amdxdna_gem_obj * +amdxdna_gem_create_obj(struct drm_device *dev, size_t size) +{ + struct amdxdna_gem_obj *abo; + + abo = kzalloc_obj(*abo); + if (!abo) + return ERR_PTR(-ENOMEM); + + abo->pinned = false; + abo->assigned_hwctx = AMDXDNA_INVALID_CTX_HANDLE; + mutex_init(&abo->lock); + + abo->mem.dma_addr = AMDXDNA_INVALID_ADDR; + abo->mem.uva = AMDXDNA_INVALID_ADDR; + abo->mem.size = size; + abo->open_ref = 0; + abo->internal = false; + INIT_LIST_HEAD(&abo->mem.umap_list); + xa_init_flags(&abo->heap_xa, XA_FLAGS_ALLOC); + INIT_WORK(&abo->hmm_unreg_work, amdxdna_hmm_unreg_work); + + return abo; +} + static void amdxdna_gem_dev_obj_free(struct drm_gem_object *gobj) { struct amdxdna_dev *xdna = to_xdna_dev(gobj->dev); @@ -756,7 +741,9 @@ static void amdxdna_gem_obj_free(struct drm_gem_object *gobj) struct amdxdna_dev *xdna = to_xdna_dev(gobj->dev); struct amdxdna_gem_obj *abo = to_xdna_obj(gobj); - amdxdna_hmm_unregister_all(abo); + /* No notifier survives this, so no new work can be queued. */ + amdxdna_hmm_unreg_umaps(abo, true); + cancel_work_sync(&abo->hmm_unreg_work); if (abo->pinned) amdxdna_gem_unpin(abo); diff --git a/drivers/accel/amdxdna/amdxdna_gem.h b/drivers/accel/amdxdna/amdxdna_gem.h index 5dfefdcf1356..9b4aa21a37c9 100644 --- a/drivers/accel/amdxdna/amdxdna_gem.h +++ b/drivers/accel/amdxdna/amdxdna_gem.h @@ -14,13 +14,11 @@ struct amdxdna_umap { struct mmu_interval_notifier notifier; struct hmm_range range; - struct work_struct hmm_unreg_work; struct amdxdna_gem_obj *abo; struct list_head node; struct kref refcnt; bool invalid; bool unmapped; - bool cleanup; }; struct amdxdna_mem { @@ -44,6 +42,7 @@ struct amdxdna_gem_obj { struct mutex lock; /* Protects: pinned, mem.kva, open_ref */ struct amdxdna_mem mem; int open_ref; + struct work_struct hmm_unreg_work; /* Below members are initialized when needed */ struct drm_mm_node mm_node; /* For AMDXDNA_BO_DEV */ From 2d2c4b248bff9ee1ac06054a9138c1a308e2e7b8 Mon Sep 17 00:00:00 2001 From: Lizhi Hou Date: Thu, 17 Sep 2026 10:19:52 -0700 Subject: [PATCH 04/46] accel/amdxdna: Set HMM_PFN_REQ_WRITE for writable BO faults When aie2_populate_range() faults in pages, it only sets HMM_PFN_REQ_FAULT. Without HMM_PFN_REQ_WRITE, handle_mm_fault() does not request write permissions and may populate the CPU page tables with read-only PTEs. Check the VMA flags and set HMM_PFN_REQ_WRITE when the VMA has VM_WRITE. Fixes: e486147c912f ("accel/amdxdna: Add BO import and export") Reviewed-by: Max Zhen Signed-off-by: Lizhi Hou Link: https://patch.msgid.link/20260917171952.1883974-1-lizhi.hou@amd.com --- drivers/accel/amdxdna/amdxdna_gem.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/accel/amdxdna/amdxdna_gem.c b/drivers/accel/amdxdna/amdxdna_gem.c index 398d0a58b53a..07651ecb2fcd 100644 --- a/drivers/accel/amdxdna/amdxdna_gem.c +++ b/drivers/accel/amdxdna/amdxdna_gem.c @@ -380,7 +380,15 @@ static int amdxdna_hmm_register(struct amdxdna_gem_obj *abo, mapp->range.notifier = &mapp->notifier; mapp->range.start = vma->vm_start; mapp->range.end = vma->vm_end; + /* + * Access permissions are fixed at mmap() time. Changing them later + * with mprotect() is not supported: the range keeps requesting the + * original permissions, so the application may see a fault failure + * or an IOMMU fault. + */ mapp->range.default_flags = HMM_PFN_REQ_FAULT; + if (vma->vm_flags & VM_WRITE) + mapp->range.default_flags |= HMM_PFN_REQ_WRITE; mapp->abo = abo; kref_init(&mapp->refcnt); From 2117e3d96a4fd1ee13fa28b31494ff5f5db6ccf4 Mon Sep 17 00:00:00 2001 From: Eva Crystal <0xiviel@gmail.com> Date: Mon, 14 Sep 2026 09:31:55 +1200 Subject: [PATCH 05/46] accel/amdxdna: clear the mailbox channel pointer when starting it fails aie2_create_context() allocates a mailbox channel, starts it, and on a failed start frees it again: hwctx->priv->mbox_chann = xdna_mailbox_alloc_channel(ndev->mbox); ... ret = xdna_mailbox_start_channel(hwctx->priv->mbox_chann, ...); if (ret) goto free_channel; ... free_channel: xdna_mailbox_free_channel(hwctx->priv->mbox_chann); hwctx->priv->mbox_chann keeps pointing at the freed channel. That pointer is the driver's own test for whether a hardware context has a usable channel: aie2_destroy_context() returns early on NULL, aie2_sched_job_run() refuses to run a job on NULL, and the message helpers return -ENODEV on NULL. A stale pointer passes all of them. It matters on the restart path. aie2_hwctx_restart() calls aie2_create_context() again on a hardware context that is already live, after aie2_hwctx_stop() has torn its channel down, and the context survives a failure there: aie2_sched_job_timedout() discards the return value entirely, and aie2_hwctx_resume() only propagates it. What is left is a live hardware context holding a freed channel, and every later user of it takes the non-NULL branch: - the next job submitted reaches xdna_mailbox_send_msg() on the freed channel, via the !mbox_chann guard in aie2_sched_job_run(), - a second command timeout reaches aie2_hwctx_stop() -> aie2_destroy_context(), - closing the device reaches aie2_hwctx_fini() -> aie2_release_resource() -> xrs_release_resource() -> aie2_xrs_unload() -> aie2_destroy_context(), and aie2_destroy_context() then calls xdna_mailbox_stop_channel() followed by xdna_mailbox_free_channel() on memory that was already freed: a use-after-free, and a second free of the same channel. The AIE4 management channel already does this correctly - aie4_mailbox_start() clears ndev->aie.mgmt_chann right after freeing it. Do the same here. The create path itself is not affected: when aie2_xrs_load() fails, xrs_allocate_resource() removes the solver node without calling ->unload, and aie2_hwctx_init() frees hwctx->priv, so the stale pointer never outlives the structure holding it. Starting a channel can fail today without this patch: the ring buffer sizes firmware reports are rejected unless both are powers of two, and request_irq() can fail. Fixes: d5b8b0347fa8 ("accel/amdxdna: Split mailbox channel create function") Cc: stable@vger.kernel.org Signed-off-by: Eva Crystal <0xiviel@gmail.com> Reviewed-by: Lizhi Hou Signed-off-by: Lizhi Hou Link: https://patch.msgid.link/2fcf0eca215090dbbd95cb86e1bf5076056d3818.1789334558.git.0xiviel@gmail.com --- drivers/accel/amdxdna/aie2_message.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/accel/amdxdna/aie2_message.c b/drivers/accel/amdxdna/aie2_message.c index b4c49259a1a2..f658760c3d48 100644 --- a/drivers/accel/amdxdna/aie2_message.c +++ b/drivers/accel/amdxdna/aie2_message.c @@ -277,6 +277,7 @@ int aie2_create_context(struct amdxdna_dev_hdl *ndev, struct amdxdna_hwctx *hwct free_channel: xdna_mailbox_free_channel(hwctx->priv->mbox_chann); + hwctx->priv->mbox_chann = NULL; del_ctx_req: aie2_destroy_context_req(ndev, hwctx->fw_ctx_id); return ret; From f2751f8754ae7ca0bba1a9536e153c8cc3aa4aae Mon Sep 17 00:00:00 2001 From: Taimuraz Kaitmazov Date: Thu, 20 Aug 2026 01:44:56 +0300 Subject: [PATCH 06/46] accel/amdxdna: do not warn when a sync request is rejected amdxdna_drm_sync_bo_ioctl() answers a failed amdxdna_flush_bo() with drm_WARN(). Both of that function's error returns are decided by the ioctl's arguments, so SYNC_BO with an offset past the end of the BO splats and taints the kernel from an unprivileged caller. Log it at debug level, since the same caller can repeat it. Fixes: dbc8fd7a03cb ("accel/amdxdna: Add expandable device heap support") Signed-off-by: Taimuraz Kaitmazov Reviewed-by: Lizhi Hou Signed-off-by: Lizhi Hou Link: https://patch.msgid.link/20260819224458.257346-4-taimuraz@kaitmazov.com --- drivers/accel/amdxdna/amdxdna_gem.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/accel/amdxdna/amdxdna_gem.c b/drivers/accel/amdxdna/amdxdna_gem.c index 07651ecb2fcd..96f8bd46f079 100644 --- a/drivers/accel/amdxdna/amdxdna_gem.c +++ b/drivers/accel/amdxdna/amdxdna_gem.c @@ -1503,7 +1503,7 @@ int amdxdna_drm_sync_bo_ioctl(struct drm_device *dev, amdxdna_gem_unpin(abo); if (ret) { - drm_WARN(&xdna->ddev, 1, "Can not get flush memory"); + XDNA_DBG(xdna, "Flush BO %d failed, ret %d", args->handle, ret); goto put_obj; } } From b1da2a6cc13d66c03c364f7b72d5a343b3ccdb3c Mon Sep 17 00:00:00 2001 From: Taimuraz Kaitmazov Date: Thu, 20 Aug 2026 01:44:58 +0300 Subject: [PATCH 07/46] accel/amdxdna: do not fail a sync for a BO with no debug context amdxdna_drm_sync_bo_ioctl() calls amdxdna_hwctx_sync_debug_bo() for every FROM_DEVICE sync, which answers -EINVAL when the BO's assigned_hwctx names no context. Only a BO attached with ATTACH_DEBUG_BO is ever given one, so an ordinary read-back sync reports failure after its flush has already run. Ask for the debug sync only when the BO has a context. An unattached BO carries AMDXDNA_INVALID_CTX_HANDLE and hwctx ids are allocated above it, so the test is exact, -EINVAL keeps meaning that the named context is gone, and the handle is not resolved twice. The field is written under dev_lock and read here without it; the context is still resolved under that lock, so a racing attach only decides whether this sync sees the buffer. Fixes: 7ea046838021 ("accel/amdxdna: Support firmware debug buffer") Suggested-by: Lizhi Hou Signed-off-by: Taimuraz Kaitmazov Reviewed-by: Lizhi Hou Signed-off-by: Lizhi Hou Link: https://patch.msgid.link/20260819224458.257346-6-taimuraz@kaitmazov.com --- drivers/accel/amdxdna/amdxdna_gem.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/accel/amdxdna/amdxdna_gem.c b/drivers/accel/amdxdna/amdxdna_gem.c index 96f8bd46f079..f4832337ec31 100644 --- a/drivers/accel/amdxdna/amdxdna_gem.c +++ b/drivers/accel/amdxdna/amdxdna_gem.c @@ -1511,7 +1511,8 @@ int amdxdna_drm_sync_bo_ioctl(struct drm_device *dev, XDNA_DBG(xdna, "Sync bo %d offset 0x%llx, size 0x%llx\n", args->handle, args->offset, args->size); - if (args->direction == SYNC_DIRECT_FROM_DEVICE) + if (abo->assigned_hwctx != AMDXDNA_INVALID_CTX_HANDLE && + args->direction == SYNC_DIRECT_FROM_DEVICE) ret = amdxdna_hwctx_sync_debug_bo(client, args->handle); put_obj: From 0b02d324f78cf0381dbef4ca52cfb4bde2177fe8 Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Sat, 1 Aug 2026 21:09:54 +0200 Subject: [PATCH 08/46] drm/nouveau/bios/init: drop unneeded semicolon When a function-like macro expands to an expression, that expression doesn't need a semicolon after it. All uses have been verified to have their own semicolons. This was found using the following Coccinelle semantic patch: @r@ identifier i : script:ocaml() { String.lowercase_ascii i = i }; expression e; @@ *#define i(...) e; Signed-off-by: Julia Lawall Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260801191002.1383835-8-Julia.Lawall@inria.fr --- drivers/gpu/drm/nouveau/nvkm/subdev/bios/init.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/bios/init.c b/drivers/gpu/drm/nouveau/nvkm/subdev/bios/init.c index b54f044c4483..3f888fb97910 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/bios/init.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/bios/init.c @@ -403,7 +403,7 @@ init_table_(struct nvbios_init *init, u16 offset, const char *name) #define init_io_condition_table(b) init_table_((b), 0x08, "io condition table") #define init_io_flag_condition_table(b) init_table_((b), 0x0a, "io flag condition table") #define init_function_table(b) init_table_((b), 0x0c, "function table") -#define init_xlat_table(b) init_table_((b), 0x10, "xlat table"); +#define init_xlat_table(b) init_table_((b), 0x10, "xlat table") static u16 init_script(struct nvkm_bios *bios, int index) From 2e854d0fca8ce9682566826a0c81e8d53f37611d Mon Sep 17 00:00:00 2001 From: liupeng Date: Mon, 24 Aug 2026 16:59:08 +0800 Subject: [PATCH 09/46] drm/nouveau: Fix typos in comments Fix misspellings of "manageable", "controller", "overridden" and "particular". Signed-off-by: liupeng Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260824085910.48533-1-liupeng01@kylinos.cn --- drivers/gpu/drm/nouveau/nouveau_bios.c | 2 +- drivers/gpu/drm/nouveau/nouveau_dp.c | 2 +- drivers/gpu/drm/nouveau/nouveau_exec.c | 2 +- drivers/gpu/drm/nouveau/nouveau_led.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nouveau_bios.c b/drivers/gpu/drm/nouveau/nouveau_bios.c index 38032bb95826..10cd606d01b9 100644 --- a/drivers/gpu/drm/nouveau/nouveau_bios.c +++ b/drivers/gpu/drm/nouveau/nouveau_bios.c @@ -323,7 +323,7 @@ get_fp_strap(struct drm_device *dev, struct nvbios *bios) /* * The fp strap is normally dictated by the "User Strap" in * PEXTDEV_BOOT_0[20:16], but on BMP cards when bit 2 of the - * Internal_Flags struct at 0x48 is set, the user strap gets overriden + * Internal_Flags struct at 0x48 is set, the user strap gets overridden * by the PCI subsystem ID during POST, but not before the previous user * strap has been committed to CR58 for CR57=0xf on head A, which may be * read and used instead diff --git a/drivers/gpu/drm/nouveau/nouveau_dp.c b/drivers/gpu/drm/nouveau/nouveau_dp.c index 738802358d85..019af307c669 100644 --- a/drivers/gpu/drm/nouveau/nouveau_dp.c +++ b/drivers/gpu/drm/nouveau/nouveau_dp.c @@ -533,7 +533,7 @@ nv50_dp_mode_valid(struct nouveau_encoder *outp, const unsigned int min_clock = 25000; unsigned int max_rate, mode_rate, ds_max_dotclock, clock = mode->clock; /* Check with the minmum bpc always, so we can advertise better modes. - * In particlar not doing this causes modes to be dropped on HDR + * In particular not doing this causes modes to be dropped on HDR * displays as we might check with a bpc of 16 even. */ const u8 bpp = 6 * 3; diff --git a/drivers/gpu/drm/nouveau/nouveau_exec.c b/drivers/gpu/drm/nouveau/nouveau_exec.c index a08ab1cfea9b..eea6619cffaa 100644 --- a/drivers/gpu/drm/nouveau/nouveau_exec.c +++ b/drivers/gpu/drm/nouveau/nouveau_exec.c @@ -23,7 +23,7 @@ * should be managed by the kernel and which by the UMD. * * The DRM_NOUVEAU_VM_BIND ioctl provides clients an interface to manage the - * userspace-managable portion of the VA space. It provides operations to map + * userspace-manageable portion of the VA space. It provides operations to map * and unmap memory. Mappings may be flagged as sparse. Sparse mappings are not * backed by a GEM object and the kernel will ignore GEM handles provided * alongside a sparse mapping. diff --git a/drivers/gpu/drm/nouveau/nouveau_led.c b/drivers/gpu/drm/nouveau/nouveau_led.c index 19e2110904d1..67088a71f6d1 100644 --- a/drivers/gpu/drm/nouveau/nouveau_led.c +++ b/drivers/gpu/drm/nouveau/nouveau_led.c @@ -65,7 +65,7 @@ nouveau_led_set_brightness(struct led_classdev *led, enum led_brightness value) duty = value * div / LED_FULL; /* for now, this is safe to directly poke those registers because: - * - A: nvidia never puts the logo led to any other PWM controler + * - A: nvidia never puts the logo led to any other PWM controller * than PDISPLAY.SOR[1].PWM. * - B: nouveau does not touch these registers anywhere else */ From fb825a46e7982460bd620f5e6a71924c647f4280 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ADra=20Canal?= Date: Tue, 15 Sep 2026 17:51:24 -0300 Subject: [PATCH 10/46] drm/vc4: Drop the undocumented brcm,vc4-v3d compatible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "brcm,vc4-v3d" is the compatible string V3D support originally shipped with in commit d3f5168a0810 ("drm/vc4: Bind and initialize the V3D engine."). Commit 90d7116061f8 ("drm/vc4: Recognize a more specific compatible string for V3D.") added "brcm,bcm2835-v3d" in v4.6 and kept the original one around, because the Raspberry Pi firmware was shipping device trees that used it. That string was never part of the binding: the documentation added in commit 4653f22e9ab0 ("dt-bindings: Add binding docs for V3D.") describes only "brcm,bcm2835-v3d", and no upstream device tree has ever used "brcm,vc4-v3d": the first in-tree V3D node, from commit 49ac67e0c39c ("ARM: bcm2835: Add VC4 to the device tree."), already carried the specific name. The firmware device trees the compatible was kept for predate v4.6, released in 2016. Drop it from both match tables. Reviewed-by: Florian Fainelli Acked-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260915-vc4-reset-control-v2-1-cb3a25b07822@igalia.com Signed-off-by: Maíra Canal --- drivers/gpu/drm/vc4/vc4_drv.c | 1 - drivers/gpu/drm/vc4/vc4_v3d.c | 1 - 2 files changed, 2 deletions(-) diff --git a/drivers/gpu/drm/vc4/vc4_drv.c b/drivers/gpu/drm/vc4/vc4_drv.c index 616caf9d9915..eb859c65a453 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.c +++ b/drivers/gpu/drm/vc4/vc4_drv.c @@ -279,7 +279,6 @@ static const struct of_device_id vc4_dma_range_matches[] = { { .compatible = "brcm,bcm2835-hvs" }, { .compatible = "brcm,bcm2835-v3d" }, { .compatible = "brcm,cygnus-v3d" }, - { .compatible = "brcm,vc4-v3d" }, {} }; diff --git a/drivers/gpu/drm/vc4/vc4_v3d.c b/drivers/gpu/drm/vc4/vc4_v3d.c index f32410420d3e..a86739873e05 100644 --- a/drivers/gpu/drm/vc4/vc4_v3d.c +++ b/drivers/gpu/drm/vc4/vc4_v3d.c @@ -531,7 +531,6 @@ static void vc4_v3d_dev_remove(struct platform_device *pdev) const struct of_device_id vc4_v3d_dt_match[] = { { .compatible = "brcm,bcm2835-v3d" }, { .compatible = "brcm,cygnus-v3d" }, - { .compatible = "brcm,vc4-v3d" }, {} }; From 6ab4aad3481903ac3743513517c290a3ebd39232 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ADra=20Canal?= Date: Tue, 15 Sep 2026 17:51:25 -0300 Subject: [PATCH 11/46] dt-bindings: display: bcm2835-v3d: Add an optional reset line MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The BCM2835 power management block drives a reset line for the V3D block that is independent of the graphics power domain, allowing the GPU to be reset without power-cycling the domain. Expose this reset line in the V3D binding. Keep the property optional, as devices like the Broadcom Cygnus don't have a reset line. Reviewed-by: Stefan Wahren Acked-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260915-vc4-reset-control-v2-2-cb3a25b07822@igalia.com Signed-off-by: Maíra Canal --- .../devicetree/bindings/display/brcm,bcm2835-v3d.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/display/brcm,bcm2835-v3d.yaml b/Documentation/devicetree/bindings/display/brcm,bcm2835-v3d.yaml index c55a8217de25..1063e686647c 100644 --- a/Documentation/devicetree/bindings/display/brcm,bcm2835-v3d.yaml +++ b/Documentation/devicetree/bindings/display/brcm,bcm2835-v3d.yaml @@ -27,6 +27,9 @@ properties: power-domains: maxItems: 1 + resets: + maxItems: 1 + required: - compatible - reg From b4448823f151d0b4a3f3f4c5622c6ade51422c4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ADra=20Canal?= Date: Tue, 15 Sep 2026 17:51:26 -0300 Subject: [PATCH 12/46] drm/vc4: Use the reset controller to recover from a GPU hang MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit vc4_reset() recovers a hung GPU by dropping the runtime PM usage count to zero so that the power domain goes down, and then taking it again. Such an unconventional approach only works if the driver knows exactly how many references it holds, which is why vc4 wrapped every pm_runtime_get_sync() call in a private refcount and mutex. Commit 670c672608a1 ("soc: bcm: bcm2835-pm: Add support for power domains under a new binding.") exposed a V3D reset line for exactly this reason, so that the block can be reset without power-cycling its domain, but the vc4 driver never picked it up. Use it now, which removes the need for the private refcount and leaves vc4_v3d_pm_get/put() as plain runtime PM wrappers. The reset line is optional, to accommodate older device trees. Device trees that do not describe one still get the driver-side recovery in vc4_irq_reset(), but the hardware is left untouched. Two in-tree platforms use the VC4 V3D block: BCM2835 gains the resets property in the next commit, and Cygnus is no worse off than it was, as its V3D node has no power domain and the power-cycle only ever gated its clock. Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20260915-vc4-reset-control-v2-3-cb3a25b07822@igalia.com Signed-off-by: Maíra Canal --- drivers/gpu/drm/vc4/vc4_drv.h | 13 +++++++----- drivers/gpu/drm/vc4/vc4_gem.c | 40 +++++++++++++++++++++-------------- drivers/gpu/drm/vc4/vc4_irq.c | 7 +++--- drivers/gpu/drm/vc4/vc4_v3d.c | 36 +++++++++++-------------------- 4 files changed, 47 insertions(+), 49 deletions(-) diff --git a/drivers/gpu/drm/vc4/vc4_drv.h b/drivers/gpu/drm/vc4/vc4_drv.h index 649032174dbd..695bc6a29d30 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.h +++ b/drivers/gpu/drm/vc4/vc4_drv.h @@ -212,14 +212,9 @@ struct vc4_dev { struct work_struct overflow_mem_work; - int power_refcount; - /* Set to true when the load tracker is active. */ bool load_tracker_enabled; - /* Mutex controlling the power refcount. */ - struct mutex power_lock; - struct { struct timer_list timer; struct work_struct reset_work; @@ -294,6 +289,13 @@ struct vc4_v3d { struct platform_device *pdev; void __iomem *regs; struct clk *clk; + + /* Reset line for the V3D block, used to recover from a GPU hang. + * NULL if the device tree does not describe one, in which case the + * GPU cannot be reset. + */ + struct reset_control *reset; + struct debugfs_regset32 regset; }; @@ -1056,6 +1058,7 @@ int vc4_v3d_bin_bo_get(struct vc4_dev *vc4, bool *used); void vc4_v3d_bin_bo_put(struct vc4_dev *vc4); int vc4_v3d_pm_get(struct vc4_dev *vc4); void vc4_v3d_pm_put(struct vc4_dev *vc4); +void vc4_v3d_init_hw(struct drm_device *dev); int vc4_v3d_debugfs_init(struct drm_minor *minor); /* vc4_validate.c */ diff --git a/drivers/gpu/drm/vc4/vc4_gem.c b/drivers/gpu/drm/vc4/vc4_gem.c index e231c906709c..3212b9167620 100644 --- a/drivers/gpu/drm/vc4/vc4_gem.c +++ b/drivers/gpu/drm/vc4/vc4_gem.c @@ -23,7 +23,7 @@ #include #include -#include +#include #include #include #include @@ -292,19 +292,22 @@ err_free_state: static void vc4_reset(struct drm_device *dev) { - struct vc4_dev *vc4 = to_vc4_dev(dev); + struct vc4_v3d *v3d = to_vc4_dev(dev)->v3d; + int ret; - DRM_INFO("Resetting GPU.\n"); + vc4_irq_disable(dev); - mutex_lock(&vc4->power_lock); - if (vc4->power_refcount) { - /* Power the device off and back on the by dropping the - * reference on runtime PM. - */ - pm_runtime_put_sync_suspend(&vc4->v3d->pdev->dev); - pm_runtime_get_sync(&vc4->v3d->pdev->dev); + if (v3d->reset) { + drm_info(dev, "Resetting GPU.\n"); + + ret = reset_control_reset(v3d->reset); + if (ret) + drm_err(dev, "Failed to reset the GPU: %d\n", ret); + + vc4_v3d_init_hw(dev); + } else { + drm_info_once(dev, "No reset line; GPU state is not reset.\n"); } - mutex_unlock(&vc4->power_lock); vc4_irq_reset(dev); @@ -320,10 +323,19 @@ vc4_reset_work(struct work_struct *work) { struct vc4_dev *vc4 = container_of(work, struct vc4_dev, hangcheck.reset_work); + int ret; + + /* Make sure the device is not suspended during the reset. */ + ret = vc4_v3d_pm_get(vc4); + if (ret) { + drm_err(&vc4->base, "Failed to resume V3D for GPU reset: %d\n", ret); + return; + } vc4_save_hang_state(&vc4->base); - vc4_reset(&vc4->base); + + vc4_v3d_pm_put(vc4); } static void @@ -1177,10 +1189,6 @@ int vc4_gem_init(struct drm_device *dev) INIT_WORK(&vc4->job_done_work, vc4_job_done_work); - ret = drmm_mutex_init(dev, &vc4->power_lock); - if (ret) - return ret; - INIT_LIST_HEAD(&vc4->purgeable.list); ret = drmm_mutex_init(dev, &vc4->purgeable.lock); diff --git a/drivers/gpu/drm/vc4/vc4_irq.c b/drivers/gpu/drm/vc4/vc4_irq.c index 7877d493d80e..3a3ea1e62dcb 100644 --- a/drivers/gpu/drm/vc4/vc4_irq.c +++ b/drivers/gpu/drm/vc4/vc4_irq.c @@ -336,10 +336,9 @@ void vc4_irq_reset(struct drm_device *dev) V3D_WRITE(V3D_INTCTL, V3D_DRIVER_IRQS); /* - * Turn all our interrupts on. Binner out of memory is the - * only one we expect to trigger at this point, since we've - * just come from poweron and haven't supplied any overflow - * memory yet. + * Turn all our interrupts on. Binner out of memory is the only + * one we expect to trigger at this point, since the reset cleared + * the overflow memory address and none has been supplied yet. */ V3D_WRITE(V3D_INTENA, V3D_DRIVER_IRQS); diff --git a/drivers/gpu/drm/vc4/vc4_v3d.c b/drivers/gpu/drm/vc4/vc4_v3d.c index a86739873e05..b40d98c9d1d2 100644 --- a/drivers/gpu/drm/vc4/vc4_v3d.c +++ b/drivers/gpu/drm/vc4/vc4_v3d.c @@ -9,6 +9,7 @@ #include #include #include +#include #include @@ -122,29 +123,13 @@ static int vc4_v3d_debugfs_ident(struct seq_file *m, void *unused) return 0; } -/* - * Wraps pm_runtime_get_sync() in a refcount, so that we can reliably - * get the pm_runtime refcount to 0 in vc4_reset(). - */ int vc4_v3d_pm_get(struct vc4_dev *vc4) { if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return -ENODEV; - mutex_lock(&vc4->power_lock); - if (vc4->power_refcount++ == 0) { - int ret = pm_runtime_get_sync(&vc4->v3d->pdev->dev); - - if (ret < 0) { - vc4->power_refcount--; - mutex_unlock(&vc4->power_lock); - return ret; - } - } - mutex_unlock(&vc4->power_lock); - - return 0; + return pm_runtime_resume_and_get(&vc4->v3d->pdev->dev); } void @@ -153,15 +138,10 @@ vc4_v3d_pm_put(struct vc4_dev *vc4) if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4)) return; - mutex_lock(&vc4->power_lock); - if (--vc4->power_refcount == 0) { - pm_runtime_mark_last_busy(&vc4->v3d->pdev->dev); - pm_runtime_put_autosuspend(&vc4->v3d->pdev->dev); - } - mutex_unlock(&vc4->power_lock); + pm_runtime_put_autosuspend(&vc4->v3d->pdev->dev); } -static void vc4_v3d_init_hw(struct drm_device *dev) +void vc4_v3d_init_hw(struct drm_device *dev) { struct vc4_dev *vc4 = to_vc4_dev(dev); @@ -447,6 +427,14 @@ static int vc4_v3d_bind(struct device *dev, struct device *master, void *data) if (IS_ERR(v3d->clk)) return dev_err_probe(dev, PTR_ERR(v3d->clk), "Failed to get V3D clock\n"); + v3d->reset = devm_reset_control_get_optional_exclusive(dev, NULL); + if (IS_ERR(v3d->reset)) + return dev_err_probe(dev, PTR_ERR(v3d->reset), + "Failed to get reset control\n"); + + if (!v3d->reset) + drm_warn(drm, "No V3D reset line in the device tree"); + ret = platform_get_irq(pdev, 0); if (ret < 0) return ret; From 3afbeb6b99ddbe082a238a659b7e7a7239f49e0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ADra=20Canal?= Date: Tue, 15 Sep 2026 17:51:27 -0300 Subject: [PATCH 13/46] ARM: dts: bcm2835: Add the V3D reset line MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The PM block exposes a reset line for the V3D block, which the vc4 driver uses to reset the GPU when recovering from a hang instead of forcing its power domain down. Wire it up in the shared V3D node, covering BCM2835, BCM2836 and BCM2837. BCM2711 already describes the same reset line for its V3D node. Reviewed-by: Florian Fainelli Reviewed-by: Stefan Wahren Link: https://patch.msgid.link/20260915-vc4-reset-control-v2-4-cb3a25b07822@igalia.com Signed-off-by: Maíra Canal --- arch/arm/boot/dts/broadcom/bcm2835-common.dtsi | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/boot/dts/broadcom/bcm2835-common.dtsi b/arch/arm/boot/dts/broadcom/bcm2835-common.dtsi index 1e76b290510d..39eea8ce1058 100644 --- a/arch/arm/boot/dts/broadcom/bcm2835-common.dtsi +++ b/arch/arm/boot/dts/broadcom/bcm2835-common.dtsi @@ -139,6 +139,7 @@ compatible = "brcm,bcm2835-v3d"; reg = <0x7ec00000 0x1000>; interrupts = <1 10>; + resets = <&pm BCM2835_RESET_V3D>; }; }; From f0459aabf9e057920567b22a852ab36966cc291a Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 1 Sep 2026 21:50:25 +0300 Subject: [PATCH 14/46] video/hdmi: Introduce HDMI version enum Add an enum to represent HDMI specification versions. This will be used by upcoming changes to associate HDMI connectors and bridges with a maximum supported version and to simplify handling of version-dependent features. Reviewed-by: Maxime Ripard Reviewed-by: Dmitry Baryshkov Link: https://patch.msgid.link/20260901-dw-hdmi-qp-scramb-v11-1-bc12954a0688@collabora.com Signed-off-by: Cristian Ciocaltea --- include/linux/hdmi.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/include/linux/hdmi.h b/include/linux/hdmi.h index 8dab78e1f61b..b80a5ee63bb2 100644 --- a/include/linux/hdmi.h +++ b/include/linux/hdmi.h @@ -27,6 +27,18 @@ #include #include +enum hdmi_version { + HDMI_VERSION_UNKNOWN, + HDMI_VERSION_1_0, + HDMI_VERSION_1_1, + HDMI_VERSION_1_2, + HDMI_VERSION_1_3, + HDMI_VERSION_1_4, + HDMI_VERSION_2_0, + HDMI_VERSION_2_1, + HDMI_VERSION_2_2, +}; + enum hdmi_packet_type { HDMI_PACKET_TYPE_NULL = 0x00, HDMI_PACKET_TYPE_AUDIO_CLOCK_REGEN = 0x01, From 54acd0c74eba2a2c9a726856f5fdc6f9e74ea2e1 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 1 Sep 2026 21:50:26 +0300 Subject: [PATCH 15/46] drm/connector: hdmi: Handle reset() state allocation failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In drmm_connector_hdmi_init(), if the reset callback fails to allocate memory, e.g. inside drm_atomic_helper_connector_reset(), connector->state will remain NULL. This is unconditionally passed to drm_connector_attach_max_bpc_property(), leading to a NULL pointer dereference. Bail out with -ENOMEM if the reset callback leaves connector->state unset. Fixes: aadb3e16b8f3 ("drm/connector: hdmi: Add output BPC to the connector state") Reported-by: Sashiko Closes: https://lore.kernel.org/all/20260723015306.C94241F000E9@smtp.kernel.org/ Link: https://lore.kernel.org/all/20260723015306.C94241F000E9@smtp.kernel.org/ Reviewed-by: Maxime Ripard Tested-by: Igor Paunovic # RK3588 Orange Pi 5 Plus, dual HDMI Tested-by: Robin Räber Link: https://patch.msgid.link/20260901-dw-hdmi-qp-scramb-v11-2-bc12954a0688@collabora.com Signed-off-by: Cristian Ciocaltea --- drivers/gpu/drm/drm_connector.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c index 8b4baed060f3..0e39431e85b2 100644 --- a/drivers/gpu/drm/drm_connector.c +++ b/drivers/gpu/drm/drm_connector.c @@ -628,6 +628,8 @@ int drmm_connector_hdmi_init(struct drm_device *dev, connector->state = state; } else if (connector->funcs->reset) { connector->funcs->reset(connector); + if (!connector->state) + return -ENOMEM; } drm_connector_attach_max_bpc_property(connector, 8, max_bpc); From 245e321b61e384d956b95a3bfa59ac25ca4609c6 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 1 Sep 2026 21:50:27 +0300 Subject: [PATCH 16/46] drm/display: hdmi: Rename drmm_connector_hdmi_init() to *_ini2() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In preparation for changing the signature of drmm_connector_hdmi_init(), i.e. dropping the vendor, product, supported_formats and max_bpc arguments, which are being moved into struct drm_connector_hdmi_funcs, temporarily rename the helper to drmm_connector_hdmi_ini2(). This lets the new signature be introduced under the original name while callers are converted incrementally. The final patch removes *_ini2() once no callers remain. This is a mechanical, same-length rename performed with sed: $ git grep -l 'drmm_connector_hdmi_init' -- '*.[ch]' \ | xargs sed -i 's/drmm_connector_hdmi_init/drmm_connector_hdmi_ini2/g' The transitional name matches the original's length so continuation-line arguments stay aligned to the opening parenthesis, keeping the diff to the identifier itself and avoiding re-alignment churn here and in the later conversions. No functional change intended. Reviewed-by: Dmitry Baryshkov Tested-by: Diederik de Haas # NanoPC-T6 LTS, Rock 5B Tested-by: Igor Paunovic # RK3588 Orange Pi 5 Plus, dual HDMI Tested-by: Robin Räber Link: https://patch.msgid.link/20260901-dw-hdmi-qp-scramb-v11-3-bc12954a0688@collabora.com Signed-off-by: Cristian Ciocaltea --- .../gpu/drm/display/drm_bridge_connector.c | 2 +- drivers/gpu/drm/drm_connector.c | 6 +-- drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c | 2 +- drivers/gpu/drm/tests/drm_connector_test.c | 42 +++++++++---------- .../drm/tests/drm_hdmi_state_helper_test.c | 2 +- drivers/gpu/drm/vc4/vc4_hdmi.c | 2 +- include/drm/drm_connector.h | 2 +- 7 files changed, 29 insertions(+), 29 deletions(-) diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c index 00feb73e63d7..d664237936ec 100644 --- a/drivers/gpu/drm/display/drm_bridge_connector.c +++ b/drivers/gpu/drm/display/drm_bridge_connector.c @@ -1013,7 +1013,7 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm, bridge_connector->hdmi_funcs.spd = drm_bridge_connector_hdmi_spd_infoframe; - ret = drmm_connector_hdmi_init(drm, connector, + ret = drmm_connector_hdmi_ini2(drm, connector, bridge_connector->bridge_hdmi->vendor, bridge_connector->bridge_hdmi->product, &drm_bridge_connector_funcs, diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c index 0e39431e85b2..5e520cff6094 100644 --- a/drivers/gpu/drm/drm_connector.c +++ b/drivers/gpu/drm/drm_connector.c @@ -543,7 +543,7 @@ int drmm_connector_init(struct drm_device *dev, EXPORT_SYMBOL(drmm_connector_init); /** - * drmm_connector_hdmi_init - Init a preallocated HDMI connector + * drmm_connector_hdmi_ini2 - Init a preallocated HDMI connector * @dev: DRM device * @connector: A pointer to the HDMI connector to init * @vendor: HDMI Controller Vendor name @@ -568,7 +568,7 @@ EXPORT_SYMBOL(drmm_connector_init); * Returns: * Zero on success, error code on failure. */ -int drmm_connector_hdmi_init(struct drm_device *dev, +int drmm_connector_hdmi_ini2(struct drm_device *dev, struct drm_connector *connector, const char *vendor, const char *product, const struct drm_connector_funcs *funcs, @@ -646,7 +646,7 @@ int drmm_connector_hdmi_init(struct drm_device *dev, return 0; } -EXPORT_SYMBOL(drmm_connector_hdmi_init); +EXPORT_SYMBOL(drmm_connector_hdmi_ini2); /** * drm_connector_attach_edid_property - attach edid property. diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c index 91844a396a2b..d98b6f12a7bc 100644 --- a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c @@ -654,7 +654,7 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master, drm_connector_helper_add(&hdmi->connector, &sun4i_hdmi_connector_helper_funcs); - ret = drmm_connector_hdmi_init(drm, &hdmi->connector, + ret = drmm_connector_hdmi_ini2(drm, &hdmi->connector, /* * NOTE: Those are likely to be * wrong, but I couldn't find the diff --git a/drivers/gpu/drm/tests/drm_connector_test.c b/drivers/gpu/drm/tests/drm_connector_test.c index beb1d50a6646..3368aedd4e66 100644 --- a/drivers/gpu/drm/tests/drm_connector_test.c +++ b/drivers/gpu/drm/tests/drm_connector_test.c @@ -669,7 +669,7 @@ static void drm_test_connector_hdmi_init_valid(struct kunit *test) struct drm_connector_init_priv *priv = test->priv; int ret; - ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, + ret = drmm_connector_hdmi_ini2(&priv->drm, &priv->connector, "Vendor", "Product", &dummy_funcs, &dummy_hdmi_funcs, @@ -689,7 +689,7 @@ static void drm_test_connector_hdmi_init_null_ddc(struct kunit *test) struct drm_connector_init_priv *priv = test->priv; int ret; - ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, + ret = drmm_connector_hdmi_ini2(&priv->drm, &priv->connector, "Vendor", "Product", &dummy_funcs, &dummy_hdmi_funcs, @@ -709,7 +709,7 @@ static void drm_test_connector_hdmi_init_null_vendor(struct kunit *test) struct drm_connector_init_priv *priv = test->priv; int ret; - ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, + ret = drmm_connector_hdmi_ini2(&priv->drm, &priv->connector, NULL, "Product", &dummy_funcs, &dummy_hdmi_funcs, @@ -729,7 +729,7 @@ static void drm_test_connector_hdmi_init_null_product(struct kunit *test) struct drm_connector_init_priv *priv = test->priv; int ret; - ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, + ret = drmm_connector_hdmi_ini2(&priv->drm, &priv->connector, "Vendor", NULL, &dummy_funcs, &dummy_hdmi_funcs, @@ -755,7 +755,7 @@ static void drm_test_connector_hdmi_init_product_valid(struct kunit *test) KUNIT_ASSERT_LT(test, strlen(product_name), DRM_CONNECTOR_HDMI_PRODUCT_LEN); - ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, + ret = drmm_connector_hdmi_ini2(&priv->drm, &priv->connector, "Vendor", product_name, &dummy_funcs, &dummy_hdmi_funcs, @@ -788,7 +788,7 @@ static void drm_test_connector_hdmi_init_product_length_exact(struct kunit *test KUNIT_ASSERT_EQ(test, strlen(product_name), DRM_CONNECTOR_HDMI_PRODUCT_LEN); - ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, + ret = drmm_connector_hdmi_ini2(&priv->drm, &priv->connector, "Vendor", product_name, &dummy_funcs, &dummy_hdmi_funcs, @@ -815,7 +815,7 @@ static void drm_test_connector_hdmi_init_product_length_too_long(struct kunit *t KUNIT_ASSERT_GT(test, strlen(product_name), DRM_CONNECTOR_HDMI_PRODUCT_LEN); - ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, + ret = drmm_connector_hdmi_ini2(&priv->drm, &priv->connector, "Vendor", product_name, &dummy_funcs, &dummy_hdmi_funcs, @@ -841,7 +841,7 @@ static void drm_test_connector_hdmi_init_vendor_valid(struct kunit *test) KUNIT_ASSERT_LT(test, strlen(vendor_name), DRM_CONNECTOR_HDMI_VENDOR_LEN); - ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, + ret = drmm_connector_hdmi_ini2(&priv->drm, &priv->connector, vendor_name, "Product", &dummy_funcs, &dummy_hdmi_funcs, @@ -873,7 +873,7 @@ static void drm_test_connector_hdmi_init_vendor_length_exact(struct kunit *test) KUNIT_ASSERT_EQ(test, strlen(vendor_name), DRM_CONNECTOR_HDMI_VENDOR_LEN); - ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, + ret = drmm_connector_hdmi_ini2(&priv->drm, &priv->connector, vendor_name, "Product", &dummy_funcs, &dummy_hdmi_funcs, @@ -900,7 +900,7 @@ static void drm_test_connector_hdmi_init_vendor_length_too_long(struct kunit *te KUNIT_ASSERT_GT(test, strlen(vendor_name), DRM_CONNECTOR_HDMI_VENDOR_LEN); - ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, + ret = drmm_connector_hdmi_ini2(&priv->drm, &priv->connector, vendor_name, "Product", &dummy_funcs, &dummy_hdmi_funcs, @@ -920,7 +920,7 @@ static void drm_test_connector_hdmi_init_bpc_invalid(struct kunit *test) struct drm_connector_init_priv *priv = test->priv; int ret; - ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, + ret = drmm_connector_hdmi_ini2(&priv->drm, &priv->connector, "Vendor", "Product", &dummy_funcs, &dummy_hdmi_funcs, @@ -940,7 +940,7 @@ static void drm_test_connector_hdmi_init_bpc_null(struct kunit *test) struct drm_connector_init_priv *priv = test->priv; int ret; - ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, + ret = drmm_connector_hdmi_ini2(&priv->drm, &priv->connector, "Vendor", "Product", &dummy_funcs, &dummy_hdmi_funcs, @@ -965,7 +965,7 @@ static void drm_test_connector_hdmi_init_bpc_8(struct kunit *test) uint64_t val; int ret; - ret = drmm_connector_hdmi_init(&priv->drm, connector, + ret = drmm_connector_hdmi_ini2(&priv->drm, connector, "Vendor", "Product", &dummy_funcs, &dummy_hdmi_funcs, @@ -1006,7 +1006,7 @@ static void drm_test_connector_hdmi_init_bpc_10(struct kunit *test) uint64_t val; int ret; - ret = drmm_connector_hdmi_init(&priv->drm, connector, + ret = drmm_connector_hdmi_ini2(&priv->drm, connector, "Vendor", "Product", &dummy_funcs, &dummy_hdmi_funcs, @@ -1047,7 +1047,7 @@ static void drm_test_connector_hdmi_init_bpc_12(struct kunit *test) uint64_t val; int ret; - ret = drmm_connector_hdmi_init(&priv->drm, connector, + ret = drmm_connector_hdmi_ini2(&priv->drm, connector, "Vendor", "Product", &dummy_funcs, &dummy_hdmi_funcs, @@ -1083,7 +1083,7 @@ static void drm_test_connector_hdmi_init_formats_empty(struct kunit *test) struct drm_connector_init_priv *priv = test->priv; int ret; - ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, + ret = drmm_connector_hdmi_ini2(&priv->drm, &priv->connector, "Vendor", "Product", &dummy_funcs, &dummy_hdmi_funcs, @@ -1103,7 +1103,7 @@ static void drm_test_connector_hdmi_init_formats_no_rgb(struct kunit *test) struct drm_connector_init_priv *priv = test->priv; int ret; - ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, + ret = drmm_connector_hdmi_ini2(&priv->drm, &priv->connector, "Vendor", "Product", &dummy_funcs, &dummy_hdmi_funcs, @@ -1161,7 +1161,7 @@ static void drm_test_connector_hdmi_init_formats_yuv420_allowed(struct kunit *te params = test->param_value; priv->connector.ycbcr_420_allowed = params->yuv420_allowed; - ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, + ret = drmm_connector_hdmi_ini2(&priv->drm, &priv->connector, "Vendor", "Product", &dummy_funcs, &dummy_hdmi_funcs, @@ -1182,7 +1182,7 @@ static void drm_test_connector_hdmi_init_type_valid(struct kunit *test) unsigned int connector_type = *(unsigned int *)test->param_value; int ret; - ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, + ret = drmm_connector_hdmi_ini2(&priv->drm, &priv->connector, "Vendor", "Product", &dummy_funcs, &dummy_hdmi_funcs, @@ -1217,7 +1217,7 @@ static void drm_test_connector_hdmi_init_type_invalid(struct kunit *test) unsigned int connector_type = *(unsigned int *)test->param_value; int ret; - ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, + ret = drmm_connector_hdmi_ini2(&priv->drm, &priv->connector, "Vendor", "Product", &dummy_funcs, &dummy_hdmi_funcs, @@ -1494,7 +1494,7 @@ static void drm_test_drm_connector_attach_broadcast_rgb_property_hdmi_connector( struct drm_property *prop; int ret; - ret = drmm_connector_hdmi_init(&priv->drm, connector, + ret = drmm_connector_hdmi_ini2(&priv->drm, connector, "Vendor", "Product", &dummy_funcs, &dummy_hdmi_funcs, diff --git a/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c b/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c index f8ae1a0bd5c0..b2e347863a77 100644 --- a/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c +++ b/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c @@ -275,7 +275,7 @@ __connector_hdmi_init(struct kunit *test, conn = &priv->connector; conn->ycbcr_420_allowed = !!(formats & BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR420)); - ret = drmm_connector_hdmi_init(drm, conn, + ret = drmm_connector_hdmi_ini2(drm, conn, "Vendor", "Product", &dummy_connector_funcs, hdmi_funcs, diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index ea1f2013b20e..5bb508a87b40 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -542,7 +542,7 @@ static int vc4_hdmi_connector_init(struct drm_device *dev, if (vc4_hdmi->variant->supports_hdr) max_bpc = 12; - ret = drmm_connector_hdmi_init(dev, connector, + ret = drmm_connector_hdmi_ini2(dev, connector, "Broadcom", "Videocore", &vc4_hdmi_connector_funcs, &vc4_hdmi_hdmi_connector_funcs, diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h index c61345e6fb08..f039bb8acf10 100644 --- a/include/drm/drm_connector.h +++ b/include/drm/drm_connector.h @@ -2616,7 +2616,7 @@ int drmm_connector_init(struct drm_device *dev, const struct drm_connector_funcs *funcs, int connector_type, struct i2c_adapter *ddc); -int drmm_connector_hdmi_init(struct drm_device *dev, +int drmm_connector_hdmi_ini2(struct drm_device *dev, struct drm_connector *connector, const char *vendor, const char *product, const struct drm_connector_funcs *funcs, From 400c9ede1ea4a4f26931a269b60de34cb26d51ec Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 1 Sep 2026 21:50:28 +0300 Subject: [PATCH 17/46] drm/connector: Add drmm_connector_hdmi_init() with new signature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In preparation for providing HDMI 2.x source capabilities, move the vendor, product, supported_formats and max_bpc parameters out of the init function into struct drm_connector_hdmi_funcs, and add new supported_hdmi_ver and supported_tmds_char_rate fields there as well. Appending more HDMI-specific arguments to that function would not scale well, hence introduce drmm_connector_hdmi_init() with the reduced signature. Additionally, add the max_tmds_char_rate field to struct drm_connector_hdmi and use the supported HDMI version to determinate the maximum TMDS character rate allowed by the specification. Some controllers, however, may support a lower rate than that version would imply. A non-zero supported_tmds_char_rate lets drivers override this default with the actual controller capability. A value of zero keeps the limit inferred from supported_hdmi_ver. Callers are converted in the following patches, while the old and now renamed *_ini2() helper is removed at the end of the series. No functional changes expected for existing callers. Tested-by: Diederik de Haas # NanoPC-T6 LTS, Rock 5B Reviewed-by: Maxime Ripard Tested-by: Igor Paunovic # RK3588 Orange Pi 5 Plus, dual HDMI Tested-by: Robin Räber Link: https://patch.msgid.link/20260901-dw-hdmi-qp-scramb-v11-4-bc12954a0688@collabora.com Signed-off-by: Cristian Ciocaltea --- drivers/gpu/drm/drm_connector.c | 137 ++++++++++++++++++++++++++++++++ include/drm/drm_connector.h | 62 ++++++++++++++- 2 files changed, 198 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c index 5e520cff6094..38cf853e867e 100644 --- a/drivers/gpu/drm/drm_connector.c +++ b/drivers/gpu/drm/drm_connector.c @@ -542,6 +542,143 @@ int drmm_connector_init(struct drm_device *dev, } EXPORT_SYMBOL(drmm_connector_init); +/** + * drmm_connector_hdmi_init - Init a preallocated HDMI connector + * @dev: DRM device + * @connector: A pointer to the HDMI connector to init + * @funcs: callbacks for this connector + * @hdmi_funcs: HDMI-related callbacks and capabilities for this connector + * @connector_type: user visible type of the connector + * @ddc: optional pointer to the associated ddc adapter + * + * Initialises a preallocated HDMI connector. Connectors can be + * subclassed as part of driver connector objects. + * + * Cleanup is automatically handled with a call to + * drm_connector_cleanup() in a DRM-managed action. + * + * The connector structure should be allocated with drmm_kzalloc(). + * + * The @drm_connector_funcs.destroy hook must be NULL. + * + * Returns: + * Zero on success, error code on failure. + */ +int drmm_connector_hdmi_init(struct drm_device *dev, + struct drm_connector *connector, + const struct drm_connector_funcs *funcs, + const struct drm_connector_hdmi_funcs *hdmi_funcs, + int connector_type, + struct i2c_adapter *ddc) +{ + int ret; + + if (!hdmi_funcs) + return -EINVAL; + + if (!hdmi_funcs->vendor || !hdmi_funcs->product) + return -EINVAL; + + if ((strlen(hdmi_funcs->vendor) > DRM_CONNECTOR_HDMI_VENDOR_LEN) || + (strlen(hdmi_funcs->product) > DRM_CONNECTOR_HDMI_PRODUCT_LEN)) + return -EINVAL; + + if (!hdmi_funcs->supported_formats || + !(hdmi_funcs->supported_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444))) + return -EINVAL; + + if (connector->ycbcr_420_allowed != + !!(hdmi_funcs->supported_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR420))) + return -EINVAL; + + if (!(hdmi_funcs->max_bpc == 8 || + hdmi_funcs->max_bpc == 10 || + hdmi_funcs->max_bpc == 12)) + return -EINVAL; + + if (!hdmi_funcs->avi.clear_infoframe || + !hdmi_funcs->avi.write_infoframe || + !hdmi_funcs->hdmi.clear_infoframe || + !hdmi_funcs->hdmi.write_infoframe) + return -EINVAL; + + if (!(connector_type == DRM_MODE_CONNECTOR_HDMIA || + connector_type == DRM_MODE_CONNECTOR_HDMIB)) + return -EINVAL; + + ret = drmm_connector_init(dev, connector, funcs, connector_type, ddc); + if (ret) + return ret; + + /* TODO: remove after conversion to new drmm_connector_hdmi_init() */ + connector->hdmi.supported_formats = hdmi_funcs->supported_formats; + + /* + * The supported HDMI version can be used to determinate the maximum + * TMDS character rate allowed by the specification. Some controllers, + * however, may support a lower rate than that version would imply. + * + * A non-zero caps->max_tmds_char_rate lets drivers override this + * default with the actual controller capability. A value of zero keeps + * the limit inferred from supported_hdmi_ver. + */ + if (hdmi_funcs->supported_hdmi_ver >= HDMI_VERSION_2_0) + connector->hdmi.max_tmds_char_rate = HDMI_2_0_TMDS_CHAR_RATE_MAX_HZ; + else if (hdmi_funcs->supported_hdmi_ver >= HDMI_VERSION_1_3) + connector->hdmi.max_tmds_char_rate = HDMI_1_3_TMDS_CHAR_RATE_MAX_HZ; + else if (hdmi_funcs->supported_hdmi_ver >= HDMI_VERSION_1_0) + connector->hdmi.max_tmds_char_rate = HDMI_1_0_TMDS_CHAR_RATE_MAX_HZ; + + if (hdmi_funcs->supported_tmds_char_rate) { + if (hdmi_funcs->supported_tmds_char_rate > connector->hdmi.max_tmds_char_rate) { + drm_err(dev, "Enforced max_tmds_char_rate exceeds %llu spec limit\n", + connector->hdmi.max_tmds_char_rate); + return -EINVAL; + } + + connector->hdmi.max_tmds_char_rate = hdmi_funcs->supported_tmds_char_rate; + } + + /* TODO: remove after conversion to new drmm_connector_hdmi_init() */ + strtomem_pad(connector->hdmi.vendor, hdmi_funcs->vendor, 0); + strtomem_pad(connector->hdmi.product, hdmi_funcs->product, 0); + + /* + * drm_connector_attach_max_bpc_property() requires the + * connector to have a state. + */ + if (connector->funcs->atomic_create_state) { + struct drm_connector_state *state; + + state = connector->funcs->atomic_create_state(connector); + if (IS_ERR(state)) + return PTR_ERR(state); + + connector->state = state; + } else if (connector->funcs->reset) { + connector->funcs->reset(connector); + if (!connector->state) + return -ENOMEM; + } + + drm_connector_attach_max_bpc_property(connector, 8, hdmi_funcs->max_bpc); + /* TODO: remove after conversion to new drmm_connector_hdmi_init() */ + connector->max_bpc = hdmi_funcs->max_bpc; + + if (hdmi_funcs->max_bpc > 8) + drm_connector_attach_hdr_output_metadata_property(connector); + + ret = drm_connector_attach_color_format_property(connector, + hdmi_funcs->supported_formats); + if (ret) + return ret; + + connector->hdmi.funcs = hdmi_funcs; + + return 0; +} +EXPORT_SYMBOL(drmm_connector_hdmi_init); + /** * drmm_connector_hdmi_ini2 - Init a preallocated HDMI connector * @dev: DRM device diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h index f039bb8acf10..be51265e8e26 100644 --- a/include/drm/drm_connector.h +++ b/include/drm/drm_connector.h @@ -1470,8 +1470,50 @@ struct drm_connector_infoframe_funcs { /** * struct drm_connector_hdmi_funcs - drm_hdmi_connector control functions + * and controller capabilities */ struct drm_connector_hdmi_funcs { + /** + * @vendor: HDMI Controller Vendor name. + */ + const char *vendor; + + /** + * @product: HDMI Controller Product name + */ + const char *product; + + /** + * @supported_hdmi_ver: + * + * Maximum HDMI specification version supported by the controller side + * of this connector. This describes the controller capability only; + * the effective link capabilities may be further restricted by the + * sink, bridge, or mode validation. + */ + enum hdmi_version supported_hdmi_ver; + + /** + * @supported_tmds_char_rate: + * + * Maximum TMDS character rate supported by the controller, in Hz. + * A value of 0 means the core should use the default limit implied by + * @supported_hdmi_ver. + */ + unsigned long long supported_tmds_char_rate; + + /** + * @supported_formats: + * + * Bitmask of @drm_output_color_format listing supported output formats. + */ + unsigned long supported_formats; + + /** + * @max_bpc: Maximum bits per char the HDMI connector supports. + */ + unsigned int max_bpc; + /** * @tmds_char_rate_valid: * @@ -2119,7 +2161,19 @@ struct drm_connector_hdmi { unsigned long supported_formats; /** - * @funcs: HDMI connector Control Functions + * @max_tmds_char_rate: Maximum TMDS character rate, in Hz, + * supported by the controller. + * + * This is inferred from &drm_connector_hdmi_funcs.supported_hdmi_ver, + * by default. However, controllers may support a lower rate than that + * specification version would imply. If that is the case, drivers are + * expected to set &drm_connector_hdmi_funcs.supported_tmds_char_rate + * to a non-zero value indicating the actual limit. + */ + unsigned long long max_tmds_char_rate; + + /** + * @funcs: HDMI connector Control Functions and controller capabilities */ const struct drm_connector_hdmi_funcs *funcs; @@ -2616,6 +2670,12 @@ int drmm_connector_init(struct drm_device *dev, const struct drm_connector_funcs *funcs, int connector_type, struct i2c_adapter *ddc); +int drmm_connector_hdmi_init(struct drm_device *dev, + struct drm_connector *connector, + const struct drm_connector_funcs *funcs, + const struct drm_connector_hdmi_funcs *hdmi_funcs, + int connector_type, + struct i2c_adapter *ddc); int drmm_connector_hdmi_ini2(struct drm_device *dev, struct drm_connector *connector, const char *vendor, const char *product, From cc6d8b2f87b97477ae25dcc1adb8e6e5a0088e4a Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 1 Sep 2026 21:50:29 +0300 Subject: [PATCH 18/46] drm/display: bridge_connector: Convert to drmm_connector_hdmi_init() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Switch drm_bridge_connector to the new drmm_connector_hdmi_init() signature. The vendor, product, supported_formats and max_bpc values now live in struct drm_connector_hdmi_funcs instead of being passed as separate arguments. This allows future HDMI 2.x capabilities to be plumbed without growing the function's argument list. Introduce the supported_hdmi_ver field in struct drm_bridge to let bridges declare the HDMI specification version they comply with, as well as max_tmds_char_rate to allow them to override the limit inferred from that version. This is particularly useful when handling chip variants with different capabilities. Additionally, add a warning in drm_bridge_add() if an HDMI bridge leaves supported_hdmi_ver unset. This alerts bridge drivers setting the DRM_BRIDGE_OP_HDMI flag that they must also declare their supported HDMI version. Tested-by: Maud Spierings Tested-by: Diederik de Haas # NanoPC-T6 LTS, Rock 5B Reviewed-by: Maxime Ripard Tested-by: Igor Paunovic # RK3588 Orange Pi 5 Plus, dual HDMI Tested-by: Robin Räber Link: https://patch.msgid.link/20260901-dw-hdmi-qp-scramb-v11-5-bc12954a0688@collabora.com Signed-off-by: Cristian Ciocaltea --- .../gpu/drm/display/drm_bridge_connector.c | 43 ++++++++++++------- drivers/gpu/drm/drm_bridge.c | 6 ++- include/drm/drm_bridge.h | 14 ++++++ 3 files changed, 46 insertions(+), 17 deletions(-) diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c index d664237936ec..63cdc6eab132 100644 --- a/drivers/gpu/drm/display/drm_bridge_connector.c +++ b/drivers/gpu/drm/display/drm_bridge_connector.c @@ -593,6 +593,8 @@ drm_bridge_connector_read_edid(struct drm_connector *connector) } static const struct drm_connector_hdmi_funcs drm_bridge_connector_hdmi_funcs = { + .supported_formats = BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444), + .max_bpc = 8, .tmds_char_rate_valid = drm_bridge_connector_tmds_char_rate_valid, .read_edid = drm_bridge_connector_read_edid, .avi = { @@ -826,8 +828,6 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm, struct drm_connector *connector; struct i2c_adapter *ddc = NULL; struct drm_bridge *panel_bridge __free(drm_bridge_put) = NULL; - unsigned int supported_formats = BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444); - unsigned int max_bpc = 8; bool support_hdcp = false; int connector_type; int ret; @@ -910,11 +910,6 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm, return ERR_PTR(-EINVAL); bridge_connector->bridge_hdmi = drm_bridge_get(bridge); - - if (bridge->supported_formats) - supported_formats = bridge->supported_formats; - if (bridge->max_bpc) - max_bpc = bridge->max_bpc; } if (bridge->ops & DRM_BRIDGE_OP_HDMI_AUDIO) { @@ -996,11 +991,31 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm, return ERR_PTR(-EINVAL); if (bridge_connector->bridge_hdmi) { - if (!connector->ycbcr_420_allowed) - supported_formats &= ~BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR420); - bridge_connector->hdmi_funcs = drm_bridge_connector_hdmi_funcs; + bridge_connector->hdmi_funcs.vendor = bridge_connector->bridge_hdmi->vendor; + bridge_connector->hdmi_funcs.product = bridge_connector->bridge_hdmi->product; + + if (bridge_connector->bridge_hdmi->supported_hdmi_ver) + bridge_connector->hdmi_funcs.supported_hdmi_ver = + bridge_connector->bridge_hdmi->supported_hdmi_ver; + + if (bridge_connector->bridge_hdmi->supported_formats) + bridge_connector->hdmi_funcs.supported_formats = + bridge_connector->bridge_hdmi->supported_formats; + + if (!connector->ycbcr_420_allowed) + bridge_connector->hdmi_funcs.supported_formats &= + ~BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR420); + + if (bridge_connector->bridge_hdmi->max_tmds_char_rate) + bridge_connector->hdmi_funcs.supported_tmds_char_rate = + bridge_connector->bridge_hdmi->max_tmds_char_rate; + + if (bridge_connector->bridge_hdmi->max_bpc) + bridge_connector->hdmi_funcs.max_bpc = + bridge_connector->bridge_hdmi->max_bpc; + if (bridge_connector->bridge_hdmi->ops & DRM_BRIDGE_OP_HDMI_AUDIO) bridge_connector->hdmi_funcs.audio = drm_bridge_connector_hdmi_audio_infoframe; @@ -1013,14 +1028,10 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm, bridge_connector->hdmi_funcs.spd = drm_bridge_connector_hdmi_spd_infoframe; - ret = drmm_connector_hdmi_ini2(drm, connector, - bridge_connector->bridge_hdmi->vendor, - bridge_connector->bridge_hdmi->product, + ret = drmm_connector_hdmi_init(drm, connector, &drm_bridge_connector_funcs, &bridge_connector->hdmi_funcs, - connector_type, ddc, - supported_formats, - max_bpc); + connector_type, ddc); if (ret) return ERR_PTR(ret); } else { diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c index 649f6b6ab5c8..4571ae1f6f81 100644 --- a/drivers/gpu/drm/drm_bridge.c +++ b/drivers/gpu/drm/drm_bridge.c @@ -460,9 +460,13 @@ void drm_bridge_add(struct drm_bridge *bridge) mutex_init(&bridge->hpd_state_mutex); mutex_init(&bridge->hpd_mutex); - if (bridge->ops & DRM_BRIDGE_OP_HDMI) + if (bridge->ops & DRM_BRIDGE_OP_HDMI) { + if (bridge->supported_hdmi_ver == HDMI_VERSION_UNKNOWN) + DRM_WARN("HDMI bridge misses supported HDMI version\n"); + bridge->ycbcr_420_allowed = !!(bridge->supported_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR420)); + } mutex_lock(&bridge_lock); list_add_tail(&bridge->list, &bridge_list); diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h index 58fff047f43b..5a5a25995471 100644 --- a/include/drm/drm_bridge.h +++ b/include/drm/drm_bridge.h @@ -1081,6 +1081,12 @@ struct drm_bridge { */ const char *product; + /** + * @supported_hdmi_ver: HDMI version the bridge is conformant with. + * This is only relevant if @DRM_BRIDGE_OP_HDMI is set. + */ + enum hdmi_version supported_hdmi_ver; + /** * @supported_formats: Bitmask of @drm_output_color_format listing * supported output formats. This is only relevant if @@ -1088,6 +1094,14 @@ struct drm_bridge { */ unsigned int supported_formats; + /** + * @max_tmds_char_rate: Maximum TMDS character rate, in Hz, the HDMI + * bridge supports. A value of 0 means the core should use the default + * limit implied by @supported_hdmi_ver. This is only relevant if + * @DRM_BRIDGE_OP_HDMI is set. + */ + unsigned long long max_tmds_char_rate; + /** * @max_bpc: Maximum bits per char the HDMI bridge supports. Allowed * values are 8, 10 and 12. This is only relevant if From 9d1e2ae7196dacc90acc00a73b92c11662eb90ca Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 1 Sep 2026 21:50:30 +0300 Subject: [PATCH 19/46] drm/probe-helper: Introduce .force_ctx() connector callback The .force() connector callback is invoked by drm_helper_probe_single_connector_modes() while the modeset acquire context it created is live, but the hook signature provides no way to pass it down, e.g. to support implementations needing to lock further objects. Add a .force_ctx() variant to drm_connector_helper_funcs, taking the acquire context and returning an error code, so that -EDEADLK propagates into the existing retry path. When provided, it takes precedence over drm_connector_funcs' .force(), which is kept for existing users. This is a prerequisite for SCDC status synchronization in the HDMI connector framework. Reviewed-by: Maxime Ripard Link: https://patch.msgid.link/20260901-dw-hdmi-qp-scramb-v11-6-bc12954a0688@collabora.com Signed-off-by: Cristian Ciocaltea --- drivers/gpu/drm/drm_probe_helper.c | 15 ++++++++++++- include/drm/drm_connector.h | 6 +++++ include/drm/drm_modeset_helper_vtables.h | 28 ++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/drm_probe_helper.c b/drivers/gpu/drm/drm_probe_helper.c index e26ead5b5e58..856b40fdaac4 100644 --- a/drivers/gpu/drm/drm_probe_helper.c +++ b/drivers/gpu/drm/drm_probe_helper.c @@ -559,6 +559,7 @@ static int __drm_helper_update_and_validate(struct drm_connector *connector, int drm_helper_probe_single_connector_modes(struct drm_connector *connector, uint32_t maxX, uint32_t maxY) { + const struct drm_connector_helper_funcs *funcs = connector->helper_private; struct drm_device *dev = connector->dev; struct drm_display_mode *mode; int count = 0, ret; @@ -592,8 +593,20 @@ retry: connector->status = connector_status_connected; else connector->status = connector_status_disconnected; - if (connector->funcs->force) + + if (funcs && funcs->force_ctx) { + ret = funcs->force_ctx(connector, &ctx); + if (ret == -EDEADLK) { + connector->status = old_status; + drm_modeset_backoff(&ctx); + goto retry; + } else if (ret < 0) { + drm_dbg_kms(dev, "[CONNECTOR:%d:%s] force_ctx failed: %d\n", + connector->base.id, connector->name, ret); + } + } else if (connector->funcs->force) { connector->funcs->force(connector); + } } else { ret = drm_helper_probe_detect(connector, &ctx, true); diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h index be51265e8e26..f2be06f12b1a 100644 --- a/include/drm/drm_connector.h +++ b/include/drm/drm_connector.h @@ -1670,6 +1670,12 @@ struct drm_connector_funcs { * the sysfs interfaces or on the kernel cmdline. In that case the * @detect callback isn't called. * + * New drivers should implement + * &drm_connector_helper_funcs.force_ctx instead, which gets passed + * the modeset acquire context and thus allows taking additional + * locks. It takes precedence over this callback when both are + * implemented. + * * FIXME: * * Note that this hook is only called by the probe helper. It's not in diff --git a/include/drm/drm_modeset_helper_vtables.h b/include/drm/drm_modeset_helper_vtables.h index dc4c5e8733c3..60b30cc26940 100644 --- a/include/drm/drm_modeset_helper_vtables.h +++ b/include/drm/drm_modeset_helper_vtables.h @@ -935,6 +935,34 @@ struct drm_connector_helper_funcs { struct drm_modeset_acquire_ctx *ctx, bool force); + /** + * @force_ctx: + * + * This function is called to update internal encoder state when the + * connector is forced to a certain state by userspace, either through + * the sysfs interfaces or on the kernel cmdline. In that case the + * @detect_ctx callback isn't called. + * + * This is the atomic version of &drm_connector_funcs.force. When + * both are implemented, this one takes precedence and + * &drm_connector_funcs.force is not called. + * + * To avoid races against concurrent connector state updates, the + * helper libraries always call this with ctx set to a valid context, + * and &drm_mode_config.connection_mutex will always be locked with + * the ctx parameter set to this ctx. This allows taking additional + * locks as required. + * + * RETURNS: + * + * Zero on success, or a negative error code otherwise. This includes + * -EDEADLK when a lock acquisition needs to be restarted, in which + * case the helpers take care of backing off and calling this function + * again. + */ + int (*force_ctx)(struct drm_connector *connector, + struct drm_modeset_acquire_ctx *ctx); + /** * @mode_valid: * From f27fb7e315216b8bddf29297542c471368a672b4 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 1 Sep 2026 21:50:31 +0300 Subject: [PATCH 20/46] drm/connector: Add HDMI 2.0 scrambler infrastructure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the connector-level infrastructure to support HDMI 2.0 scrambling: - A drm_connector_hdmi_scrambler_supported() helper to report whether the source supports the scrambling capability - A .scrambler_{enable,disable}() pair of callbacks in drm_connector_hdmi_funcs to allow scrambling setup on the source side - A scrambler_needed flag to be managed by the hdmi state helpers based on the negotiated TMDS character rate and the source/sink scrambling capabilities - A scrambler_enabled flag to track whether scrambling is currently active - A delayed work item (scdc_work) to monitor sink-side scrambling status and retry the setup if the sink resets it - A scdc_work_initialized flag to support lazy initialization of the work item on the first scrambling enable and guard the teardown paths These are intended to be used by SCDC scrambling helpers to coordinate scrambling setup and teardown between the source driver and the DRM core. Tested-by: Maud Spierings Tested-by: Diederik de Haas # NanoPC-T6 LTS, Rock 5B Reviewed-by: Maxime Ripard Tested-by: Igor Paunovic # RK3588 Orange Pi 5 Plus, dual HDMI Tested-by: Robin Räber Link: https://patch.msgid.link/20260901-dw-hdmi-qp-scramb-v11-7-bc12954a0688@collabora.com Signed-off-by: Cristian Ciocaltea --- drivers/gpu/drm/drm_connector.c | 10 +++++ include/drm/drm_connector.h | 79 +++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+) diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c index 38cf853e867e..d94c86bfed86 100644 --- a/drivers/gpu/drm/drm_connector.c +++ b/drivers/gpu/drm/drm_connector.c @@ -602,6 +602,13 @@ int drmm_connector_hdmi_init(struct drm_device *dev, !hdmi_funcs->hdmi.write_infoframe) return -EINVAL; + if (hdmi_funcs->supported_hdmi_ver >= HDMI_VERSION_2_0 && + (!hdmi_funcs->scrambler_enable || + !hdmi_funcs->scrambler_disable)) { + drm_err(dev, "Scrambler callbacks missing for HDMI 2.x\n"); + return -EINVAL; + } + if (!(connector_type == DRM_MODE_CONNECTOR_HDMIA || connector_type == DRM_MODE_CONNECTOR_HDMIB)) return -EINVAL; @@ -919,6 +926,9 @@ void drm_connector_cleanup(struct drm_connector *connector) DRM_CONNECTOR_REGISTERED)) drm_connector_unregister(connector); + if (connector->hdmi.scdc_work_initialized) + cancel_delayed_work_sync(&connector->hdmi.scdc_work); + platform_device_unregister(connector->hdmi_audio.codec_pdev); if (connector->privacy_screen) { diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h index f2be06f12b1a..2ee7c59329aa 100644 --- a/include/drm/drm_connector.h +++ b/include/drm/drm_connector.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -1198,6 +1199,17 @@ struct drm_connector_hdmi_state { * @tmds_char_rate: TMDS Character Rate, in Hz. */ unsigned long long tmds_char_rate; + + /** + * @scrambler_needed: Whether HDMI 2.0 SCDC scrambling is required + * for the negotiated mode/bpc/format. + * + * Computed by drm_atomic_helper_connector_hdmi_check() according to + * the HDMI 2.0 specification: scrambling is mandatory above a 340 MHz + * TMDS character rate. Optional scrambling at lower rates is + * deliberately not requested by the helper. + */ + bool scrambler_needed; }; /** @@ -1548,6 +1560,36 @@ struct drm_connector_hdmi_funcs { */ const struct drm_edid *(*read_edid)(struct drm_connector *connector); + /** + * @scrambler_enable: + * + * The callback is invoked via @drm_connector_hdmi_enable_scrambling + * during commit to setup SCDC scrambling and high TMDS clock ratio on + * the source side. + * + * The @scrambler_enable callback is mandatory if HDMI 2.0 is to be + * supported. + * + * Returns: + * 0 on success, a negative error code otherwise + */ + int (*scrambler_enable)(struct drm_connector *connector); + + /** + * @scrambler_disable: + * + * The callback is invoked via @drm_connector_hdmi_disable_scrambling + * during commit to tear down SCDC scrambling and high TMDS clock ratio + * on the source side. + * + * The @scrambler_disable callback is mandatory if HDMI 2.0 is to be + * supported. + * + * Returns: + * 0 on success, a negative error code otherwise + */ + int (*scrambler_disable)(struct drm_connector *connector); + /** * @avi: * @@ -2178,6 +2220,25 @@ struct drm_connector_hdmi { */ unsigned long long max_tmds_char_rate; + /** + * @scrambler_enabled: Tracks whether HDMI 2.0 scrambler is currently enabled. + */ + bool scrambler_enabled; + + /** + * @scdc_work: Work item currently used to monitor sink-side scrambling + * status and retry setup if the sink resets it. + */ + struct delayed_work scdc_work; + + /** + * @scdc_work_initialized: Tracks whether @scdc_work has been set up via + * INIT_DELAYED_WORK(). The work item is initialized lazily on the first + * scrambling enable, so this guards the teardown paths against touching + * an uninitialized work item. + */ + bool scdc_work_initialized; + /** * @funcs: HDMI connector Control Functions and controller capabilities */ @@ -2769,6 +2830,24 @@ drm_connector_is_unregistered(struct drm_connector *connector) DRM_CONNECTOR_UNREGISTERED; } +/** + * drm_connector_hdmi_scrambler_supported - does connector support HDMI + * scrambling? + * @connector: DRM connector + * + * Checks whether or not @connector driver supports HDMI scrambling, based on + * the HDMI version advertised by the controller. + * + * Returns: + * True if the connector supports scrambling, false otherwise. + */ +static inline bool +drm_connector_hdmi_scrambler_supported(const struct drm_connector *connector) +{ + return connector->hdmi.funcs && + connector->hdmi.funcs->supported_hdmi_ver >= HDMI_VERSION_2_0; +} + void drm_connector_oob_hotplug_event(struct fwnode_handle *connector_fwnode, enum drm_connector_status status); enum drm_connector_color_format From 62cdbd8d50c510e8b61266e043c04870b7877b8b Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 1 Sep 2026 21:50:32 +0300 Subject: [PATCH 21/46] drm/display: scdc-helper: Add macro for connector-prefixed debug messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce the drm_scdc_dbg() wrapper over drm_dbg_kms() to help getting rid of the boilerplate around prefixing the debug messages with the connector information. Acked-by: Maxime Ripard Tested-by: Maud Spierings Tested-by: Diederik de Haas # NanoPC-T6 LTS, Rock 5B Tested-by: Igor Paunovic # RK3588 Orange Pi 5 Plus, dual HDMI Tested-by: Robin Räber Link: https://patch.msgid.link/20260901-dw-hdmi-qp-scramb-v11-8-bc12954a0688@collabora.com Signed-off-by: Cristian Ciocaltea --- drivers/gpu/drm/display/drm_scdc_helper.c | 24 +++++++++-------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/drivers/gpu/drm/display/drm_scdc_helper.c b/drivers/gpu/drm/display/drm_scdc_helper.c index d00da20862fc..1e7c41d4f926 100644 --- a/drivers/gpu/drm/display/drm_scdc_helper.c +++ b/drivers/gpu/drm/display/drm_scdc_helper.c @@ -58,6 +58,10 @@ #define SCDC_I2C_SLAVE_ADDRESS 0x54 +#define drm_scdc_dbg(connector, fmt, ...) \ + drm_dbg_kms((connector)->dev, "[CONNECTOR:%d:%s] " fmt, \ + (connector)->base.id, (connector)->name, ##__VA_ARGS__) + static const char *drm_scdc_frl_rate_str(enum drm_scdc_frl_rate rate) { switch (rate) { @@ -193,9 +197,7 @@ bool drm_scdc_get_scrambling_status(struct drm_connector *connector) ret = drm_scdc_readb(connector->ddc, SCDC_SCRAMBLER_STATUS, &status); if (ret < 0) { - drm_dbg_kms(connector->dev, - "[CONNECTOR:%d:%s] Failed to read scrambling status: %d\n", - connector->base.id, connector->name, ret); + drm_scdc_dbg(connector, "Failed to read scrambling status: %d\n", ret); return false; } @@ -223,9 +225,7 @@ bool drm_scdc_set_scrambling(struct drm_connector *connector, ret = drm_scdc_readb(connector->ddc, SCDC_TMDS_CONFIG, &config); if (ret < 0) { - drm_dbg_kms(connector->dev, - "[CONNECTOR:%d:%s] Failed to read TMDS config: %d\n", - connector->base.id, connector->name, ret); + drm_scdc_dbg(connector, "Failed to read TMDS config: %d\n", ret); return false; } @@ -236,9 +236,7 @@ bool drm_scdc_set_scrambling(struct drm_connector *connector, ret = drm_scdc_writeb(connector->ddc, SCDC_TMDS_CONFIG, config); if (ret < 0) { - drm_dbg_kms(connector->dev, - "[CONNECTOR:%d:%s] Failed to enable scrambling: %d\n", - connector->base.id, connector->name, ret); + drm_scdc_dbg(connector, "Failed to enable scrambling: %d\n", ret); return false; } @@ -283,9 +281,7 @@ bool drm_scdc_set_high_tmds_clock_ratio(struct drm_connector *connector, ret = drm_scdc_readb(connector->ddc, SCDC_TMDS_CONFIG, &config); if (ret < 0) { - drm_dbg_kms(connector->dev, - "[CONNECTOR:%d:%s] Failed to read TMDS config: %d\n", - connector->base.id, connector->name, ret); + drm_scdc_dbg(connector, "Failed to read TMDS config: %d\n", ret); return false; } @@ -296,9 +292,7 @@ bool drm_scdc_set_high_tmds_clock_ratio(struct drm_connector *connector, ret = drm_scdc_writeb(connector->ddc, SCDC_TMDS_CONFIG, config); if (ret < 0) { - drm_dbg_kms(connector->dev, - "[CONNECTOR:%d:%s] Failed to set TMDS clock ratio: %d\n", - connector->base.id, connector->name, ret); + drm_scdc_dbg(connector, "Failed to set TMDS clock ratio: %d\n", ret); return false; } From f57c2198380fcf475404c159c784d0ee70fa091b Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 1 Sep 2026 21:50:33 +0300 Subject: [PATCH 22/46] drm/display: scdc-helper: Add helper to set SCDC version information MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The HDMI 2.x specs mandate that compliant Sink devices report their SCDC version in the Sink Version register, which reads as 1 on any SCDC-capable sink. There is also a dedicated Source Version register. Writing it is not compulsory, but the spec advises that compliant Source devices do so, in which case the value must be 1. Add drm_scdc_set_source_version() to follow this recommendation. The Sink Version register is read first, both to log the advertised SCDC version and to guard against non-conformant devices: a sink reporting version 0 is either not SCDC-version-aware or broken, so writing the source version gains nothing and risks upsetting such hardware. In that case the write is skipped. The source version is passed as a parameter rather than hardcoded, as future spec revisions may define additional rules for the allowable version values. The written value is additionally clamped to the sink's reported version so the source never advertises a version the sink does not understand. Acked-by: Maxime Ripard Reviewed-by: Dmitry Baryshkov Tested-by: Maud Spierings Tested-by: Diederik de Haas # NanoPC-T6 LTS, Rock 5B Tested-by: Igor Paunovic # RK3588 Orange Pi 5 Plus, dual HDMI Tested-by: Robin Räber Link: https://patch.msgid.link/20260901-dw-hdmi-qp-scramb-v11-9-bc12954a0688@collabora.com Signed-off-by: Cristian Ciocaltea --- drivers/gpu/drm/display/drm_scdc_helper.c | 50 +++++++++++++++++++++-- include/drm/display/drm_scdc_helper.h | 2 + 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/display/drm_scdc_helper.c b/drivers/gpu/drm/display/drm_scdc_helper.c index 1e7c41d4f926..74388481fc55 100644 --- a/drivers/gpu/drm/display/drm_scdc_helper.c +++ b/drivers/gpu/drm/display/drm_scdc_helper.c @@ -22,12 +22,13 @@ */ #include -#include -#include -#include #include #include +#include +#include +#include #include +#include #include #include @@ -306,6 +307,49 @@ bool drm_scdc_set_high_tmds_clock_ratio(struct drm_connector *connector, } EXPORT_SYMBOL(drm_scdc_set_high_tmds_clock_ratio); +/** + * drm_scdc_set_source_version - set SCDC source version on the sink + * @connector: connector + * @ver: source version to advertise (per spec, 1) + * + * Reads the sink version for diagnostics and as a guard, then writes + * the source version unless the sink reports version 0. + * + * Returns: + * 0 on success or when skipped; a negative error code when either + * the read or the write failed. + */ +int drm_scdc_set_source_version(struct drm_connector *connector, u8 ver) +{ + u8 sink_ver; + int ret; + + ret = drm_scdc_readb(connector->ddc, SCDC_SINK_VERSION, &sink_ver); + if (ret) { + drm_scdc_dbg(connector, "Failed to read SCDC_SINK_VERSION: %d\n", ret); + return ret; + } + + drm_scdc_dbg(connector, "Sink reported SCDC ver. %u\n", sink_ver); + + /* + * Only advertise our source version to sinks that report a + * non-zero sink version. A sink reporting version 0 is either + * not SCDC-version-aware or non-conformant; writing the source + * version gains nothing and may upset broken hardware. + */ + if (sink_ver) { + ret = drm_scdc_writeb(connector->ddc, SCDC_SOURCE_VERSION, + min_t(u8, sink_ver, ver)); + if (ret) + drm_scdc_dbg(connector, + "Failed to write SCDC_SOURCE_VERSION: %d\n", ret); + } + + return ret; +} +EXPORT_SYMBOL(drm_scdc_set_source_version); + static void drm_scdc_parse_status0_flags(u8 val, struct drm_scdc_status_flags *flags) { diff --git a/include/drm/display/drm_scdc_helper.h b/include/drm/display/drm_scdc_helper.h index a3b20adaac7e..6a8be1f16ce0 100644 --- a/include/drm/display/drm_scdc_helper.h +++ b/include/drm/display/drm_scdc_helper.h @@ -168,6 +168,8 @@ bool drm_scdc_get_scrambling_status(struct drm_connector *connector); bool drm_scdc_set_scrambling(struct drm_connector *connector, bool enable); bool drm_scdc_set_high_tmds_clock_ratio(struct drm_connector *connector, bool set); +int drm_scdc_set_source_version(struct drm_connector *connector, u8 ver); + int drm_scdc_read_state(struct drm_connector *connector, struct drm_scdc_state *state); void drm_scdc_debugfs_init(struct drm_connector *connector, struct dentry *root); From d653818324edc79d8dd55f77496220d3849910d0 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 1 Sep 2026 21:50:34 +0300 Subject: [PATCH 23/46] drm/display: hdmi: Add HDMI 2.0 scrambling management helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add helpers to manage the full lifecycle of HDMI 2.0 SCDC scrambling on both source and sink sides: - drm_hdmi_mode_needs_scrambling(): checks whether a mode, bits per component and output format combination requires scrambling, which is needed when the resulting TMDS character rate exceeds the HDMI 1.3 maximum of 340 MHz - drm_connector_hdmi_enable_scrambling(): configures SCDC scrambling and high TMDS clock ratio and starts a periodic work item that monitors the sink's SCDC scrambling status, retrying setup when the sink loses state - drm_connector_hdmi_disable_scrambling(): tears down scrambling on both sides and cancels the monitoring work - drm_connector_hdmi_sync_scdc(): triggers a CRTC reset on reconnection to restore SCDC state lost during sink disconnects within an active display pipeline Tested-by: Maud Spierings Tested-by: Diederik de Haas # NanoPC-T6 LTS, Rock 5B Reviewed-by: Maxime Ripard Tested-by: Igor Paunovic # RK3588 Orange Pi 5 Plus, dual HDMI Tested-by: Robin Räber Link: https://patch.msgid.link/20260901-dw-hdmi-qp-scramb-v11-10-bc12954a0688@collabora.com Signed-off-by: Cristian Ciocaltea --- drivers/gpu/drm/display/drm_hdmi_helper.c | 297 ++++++++++++++++++++++ include/drm/display/drm_hdmi_helper.h | 15 ++ 2 files changed, 312 insertions(+) diff --git a/drivers/gpu/drm/display/drm_hdmi_helper.c b/drivers/gpu/drm/display/drm_hdmi_helper.c index 5cb0b033b171..bf1c7963b984 100644 --- a/drivers/gpu/drm/display/drm_hdmi_helper.c +++ b/drivers/gpu/drm/display/drm_hdmi_helper.c @@ -4,12 +4,20 @@ #include #include +#include + +#include +#include #include +#include #include #include +#include #include #include +#define DRM_HDMI_SCDC_POLL_DELAY_MS 1000 + static inline bool is_eotf_supported(u8 output_eotf, u8 sink_eotf) { return sink_eotf & BIT(output_eotf); @@ -426,3 +434,292 @@ drm_hdmi_acr_get_n_cts(unsigned long long tmds_char_rate, *out_cts = cts; } EXPORT_SYMBOL(drm_hdmi_acr_get_n_cts); + +/** + * drm_hdmi_mode_needs_scrambling() - Check if an HDMI mode requires scrambling + * @mode: Display mode to check + * @bpc: Bits per color component + * @fmt: Output pixel format + * + * Computes the TMDS character rate for the given mode, bits per component and + * output format, and checks whether it exceeds the HDMI 1.3 maximum TMDS + * character rate of 340 MHz. + * + * Returns: + * true if the mode requires scrambling, false otherwise. + */ +bool drm_hdmi_mode_needs_scrambling(const struct drm_display_mode *mode, + unsigned int bpc, + enum drm_output_color_format fmt) +{ + unsigned long long tmds_char_rate; + + tmds_char_rate = drm_hdmi_compute_mode_clock(mode, bpc, fmt); + + return tmds_char_rate > HDMI_1_3_TMDS_CHAR_RATE_MAX_HZ; +} +EXPORT_SYMBOL(drm_hdmi_mode_needs_scrambling); + +static bool drm_scdc_sink_supports_scrambling(struct drm_connector *connector) +{ + const struct drm_display_info *info = &connector->display_info; + + return info->is_hdmi && + info->hdmi.scdc.supported && + info->hdmi.scdc.scrambling.supported; +} + +static int drm_connector_hdmi_try_scrambling_setup(struct drm_connector *connector) +{ + bool done; + + done = drm_scdc_set_high_tmds_clock_ratio(connector, true); + if (!done) + return -EIO; + + done = drm_scdc_set_scrambling(connector, true); + if (!done) + return -EIO; + + if (READ_ONCE(connector->hdmi.scrambler_enabled)) + schedule_delayed_work(&connector->hdmi.scdc_work, + msecs_to_jiffies(DRM_HDMI_SCDC_POLL_DELAY_MS)); + + return 0; +} + +static void drm_connector_hdmi_scdc_work(struct work_struct *work) +{ + struct drm_connector *connector; + struct drm_connector_hdmi *hdmi; + + hdmi = container_of(to_delayed_work(work), struct drm_connector_hdmi, + scdc_work); + connector = container_of(hdmi, struct drm_connector, hdmi); + + /* + * The connector, and hence this work item, may outlive the source + * driver, e.g. when userspace keeps a DRM file descriptor open past + * unbind. Never poke the DDC bus after the connector got unregistered, + * as the underlying I2C adapter may already be gone. + */ + if (drm_connector_is_unregistered(connector)) + return; + + if (READ_ONCE(connector->hdmi.scrambler_enabled) && + !drm_scdc_get_scrambling_status(connector)) + drm_connector_hdmi_try_scrambling_setup(connector); +} + +/** + * drm_connector_hdmi_enable_scrambling() - enable scrambling and monitor SCDC status + * @connector: connector + * @conn_state: connector state + * + * Enables scrambling and high TMDS clock ratio on both source and sink sides. + * Additionally, use a delayed work item to monitor the scrambling status on + * the sink side and retry the operation, as some displays refuse to set the + * scrambling bit right away. + * + * The work item is cancelled by drm_connector_hdmi_disable_scrambling(), hence + * source drivers must ensure the display pipeline is shut down on unbind, e.g. + * via drm_atomic_helper_shutdown(). Otherwise the work may outlive the driver's + * devres-managed resources, since the connector itself is only cleaned up when + * the last reference to the DRM device is dropped. + * + * Returns: + * Zero if scrambling is set successfully, an error code otherwise. + */ +int drm_connector_hdmi_enable_scrambling(struct drm_connector *connector, + const struct drm_connector_state *conn_state) +{ + struct drm_connector_hdmi *hdmi = &connector->hdmi; + struct drm_device *dev = connector->dev; + int ret; + + if (!conn_state) + return -EINVAL; + + if (!conn_state->hdmi.scrambler_needed) + return 0; + + if (!drm_connector_hdmi_scrambler_supported(connector)) { + drm_dbg_kms(dev, "Source doesn't support scrambling.\n"); + return -EINVAL; + } + + if (!drm_scdc_sink_supports_scrambling(connector)) { + drm_dbg_kms(dev, "Sink doesn't support scrambling.\n"); + return -EINVAL; + } + + if (!connector->ddc) + return -EINVAL; + + drm_dbg_kms(dev, "Enabling scrambling\n"); + + if (!hdmi->scdc_work_initialized) { + INIT_DELAYED_WORK(&hdmi->scdc_work, + drm_connector_hdmi_scdc_work); + hdmi->scdc_work_initialized = true; + } + + WRITE_ONCE(hdmi->scrambler_enabled, true); + + ret = drm_connector_hdmi_try_scrambling_setup(connector); + if (!ret) + ret = hdmi->funcs->scrambler_enable(connector); + + if (ret) { + WRITE_ONCE(hdmi->scrambler_enabled, false); + cancel_delayed_work_sync(&hdmi->scdc_work); + + drm_scdc_set_scrambling(connector, false); + drm_scdc_set_high_tmds_clock_ratio(connector, false); + } + + return ret; +} +EXPORT_SYMBOL(drm_connector_hdmi_enable_scrambling); + +/** + * drm_connector_hdmi_disable_scrambling() - disable scrambling and SCDC monitoring + * @connector: connector + * + * Disables scrambling and high TMDS clock ratio on both source and sink sides. + * Also cancels the SCDC status monitoring work item, if it is still pending. + * + * Returns: + * Zero if scrambling is reset successfully, an error code otherwise. + */ +int drm_connector_hdmi_disable_scrambling(struct drm_connector *connector) +{ + struct drm_connector_hdmi *hdmi = &connector->hdmi; + struct drm_device *dev = connector->dev; + + if (!READ_ONCE(hdmi->scrambler_enabled)) + return 0; + + drm_dbg_kms(dev, "Disabling scrambling\n"); + + WRITE_ONCE(hdmi->scrambler_enabled, false); + + /* + * A driver may force scrambler_enabled at init to trigger a disable + * at boot (e.g. vc4), bypassing the enable path that sets up the work + * item. Only cancel it once it has actually been initialized. + */ + if (hdmi->scdc_work_initialized) + cancel_delayed_work_sync(&hdmi->scdc_work); + + if (connector->ddc && + connector->status == connector_status_connected && + drm_scdc_sink_supports_scrambling(connector)) { + drm_scdc_set_scrambling(connector, false); + drm_scdc_set_high_tmds_clock_ratio(connector, false); + } + + return hdmi->funcs->scrambler_disable(connector); +} +EXPORT_SYMBOL(drm_connector_hdmi_disable_scrambling); + +/** + * drm_connector_hdmi_sync_scdc() - resync the sink-side SCDC upon reconnect + * @connector: connector + * @plugged: connector plugged status event + * @ctx: initialized lock acquisition context + * + * When receiving hotplug disconnect/reconnect event, while the display is + * still active (CRTC enabled), the SCDC status on the sink side is reset + * and must be explicitly restored. + * + * The typical solution for this is to trigger an empty modeset in + * drm_connector_helper_funcs.detect_ctx(), which is what this helper does + * by triggering a CRTC reset on reconnection. + * + * When making use of the HDMI connector framework, this is automatically + * triggered via drm_atomic_helper_connector_hdmi_hotplug(). + * + * Returns: + * Zero on success, an error code otherwise, including -EDEADLK. + */ +int drm_connector_hdmi_sync_scdc(struct drm_connector *connector, bool plugged, + struct drm_modeset_acquire_ctx *ctx) +{ + struct drm_connector_hdmi *hdmi = &connector->hdmi; + struct drm_device *dev = connector->dev; + struct drm_connector_state *conn_state; + struct drm_crtc *crtc; + u8 config; + int ret; + + if (!connector || !plugged || !ctx) + return 0; + + /* + * This is normally part of .detect_ctx() call path, which already holds + * connection_mutex through @ctx. However, re-acquiring it with the + * same context is a no-op and makes the helper safe under any caller. + */ + ret = drm_modeset_lock(&connector->dev->mode_config.connection_mutex, ctx); + if (ret) + return ret; + + /* TODO: Also handle HDMI 2.1 FRL link training */ + + if (!drm_connector_hdmi_scrambler_supported(connector) || + !READ_ONCE(hdmi->scrambler_enabled)) + return 0; + + conn_state = connector->state; + if (!conn_state) + return 0; + + crtc = conn_state->crtc; + if (!crtc) + return 0; + + ret = drm_modeset_lock(&crtc->mutex, ctx); + if (ret) + return ret; + + if (!crtc->state || !crtc->state->active) + return 0; + + if (!connector->ddc || !drm_scdc_sink_supports_scrambling(connector)) + return 0; + + if (conn_state->commit && + !try_wait_for_completion(&conn_state->commit->hw_done)) + return 0; + + ret = drm_scdc_readb(connector->ddc, SCDC_TMDS_CONFIG, &config); + if (ret) { + drm_dbg_kms(dev, "Failed to read TMDS config: %d\n", ret); + return ret; + } + + if (config & SCDC_SCRAMBLING_ENABLE) + return 0; + + /* + * HDMI 2.0 requires that one should not send scrambled data prior + * to configuring the sink scrambling, and that TMDS clock/data + * transmission should be suspended when changing the TMDS clock rate + * in the sink. So let's just do a full modeset here, even though some + * sinks would be perfectly happy if were to just reconfigure the SCDC + * settings on the fly. + * + * The disable/re-enable cycle triggered by the reset should call + * drm_connector_hdmi_enable_scrambling() during re-enable, thus + * properly configuring the sink before data transmission resumes. + */ + drm_dbg_kms(dev, "Resetting CRTC to restore SCDC status\n"); + + ret = drm_atomic_helper_reset_crtc(crtc, ctx); + if (ret && ret != -EDEADLK) + drm_dbg_kms(dev, "Failed to reset CRTC: %d\n", ret); + + return ret; +} +EXPORT_SYMBOL(drm_connector_hdmi_sync_scdc); diff --git a/include/drm/display/drm_hdmi_helper.h b/include/drm/display/drm_hdmi_helper.h index 9c31ed90516b..2cdd6e3bc090 100644 --- a/include/drm/display/drm_hdmi_helper.h +++ b/include/drm/display/drm_hdmi_helper.h @@ -8,6 +8,7 @@ struct drm_connector; struct drm_connector_state; struct drm_display_mode; +struct drm_modeset_acquire_ctx; enum drm_output_color_format; void @@ -35,4 +36,18 @@ drm_hdmi_acr_get_n_cts(unsigned long long tmds_char_rate, unsigned int *out_n, unsigned int *out_cts); +bool +drm_hdmi_mode_needs_scrambling(const struct drm_display_mode *mode, + unsigned int bpc, enum drm_output_color_format fmt); + +int +drm_connector_hdmi_enable_scrambling(struct drm_connector *connector, + const struct drm_connector_state *conn_state); +int +drm_connector_hdmi_disable_scrambling(struct drm_connector *connector); + +int +drm_connector_hdmi_sync_scdc(struct drm_connector *connector, bool plugged, + struct drm_modeset_acquire_ctx *ctx); + #endif From bc4dcf6f93778b182ad90233d3049acd9227136c Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 1 Sep 2026 21:50:35 +0300 Subject: [PATCH 24/46] drm/display: hdmi: Advertise SCDC source version when scrambling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The HDMI 2.0 spec advises that compliant Source devices report their SCDC version in the Source Version register. Do so when enabling scrambling by calling drm_scdc_set_source_version(). Failures are non-fatal: SCDC version negotiation is purely informational and does not gate scrambling or any high-bitrate functionality, so a flaky DDC bus should not block scrambling setup. Acked-by: Maxime Ripard Tested-by: Maud Spierings Tested-by: Diederik de Haas # NanoPC-T6 LTS, Rock 5B Tested-by: Igor Paunovic # RK3588 Orange Pi 5 Plus, dual HDMI Tested-by: Robin Räber Link: https://patch.msgid.link/20260901-dw-hdmi-qp-scramb-v11-11-bc12954a0688@collabora.com Signed-off-by: Cristian Ciocaltea --- drivers/gpu/drm/display/drm_hdmi_helper.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/gpu/drm/display/drm_hdmi_helper.c b/drivers/gpu/drm/display/drm_hdmi_helper.c index bf1c7963b984..f85cce3d6254 100644 --- a/drivers/gpu/drm/display/drm_hdmi_helper.c +++ b/drivers/gpu/drm/display/drm_hdmi_helper.c @@ -16,6 +16,7 @@ #include #include +#define DRM_HDMI_SCDC_SOURCE_VERSION 1 #define DRM_HDMI_SCDC_POLL_DELAY_MS 1000 static inline bool is_eotf_supported(u8 output_eotf, u8 sink_eotf) @@ -556,6 +557,12 @@ int drm_connector_hdmi_enable_scrambling(struct drm_connector *connector, if (!connector->ddc) return -EINVAL; + /* + * Advertise our SCDC source version. This is purely informational and + * does not gate scrambling, so failures are non-fatal. + */ + drm_scdc_set_source_version(connector, DRM_HDMI_SCDC_SOURCE_VERSION); + drm_dbg_kms(dev, "Enabling scrambling\n"); if (!hdmi->scdc_work_initialized) { From 44fd815ec6ab5ae8e01de26529fe867bd0c07da1 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 1 Sep 2026 21:50:39 +0300 Subject: [PATCH 25/46] drm/bridge: Remove redundant error check in drm_bridge_helper_reset_crtc() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the no-op error check after drm_atomic_helper_reset_crtc() since the goto target is the immediately following label and the return value is already propagated correctly without it. Reviewed-by: Dmitry Baryshkov Tested-by: Maud Spierings Tested-by: Diederik de Haas # NanoPC-T6 LTS, Rock 5B Tested-by: Igor Paunovic # RK3588 Orange Pi 5 Plus, dual HDMI Tested-by: Robin Räber Link: https://patch.msgid.link/20260901-dw-hdmi-qp-scramb-v11-15-bc12954a0688@collabora.com Signed-off-by: Cristian Ciocaltea --- drivers/gpu/drm/drm_bridge_helper.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/gpu/drm/drm_bridge_helper.c b/drivers/gpu/drm/drm_bridge_helper.c index 420f29cf3e54..0a3c8fee66b3 100644 --- a/drivers/gpu/drm/drm_bridge_helper.c +++ b/drivers/gpu/drm/drm_bridge_helper.c @@ -50,8 +50,6 @@ int drm_bridge_helper_reset_crtc(struct drm_bridge *bridge, crtc = connector->state->crtc; ret = drm_atomic_helper_reset_crtc(crtc, ctx); - if (ret) - goto out; out: drm_modeset_unlock(&dev->mode_config.connection_mutex); From 2e37ecb36d8e989a514d5f921eadd91ed178861d Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 1 Sep 2026 21:50:40 +0300 Subject: [PATCH 26/46] drm/bridge: Add bridge ops for source-side HDMI 2.0 scrambling Bridges advertising HDMI 2.0 must be able to enable and disable source-side scrambling and the high TMDS clock ratio when the configured mode exceeds 340 MHz. Add .hdmi_scrambler_{enable|disable}() callbacks covering the source-side programming only; sink-side SCDC negotiation remains the job of the HDMI connector helpers. Reviewed-by: Dmitry Baryshkov Link: https://patch.msgid.link/20260901-dw-hdmi-qp-scramb-v11-16-bc12954a0688@collabora.com Signed-off-by: Cristian Ciocaltea --- include/drm/drm_bridge.h | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h index 5a5a25995471..77b3ffe69230 100644 --- a/include/drm/drm_bridge.h +++ b/include/drm/drm_bridge.h @@ -561,6 +561,41 @@ struct drm_bridge_funcs { const struct drm_display_mode *mode, unsigned long long tmds_rate); + /** + * @hdmi_scrambler_enable: + * + * Enable source-side scrambling and the high TMDS clock ratio for the + * mode currently being applied. + * + * This only programs the source side. Sink-side SCDC configuration is + * handled by the HDMI connector helpers. + * + * Mandatory for bridges that set the DRM_BRIDGE_OP_HDMI flag in their + * &drm_bridge->ops and advertise HDMI_VERSION_2_0 or later via + * &drm_bridge->supported_hdmi_ver; unused otherwise. + * + * Returns: + * 0 on success, a negative error code otherwise. + */ + int (*hdmi_scrambler_enable)(struct drm_bridge *bridge); + + /** + * @hdmi_scrambler_disable: + * + * Disable source-side scrambling and the high TMDS clock ratio. + * + * This only programs the source side. Sink-side SCDC configuration is + * handled by the HDMI connector helpers. + * + * Mandatory for bridges that set the DRM_BRIDGE_OP_HDMI flag in their + * &drm_bridge->ops and advertise HDMI_VERSION_2_0 or later via + * &drm_bridge->supported_hdmi_ver; unused otherwise. + * + * Returns: + * 0 on success, a negative error code otherwise. + */ + int (*hdmi_scrambler_disable)(struct drm_bridge *bridge); + /** * @hdmi_clear_avi_infoframe: * From 4cf0eae2fc53b6933fe930450b46b20b0cfac4f2 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 1 Sep 2026 21:50:41 +0300 Subject: [PATCH 27/46] drm/display: bridge_connector: Use cached connector status in .get_modes() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the active drm_bridge_connector_detect() call in get_modes() with a read of the already-cached connector->status. The .get_modes() callback is only invoked from drm_helper_probe_single_connector_modes(), which has already retrieved the connector status. Calling detect again is redundant and triggers a duplicate hotplug event. This is also a prerequisite for switching to the .detect_ctx() hook, which requires a drm_modeset_acquire_ctx not available in the .get_modes() path. Reviewed-by: Dmitry Baryshkov Tested-by: Maud Spierings Tested-by: Diederik de Haas # NanoPC-T6 LTS, Rock 5B Tested-by: Igor Paunovic # RK3588 Orange Pi 5 Plus, dual HDMI Tested-by: Robin Räber Link: https://patch.msgid.link/20260901-dw-hdmi-qp-scramb-v11-17-bc12954a0688@collabora.com Signed-off-by: Cristian Ciocaltea --- drivers/gpu/drm/display/drm_bridge_connector.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c index 63cdc6eab132..a73c177a3c10 100644 --- a/drivers/gpu/drm/display/drm_bridge_connector.c +++ b/drivers/gpu/drm/display/drm_bridge_connector.c @@ -328,12 +328,10 @@ static const struct drm_connector_funcs drm_bridge_connector_funcs = { static int drm_bridge_connector_get_modes_edid(struct drm_connector *connector, struct drm_bridge *bridge) { - enum drm_connector_status status; const struct drm_edid *drm_edid; int n; - status = drm_bridge_connector_detect(connector, false); - if (status != connector_status_connected) + if (connector->status != connector_status_connected) goto no_edid; drm_edid = drm_bridge_edid_read(bridge, connector); From a23d96ee6afbe7148c462f1ce017425628b4db45 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 1 Sep 2026 21:50:42 +0300 Subject: [PATCH 28/46] drm/display: bridge_connector: Switch to .detect_ctx() connector helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the .detect() connector_funcs callback with the atomic-aware .detect_ctx() connector_helper_funcs hook. This is preparatory work to allow propagating the modeset acquire context through drm_atomic_helper_connector_hdmi_hotplug() to the HDMI connector framework, enabling SCDC state recovery via CRTC reset when the sink reconnects during an active display pipeline. Reviewed-by: Dmitry Baryshkov Tested-by: Maud Spierings Tested-by: Diederik de Haas # NanoPC-T6 LTS, Rock 5B Tested-by: Igor Paunovic # RK3588 Orange Pi 5 Plus, dual HDMI Tested-by: Robin Räber Link: https://patch.msgid.link/20260901-dw-hdmi-qp-scramb-v11-18-bc12954a0688@collabora.com Signed-off-by: Cristian Ciocaltea --- .../gpu/drm/display/drm_bridge_connector.c | 69 ++++++++++--------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c index a73c177a3c10..4ad3cd107ecd 100644 --- a/drivers/gpu/drm/display/drm_bridge_connector.c +++ b/drivers/gpu/drm/display/drm_bridge_connector.c @@ -208,39 +208,6 @@ static void drm_bridge_connector_disable_hpd(struct drm_connector *connector) * Bridge Connector Functions */ -static enum drm_connector_status -drm_bridge_connector_detect(struct drm_connector *connector, bool force) -{ - struct drm_bridge_connector *bridge_connector = - to_drm_bridge_connector(connector); - struct drm_bridge *detect = bridge_connector->bridge_detect; - struct drm_bridge *hdmi = bridge_connector->bridge_hdmi; - enum drm_connector_status status; - - if (detect) { - status = detect->funcs->detect(detect, connector); - - if (hdmi) - drm_atomic_helper_connector_hdmi_hotplug(connector, status); - - drm_bridge_connector_hpd_notify(connector, status); - } else { - switch (connector->connector_type) { - case DRM_MODE_CONNECTOR_DPI: - case DRM_MODE_CONNECTOR_LVDS: - case DRM_MODE_CONNECTOR_DSI: - case DRM_MODE_CONNECTOR_eDP: - status = connector_status_connected; - break; - default: - status = connector_status_unknown; - break; - } - } - - return status; -} - static void drm_bridge_connector_force(struct drm_connector *connector) { struct drm_bridge_connector *bridge_connector = @@ -310,7 +277,6 @@ drm_bridge_connector_color_format(const struct drm_connector_state *conn_state) } static const struct drm_connector_funcs drm_bridge_connector_funcs = { - .detect = drm_bridge_connector_detect, .force = drm_bridge_connector_force, .fill_modes = drm_helper_probe_single_connector_modes, .atomic_create_state = drm_bridge_connector_create_state, @@ -325,6 +291,40 @@ static const struct drm_connector_funcs drm_bridge_connector_funcs = { * Bridge Connector Helper Functions */ +static int drm_bridge_connector_detect_ctx(struct drm_connector *connector, + struct drm_modeset_acquire_ctx *ctx, + bool force) +{ + struct drm_bridge_connector *bridge_connector = + to_drm_bridge_connector(connector); + struct drm_bridge *detect = bridge_connector->bridge_detect; + struct drm_bridge *hdmi = bridge_connector->bridge_hdmi; + enum drm_connector_status status; + + if (detect) { + status = detect->funcs->detect(detect, connector); + + if (hdmi) + drm_atomic_helper_connector_hdmi_hotplug(connector, status); + + drm_bridge_connector_hpd_notify(connector, status); + } else { + switch (connector->connector_type) { + case DRM_MODE_CONNECTOR_DPI: + case DRM_MODE_CONNECTOR_LVDS: + case DRM_MODE_CONNECTOR_DSI: + case DRM_MODE_CONNECTOR_eDP: + status = connector_status_connected; + break; + default: + status = connector_status_unknown; + break; + } + } + + return status; +} + static int drm_bridge_connector_get_modes_edid(struct drm_connector *connector, struct drm_bridge *bridge) { @@ -416,6 +416,7 @@ static int drm_bridge_connector_atomic_check(struct drm_connector *connector, static const struct drm_connector_helper_funcs drm_bridge_connector_helper_funcs = { .get_modes = drm_bridge_connector_get_modes, + .detect_ctx = drm_bridge_connector_detect_ctx, .mode_valid = drm_bridge_connector_mode_valid, .enable_hpd = drm_bridge_connector_enable_hpd, .disable_hpd = drm_bridge_connector_disable_hpd, From 8e5aa7fe23dbe6e3cba93f4eb1f5f56e2ad70ee7 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 1 Sep 2026 21:50:43 +0300 Subject: [PATCH 29/46] drm/display: bridge_connector: Wire up HDMI 2.0 scrambler callbacks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Connect the bridge connector's .scrambler_{enable|disable} callbacks to the underlying bridge's .hdmi_scrambler_{enable|disable} funcs when HDMI 2.0 conformance is advertised. This completes the bridge connector plumbing so that the SCDC scrambling helpers can control source-side scrambling through the bridge chain. Tested-by: Maud Spierings Tested-by: Diederik de Haas # NanoPC-T6 LTS, Rock 5B Reviewed-by: Maxime Ripard Tested-by: Igor Paunovic # RK3588 Orange Pi 5 Plus, dual HDMI Tested-by: Robin Räber Link: https://patch.msgid.link/20260901-dw-hdmi-qp-scramb-v11-19-bc12954a0688@collabora.com Signed-off-by: Cristian Ciocaltea --- .../gpu/drm/display/drm_bridge_connector.c | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c index 4ad3cd107ecd..0f85ca2bf209 100644 --- a/drivers/gpu/drm/display/drm_bridge_connector.c +++ b/drivers/gpu/drm/display/drm_bridge_connector.c @@ -577,6 +577,32 @@ static int drm_bridge_connector_write_spd_infoframe(struct drm_connector *connec return bridge->funcs->hdmi_write_spd_infoframe(bridge, buffer, len); } +static int drm_bridge_connector_scrambler_enable(struct drm_connector *connector) +{ + struct drm_bridge_connector *bridge_connector = + to_drm_bridge_connector(connector); + struct drm_bridge *bridge; + + bridge = bridge_connector->bridge_hdmi; + if (!bridge) + return -EINVAL; + + return bridge->funcs->hdmi_scrambler_enable(bridge); +} + +static int drm_bridge_connector_scrambler_disable(struct drm_connector *connector) +{ + struct drm_bridge_connector *bridge_connector = + to_drm_bridge_connector(connector); + struct drm_bridge *bridge; + + bridge = bridge_connector->bridge_hdmi; + if (!bridge) + return -EINVAL; + + return bridge->funcs->hdmi_scrambler_disable(bridge); +} + static const struct drm_edid * drm_bridge_connector_read_edid(struct drm_connector *connector) { @@ -604,7 +630,7 @@ static const struct drm_connector_hdmi_funcs drm_bridge_connector_hdmi_funcs = { .clear_infoframe = drm_bridge_connector_clear_hdmi_infoframe, .write_infoframe = drm_bridge_connector_write_hdmi_infoframe, }, - /* audio, hdr_drm and spd are set dynamically during init */ + /* scrambler, audio, hdr_drm and spd are set dynamically during init */ }; static const struct drm_connector_infoframe_funcs drm_bridge_connector_hdmi_audio_infoframe = { @@ -908,6 +934,11 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm, !bridge->funcs->hdmi_clear_spd_infoframe)) return ERR_PTR(-EINVAL); + if (bridge->supported_hdmi_ver >= HDMI_VERSION_2_0 && + (!bridge->funcs->hdmi_scrambler_enable || + !bridge->funcs->hdmi_scrambler_disable)) + return ERR_PTR(-EINVAL); + bridge_connector->bridge_hdmi = drm_bridge_get(bridge); } @@ -1027,6 +1058,13 @@ struct drm_connector *drm_bridge_connector_init(struct drm_device *drm, bridge_connector->hdmi_funcs.spd = drm_bridge_connector_hdmi_spd_infoframe; + if (bridge_connector->bridge_hdmi->supported_hdmi_ver >= HDMI_VERSION_2_0) { + bridge_connector->hdmi_funcs.scrambler_enable = + drm_bridge_connector_scrambler_enable; + bridge_connector->hdmi_funcs.scrambler_disable = + drm_bridge_connector_scrambler_disable; + } + ret = drmm_connector_hdmi_init(drm, connector, &drm_bridge_connector_funcs, &bridge_connector->hdmi_funcs, From 0f7bb8f4d26abbc27e78e633b44cac2279f0ce1e Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 1 Sep 2026 21:50:44 +0300 Subject: [PATCH 30/46] drm/display: hdmi-state-helper: Add source TMDS rate validation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Validate the computed TMDS character rate against connector->hdmi.max_tmds_char_rate, when provided by the driver/source. This gives HDMI connectors common support for rejecting modes whose TMDS character rate exceeds the source supported limit, while still allowing drivers with custom validation requirements to implement their own tmds_char_rate_valid() callback. Tested-by: Maud Spierings Tested-by: Diederik de Haas # NanoPC-T6 LTS, Rock 5B Reviewed-by: Maxime Ripard Tested-by: Igor Paunovic # RK3588 Orange Pi 5 Plus, dual HDMI Tested-by: Robin Räber Link: https://patch.msgid.link/20260901-dw-hdmi-qp-scramb-v11-20-bc12954a0688@collabora.com Signed-off-by: Cristian Ciocaltea --- drivers/gpu/drm/display/drm_hdmi_state_helper.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/gpu/drm/display/drm_hdmi_state_helper.c b/drivers/gpu/drm/display/drm_hdmi_state_helper.c index bccc5d7c8eed..3ef6de31dc02 100644 --- a/drivers/gpu/drm/display/drm_hdmi_state_helper.c +++ b/drivers/gpu/drm/display/drm_hdmi_state_helper.c @@ -562,6 +562,10 @@ hdmi_clock_valid(const struct drm_connector *connector, if (info->max_tmds_clock && clock > info->max_tmds_clock * 1000) return MODE_CLOCK_HIGH; + if (connector->hdmi.max_tmds_char_rate && + clock > connector->hdmi.max_tmds_char_rate) + return MODE_CLOCK_HIGH; + if (funcs && funcs->tmds_char_rate_valid) { enum drm_mode_status status; From 43c92f4c887e6ca6bdac1f80ed8087226c71b756 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 1 Sep 2026 21:50:45 +0300 Subject: [PATCH 31/46] drm/display: hdmi-state-helper: Pass acquire ctx to hotplug helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In preparation for supporting SCDC status synchronization on hotplug events, extend drm_atomic_helper_connector_hdmi_hotplug() and drm_atomic_helper_connector_hdmi_update() to take a struct drm_modeset_acquire_ctx argument. This will be used when a CRTC reset is required. Additionally, change their return type from void to int to allow propagation of errors such as -EDEADLK caused by lock contention. Update existing callers accordingly. Note that in the VC4 case, the return code is ignored, as it cannot fail: being in the .detect_ctx() call path, the connection mutex is already held through ctx, and it bails out early because the driver relies on a local SCDC state-management implementation, and therefore does not yet expose the scrambler capability through the common helpers. Tested-by: Diederik de Haas # NanoPC-T6 LTS, Rock 5B Reviewed-by: Maxime Ripard Tested-by: Igor Paunovic # RK3588 Orange Pi 5 Plus, dual HDMI Tested-by: Robin Räber Link: https://patch.msgid.link/20260901-dw-hdmi-qp-scramb-v11-21-bc12954a0688@collabora.com Signed-off-by: Cristian Ciocaltea --- .../gpu/drm/display/drm_bridge_connector.c | 8 +++++-- .../gpu/drm/display/drm_hdmi_state_helper.c | 21 +++++++++++++------ drivers/gpu/drm/vc4/vc4_hdmi.c | 2 +- include/drm/display/drm_hdmi_state_helper.h | 6 ++++-- 4 files changed, 26 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c index 0f85ca2bf209..b5a6e2e8bcd5 100644 --- a/drivers/gpu/drm/display/drm_bridge_connector.c +++ b/drivers/gpu/drm/display/drm_bridge_connector.c @@ -300,12 +300,16 @@ static int drm_bridge_connector_detect_ctx(struct drm_connector *connector, struct drm_bridge *detect = bridge_connector->bridge_detect; struct drm_bridge *hdmi = bridge_connector->bridge_hdmi; enum drm_connector_status status; + int ret; if (detect) { status = detect->funcs->detect(detect, connector); - if (hdmi) - drm_atomic_helper_connector_hdmi_hotplug(connector, status); + if (hdmi) { + ret = drm_atomic_helper_connector_hdmi_hotplug(connector, ctx, status); + if (ret == -EDEADLK) + return ret; + } drm_bridge_connector_hpd_notify(connector, status); } else { diff --git a/drivers/gpu/drm/display/drm_hdmi_state_helper.c b/drivers/gpu/drm/display/drm_hdmi_state_helper.c index 3ef6de31dc02..485fcca2de4b 100644 --- a/drivers/gpu/drm/display/drm_hdmi_state_helper.c +++ b/drivers/gpu/drm/display/drm_hdmi_state_helper.c @@ -1356,8 +1356,9 @@ drm_atomic_helper_connector_hdmi_clear_audio_infoframe(struct drm_connector *con } EXPORT_SYMBOL(drm_atomic_helper_connector_hdmi_clear_audio_infoframe); -static void +static int drm_atomic_helper_connector_hdmi_update(struct drm_connector *connector, + struct drm_modeset_acquire_ctx *ctx, enum drm_connector_status status) { const struct drm_edid *drm_edid; @@ -1367,7 +1368,7 @@ drm_atomic_helper_connector_hdmi_update(struct drm_connector *connector, drm_connector_hdmi_audio_plugged_notify(connector, false); drm_edid_connector_update(connector, NULL); drm_connector_cec_phys_addr_invalidate(connector); - return; + return 0; } if (connector->hdmi.funcs->read_edid) @@ -1384,20 +1385,28 @@ drm_atomic_helper_connector_hdmi_update(struct drm_connector *connector, drm_connector_hdmi_audio_plugged_notify(connector, true); drm_connector_cec_phys_addr_set(connector); } + + return 0; } /** * drm_atomic_helper_connector_hdmi_hotplug - Handle the hotplug event for the HDMI connector * @connector: A pointer to the HDMI connector + * @ctx: Lock acquisition context to be used for resetting CRTC * @status: Connection status * * This function should be called as a part of the .detect() / .detect_ctx() * callbacks for all status changes. + * + * Returns: + * Zero on success, error code on failure. + * If @ctx is set, it might also return -EDEADLK. */ -void drm_atomic_helper_connector_hdmi_hotplug(struct drm_connector *connector, - enum drm_connector_status status) +int drm_atomic_helper_connector_hdmi_hotplug(struct drm_connector *connector, + struct drm_modeset_acquire_ctx *ctx, + enum drm_connector_status status) { - drm_atomic_helper_connector_hdmi_update(connector, status); + return drm_atomic_helper_connector_hdmi_update(connector, ctx, status); } EXPORT_SYMBOL(drm_atomic_helper_connector_hdmi_hotplug); @@ -1412,6 +1421,6 @@ EXPORT_SYMBOL(drm_atomic_helper_connector_hdmi_hotplug); */ void drm_atomic_helper_connector_hdmi_force(struct drm_connector *connector) { - drm_atomic_helper_connector_hdmi_update(connector, connector->status); + drm_atomic_helper_connector_hdmi_update(connector, NULL, connector->status); } EXPORT_SYMBOL(drm_atomic_helper_connector_hdmi_force); diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index 5bb508a87b40..3a90dbb61f4e 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -375,7 +375,7 @@ static void vc4_hdmi_handle_hotplug(struct vc4_hdmi *vc4_hdmi, * the lock for now. */ - drm_atomic_helper_connector_hdmi_hotplug(connector, status); + drm_atomic_helper_connector_hdmi_hotplug(connector, ctx, status); if (status != connector_status_connected) return; diff --git a/include/drm/display/drm_hdmi_state_helper.h b/include/drm/display/drm_hdmi_state_helper.h index 596c57d9d250..2439bad15c4b 100644 --- a/include/drm/display/drm_hdmi_state_helper.h +++ b/include/drm/display/drm_hdmi_state_helper.h @@ -8,6 +8,7 @@ struct drm_atomic_commit; struct drm_connector; struct drm_connector_state; struct drm_display_mode; +struct drm_modeset_acquire_ctx; struct hdmi_audio_infoframe; enum drm_connector_status; @@ -23,8 +24,9 @@ int drm_atomic_helper_connector_hdmi_update_audio_infoframe(struct drm_connector int drm_atomic_helper_connector_hdmi_clear_audio_infoframe(struct drm_connector *connector); int drm_atomic_helper_connector_hdmi_update_infoframes(struct drm_connector *connector, struct drm_atomic_commit *state); -void drm_atomic_helper_connector_hdmi_hotplug(struct drm_connector *connector, - enum drm_connector_status status); +int drm_atomic_helper_connector_hdmi_hotplug(struct drm_connector *connector, + struct drm_modeset_acquire_ctx *ctx, + enum drm_connector_status status); void drm_atomic_helper_connector_hdmi_force(struct drm_connector *connector); enum drm_mode_status From 8ed0167d56e3d2ae3a8f8b1423d59de60aa457d4 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 1 Sep 2026 21:50:46 +0300 Subject: [PATCH 32/46] drm/display: hdmi-state-helper: Add drm_atomic_helper_connector_hdmi_force_ctx() Introduce drm_atomic_helper_connector_hdmi_force_ctx() as the HDMI connector implementation of the .force_ctx() callback. It receives the modeset acquire context, allowing it to take additional locks, e.g. to support SCDC status synchronization. Since migrating from the .force() callback is somewhat more involved, its users will be migrated incrementally, after which drm_atomic_helper_connector_hdmi_force() will be removed. Reviewed-by: Maxime Ripard Link: https://patch.msgid.link/20260901-dw-hdmi-qp-scramb-v11-22-bc12954a0688@collabora.com Signed-off-by: Cristian Ciocaltea --- .../gpu/drm/display/drm_hdmi_state_helper.c | 21 +++++++++++++++++++ include/drm/display/drm_hdmi_state_helper.h | 2 ++ 2 files changed, 23 insertions(+) diff --git a/drivers/gpu/drm/display/drm_hdmi_state_helper.c b/drivers/gpu/drm/display/drm_hdmi_state_helper.c index 485fcca2de4b..8045b1d17d6e 100644 --- a/drivers/gpu/drm/display/drm_hdmi_state_helper.c +++ b/drivers/gpu/drm/display/drm_hdmi_state_helper.c @@ -1424,3 +1424,24 @@ void drm_atomic_helper_connector_hdmi_force(struct drm_connector *connector) drm_atomic_helper_connector_hdmi_update(connector, NULL, connector->status); } EXPORT_SYMBOL(drm_atomic_helper_connector_hdmi_force); + +/** + * drm_atomic_helper_connector_hdmi_force_ctx - HDMI Connector implementation + * of the force_ctx callback + * @connector: A pointer to the HDMI connector + * @ctx: Lock acquisition context to be used for resetting CRTC + * + * This function implements the .force_ctx() callback for the HDMI connectors. + * It can either be used directly as the callback or should be called from + * within the .force_ctx() callback implementation to maintain the HDMI-specific + * connector's data. + * + * Returns: + * Zero on success, error code on failure, including -EDEADLK. + */ +int drm_atomic_helper_connector_hdmi_force_ctx(struct drm_connector *connector, + struct drm_modeset_acquire_ctx *ctx) +{ + return drm_atomic_helper_connector_hdmi_update(connector, ctx, connector->status); +} +EXPORT_SYMBOL(drm_atomic_helper_connector_hdmi_force_ctx); diff --git a/include/drm/display/drm_hdmi_state_helper.h b/include/drm/display/drm_hdmi_state_helper.h index 2439bad15c4b..965342f6bbc9 100644 --- a/include/drm/display/drm_hdmi_state_helper.h +++ b/include/drm/display/drm_hdmi_state_helper.h @@ -28,6 +28,8 @@ int drm_atomic_helper_connector_hdmi_hotplug(struct drm_connector *connector, struct drm_modeset_acquire_ctx *ctx, enum drm_connector_status status); void drm_atomic_helper_connector_hdmi_force(struct drm_connector *connector); +int drm_atomic_helper_connector_hdmi_force_ctx(struct drm_connector *connector, + struct drm_modeset_acquire_ctx *ctx); enum drm_mode_status drm_hdmi_connector_mode_valid(struct drm_connector *connector, From 6867cd55ee5530851847ebed6ebab0b9f329e5ba Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 1 Sep 2026 21:50:47 +0300 Subject: [PATCH 33/46] drm/display: hdmi-state-helper: Sync SCDC state on hotplug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit drm_atomic_helper_connector_hdmi_hotplug() does not currently synchronize SCDC status on hotplug events, leaving the scrambler state potentially inconsistent after (re)connect. Hook drm_connector_hdmi_sync_scdc() into both the connect and disconnect paths, replacing the existing TODOs around missing scrambler handling. Note that per .detect_ctx() contract, implementations shall return a drm_connector_status value or -EDEADLK, nothing else. Therefore, handle deadlocks and just report any other errors. For consistency, also report the errors from drm_edid_connector_update(). Tested-by: Maud Spierings Tested-by: Diederik de Haas # NanoPC-T6 LTS, Rock 5B Reviewed-by: Maxime Ripard Tested-by: Igor Paunovic # RK3588 Orange Pi 5 Plus, dual HDMI Tested-by: Robin Räber Link: https://patch.msgid.link/20260901-dw-hdmi-qp-scramb-v11-23-bc12954a0688@collabora.com Signed-off-by: Cristian Ciocaltea --- .../gpu/drm/display/drm_hdmi_state_helper.c | 34 +++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/display/drm_hdmi_state_helper.c b/drivers/gpu/drm/display/drm_hdmi_state_helper.c index 8045b1d17d6e..1e269b203b98 100644 --- a/drivers/gpu/drm/display/drm_hdmi_state_helper.c +++ b/drivers/gpu/drm/display/drm_hdmi_state_helper.c @@ -1362,12 +1362,29 @@ drm_atomic_helper_connector_hdmi_update(struct drm_connector *connector, enum drm_connector_status status) { const struct drm_edid *drm_edid; + int ret; if (status == connector_status_disconnected) { - // TODO: also handle scramber, HDMI sink disconnected. + /* + * .detect_ctx() can only ever return a drm_connector_status or + * -EDEADLK. Handle deadlocks, and report any !EDEADLK error. + */ + ret = drm_connector_hdmi_sync_scdc(connector, false, ctx); + if (ret) { + if (ret == -EDEADLK) + return ret; + + drm_warn(connector->dev, "Ignored SCDC sync error: %d\n", ret); + } + drm_connector_hdmi_audio_plugged_notify(connector, false); - drm_edid_connector_update(connector, NULL); + + ret = drm_edid_connector_update(connector, NULL); + if (ret) + drm_warn(connector->dev, "Ignored EDID update error: %d\n", ret); + drm_connector_cec_phys_addr_invalidate(connector); + return 0; } @@ -1381,7 +1398,18 @@ drm_atomic_helper_connector_hdmi_update(struct drm_connector *connector, drm_edid_free(drm_edid); if (status == connector_status_connected) { - // TODO: also handle scramber, HDMI sink is now connected. + /* + * .detect_ctx() can only ever return a drm_connector_status or + * -EDEADLK. Handle deadlocks, and report any !EDEADLK error. + */ + ret = drm_connector_hdmi_sync_scdc(connector, true, ctx); + if (ret) { + if (ret == -EDEADLK) + return ret; + + drm_warn(connector->dev, "Ignored SCDC sync error: %d\n", ret); + } + drm_connector_hdmi_audio_plugged_notify(connector, true); drm_connector_cec_phys_addr_set(connector); } From 23eef7a1c5f341315e66c1ef60ce525b64f0a2e3 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 1 Sep 2026 21:50:48 +0300 Subject: [PATCH 34/46] drm/display: hdmi-state-helper: Set HDMI scrambling requirement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Set drm_connector_hdmi_state.scrambler_needed when the computed TMDS character rate exceeds the HDMI 1.3 maximum TMDS character rate. HDMI 2.0 requires scrambling above 340 MHz. Centralize that clock-based requirement in the HDMI state helper so drivers do not need to open-code the threshold check. Acked-by: Maxime Ripard Tested-by: Maud Spierings Tested-by: Diederik de Haas # NanoPC-T6 LTS, Rock 5B Tested-by: Igor Paunovic # RK3588 Orange Pi 5 Plus, dual HDMI Tested-by: Robin Räber Link: https://patch.msgid.link/20260901-dw-hdmi-qp-scramb-v11-24-bc12954a0688@collabora.com Signed-off-by: Cristian Ciocaltea --- drivers/gpu/drm/display/drm_hdmi_state_helper.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/display/drm_hdmi_state_helper.c b/drivers/gpu/drm/display/drm_hdmi_state_helper.c index 1e269b203b98..7cdb7ca3dc12 100644 --- a/drivers/gpu/drm/display/drm_hdmi_state_helper.c +++ b/drivers/gpu/drm/display/drm_hdmi_state_helper.c @@ -596,6 +596,9 @@ hdmi_compute_clock(const struct drm_connector *connector, conn_state->hdmi.tmds_char_rate = clock; + /* TODO: also check drm_display_info.hdmi.scdc.scrambling.low_rates */ + conn_state->hdmi.scrambler_needed = (clock > HDMI_1_3_TMDS_CHAR_RATE_MAX_HZ); + return 0; } From b4e30bf687ab5131ca5aa063abda6d0e9ddbc8e4 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 1 Sep 2026 21:50:49 +0300 Subject: [PATCH 35/46] drm/display: bridge_connector: Switch to .force_ctx() connector helper Turn drm_bridge_connector_force() into a .force_ctx() implementation and hand the acquire context over to drm_atomic_helper_connector_hdmi_force_ctx(), so that the HDMI connector framework can reset the CRTC to recover the SCDC state when the sink reconnects while the display pipeline is active. No functional change for non-HDMI bridge connectors, which keep behaving as a no-op. Reviewed-by: Maxime Ripard Link: https://patch.msgid.link/20260901-dw-hdmi-qp-scramb-v11-25-bc12954a0688@collabora.com Signed-off-by: Cristian Ciocaltea --- .../gpu/drm/display/drm_bridge_connector.c | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c index b5a6e2e8bcd5..707d7143e475 100644 --- a/drivers/gpu/drm/display/drm_bridge_connector.c +++ b/drivers/gpu/drm/display/drm_bridge_connector.c @@ -208,16 +208,6 @@ static void drm_bridge_connector_disable_hpd(struct drm_connector *connector) * Bridge Connector Functions */ -static void drm_bridge_connector_force(struct drm_connector *connector) -{ - struct drm_bridge_connector *bridge_connector = - to_drm_bridge_connector(connector); - struct drm_bridge *hdmi = bridge_connector->bridge_hdmi; - - if (hdmi) - drm_atomic_helper_connector_hdmi_force(connector); -} - static void drm_bridge_connector_debugfs_init(struct drm_connector *connector, struct dentry *root) { @@ -277,7 +267,6 @@ drm_bridge_connector_color_format(const struct drm_connector_state *conn_state) } static const struct drm_connector_funcs drm_bridge_connector_funcs = { - .force = drm_bridge_connector_force, .fill_modes = drm_helper_probe_single_connector_modes, .atomic_create_state = drm_bridge_connector_create_state, .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, @@ -329,6 +318,19 @@ static int drm_bridge_connector_detect_ctx(struct drm_connector *connector, return status; } +static int drm_bridge_connector_force_ctx(struct drm_connector *connector, + struct drm_modeset_acquire_ctx *ctx) +{ + struct drm_bridge_connector *bridge_connector = + to_drm_bridge_connector(connector); + struct drm_bridge *hdmi = bridge_connector->bridge_hdmi; + + if (hdmi) + return drm_atomic_helper_connector_hdmi_force_ctx(connector, ctx); + + return 0; +} + static int drm_bridge_connector_get_modes_edid(struct drm_connector *connector, struct drm_bridge *bridge) { @@ -421,6 +423,7 @@ static int drm_bridge_connector_atomic_check(struct drm_connector *connector, static const struct drm_connector_helper_funcs drm_bridge_connector_helper_funcs = { .get_modes = drm_bridge_connector_get_modes, .detect_ctx = drm_bridge_connector_detect_ctx, + .force_ctx = drm_bridge_connector_force_ctx, .mode_valid = drm_bridge_connector_mode_valid, .enable_hpd = drm_bridge_connector_enable_hpd, .disable_hpd = drm_bridge_connector_disable_hpd, From 730f8554f35c5a7c404fbba964a6cf9d45908be5 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Thu, 3 Sep 2026 17:15:51 -0400 Subject: [PATCH 36/46] drm/virtio: preserve damage clips for rendered resources virtio_gpu_plane_atomic_check() unconditionally ignores FB_DAMAGE_CLIPS when the framebuffer changes. This forces a full resource flush on every page flip when a compositor rotates through multiple framebuffers, even when userspace supplies accumulated per-buffer damage. The full update is required for dumb buffers because their contents are uploaded per buffer. Rendered resources are already coherent on the host, so replacing their damage with the full plane only discards useful information. Restrict ignore_damage_clips to dumb buffers. This allows damage-aware compositors to propagate partial updates through RESOURCE_FLUSH while preserving the existing full-upload behavior for dumb buffers. Signed-off-by: Christian Hergert Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Signed-off-by: Dmitry Osipenko Link: https://patch.msgid.link/20260903211914.1109100-2-lyude@redhat.com --- drivers/gpu/drm/virtio/virtgpu_plane.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/virtio/virtgpu_plane.c b/drivers/gpu/drm/virtio/virtgpu_plane.c index 946f163013b8..8225085ce6c0 100644 --- a/drivers/gpu/drm/virtio/virtgpu_plane.c +++ b/drivers/gpu/drm/virtio/virtgpu_plane.c @@ -105,6 +105,7 @@ static int virtio_gpu_plane_atomic_check(struct drm_plane *plane, plane); struct drm_plane_state *old_plane_state = drm_atomic_get_old_plane_state(state, plane); + struct virtio_gpu_object *bo; bool is_cursor = plane->type == DRM_PLANE_TYPE_CURSOR; struct drm_crtc_state *crtc_state; int ret; @@ -115,9 +116,12 @@ static int virtio_gpu_plane_atomic_check(struct drm_plane *plane, /* * Ignore damage clips if the framebuffer attached to the plane's state * has changed since the last plane update (page-flip). In this case, a - * full plane update should happen because uploads are done per-buffer. + * full plane update should happen for dumb buffers because uploads are + * done per-buffer. Rendered resources are already coherent on the host, + * so preserve userspace's accumulated per-buffer damage for those. */ - if (old_plane_state->fb != new_plane_state->fb) + bo = gem_to_virtio_gpu_obj(new_plane_state->fb->obj[0]); + if (old_plane_state->fb != new_plane_state->fb && bo->dumb) new_plane_state->ignore_damage_clips = true; crtc_state = drm_atomic_get_crtc_state(state, From a9cc9905ddb7e7ce98e19b7e508aa3c78661afef Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Thu, 3 Sep 2026 17:15:52 -0400 Subject: [PATCH 37/46] drm/virtio: Recalculate ignore_damage_clips on every update Plane state duplication preserves ignore_damage_clips. Once fbdev sets it for a dumb buffer, later updates using rendered resources continue to ignore damage because the flag is never cleared. Assign ignore_damage_clips on every atomic check so rendered resources can use supplied damage after an earlier dumb-buffer update. Signed-off-by: Christian Hergert Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Signed-off-by: Dmitry Osipenko Link: https://patch.msgid.link/20260903211914.1109100-3-lyude@redhat.com --- drivers/gpu/drm/virtio/virtgpu_plane.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/virtio/virtgpu_plane.c b/drivers/gpu/drm/virtio/virtgpu_plane.c index 8225085ce6c0..f07013b2f2ad 100644 --- a/drivers/gpu/drm/virtio/virtgpu_plane.c +++ b/drivers/gpu/drm/virtio/virtgpu_plane.c @@ -121,8 +121,8 @@ static int virtio_gpu_plane_atomic_check(struct drm_plane *plane, * so preserve userspace's accumulated per-buffer damage for those. */ bo = gem_to_virtio_gpu_obj(new_plane_state->fb->obj[0]); - if (old_plane_state->fb != new_plane_state->fb && bo->dumb) - new_plane_state->ignore_damage_clips = true; + new_plane_state->ignore_damage_clips = + old_plane_state->fb != new_plane_state->fb && bo->dumb; crtc_state = drm_atomic_get_crtc_state(state, new_plane_state->crtc); From 643f9c6d862e3d806cc9924049d7967223025ee9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20K=C3=B6nig?= Date: Tue, 15 Sep 2026 17:25:38 +0200 Subject: [PATCH 38/46] MAINTAINERS: change TTM maintainers to Natalie and Arun MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The blunt reality is that I don't have time to look into patches or bugs any more as it would be necessary. The same counts for Ray. Natalie has been working on the dmem cgroup patches for TTM and agreed to take over looking more into TTM. Arun is working in my team and already maintaining AMDs VRAM backend for TTM and the underlying buddy allocator. Add Thomas as reviewer as well, better to have more eyes on that component. Signed-off-by: Christian König Acked-by: Matthew Brost Acked-by: Huang Rui Acked-by: Natalie Vock Acked-by: Matthew Auld Acked-by: Thomas Hellström Acked-by: Arunpravin Paneer Selvam Link: https://lore.kernel.org/r/20260915154804.605772-1-christian.koenig@amd.com --- MAINTAINERS | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index ff8b3cade00f..40fdb0ecc9c1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9143,10 +9143,11 @@ F: drivers/gpu/drm/drm_privacy_screen* F: include/drm/drm_privacy_screen* DRM TTM SUBSYSTEM -M: Christian Koenig -M: Huang Rui +M: Natalie Vock +M: Arun Pravin R: Matthew Auld R: Matthew Brost +R: Thomas Hellström L: dri-devel@lists.freedesktop.org S: Maintained T: git https://gitlab.freedesktop.org/drm/misc/kernel.git From af8296450df7678c94ae03054887639e3d4d35de Mon Sep 17 00:00:00 2001 From: Andreas Kemnade Date: Tue, 8 Sep 2026 19:22:13 +0200 Subject: [PATCH 39/46] drm/omap: dsi: print errors with device prefix There are two dsi controllers on OMAP4/5. If both are in use, messages without device prefix have no indication which controller they are coming from. That makes debugging harder. Replace DSSERR/DBG macros with dev_-prefixed print functions where the device is reliably accessible. Also replace function names by %s ... __func__ constructs and remove some debug outputs to satisfy checkpatch. Signed-off-by: Andreas Kemnade Reviewed-by: Luca Ceresoli Link: https://patch.msgid.link/20260908172213.446608-1-andreas@kemnade.info Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/omapdrm/dss/dsi.c | 301 +++++++++++++++--------------- 1 file changed, 148 insertions(+), 153 deletions(-) diff --git a/drivers/gpu/drm/omapdrm/dss/dsi.c b/drivers/gpu/drm/omapdrm/dss/dsi.c index 6723c3d422c9..838b34ea7e31 100644 --- a/drivers/gpu/drm/omapdrm/dss/dsi.c +++ b/drivers/gpu/drm/omapdrm/dss/dsi.c @@ -338,7 +338,7 @@ static void dsi_handle_irq_errors(struct dsi_data *dsi, u32 irqstatus, int i; if (irqstatus & DSI_IRQ_ERROR_MASK) { - DSSERR("DSI error, irqstatus %x\n", irqstatus); + dev_err(dsi->dev, "DSI error, irqstatus %x\n", irqstatus); print_irq_status(irqstatus); spin_lock(&dsi->errors_lock); dsi->errors |= irqstatus & DSI_IRQ_ERROR_MASK; @@ -349,8 +349,8 @@ static void dsi_handle_irq_errors(struct dsi_data *dsi, u32 irqstatus, for (i = 0; i < 4; ++i) { if (vcstatus[i] & DSI_VC_IRQ_ERROR_MASK) { - DSSERR("DSI VC(%d) error, vc irqstatus %x\n", - i, vcstatus[i]); + dev_err(dsi->dev, "DSI VC(%d) error, vc irqstatus %x\n", + i, vcstatus[i]); print_irq_status_vc(i, vcstatus[i]); } else if (debug_irq) { print_irq_status_vc(i, vcstatus[i]); @@ -358,7 +358,7 @@ static void dsi_handle_irq_errors(struct dsi_data *dsi, u32 irqstatus, } if (ciostatus & DSI_CIO_IRQ_ERROR_MASK) { - DSSERR("DSI CIO error, cio irqstatus %x\n", ciostatus); + dev_err(dsi->dev, "DSI CIO error, cio irqstatus %x\n", ciostatus); print_irq_status_cio(ciostatus); } else if (debug_irq) { print_irq_status_cio(ciostatus); @@ -694,8 +694,6 @@ static int dsi_runtime_get(struct dsi_data *dsi) { int r; - DSSDBG("dsi_runtime_get\n"); - r = pm_runtime_get_sync(dsi->dev); if (WARN_ON(r < 0)) { pm_runtime_put_noidle(dsi->dev); @@ -708,8 +706,6 @@ static void dsi_runtime_put(struct dsi_data *dsi) { int r; - DSSDBG("dsi_runtime_put\n"); - r = pm_runtime_put_sync(dsi->dev); WARN_ON(r < 0 && r != -ENOSYS); } @@ -751,13 +747,13 @@ static void _dsi_print_reset_status(struct dsi_data *dsi) static inline int dsi_if_enable(struct dsi_data *dsi, bool enable) { - DSSDBG("dsi_if_enable(%d)\n", enable); + dev_dbg(dsi->dev, "dsi_if_enable(%d)\n", enable); enable = enable ? 1 : 0; REG_FLD_MOD(dsi, DSI_CTRL, enable, 0, 0); /* IF_EN */ if (!wait_for_bit_change(dsi, DSI_CTRL, 0, enable)) { - DSSERR("Failed to set dsi_if_enable to %d\n", enable); + dev_err(dsi->dev, "Failed to set dsi_if_enable to %d\n", enable); return -EIO; } @@ -832,7 +828,7 @@ static int dsi_set_lp_clk_divisor(struct dsi_data *dsi) lp_clk = dsi_fclk / 2 / lp_clk_div; - DSSDBG("LP_CLK_DIV %u, LP_CLK %lu\n", lp_clk_div, lp_clk); + dev_dbg(dsi->dev, "LP_CLK_DIV %u, LP_CLK %lu\n", lp_clk_div, lp_clk); dsi->current_lp_cinfo.lp_clk = lp_clk; dsi->current_lp_cinfo.lp_clk_div = lp_clk_div; @@ -880,8 +876,8 @@ static int dsi_pll_power(struct dsi_data *dsi, enum dsi_pll_power_state state) /* PLL_PWR_STATUS */ while (FLD_GET(dsi_read_reg(dsi, DSI_CLK_CTRL), 29, 28) != state) { if (++t > 1000) { - DSSERR("Failed to set DSI PLL power mode to %d\n", - state); + dev_err(dsi->dev, "Failed to set DSI PLL power mode to %d\n", + state); return -ENODEV; } udelay(1); @@ -907,7 +903,7 @@ static int dsi_pll_enable(struct dss_pll *pll) struct dsi_data *dsi = container_of(pll, struct dsi_data, pll); int r = 0; - DSSDBG("PLL init\n"); + dev_dbg(dsi->dev, "PLL init\n"); r = dsi_runtime_get(dsi); if (r) @@ -926,7 +922,7 @@ static int dsi_pll_enable(struct dss_pll *pll) dispc_pck_free_enable(dsi->dss->dispc, 1); if (!wait_for_bit_change(dsi, DSI_PLL_STATUS, 0, 1)) { - DSSERR("PLL not coming out of reset.\n"); + dev_err(dsi->dev, "PLL not coming out of reset.\n"); r = -ENODEV; dispc_pck_free_enable(dsi->dss->dispc, 0); goto err1; @@ -941,7 +937,7 @@ static int dsi_pll_enable(struct dss_pll *pll) if (r) goto err1; - DSSDBG("PLL init done\n"); + dev_dbg(dsi->dev, "PLL init done\n"); return 0; err1: @@ -963,7 +959,7 @@ static void dsi_pll_disable(struct dss_pll *pll) dsi_disable_scp_clk(dsi); dsi_runtime_put(dsi); - DSSDBG("PLL disable done\n"); + dev_dbg(dsi->dev, "PLL disable done\n"); } static int dsi_dump_dsi_clocks(struct seq_file *s, void *p) @@ -1227,7 +1223,7 @@ static int dsi_cio_power(struct dsi_data *dsi, enum dsi_cio_power_state state) while (FLD_GET(dsi_read_reg(dsi, DSI_COMPLEXIO_CFG1), 26, 25) != state) { if (++t > 1000) { - DSSERR("failed to set complexio power state to " + dev_err(dsi->dev, "failed to set complexio power state to " "%d\n", state); return -ENODEV; } @@ -1368,20 +1364,19 @@ static void dsi_cio_timings(struct dsi_data *dsi) /* min tclk-prepare + tclk-zero = 300ns */ tclk_zero = ns2ddr(dsi, 260); - DSSDBG("ths_prepare %u (%uns), ths_prepare_ths_zero %u (%uns)\n", + dev_dbg(dsi->dev, "ths_prepare %u (%uns), ths_prepare_ths_zero %u (%uns)\n", ths_prepare, ddr2ns(dsi, ths_prepare), ths_prepare_ths_zero, ddr2ns(dsi, ths_prepare_ths_zero)); - DSSDBG("ths_trail %u (%uns), ths_exit %u (%uns)\n", - ths_trail, ddr2ns(dsi, ths_trail), - ths_exit, ddr2ns(dsi, ths_exit)); + dev_dbg(dsi->dev, "ths_trail %u (%uns), ths_exit %u (%uns)\n", + ths_trail, ddr2ns(dsi, ths_trail), + ths_exit, ddr2ns(dsi, ths_exit)); - DSSDBG("tlpx_half %u (%uns), tclk_trail %u (%uns), " - "tclk_zero %u (%uns)\n", - tlpx_half, ddr2ns(dsi, tlpx_half), - tclk_trail, ddr2ns(dsi, tclk_trail), - tclk_zero, ddr2ns(dsi, tclk_zero)); - DSSDBG("tclk_prepare %u (%uns)\n", - tclk_prepare, ddr2ns(dsi, tclk_prepare)); + dev_dbg(dsi->dev, "tlpx_half %u (%uns), tclk_trail %u (%uns), tclk_zero %u (%uns)\n", + tlpx_half, ddr2ns(dsi, tlpx_half), + tclk_trail, ddr2ns(dsi, tclk_trail), + tclk_zero, ddr2ns(dsi, tclk_zero)); + dev_dbg(dsi->dev, "tclk_prepare %u (%uns)\n", + tclk_prepare, ddr2ns(dsi, tclk_prepare)); /* program timings */ @@ -1447,8 +1442,8 @@ static int dsi_cio_wait_tx_clk_esc_reset(struct dsi_data *dsi) if (!in_use[i] || (l & (1 << offsets[i]))) continue; - DSSERR("CIO TXCLKESC%d domain not coming " \ - "out of reset\n", i); + dev_err(dsi->dev, + "CIO TXCLKESC%d domain not coming out of reset\n", i); } return -EIO; } @@ -1553,7 +1548,7 @@ static int dsi_cio_init(struct dsi_data *dsi) int r; u32 l; - DSSDBG("DSI CIO init starts"); + dev_dbg(dsi->dev, "DSI CIO init starts"); r = dsi_enable_pads(dsi, dsi_get_lane_mask(dsi)); if (r) @@ -1567,7 +1562,7 @@ static int dsi_cio_init(struct dsi_data *dsi) dsi_read_reg(dsi, DSI_DSIPHY_CFG5); if (!wait_for_bit_change(dsi, DSI_DSIPHY_CFG5, 30, 1)) { - DSSERR("CIO SCP Clock domain not coming out of reset.\n"); + dev_err(dsi->dev, "CIO SCP Clock domain not coming out of reset.\n"); r = -EIO; goto err_scp_clk_dom; } @@ -1589,7 +1584,7 @@ static int dsi_cio_init(struct dsi_data *dsi) goto err_cio_pwr; if (!wait_for_bit_change(dsi, DSI_COMPLEXIO_CFG1, 29, 1)) { - DSSERR("CIO PWR clock domain not coming out of reset.\n"); + dev_err(dsi->dev, "CIO PWR clock domain not coming out of reset.\n"); r = -ENODEV; goto err_cio_pwr_dom; } @@ -1612,7 +1607,7 @@ static int dsi_cio_init(struct dsi_data *dsi) !(dsi->dsidev->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS), 13, 13); - DSSDBG("CIO init done\n"); + dev_dbg(dsi->dev, "CIO init done\n"); return 0; @@ -1655,7 +1650,7 @@ static void dsi_config_tx_fifo(struct dsi_data *dsi, int size = dsi->vc[i].tx_fifo_size; if (add + size > 4) { - DSSERR("Illegal FIFO configuration\n"); + dev_err(dsi->dev, "Illegal FIFO configuration\n"); BUG(); return; } @@ -1687,7 +1682,7 @@ static void dsi_config_rx_fifo(struct dsi_data *dsi, int size = dsi->vc[i].rx_fifo_size; if (add + size > 4) { - DSSERR("Illegal FIFO configuration\n"); + dev_err(dsi->dev, "Illegal FIFO configuration\n"); BUG(); return; } @@ -1710,7 +1705,7 @@ static int dsi_force_tx_stop_mode_io(struct dsi_data *dsi) dsi_write_reg(dsi, DSI_TIMING1, r); if (!wait_for_bit_change(dsi, DSI_TIMING1, 15, 0)) { - DSSERR("TX_STOP bit not going down\n"); + dev_err(dsi->dev, "TX_STOP bit not going down\n"); return -EIO; } @@ -1755,7 +1750,7 @@ static int dsi_sync_vc_vp(struct dsi_data *dsi, int vc) if (REG_GET(dsi, DSI_VC_TE(vc), bit, bit)) { if (wait_for_completion_timeout(&completion, msecs_to_jiffies(10)) == 0) { - DSSERR("Failed to complete previous frame transfer\n"); + dev_err(dsi->dev, "Failed to complete previous frame transfer\n"); r = -EIO; goto err1; } @@ -1801,7 +1796,7 @@ static int dsi_sync_vc_l4(struct dsi_data *dsi, int vc) if (REG_GET(dsi, DSI_VC_CTRL(vc), 5, 5)) { if (wait_for_completion_timeout(&completion, msecs_to_jiffies(10)) == 0) { - DSSERR("Failed to complete previous l4 transfer\n"); + dev_err(dsi->dev, "Failed to complete previous l4 transfer\n"); r = -EIO; goto err1; } @@ -1840,15 +1835,15 @@ static int dsi_sync_vc(struct dsi_data *dsi, int vc) static int dsi_vc_enable(struct dsi_data *dsi, int vc, bool enable) { - DSSDBG("dsi_vc_enable vc %d, enable %d\n", - vc, enable); + dev_dbg(dsi->dev, "dsi_vc_enable vc %d, enable %d\n", + vc, enable); enable = enable ? 1 : 0; REG_FLD_MOD(dsi, DSI_VC_CTRL(vc), enable, 0, 0); if (!wait_for_bit_change(dsi, DSI_VC_CTRL(vc), 0, enable)) { - DSSERR("Failed to set dsi_vc_enable to %d\n", enable); + dev_err(dsi->dev, "Failed to set VC_EN to %d\n", enable); return -EIO; } @@ -1859,13 +1854,13 @@ static void dsi_vc_initial_config(struct dsi_data *dsi, int vc) { u32 r; - DSSDBG("Initial config of VC %d", vc); + dev_dbg(dsi->dev, "Initial config of VC %d", vc); r = dsi_read_reg(dsi, DSI_VC_CTRL(vc)); if (FLD_GET(r, 15, 15)) /* VC_BUSY */ - DSSERR("VC(%d) busy when trying to configure it!\n", - vc); + dev_err(dsi->dev, "VC(%d) busy when trying to configure it!\n", + vc); r = FLD_MOD(r, 0, 1, 1); /* SOURCE, 0 = L4 */ r = FLD_MOD(r, 0, 2, 2); /* BTA_SHORT_EN */ @@ -1890,7 +1885,7 @@ static void dsi_vc_enable_hs(struct omap_dss_device *dssdev, int vc, { struct dsi_data *dsi = to_dsi_data(dssdev); - DSSDBG("dsi_vc_enable_hs(%d, %d)\n", vc, enable); + dev_dbg(dsi->dev, "%s(%d, %d)\n", __func__, vc, enable); if (REG_GET(dsi, DSI_VC_CTRL(vc), 9, 9) == enable) return; @@ -1913,49 +1908,49 @@ static void dsi_vc_flush_long_data(struct dsi_data *dsi, int vc) while (REG_GET(dsi, DSI_VC_CTRL(vc), 20, 20)) { u32 val; val = dsi_read_reg(dsi, DSI_VC_SHORT_PACKET_HEADER(vc)); - DSSDBG("\t\tb1 %#02x b2 %#02x b3 %#02x b4 %#02x\n", - (val >> 0) & 0xff, - (val >> 8) & 0xff, - (val >> 16) & 0xff, - (val >> 24) & 0xff); + dev_dbg(dsi->dev, "\t\tb1 %#02x b2 %#02x b3 %#02x b4 %#02x\n", + (val >> 0) & 0xff, + (val >> 8) & 0xff, + (val >> 16) & 0xff, + (val >> 24) & 0xff); } } -static void dsi_show_rx_ack_with_err(u16 err) +static void dsi_show_rx_ack_with_err(struct dsi_data *dsi, u16 err) { - DSSERR("\tACK with ERROR (%#x):\n", err); + dev_err(dsi->dev, "\tACK with ERROR (%#x):\n", err); if (err & (1 << 0)) - DSSERR("\t\tSoT Error\n"); + dev_err(dsi->dev, "\t\tSoT Error\n"); if (err & (1 << 1)) - DSSERR("\t\tSoT Sync Error\n"); + dev_err(dsi->dev, "\t\tSoT Sync Error\n"); if (err & (1 << 2)) - DSSERR("\t\tEoT Sync Error\n"); + dev_err(dsi->dev, "\t\tEoT Sync Error\n"); if (err & (1 << 3)) - DSSERR("\t\tEscape Mode Entry Command Error\n"); + dev_err(dsi->dev, "\t\tEscape Mode Entry Command Error\n"); if (err & (1 << 4)) - DSSERR("\t\tLP Transmit Sync Error\n"); + dev_err(dsi->dev, "\t\tLP Transmit Sync Error\n"); if (err & (1 << 5)) - DSSERR("\t\tHS Receive Timeout Error\n"); + dev_err(dsi->dev, "\t\tHS Receive Timeout Error\n"); if (err & (1 << 6)) - DSSERR("\t\tFalse Control Error\n"); + dev_err(dsi->dev, "\t\tFalse Control Error\n"); if (err & (1 << 7)) - DSSERR("\t\t(reserved7)\n"); + dev_err(dsi->dev, "\t\t(reserved7)\n"); if (err & (1 << 8)) - DSSERR("\t\tECC Error, single-bit (corrected)\n"); + dev_err(dsi->dev, "\t\tECC Error, single-bit (corrected)\n"); if (err & (1 << 9)) - DSSERR("\t\tECC Error, multi-bit (not corrected)\n"); + dev_err(dsi->dev, "\t\tECC Error, multi-bit (not corrected)\n"); if (err & (1 << 10)) - DSSERR("\t\tChecksum Error\n"); + dev_err(dsi->dev, "\t\tChecksum Error\n"); if (err & (1 << 11)) - DSSERR("\t\tData type not recognized\n"); + dev_err(dsi->dev, "\t\tData type not recognized\n"); if (err & (1 << 12)) - DSSERR("\t\tInvalid VC ID\n"); + dev_err(dsi->dev, "\t\tInvalid VC ID\n"); if (err & (1 << 13)) - DSSERR("\t\tInvalid Transmission Length\n"); + dev_err(dsi->dev, "\t\tInvalid Transmission Length\n"); if (err & (1 << 14)) - DSSERR("\t\t(reserved14)\n"); + dev_err(dsi->dev, "\t\t(reserved14)\n"); if (err & (1 << 15)) - DSSERR("\t\tDSI Protocol Violation\n"); + dev_err(dsi->dev, "\t\tDSI Protocol Violation\n"); } static u16 dsi_vc_flush_receive_data(struct dsi_data *dsi, int vc) @@ -1965,23 +1960,23 @@ static u16 dsi_vc_flush_receive_data(struct dsi_data *dsi, int vc) u32 val; u8 dt; val = dsi_read_reg(dsi, DSI_VC_SHORT_PACKET_HEADER(vc)); - DSSERR("\trawval %#08x\n", val); + dev_err(dsi->dev, "\trawval %#08x\n", val); dt = FLD_GET(val, 5, 0); if (dt == MIPI_DSI_RX_ACKNOWLEDGE_AND_ERROR_REPORT) { u16 err = FLD_GET(val, 23, 8); - dsi_show_rx_ack_with_err(err); + dsi_show_rx_ack_with_err(dsi, err); } else if (dt == MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_1BYTE) { - DSSERR("\tDCS short response, 1 byte: %#x\n", - FLD_GET(val, 23, 8)); + dev_err(dsi->dev, "\tDCS short response, 1 byte: %#x\n", + FLD_GET(val, 23, 8)); } else if (dt == MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_2BYTE) { - DSSERR("\tDCS short response, 2 byte: %#x\n", - FLD_GET(val, 23, 8)); + dev_err(dsi->dev, "\tDCS short response, 2 byte: %#x\n", + FLD_GET(val, 23, 8)); } else if (dt == MIPI_DSI_RX_DCS_LONG_READ_RESPONSE) { - DSSERR("\tDCS long response, len %d\n", - FLD_GET(val, 23, 8)); + dev_err(dsi->dev, "\tDCS long response, len %d\n", + FLD_GET(val, 23, 8)); dsi_vc_flush_long_data(dsi, vc); } else { - DSSERR("\tunknown datatype 0x%02x\n", dt); + dev_err(dsi->dev, "\tunknown datatype 0x%02x\n", dt); } } return 0; @@ -1990,13 +1985,13 @@ static u16 dsi_vc_flush_receive_data(struct dsi_data *dsi, int vc) static int dsi_vc_send_bta(struct dsi_data *dsi, int vc) { if (dsi->debug_write || dsi->debug_read) - DSSDBG("dsi_vc_send_bta %d\n", vc); + dev_dbg(dsi->dev, "dsi_vc_send_bta %d\n", vc); WARN_ON(!dsi_bus_is_locked(dsi)); /* RX_FIFO_NOT_EMPTY */ if (REG_GET(dsi, DSI_VC_CTRL(vc), 20, 20)) { - DSSERR("rx fifo not empty when sending BTA, dumping data:\n"); + dev_err(dsi->dev, "rx fifo not empty when sending BTA, dumping data:\n"); dsi_vc_flush_receive_data(dsi, vc); } @@ -2031,14 +2026,14 @@ static int dsi_vc_send_bta_sync(struct omap_dss_device *dssdev, int vc) if (wait_for_completion_timeout(&completion, msecs_to_jiffies(500)) == 0) { - DSSERR("Failed to receive BTA\n"); + dev_err(dsi->dev, "Failed to receive BTA\n"); r = -EIO; goto err2; } err = dsi_get_errors(dsi); if (err) { - DSSERR("Error while sending BTA: %x\n", err); + dev_err(dsi->dev, "Error while sending BTA: %x\n", err); r = -EIO; goto err2; } @@ -2092,11 +2087,11 @@ static int dsi_vc_send_long(struct dsi_data *dsi, int vc, u8 b1, b2, b3, b4; if (dsi->debug_write) - DSSDBG("dsi_vc_send_long, %zu bytes\n", msg->tx_len); + dev_dbg(dsi->dev, "%s, %zu bytes\n", __func__, msg->tx_len); /* len + header */ if (dsi->vc[vc].tx_fifo_size * 32 * 4 < msg->tx_len + 4) { - DSSERR("unable to send long packet: packet too long.\n"); + dev_err(dsi->dev, "unable to send long packet: packet too long.\n"); return -EINVAL; } @@ -2105,7 +2100,7 @@ static int dsi_vc_send_long(struct dsi_data *dsi, int vc, p = msg->tx_buf; for (i = 0; i < msg->tx_len >> 2; i++) { if (dsi->debug_write) - DSSDBG("\tsending full packet %d\n", i); + dev_dbg(dsi->dev, "\tsending full packet %d\n", i); b1 = *p++; b2 = *p++; @@ -2120,7 +2115,7 @@ static int dsi_vc_send_long(struct dsi_data *dsi, int vc, b1 = 0; b2 = 0; b3 = 0; if (dsi->debug_write) - DSSDBG("\tsending remainder bytes %d\n", i); + dev_dbg(dsi->dev, "\tsending remainder bytes %d\n", i); switch (i) { case 3: @@ -2157,11 +2152,11 @@ static int dsi_vc_send_short(struct dsi_data *dsi, int vc, WARN_ON(!dsi_bus_is_locked(dsi)); if (dsi->debug_write) - DSSDBG("dsi_vc_send_short(vc%d, dt %#x, b1 %#x, b2 %#x)\n", - vc, msg->type, pkt.header[1], pkt.header[2]); + dev_dbg(dsi->dev, "%s(vc%d, dt %#x, b1 %#x, b2 %#x)\n", + __func__, vc, msg->type, pkt.header[1], pkt.header[2]); if (FLD_GET(dsi_read_reg(dsi, DSI_VC_CTRL(vc)), 16, 16)) { - DSSERR("ERROR FIFO FULL, aborting transfer\n"); + dev_err(dsi->dev, "ERROR FIFO FULL, aborting transfer\n"); return -EINVAL; } @@ -2216,20 +2211,20 @@ static int dsi_vc_write_common(struct omap_dss_device *dssdev, int vc, if (msg->flags & MIPI_DSI_MSG_REQ_ACK) { r = dsi_vc_send_bta_sync(dssdev, vc); if (r) { - DSSERR("bta sync failed\n"); + dev_err(dsi->dev, "bta sync failed\n"); return r; } } else { err = dsi_get_errors(dsi); if (err) { - DSSERR("Error while sending: %x\n", err); + dev_err(dsi->dev, "Error while sending: %x\n", err); return -EIO; } } /* RX_FIFO_NOT_EMPTY */ if (REG_GET(dsi, DSI_VC_CTRL(vc), 20, 20)) { - DSSERR("rx fifo not empty after write, dumping data:\n"); + dev_err(dsi->dev, "rx fifo not empty after write, dumping data:\n"); dsi_vc_flush_receive_data(dsi, vc); return -EIO; } @@ -2246,18 +2241,18 @@ static int dsi_vc_read_rx_fifo(struct dsi_data *dsi, int vc, u8 *buf, /* RX_FIFO_NOT_EMPTY */ if (REG_GET(dsi, DSI_VC_CTRL(vc), 20, 20) == 0) { - DSSERR("RX fifo empty when trying to read.\n"); + dev_err(dsi->dev, "RX fifo empty when trying to read.\n"); r = -EIO; goto err; } val = dsi_read_reg(dsi, DSI_VC_SHORT_PACKET_HEADER(vc)); if (dsi->debug_read) - DSSDBG("\theader: %08x\n", val); + dev_dbg(dsi->dev, "\theader: %08x\n", val); dt = FLD_GET(val, 5, 0); if (dt == MIPI_DSI_RX_ACKNOWLEDGE_AND_ERROR_REPORT) { u16 err = FLD_GET(val, 23, 8); - dsi_show_rx_ack_with_err(err); + dsi_show_rx_ack_with_err(dsi, err); r = -EIO; goto err; @@ -2266,7 +2261,7 @@ static int dsi_vc_read_rx_fifo(struct dsi_data *dsi, int vc, u8 *buf, MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_1BYTE)) { u8 data = FLD_GET(val, 15, 8); if (dsi->debug_read) - DSSDBG("\t%s short response, 1 byte: %02x\n", + dev_dbg(dsi->dev, "\t%s short response, 1 byte: %02x\n", type == DSS_DSI_CONTENT_GENERIC ? "GENERIC" : "DCS", data); @@ -2283,7 +2278,7 @@ static int dsi_vc_read_rx_fifo(struct dsi_data *dsi, int vc, u8 *buf, MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_2BYTE)) { u16 data = FLD_GET(val, 23, 8); if (dsi->debug_read) - DSSDBG("\t%s short response, 2 byte: %04x\n", + dev_dbg(dsi->dev, "\t%s short response, 2 byte: %04x\n", type == DSS_DSI_CONTENT_GENERIC ? "GENERIC" : "DCS", data); @@ -2302,7 +2297,7 @@ static int dsi_vc_read_rx_fifo(struct dsi_data *dsi, int vc, u8 *buf, int w; int len = FLD_GET(val, 23, 8); if (dsi->debug_read) - DSSDBG("\t%s long response, len %d\n", + dev_dbg(dsi->dev, "\t%s long response, len %d\n", type == DSS_DSI_CONTENT_GENERIC ? "GENERIC" : "DCS", len); @@ -2317,11 +2312,11 @@ static int dsi_vc_read_rx_fifo(struct dsi_data *dsi, int vc, u8 *buf, val = dsi_read_reg(dsi, DSI_VC_SHORT_PACKET_HEADER(vc)); if (dsi->debug_read) - DSSDBG("\t\t%02x %02x %02x %02x\n", - (val >> 0) & 0xff, - (val >> 8) & 0xff, - (val >> 16) & 0xff, - (val >> 24) & 0xff); + dev_dbg(dsi->dev, "\t\t%02x %02x %02x %02x\n", + (val >> 0) & 0xff, + (val >> 8) & 0xff, + (val >> 16) & 0xff, + (val >> 24) & 0xff); for (b = 0; b < 4; ++b) { if (w < len) @@ -2333,13 +2328,13 @@ static int dsi_vc_read_rx_fifo(struct dsi_data *dsi, int vc, u8 *buf, return len; } else { - DSSERR("\tunknown datatype 0x%02x\n", dt); + dev_err(dsi->dev, "\tunknown datatype 0x%02x\n", dt); r = -EIO; goto err; } err: - DSSERR("dsi_vc_read_rx_fifo(vc %d type %s) failed\n", vc, + dev_err(dsi->dev, "%s(vc %d type %s) failed\n", __func__, vc, type == DSS_DSI_CONTENT_GENERIC ? "GENERIC" : "DCS"); return r; @@ -2353,7 +2348,7 @@ static int dsi_vc_dcs_read(struct omap_dss_device *dssdev, int vc, int r; if (dsi->debug_read) - DSSDBG("%s(vc %d, cmd %x)\n", __func__, vc, cmd); + dev_dbg(dsi->dev, "%s(vc %d, cmd %x)\n", __func__, vc, cmd); r = dsi_vc_send_short(dsi, vc, msg); if (r) @@ -2375,7 +2370,7 @@ static int dsi_vc_dcs_read(struct omap_dss_device *dssdev, int vc, return 0; err: - DSSERR("%s(vc %d, cmd 0x%02x) failed\n", __func__, vc, cmd); + dev_err(dsi->dev, "%s(vc %d, cmd 0x%02x) failed\n", __func__, vc, cmd); return r; } @@ -2405,7 +2400,7 @@ static int dsi_vc_generic_read(struct omap_dss_device *dssdev, int vc, return 0; err: - DSSERR("%s(vc %d, reqlen %zu) failed\n", __func__, vc, msg->tx_len); + dev_err(dsi->dev, "%s(vc %d, reqlen %zu) failed\n", __func__, vc, msg->tx_len); return r; } @@ -2430,10 +2425,10 @@ static void dsi_set_lp_rx_timeout(struct dsi_data *dsi, unsigned int ticks, total_ticks = ticks * (x16 ? 16 : 1) * (x4 ? 4 : 1); - DSSDBG("LP_RX_TO %lu ticks (%#x%s%s) = %lu ns\n", - total_ticks, - ticks, x4 ? " x4" : "", x16 ? " x16" : "", - (total_ticks * 1000) / (fck / 1000 / 1000)); + dev_dbg(dsi->dev, "LP_RX_TO %lu ticks (%#x%s%s) = %lu ns\n", + total_ticks, + ticks, x4 ? " x4" : "", x16 ? " x16" : "", + (total_ticks * 1000) / (fck / 1000 / 1000)); } static void dsi_set_ta_timeout(struct dsi_data *dsi, unsigned int ticks, @@ -2457,10 +2452,10 @@ static void dsi_set_ta_timeout(struct dsi_data *dsi, unsigned int ticks, total_ticks = ticks * (x16 ? 16 : 1) * (x8 ? 8 : 1); - DSSDBG("TA_TO %lu ticks (%#x%s%s) = %lu ns\n", - total_ticks, - ticks, x8 ? " x8" : "", x16 ? " x16" : "", - (total_ticks * 1000) / (fck / 1000 / 1000)); + dev_dbg(dsi->dev, "TA_TO %lu ticks (%#x%s%s) = %lu ns\n", + total_ticks, + ticks, x8 ? " x8" : "", x16 ? " x16" : "", + (total_ticks * 1000) / (fck / 1000 / 1000)); } static void dsi_set_stop_state_counter(struct dsi_data *dsi, unsigned int ticks, @@ -2484,10 +2479,10 @@ static void dsi_set_stop_state_counter(struct dsi_data *dsi, unsigned int ticks, total_ticks = ticks * (x16 ? 16 : 1) * (x4 ? 4 : 1); - DSSDBG("STOP_STATE_COUNTER %lu ticks (%#x%s%s) = %lu ns\n", - total_ticks, - ticks, x4 ? " x4" : "", x16 ? " x16" : "", - (total_ticks * 1000) / (fck / 1000 / 1000)); + dev_dbg(dsi->dev, "STOP_STATE_COUNTER %lu ticks (%#x%s%s) = %lu ns\n", + total_ticks, + ticks, x4 ? " x4" : "", x16 ? " x16" : "", + (total_ticks * 1000) / (fck / 1000 / 1000)); } static void dsi_set_hs_tx_timeout(struct dsi_data *dsi, unsigned int ticks, @@ -2511,10 +2506,10 @@ static void dsi_set_hs_tx_timeout(struct dsi_data *dsi, unsigned int ticks, total_ticks = ticks * (x16 ? 16 : 1) * (x4 ? 4 : 1); - DSSDBG("HS_TX_TO %lu ticks (%#x%s%s) = %lu ns\n", - total_ticks, - ticks, x4 ? " x4" : "", x16 ? " x16" : "", - (total_ticks * 1000) / (fck / 1000 / 1000)); + dev_dbg(dsi->dev, "HS_TX_TO %lu ticks (%#x%s%s) = %lu ns\n", + total_ticks, + ticks, x4 ? " x4" : "", x16 ? " x16" : "", + (total_ticks * 1000) / (fck / 1000 / 1000)); } static void dsi_config_vp_num_line_buffers(struct dsi_data *dsi) @@ -2734,11 +2729,11 @@ static void dsi_config_cmd_mode_interleaving(struct dsi_data *dsi) lp_clk_div, dsi_fclk_hsdiv); } - DSSDBG("DSI HS interleaving(TXBYTECLKHS) HSA %d, HFP %d, HBP %d, BLLP %d\n", + dev_dbg(dsi->dev, "DSI HS interleaving(TXBYTECLKHS) HSA %d, HFP %d, HBP %d, BLLP %d\n", hsa_interleave_hs, hfp_interleave_hs, hbp_interleave_hs, bl_interleave_hs); - DSSDBG("DSI LP interleaving(bytes) HSA %d, HFP %d, HBP %d, BLLP %d\n", + dev_dbg(dsi->dev, "DSI LP interleaving(bytes) HSA %d, HFP %d, HBP %d, BLLP %d\n", hsa_interleave_lp, hfp_interleave_lp, hbp_interleave_lp, bl_interleave_lp); @@ -2874,9 +2869,9 @@ static void dsi_proto_timings(struct dsi_data *dsi) r = FLD_MOD(r, ddr_clk_post, 7, 0); dsi_write_reg(dsi, DSI_CLK_TIMING, r); - DSSDBG("ddr_clk_pre %u, ddr_clk_post %u\n", - ddr_clk_pre, - ddr_clk_post); + dev_dbg(dsi->dev, "ddr_clk_pre %u, ddr_clk_post %u\n", + ddr_clk_pre, + ddr_clk_post); enter_hs_mode_lat = 1 + DIV_ROUND_UP(tlpx, 4) + DIV_ROUND_UP(ths_prepare, 4) + @@ -2888,8 +2883,8 @@ static void dsi_proto_timings(struct dsi_data *dsi) FLD_VAL(exit_hs_mode_lat, 15, 0); dsi_write_reg(dsi, DSI_VM_TIMING7, r); - DSSDBG("enter_hs_mode_lat %u, exit_hs_mode_lat %u\n", - enter_hs_mode_lat, exit_hs_mode_lat); + dev_dbg(dsi->dev, "enter_hs_mode_lat %u, exit_hs_mode_lat %u\n", + enter_hs_mode_lat, exit_hs_mode_lat); if (dsi->mode == OMAP_DSS_DSI_VIDEO_MODE) { /* TODO: Implement a video mode check_timings function */ @@ -2915,9 +2910,9 @@ static void dsi_proto_timings(struct dsi_data *dsi) tl = DIV_ROUND_UP(4, ndl) + (hsync_end ? hsa : 0) + t_he + hfp + DIV_ROUND_UP(width_bytes + 6, ndl) + hbp; - DSSDBG("HBP: %d, HFP: %d, HSA: %d, TL: %d TXBYTECLKHS\n", hbp, + dev_dbg(dsi->dev, "HBP: %d, HFP: %d, HSA: %d, TL: %d TXBYTECLKHS\n", hbp, hfp, hsync_end ? hsa : 0, tl); - DSSDBG("VBP: %d, VFP: %d, VSA: %d, VACT: %d lines\n", vbp, vfp, + dev_dbg(dsi->dev, "VBP: %d, VFP: %d, VSA: %d, VACT: %d lines\n", vbp, vfp, vsa, vm->vactive); r = dsi_read_reg(dsi, DSI_VM_TIMING1); @@ -3113,7 +3108,7 @@ static void dsi_update_screen_dispc(struct dsi_data *dsi) u16 w = dsi->vm.hactive; u16 h = dsi->vm.vactive; - DSSDBG("dsi_update_screen_dispc(%dx%d)\n", w, h); + dev_dbg(dsi->dev, "%s(%dx%d)\n", __func__, w, h); bytespp = mipi_dsi_pixel_format_to_bpp(dsi->pix_fmt) / 8; bytespl = w * bytespp; @@ -3208,7 +3203,7 @@ static void dsi_framedone_timeout_work_callback(struct work_struct *work) * on the HW is buggy, and would probably require resetting the whole * DSI */ - DSSERR("Framedone not received for 250ms!\n"); + dev_err(dsi->dev, "Framedone not received for 250ms!\n"); dsi_handle_framedone(dsi, -ETIMEDOUT); } @@ -3224,7 +3219,7 @@ static void dsi_framedone_irq_callback(void *data) cancel_delayed_work(&dsi->framedone_timeout_work); - DSSDBG("Framedone received!\n"); + dev_dbg(dsi->dev, "Framedone received!\n"); dsi_handle_framedone(dsi, 0); } @@ -3259,7 +3254,7 @@ static int dsi_update_channel(struct omap_dss_device *dssdev, int vc) goto err; } - DSSDBG("dsi_update_channel: %d", vc); + dev_dbg(dsi->dev, "%s: %d", __func__, vc); /* * Transition to LP here. If we don't send something here, the @@ -3309,7 +3304,7 @@ static int dsi_configure_dispc_clocks(struct dsi_data *dsi) r = dispc_calc_clock_rates(dsi->dss->dispc, fck, &dispc_cinfo); if (r) { - DSSERR("Failed to calc dispc clocks\n"); + dev_err(dsi->dev, "Failed to calc dispc clocks\n"); return r; } @@ -3331,7 +3326,7 @@ static int dsi_init_dispc(struct dsi_data *dsi) r = dss_mgr_register_framedone_handler(&dsi->output, dsi_framedone_irq_callback, dsi); if (r) { - DSSERR("can't register FRAMEDONE handler\n"); + dev_err(dsi->dev, "can't register FRAMEDONE handler\n"); goto err; } @@ -3383,7 +3378,7 @@ static int dsi_configure_dsi_clocks(struct dsi_data *dsi) r = dss_pll_set_config(&dsi->pll, &cinfo); if (r) { - DSSERR("Failed to set dsi clocks\n"); + dev_err(dsi->dev, "Failed to set dsi clocks\n"); return r; } @@ -3436,7 +3431,7 @@ static int dsi_init_dsi(struct dsi_data *dsi) dsi->module_id == 0 ? DSS_CLK_SRC_PLL1_2 : DSS_CLK_SRC_PLL2_2); - DSSDBG("PLL OK\n"); + dev_dbg(dsi->dev, "PLL OK\n"); if (!dsi->vdds_dsi_enabled) { r = regulator_enable(dsi->vdds_dsi_reg); @@ -3526,7 +3521,7 @@ err_init_dsi: dsi_runtime_put(dsi); err_get_dsi: mutex_unlock(&dsi->lock); - DSSDBG("dsi_enable FAILED\n"); + dev_dbg(dsi->dev, "%s FAILED\n", __func__); } static void dsi_disable(struct dsi_data *dsi) @@ -4095,7 +4090,7 @@ static int dsi_set_config(struct omap_dss_device *dssdev, r = __dsi_calc_config(dsi, mode, &ctx); if (r) { - DSSERR("failed to find suitable DSI clock settings\n"); + dev_err(dsi->dev, "failed to find suitable DSI clock settings\n"); goto err; } @@ -4255,7 +4250,7 @@ static int dsi_get_clocks(struct dsi_data *dsi) clk = devm_clk_get(dsi->dev, "fck"); if (IS_ERR(clk)) { - DSSERR("can't get fck\n"); + dev_err(dsi->dev, "can't get fck\n"); return PTR_ERR(clk); } @@ -4361,12 +4356,12 @@ static int omap_dsi_host_attach(struct mipi_dsi_host *host, int r; if (dsi->dsidev) { - DSSERR("dsi client already attached\n"); + dev_err(dsi->dev, "dsi client already attached\n"); return -EBUSY; } if (mipi_dsi_pixel_format_to_bpp(client->format) < 0) { - DSSERR("invalid pixel format\n"); + dev_err(dsi->dev, "invalid pixel format\n"); return -EINVAL; } @@ -4527,7 +4522,7 @@ static int dsi_init_pll_data(struct dss_device *dss, struct dsi_data *dsi) clk = devm_clk_get(dsi->dev, "sys_clk"); if (IS_ERR(clk)) { - DSSERR("can't get sys_clk\n"); + dev_err(dsi->dev, "can't get sys_clk\n"); return PTR_ERR(clk); } @@ -4935,21 +4930,21 @@ static int dsi_probe(struct platform_device *pdev) dsi->irq = platform_get_irq(pdev, 0); if (dsi->irq < 0) { - DSSERR("platform_get_irq failed\n"); + dev_err(dsi->dev, "platform_get_irq failed\n"); return -ENODEV; } r = devm_request_irq(dev, dsi->irq, omap_dsi_irq_handler, IRQF_SHARED, dev_name(dev), dsi); if (r < 0) { - DSSERR("request_irq failed\n"); + dev_err(dsi->dev, "request_irq failed\n"); return r; } dsi->vdds_dsi_reg = devm_regulator_get(dev, "vdd"); if (IS_ERR(dsi->vdds_dsi_reg)) { if (PTR_ERR(dsi->vdds_dsi_reg) != -EPROBE_DEFER) - DSSERR("can't get DSI VDD regulator\n"); + dev_err(dsi->dev, "can't get DSI VDD regulator\n"); return PTR_ERR(dsi->vdds_dsi_reg); } @@ -4964,7 +4959,7 @@ static int dsi_probe(struct platform_device *pdev) d++; if (d->address == 0) { - DSSERR("unsupported DSI module\n"); + dev_err(dsi->dev, "unsupported DSI module\n"); return -ENODEV; } @@ -5014,7 +5009,7 @@ static int dsi_probe(struct platform_device *pdev) r = dsi_probe_of(dsi); if (r) { - DSSERR("Invalid DSI DT data\n"); + dev_err(dsi->dev, "Invalid DSI DT data\n"); goto err_pm_disable; } From 402491eefc5ea9a40e19b08dccf0c045aec4efc2 Mon Sep 17 00:00:00 2001 From: Brajesh Gupta Date: Fri, 18 Sep 2026 09:56:34 +0530 Subject: [PATCH 40/46] drm/imagination: Add job type info to pvr_job_submit_fw trace event Update trace event pvr_job_submit_fw to additionally capture and display type of the submitted job to easily map events as per type. Reviewed-by: Alexandru Dadu Reviewed-by: Alessio Belle Link: https://patch.msgid.link/20260918-b4-submitjob_tracepoint-v2-1-d9b104de07b8@imgtec.com Signed-off-by: Brajesh Gupta --- drivers/gpu/drm/imagination/pvr_trace.h | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/imagination/pvr_trace.h b/drivers/gpu/drm/imagination/pvr_trace.h index 943d6b2418cb..4be7d4b8a357 100644 --- a/drivers/gpu/drm/imagination/pvr_trace.h +++ b/drivers/gpu/drm/imagination/pvr_trace.h @@ -90,21 +90,25 @@ TRACE_EVENT(pvr_job_create, __entry->sync_op_count) ); -#undef PVR_JOB_TYPE_TO_STR #undef PVR_JOB_GET_HWRT_FW_ADDR TRACE_EVENT(pvr_job_submit_fw, TP_PROTO(struct pvr_job *job), TP_ARGS(job), TP_STRUCT__entry(__field(struct pvr_job *, job) - __field(u32, done_seqno)), + __field(u32, done_seqno) + __field(enum drm_pvr_job_type, job_type)), TP_fast_assign(__entry->job = job; - __entry->done_seqno = job->done_fence->seqno;), - TP_printk("job=%p done_seqno=%u", + __entry->done_seqno = job->done_fence->seqno; + __entry->job_type = job->type;), + TP_printk("job=%p done_seqno=%u job_type=%s", __entry->job, - __entry->done_seqno) + __entry->done_seqno, + PVR_JOB_TYPE_TO_STR(__entry->job_type)) ); +#undef PVR_JOB_TYPE_TO_STR + TRACE_EVENT(pvr_job_done, TP_PROTO(struct pvr_job *job), TP_ARGS(job), From 8a43738e56d9eb76a573cabab4c9c0e73eab23b2 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Fri, 18 Sep 2026 13:05:14 +0100 Subject: [PATCH 41/46] drm/panel-edp: add CSOT MNE007QB1-1 entry 14" 1920x1200 IPS panel used on Lenovo ThinkPad T14s Gen 7 (Mahua). The panel identifies as CSW 0x1465 over eDP AUX and is not in edp_panels[] today, so probe falls back to conservative timings. EDID, read over the panel's DP AUX DDC bus: 00 ff ff ff ff ff ff 00 0e 77 65 14 00 00 00 00 00 23 01 04 a5 1e 13 78 07 37 3a a3 55 4d 9c 25 0c 50 55 00 00 00 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 35 3c 80 a0 70 b0 23 40 30 20 66 00 2d bc 10 00 00 18 00 00 00 fd 00 1e 3c 4a 4a 10 01 0a 20 20 20 20 20 20 00 00 00 fe 00 43 53 4f 54 20 54 33 0a 20 20 20 20 20 00 00 00 fc 00 4d 4e 45 30 30 37 51 42 31 2d 31 0a 20 01 6a 70 20 79 02 00 20 00 0c 40 8b f6 65 14 00 00 00 00 00 19 00 21 00 1d c8 0b 5d 07 80 07 b0 04 00 3d 7a 54 cc 94 99 66 52 0f 02 35 54 00 80 00 80 00 80 12 78 26 00 09 02 00 00 00 00 00 01 00 00 22 00 14 0f 5a 02 05 7f 07 9f 00 2f 00 1f 00 af 04 22 00 05 00 05 00 81 00 15 74 1a 00 00 03 51 1e 3c 00 00 00 00 00 00 3c 00 00 00 00 8d 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 11 90 Manufacturer: CSW Model: 0x1465 Made in: 2025 Display Product Name: 'MNE007QB1-1' ASCII String: 'CSOT T3' DTD 1: 1920x1200 60.006199 Hz 16:10 (302 mm x 189 mm) Datasheet was not available, so timings are not datasheet-derived. The delay_200_500_e50 preset matches the sibling CSOT MNE007QB/QS entries already in the file. Assisted-by: Claude:claude-opus-4-7 Signed-off-by: Srinivas Kandagatla Reviewed-by: Abel Vesa Reviewed-by: Douglas Anderson Signed-off-by: Douglas Anderson Link: https://patch.msgid.link/20260918120515.595782-3-srinivas.kandagatla@oss.qualcomm.com --- drivers/gpu/drm/panel/panel-edp.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/panel/panel-edp.c b/drivers/gpu/drm/panel/panel-edp.c index b66751df502c..29f250e6e64d 100644 --- a/drivers/gpu/drm/panel/panel-edp.c +++ b/drivers/gpu/drm/panel/panel-edp.c @@ -2141,6 +2141,7 @@ static const struct edp_panel_entry edp_panels[] = { EDP_PANEL_ENTRY('C', 'S', 'W', 0x144b, &delay_200_500_e80, "MNE001BS1-4"), EDP_PANEL_ENTRY('C', 'S', 'W', 0x1457, &delay_80_500_e80_p2e200, "MNE007QS3-8"), EDP_PANEL_ENTRY('C', 'S', 'W', 0x1462, &delay_200_500_e50, "MNE007QS5-2"), + EDP_PANEL_ENTRY('C', 'S', 'W', 0x1465, &delay_200_500_e50, "MNE007QB1-1"), EDP_PANEL_ENTRY('C', 'S', 'W', 0x1468, &delay_200_500_e50, "MNE007QB2-2"), EDP_PANEL_ENTRY('C', 'S', 'W', 0x146e, &delay_80_500_e50_d50, "MNE007QB3-1"), EDP_PANEL_ENTRY('C', 'S', 'W', 0x147c, &delay_200_500_e50_d100, "MNE007QB3-1"), From 8ef59ee794076e2b58cff357b12de2ba5d441271 Mon Sep 17 00:00:00 2001 From: Erick Karanja Date: Mon, 21 Sep 2026 22:00:14 +0300 Subject: [PATCH 42/46] drm/v3d: Restrict v3d_perfmon_stop_locked() to v3d_perfmon.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v3d_perfmon_stop_locked() is only used within v3d_perfmon.c. Make the function file-local and remove its declaration from v3d_drv.h. Signed-off-by: Erick Karanja Link: https://patch.msgid.link/20260921190014.38231-1-karanja99erick@gmail.com Reviewed-by: Maíra Canal Signed-off-by: Maíra Canal --- drivers/gpu/drm/v3d/v3d_drv.h | 2 -- drivers/gpu/drm/v3d/v3d_perfmon.c | 5 +++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/v3d/v3d_drv.h b/drivers/gpu/drm/v3d/v3d_drv.h index 79ef20c89ff2..26f8c3f635f9 100644 --- a/drivers/gpu/drm/v3d/v3d_drv.h +++ b/drivers/gpu/drm/v3d/v3d_drv.h @@ -697,8 +697,6 @@ void v3d_perfmon_put(struct v3d_perfmon *perfmon); void v3d_perfmon_start(struct v3d_dev *v3d, struct v3d_perfmon *perfmon); void v3d_perfmon_stop(struct v3d_dev *v3d, struct v3d_perfmon *perfmon, bool capture); -void v3d_perfmon_stop_locked(struct v3d_dev *v3d, struct v3d_perfmon *perfmon, - bool capture); void v3d_perfmon_suspend(struct v3d_dev *v3d); void v3d_perfmon_resume(struct v3d_dev *v3d); struct v3d_perfmon *v3d_perfmon_find(struct v3d_file_priv *v3d_priv, int id); diff --git a/drivers/gpu/drm/v3d/v3d_perfmon.c b/drivers/gpu/drm/v3d/v3d_perfmon.c index 07dab7fb3060..4cadc3e17280 100644 --- a/drivers/gpu/drm/v3d/v3d_perfmon.c +++ b/drivers/gpu/drm/v3d/v3d_perfmon.c @@ -305,8 +305,9 @@ static void v3d_perfmon_capture_locked(struct v3d_dev *v3d, v3d_pm_runtime_put(v3d); } -void v3d_perfmon_stop_locked(struct v3d_dev *v3d, struct v3d_perfmon *perfmon, - bool capture) +static void +v3d_perfmon_stop_locked(struct v3d_dev *v3d, struct v3d_perfmon *perfmon, + bool capture) { lockdep_assert_held(&v3d->perfmon_state.lock); From 73ef663c75688168e31dbe9c585b939be410a945 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 16 Sep 2026 10:21:12 +0000 Subject: [PATCH 43/46] drm/mali-dp: Fix runtime PM leak in malidp_crtc_atomic_enable() malidp_crtc_atomic_enable() resumes the device with pm_runtime_get_sync() and returns early when the resume fails. The runtime PM usage count is incremented by pm_runtime_get_sync() even when the resume fails, so returning without dropping the reference leaks it and, once the error has been hit, prevents the device from ever suspending again. Drop the runtime PM reference with pm_runtime_put_noidle() on the resume failure path before returning. Fixes: 85f6421889ec ("drm: mali-dp: Enable power management for the device.") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Acked-by: Liviu Dudau Link: https://patch.msgid.link/20260916102112.2018372-1-vulab@iscas.ac.cn Signed-off-by: Liviu Dudau --- drivers/gpu/drm/arm/malidp_crtc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/arm/malidp_crtc.c b/drivers/gpu/drm/arm/malidp_crtc.c index 157ff148efe7..ec7b9db8b0b4 100644 --- a/drivers/gpu/drm/arm/malidp_crtc.c +++ b/drivers/gpu/drm/arm/malidp_crtc.c @@ -56,6 +56,7 @@ static void malidp_crtc_atomic_enable(struct drm_crtc *crtc, if (err < 0) { DRM_DEBUG_DRIVER("Failed to enable runtime power management: %d\n", err); + pm_runtime_put_noidle(crtc->dev->dev); return; } From 2302669bb4b52d581cc2589b90b3af7bb3783a28 Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Thu, 10 Sep 2026 21:16:01 +0900 Subject: [PATCH 44/46] drm/sched: Free the run queues at the end of drm_sched_fini() drm_sched_fini() frees the run queues at the top of teardown but the array holding them at the bottom. The early half is on the wrong side of cancel_delayed_work_sync(&sched->work_tdr), which waits for a timeout handler that can still walk sched->sched_rq[i] through drm_sched_increase_karma(). No correct driver can be there, since every fence returned from run_job() must be signaled before drm_sched_fini() is called. Free the entries next to the array anyway, so run-queue teardown happens in one place. Link: https://lore.kernel.org/dri-devel/20260910054605.634135-1-donggeunyoo.kernel@gmail.com/ Assisted-by: Claude:claude-fable-5 Signed-off-by: Donggeun Yoo Reviewed-by: Tvrtko Ursulin Signed-off-by: Philipp Stanner Link: https://patch.msgid.link/20260910121601.805032-1-donggeunyoo.kernel@gmail.com --- drivers/gpu/drm/scheduler/sched_main.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/scheduler/sched_main.c b/drivers/gpu/drm/scheduler/sched_main.c index e4835b99ca60..aba5545033ac 100644 --- a/drivers/gpu/drm/scheduler/sched_main.c +++ b/drivers/gpu/drm/scheduler/sched_main.c @@ -1210,9 +1210,6 @@ void drm_sched_fini(struct drm_gpu_scheduler *sched) drm_sched_wqueue_stop(sched); - for (i = DRM_SCHED_PRIORITY_KERNEL; i < sched->num_rqs; i++) - kfree(sched->sched_rq[i]); - /* Wakeup everyone stuck in drm_sched_entity_flush for this scheduler */ wake_up_all(&sched->job_scheduled); @@ -1226,6 +1223,9 @@ void drm_sched_fini(struct drm_gpu_scheduler *sched) if (sched->own_submit_wq) destroy_workqueue(sched->submit_wq); sched->ready = false; + + for (i = DRM_SCHED_PRIORITY_KERNEL; i < sched->num_rqs; i++) + kfree(sched->sched_rq[i]); kfree(sched->sched_rq); sched->sched_rq = NULL; From 537684e232fb590659a1448fca8a23d77d948ab1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ADra=20Canal?= Date: Tue, 22 Sep 2026 10:24:50 -0300 Subject: [PATCH 45/46] drm/v3d: Skip perfmon serialization while the device has no perfmon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every job merges its finished fence into the per-queue accumulator so that a job carrying a perfmon can later depend on everything still in flight. This is a (relatively high) cost that every job pays before it is submitted. Perfmons only exist while userspace runs a performance query, so the common case is a client paying that on every job for a dependency that is never actually added, increasing the submission latency. Address this situation by counting the number of perfmons alive on the device and returning early while that count is zero. This means that if no perfmon exists, v3d_serialize_for_perfmon() will bail out immediately. Jobs submitted while no perfmon is alive stay unaccounted and may overlap the first measured job. The count returns to zero whenever the last perfmon is destroyed, so that window reopens on every measurement cycle. Closing it would mean merging a fence on every submission for the lifetime of the device, so it is a reasonable compromise for the average use case. Reviewed-by: Iago Toral Quiroga Link: https://patch.msgid.link/20260922-v3d-quick-exit-perfmon-serialize-v2-1-2a8021dc32f2@igalia.com Signed-off-by: Maíra Canal --- drivers/gpu/drm/v3d/v3d_drv.h | 13 +++++++++++-- drivers/gpu/drm/v3d/v3d_perfmon.c | 8 ++++++-- drivers/gpu/drm/v3d/v3d_submit.c | 7 +++++++ 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/v3d/v3d_drv.h b/drivers/gpu/drm/v3d/v3d_drv.h index 26f8c3f635f9..02b6ab7dc72b 100644 --- a/drivers/gpu/drm/v3d/v3d_drv.h +++ b/drivers/gpu/drm/v3d/v3d_drv.h @@ -84,6 +84,8 @@ struct v3d_queue_state { * This way, only events related to a specific submission will be counted. */ struct v3d_perfmon { + struct v3d_dev *v3d; + /* Tracks the number of users of the perfmon, when this counter reaches * zero the perfmon is destroyed. */ @@ -184,6 +186,11 @@ struct v3d_dev { /* Perfmon currently programmed in HW (or NULL if none). */ struct v3d_perfmon *active; + /* Number of perfmons alive on this device. Jobs are not + * serialized if the number is zero. + */ + atomic_t nperfmons; + /* Finished fence of the most recently submitted job that * opened a serialization window (i.e. a job with a non-global * perfmon attached). @@ -191,8 +198,10 @@ struct v3d_dev { struct dma_fence *fence; /* Finished fence of the most recently submitted job on each HW - * queue. Used so that a new perfmon-carrying job can depend on - * every job currently in-flight across all queues. + * queue, which is used so that a new perfmon-carrying job can + * depend on every job currently in-flight across all queues. + * + * Finished fences are only tracked if @nperfmons > 0. */ struct dma_fence *last_hw_fence[V3D_MAX_QUEUES]; } perfmon_state; diff --git a/drivers/gpu/drm/v3d/v3d_perfmon.c b/drivers/gpu/drm/v3d/v3d_perfmon.c index 4cadc3e17280..c00f18d4a425 100644 --- a/drivers/gpu/drm/v3d/v3d_perfmon.c +++ b/drivers/gpu/drm/v3d/v3d_perfmon.c @@ -217,8 +217,10 @@ void v3d_perfmon_get(struct v3d_perfmon *perfmon) void v3d_perfmon_put(struct v3d_perfmon *perfmon) { - if (perfmon && refcount_dec_and_test(&perfmon->refcnt)) + if (perfmon && refcount_dec_and_test(&perfmon->refcnt)) { + atomic_dec(&perfmon->v3d->perfmon_state.nperfmons); kfree(perfmon); + } } static void v3d_perfmon_hw_start(struct v3d_dev *v3d, struct v3d_perfmon *perfmon) @@ -435,13 +437,15 @@ int v3d_perfmon_create_ioctl(struct drm_device *dev, void *data, perfmon->counters[i] = req->counters[i]; perfmon->ncounters = req->ncounters; + perfmon->v3d = v3d; refcount_set(&perfmon->refcnt, 1); + atomic_inc(&v3d->perfmon_state.nperfmons); ret = xa_alloc(&v3d_priv->perfmons, &id, perfmon, xa_limit_32b, GFP_KERNEL); if (ret < 0) { - kfree(perfmon); + v3d_perfmon_put(perfmon); return ret; } diff --git a/drivers/gpu/drm/v3d/v3d_submit.c b/drivers/gpu/drm/v3d/v3d_submit.c index 834d52030979..bc3c43fd4fd9 100644 --- a/drivers/gpu/drm/v3d/v3d_submit.c +++ b/drivers/gpu/drm/v3d/v3d_submit.c @@ -357,6 +357,10 @@ v3d_attach_perfmon_to_jobs(struct v3d_submit *submit, u32 perfmon_id) * * We don't serialize the jobs when using a global perfmon as it's expected to * track concurrent activity from all jobs. + * + * Keeping track of the in-flight jobs costs a fence merge per job, so it is + * only done while at least one perfmon is alive. Jobs submitted while no + * perfmon exists go untracked and may overlap the first measured job. */ static int v3d_serialize_for_perfmon(struct v3d_job *job) @@ -368,6 +372,9 @@ v3d_serialize_for_perfmon(struct v3d_job *job) lockdep_assert_held(&v3d->sched_lock); + if (!atomic_read(&v3d->perfmon_state.nperfmons)) + return 0; + scoped_guard(spinlock_irqsave, &v3d->perfmon_state.lock) is_global_perfmon = !!v3d->global_perfmon; From 6e375de99d0c420169481fcd36064177ab55b09d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ADra=20Canal?= Date: Tue, 22 Sep 2026 10:24:51 -0300 Subject: [PATCH 46/46] drm/v3d: Skip perfmon serialization while a global perfmon is set MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A global perfmon counts concurrent activity from every job, so no dependency is added while one is set. Skip the accumulator for the jobs carrying no perfmon while a global perfmon is enabled, which is every job in the common case. Jobs carrying their own perfmon stay tracked. The global perfmon may be cleared before such a job runs, and the next job carrying a perfmon has to wait for it, otherwise both are in flight at once and only one gets programmed in the HW. Reviewed-by: Iago Toral Quiroga Link: https://patch.msgid.link/20260922-v3d-quick-exit-perfmon-serialize-v2-2-2a8021dc32f2@igalia.com Signed-off-by: Maíra Canal --- drivers/gpu/drm/v3d/v3d_drv.h | 3 ++- drivers/gpu/drm/v3d/v3d_submit.c | 18 ++++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/v3d/v3d_drv.h b/drivers/gpu/drm/v3d/v3d_drv.h index 02b6ab7dc72b..d40610a34db9 100644 --- a/drivers/gpu/drm/v3d/v3d_drv.h +++ b/drivers/gpu/drm/v3d/v3d_drv.h @@ -201,7 +201,8 @@ struct v3d_dev { * queue, which is used so that a new perfmon-carrying job can * depend on every job currently in-flight across all queues. * - * Finished fences are only tracked if @nperfmons > 0. + * Finished fences are only tracked if @nperfmons > 0 and no + * global perfmon is set. */ struct dma_fence *last_hw_fence[V3D_MAX_QUEUES]; } perfmon_state; diff --git a/drivers/gpu/drm/v3d/v3d_submit.c b/drivers/gpu/drm/v3d/v3d_submit.c index bc3c43fd4fd9..7548faab67ba 100644 --- a/drivers/gpu/drm/v3d/v3d_submit.c +++ b/drivers/gpu/drm/v3d/v3d_submit.c @@ -359,15 +359,15 @@ v3d_attach_perfmon_to_jobs(struct v3d_submit *submit, u32 perfmon_id) * track concurrent activity from all jobs. * * Keeping track of the in-flight jobs costs a fence merge per job, so it is - * only done while at least one perfmon is alive. Jobs submitted while no - * perfmon exists go untracked and may overlap the first measured job. + * only done while at least one perfmon is alive, and is skipped for the jobs + * carrying no perfmon while a global perfmon is set. Jobs submitted while + * tracking is off go untracked and may overlap the first measured job. */ static int v3d_serialize_for_perfmon(struct v3d_job *job) { struct v3d_dev *v3d = job->v3d; struct dma_fence *merged; - bool is_global_perfmon; int ret; lockdep_assert_held(&v3d->sched_lock); @@ -375,11 +375,10 @@ v3d_serialize_for_perfmon(struct v3d_job *job) if (!atomic_read(&v3d->perfmon_state.nperfmons)) return 0; - scoped_guard(spinlock_irqsave, &v3d->perfmon_state.lock) - is_global_perfmon = !!v3d->global_perfmon; - - if (is_global_perfmon) - goto publish; + scoped_guard(spinlock_irqsave, &v3d->perfmon_state.lock) { + if (!job->perfmon && v3d->global_perfmon) + return 0; + } if (job->perfmon) { for (enum v3d_queue q = 0; q < V3D_MAX_QUEUES; q++) { @@ -400,7 +399,6 @@ v3d_serialize_for_perfmon(struct v3d_job *job) return ret; } -publish: /* * Accumulate every in-flight job on this queue into one merged fence. * A HW queue is fed by several scheduler entities (one per-fd), so jobs @@ -414,7 +412,7 @@ publish: dma_fence_put(v3d->perfmon_state.last_hw_fence[job->queue]); v3d->perfmon_state.last_hw_fence[job->queue] = merged; - if (job->perfmon && !is_global_perfmon) { + if (job->perfmon) { dma_fence_put(v3d->perfmon_state.fence); v3d->perfmon_state.fence = dma_fence_get(job->done_fence); }