From 565ed40b5fc1242f7538a016fce5a85f802d4fb5 Mon Sep 17 00:00:00 2001 From: Ketil Johnsen Date: Fri, 19 Dec 2025 10:35:44 +0100 Subject: [PATCH 01/48] drm/panthor: Evict groups before VM termination Ensure all related groups are evicted and suspended before VM destruction takes place. This fixes an issue where panthor_vm_destroy() destroys and unmaps the heap context while there are still on slot groups using this. The FW will do a write out to the heap context when a CSG (group) is suspended, so a premature unmap of the heap context will cause a GPU page fault. This page fault is quite harmless, and do not affect the continued operation of the GPU. Fixes: 647810ec2476 ("drm/panthor: Add the MMU/VM logical block") Reviewed-by: Boris Brezillon Signed-off-by: Ketil Johnsen Reviewed-by: Liviu Dudau Reviewed-by: Steven Price Link: https://patch.msgid.link/20251219093546.1227697-1-ketil.johnsen@arm.com Co-developed-by: Boris Brezillon Signed-off-by: Boris Brezillon --- drivers/gpu/drm/panthor/panthor_mmu.c | 4 ++++ drivers/gpu/drm/panthor/panthor_sched.c | 14 ++++++++++++++ drivers/gpu/drm/panthor/panthor_sched.h | 1 + 3 files changed, 19 insertions(+) diff --git a/drivers/gpu/drm/panthor/panthor_mmu.c b/drivers/gpu/drm/panthor/panthor_mmu.c index 473a8bebd61e..b44753f91e40 100644 --- a/drivers/gpu/drm/panthor/panthor_mmu.c +++ b/drivers/gpu/drm/panthor/panthor_mmu.c @@ -1504,6 +1504,10 @@ static void panthor_vm_destroy(struct panthor_vm *vm) vm->destroyed = true; + /* Tell scheduler to stop all GPU work related to this VM */ + if (refcount_read(&vm->as.active_cnt) > 0) + panthor_sched_prepare_for_vm_destruction(vm->ptdev); + mutex_lock(&vm->heaps.lock); panthor_heap_pool_destroy(vm->heaps.pool); vm->heaps.pool = NULL; diff --git a/drivers/gpu/drm/panthor/panthor_sched.c b/drivers/gpu/drm/panthor/panthor_sched.c index 0f83e778d89a..ca272dbae14d 100644 --- a/drivers/gpu/drm/panthor/panthor_sched.c +++ b/drivers/gpu/drm/panthor/panthor_sched.c @@ -2786,6 +2786,20 @@ void panthor_sched_report_mmu_fault(struct panthor_device *ptdev) sched_queue_delayed_work(ptdev->scheduler, tick, 0); } +void panthor_sched_prepare_for_vm_destruction(struct panthor_device *ptdev) +{ + /* FW can write out internal state, like the heap context, during CSG + * suspend. It is therefore important that the scheduler has fully + * evicted any pending and related groups before VM destruction can + * safely continue. Failure to do so can lead to GPU page faults. + * A controlled termination of a Panthor instance involves destroying + * the group(s) before the VM. This means any relevant group eviction + * has already been initiated by this point, and we just need to + * ensure that any pending tick_work() has been completed. + */ + flush_work(&ptdev->scheduler->tick_work.work); +} + void panthor_sched_resume(struct panthor_device *ptdev) { /* Force a tick to re-evaluate after a resume. */ diff --git a/drivers/gpu/drm/panthor/panthor_sched.h b/drivers/gpu/drm/panthor/panthor_sched.h index f4a475aa34c0..9a8692de8ade 100644 --- a/drivers/gpu/drm/panthor/panthor_sched.h +++ b/drivers/gpu/drm/panthor/panthor_sched.h @@ -50,6 +50,7 @@ void panthor_sched_suspend(struct panthor_device *ptdev); void panthor_sched_resume(struct panthor_device *ptdev); void panthor_sched_report_mmu_fault(struct panthor_device *ptdev); +void panthor_sched_prepare_for_vm_destruction(struct panthor_device *ptdev); void panthor_sched_report_fw_events(struct panthor_device *ptdev, u32 events); void panthor_fdinfo_gather_group_samples(struct panthor_file *pfile); From 8e7460eac786c72f48c4e04ce9be692b939428ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Larumbe?= Date: Wed, 17 Dec 2025 21:32:33 +0000 Subject: [PATCH 02/48] drm/panthor: Support partial unmaps of huge pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 33729a5fc0ca ("iommu/io-pgtable-arm: Remove split on unmap behavior") did away with the treatment of partial unmaps of huge IOPTEs. In the case of Panthor, that means an attempt to run a VM_BIND unmap operation on a memory region whose start address and size aren't 2MiB aligned, in the event it intersects with a huge page, would lead to ARM IOMMU management code to fail and a warning being raised. Presently, and for lack of a better alternative, it's best to have Panthor handle partial unmaps at the driver level, by unmapping entire huge pages and remapping the difference between them and the requested unmap region. This could change in the future when the VM_BIND uAPI is expanded to enforce huge page alignment and map/unmap operational constraints that render this code unnecessary. When a partial unmap for a huge PTE is attempted, we also need to expand the locked region to encompass whole huge pages. Signed-off-by: Adrián Larumbe Reviewed-by: Boris Brezillon Reviewed-by: Steven Price Link: https://patch.msgid.link/20251217213252.677020-2-adrian.larumbe@collabora.com Signed-off-by: Boris Brezillon --- drivers/gpu/drm/panthor/panthor_mmu.c | 100 +++++++++++++++++++++++--- 1 file changed, 92 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/panthor/panthor_mmu.c b/drivers/gpu/drm/panthor/panthor_mmu.c index b44753f91e40..b888fff05efe 100644 --- a/drivers/gpu/drm/panthor/panthor_mmu.c +++ b/drivers/gpu/drm/panthor/panthor_mmu.c @@ -533,12 +533,12 @@ static int as_send_cmd_and_wait(struct panthor_device *ptdev, u32 as_nr, u32 cmd return status; } -static u64 pack_region_range(struct panthor_device *ptdev, u64 region_start, u64 size) +static u64 pack_region_range(struct panthor_device *ptdev, u64 *region_start, u64 *size) { u8 region_width; - u64 region_end = region_start + size; + u64 region_end = *region_start + *size; - if (drm_WARN_ON_ONCE(&ptdev->base, !size)) + if (drm_WARN_ON_ONCE(&ptdev->base, !*size)) return 0; /* @@ -549,16 +549,17 @@ static u64 pack_region_range(struct panthor_device *ptdev, u64 region_start, u64 * change, the desired region starts with this bit (and subsequent bits) * zeroed and ends with the bit (and subsequent bits) set to one. */ - region_width = max(fls64(region_start ^ (region_end - 1)), + region_width = max(fls64(*region_start ^ (region_end - 1)), const_ilog2(AS_LOCK_REGION_MIN_SIZE)) - 1; /* * Mask off the low bits of region_start (which would be ignored by * the hardware anyway) */ - region_start &= GENMASK_ULL(63, region_width); + *region_start &= GENMASK_ULL(63, region_width); + *size = 1ull << (region_width + 1); - return region_width | region_start; + return region_width | *region_start; } static int panthor_mmu_as_enable(struct panthor_device *ptdev, u32 as_nr, @@ -1641,12 +1642,19 @@ static int panthor_vm_lock_region(struct panthor_vm *vm, u64 start, u64 size) struct panthor_device *ptdev = vm->ptdev; int ret = 0; + /* sm_step_remap() can call panthor_vm_lock_region() to account for + * the wider unmap needed when doing a partial huge page unamp. We + * need to ignore the lock if it's already part of the locked region. + */ + if (start >= vm->locked_region.start && + start + size <= vm->locked_region.start + vm->locked_region.size) + return 0; + mutex_lock(&ptdev->mmu->as.slots_lock); - drm_WARN_ON(&ptdev->base, vm->locked_region.start || vm->locked_region.size); if (vm->as.id >= 0 && size) { /* Lock the region that needs to be updated */ gpu_write64(ptdev, AS_LOCKADDR(vm->as.id), - pack_region_range(ptdev, start, size)); + pack_region_range(ptdev, &start, &size)); /* If the lock succeeded, update the locked_region info. */ ret = as_send_cmd_and_wait(ptdev, vm->as.id, AS_COMMAND_LOCK); @@ -2106,6 +2114,48 @@ static int panthor_gpuva_sm_step_map(struct drm_gpuva_op *op, void *priv) return 0; } +static bool +iova_mapped_as_huge_page(struct drm_gpuva_op_map *op, u64 addr) +{ + const struct page *pg; + pgoff_t bo_offset; + + bo_offset = addr - op->va.addr + op->gem.offset; + pg = to_panthor_bo(op->gem.obj)->base.pages[bo_offset >> PAGE_SHIFT]; + + return folio_size(page_folio(pg)) >= SZ_2M; +} + +static void +unmap_hugepage_align(const struct drm_gpuva_op_remap *op, + u64 *unmap_start, u64 *unmap_range) +{ + u64 aligned_unmap_start, aligned_unmap_end, unmap_end; + + unmap_end = *unmap_start + *unmap_range; + aligned_unmap_start = ALIGN_DOWN(*unmap_start, SZ_2M); + aligned_unmap_end = ALIGN(unmap_end, SZ_2M); + + /* If we're dealing with a huge page, make sure the unmap region is + * aligned on the start of the page. + */ + if (op->prev && aligned_unmap_start < *unmap_start && + op->prev->va.addr <= aligned_unmap_start && + iova_mapped_as_huge_page(op->prev, *unmap_start)) { + *unmap_range += *unmap_start - aligned_unmap_start; + *unmap_start = aligned_unmap_start; + } + + /* If we're dealing with a huge page, make sure the unmap region is + * aligned on the end of the page. + */ + if (op->next && aligned_unmap_end > unmap_end && + op->next->va.addr + op->next->va.range >= aligned_unmap_end && + iova_mapped_as_huge_page(op->next, unmap_end - 1)) { + *unmap_range += aligned_unmap_end - unmap_end; + } +} + static int panthor_gpuva_sm_step_remap(struct drm_gpuva_op *op, void *priv) { @@ -2114,16 +2164,50 @@ static int panthor_gpuva_sm_step_remap(struct drm_gpuva_op *op, struct panthor_vm_op_ctx *op_ctx = vm->op_ctx; struct panthor_vma *prev_vma = NULL, *next_vma = NULL; u64 unmap_start, unmap_range; + int ret; drm_gpuva_op_remap_to_unmap_range(&op->remap, &unmap_start, &unmap_range); + + /* + * ARM IOMMU page table management code disallows partial unmaps of huge pages, + * so when a partial unmap is requested, we must first unmap the entire huge + * page and then remap the difference between the huge page minus the requested + * unmap region. Calculating the right start address and range for the expanded + * unmap operation is the responsibility of the following function. + */ + unmap_hugepage_align(&op->remap, &unmap_start, &unmap_range); + + /* If the range changed, we might have to lock a wider region to guarantee + * atomicity. panthor_vm_lock_region() bails out early if the new region + * is already part of the locked region, so no need to do this check here. + */ + panthor_vm_lock_region(vm, unmap_start, unmap_range); panthor_vm_unmap_pages(vm, unmap_start, unmap_range); if (op->remap.prev) { + struct panthor_gem_object *bo = to_panthor_bo(op->remap.prev->gem.obj); + u64 offset = op->remap.prev->gem.offset + unmap_start - op->remap.prev->va.addr; + u64 size = op->remap.prev->va.addr + op->remap.prev->va.range - unmap_start; + + ret = panthor_vm_map_pages(vm, unmap_start, flags_to_prot(unmap_vma->flags), + bo->base.sgt, offset, size); + if (ret) + return ret; + prev_vma = panthor_vm_op_ctx_get_vma(op_ctx); panthor_vma_init(prev_vma, unmap_vma->flags); } if (op->remap.next) { + struct panthor_gem_object *bo = to_panthor_bo(op->remap.next->gem.obj); + u64 addr = op->remap.next->va.addr; + u64 size = unmap_start + unmap_range - op->remap.next->va.addr; + + ret = panthor_vm_map_pages(vm, addr, flags_to_prot(unmap_vma->flags), + bo->base.sgt, op->remap.next->gem.offset, size); + if (ret) + return ret; + next_vma = panthor_vm_op_ctx_get_vma(op_ctx); panthor_vma_init(next_vma, unmap_vma->flags); } From c2b40b1a4fbb0e9c078224bec3ea2e17f8076429 Mon Sep 17 00:00:00 2001 From: Francesco Valla Date: Wed, 17 Dec 2025 09:06:54 +0100 Subject: [PATCH 03/48] drm/draw: add drm_draw_can_convert_from_xrgb8888 Add drm_draw_can_convert_from_xrgb8888() function that can be used to determine if a XRGB8888 color can be converted to the specified format. Reviewed-by: Jocelyn Falempe Signed-off-by: Francesco Valla Link: https://patch.msgid.link/20251217-drm_draw_conv_check-v3-1-15b6f8bc1cbc@valla.it Signed-off-by: Jocelyn Falempe --- drivers/gpu/drm/drm_draw.c | 29 +++++++++++++++++++++++++++++ drivers/gpu/drm/drm_draw_internal.h | 2 ++ 2 files changed, 31 insertions(+) diff --git a/drivers/gpu/drm/drm_draw.c b/drivers/gpu/drm/drm_draw.c index 5b956229c82f..5043baf901b8 100644 --- a/drivers/gpu/drm/drm_draw.c +++ b/drivers/gpu/drm/drm_draw.c @@ -15,6 +15,35 @@ #include "drm_draw_internal.h" #include "drm_format_internal.h" +/** + * drm_draw_can_convert_from_xrgb8888 - check if xrgb8888 can be converted to the desired format + * @format: format + * + * Returns: + * True if XRGB8888 can be converted to the specified format, false otherwise. + */ +bool drm_draw_can_convert_from_xrgb8888(u32 format) +{ + switch (format) { + case DRM_FORMAT_RGB565: + case DRM_FORMAT_RGBA5551: + case DRM_FORMAT_XRGB1555: + case DRM_FORMAT_ARGB1555: + case DRM_FORMAT_RGB888: + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_XRGB2101010: + case DRM_FORMAT_ARGB2101010: + case DRM_FORMAT_ABGR2101010: + return true; + default: + return false; + } +} +EXPORT_SYMBOL(drm_draw_can_convert_from_xrgb8888); + /** * drm_draw_color_from_xrgb8888 - convert one pixel from xrgb8888 to the desired format * @color: input color, in xrgb8888 format diff --git a/drivers/gpu/drm/drm_draw_internal.h b/drivers/gpu/drm/drm_draw_internal.h index 20cb404e23ea..261967145635 100644 --- a/drivers/gpu/drm/drm_draw_internal.h +++ b/drivers/gpu/drm/drm_draw_internal.h @@ -24,6 +24,8 @@ static inline const u8 *drm_draw_get_char_bitmap(const struct font_desc *font, return font->data + (c * font->height) * font_pitch; } +bool drm_draw_can_convert_from_xrgb8888(u32 format); + u32 drm_draw_color_from_xrgb8888(u32 color, u32 format); void drm_draw_blit16(struct iosys_map *dmap, unsigned int dpitch, From 15156936b26f14c403d75a9353c84c5b17312b61 Mon Sep 17 00:00:00 2001 From: Francesco Valla Date: Wed, 17 Dec 2025 09:06:55 +0100 Subject: [PATCH 04/48] drm/log: avoid WARN when searching for usable format Use drm_draw_can_convert_from_xrgb8888() instead of drm_draw_color_from_xrgb8888() while searching for a usable color format. This avoids a WARN in case the first format is not usable. Reviewed-by: Jocelyn Falempe Signed-off-by: Francesco Valla Link: https://patch.msgid.link/20251217-drm_draw_conv_check-v3-2-15b6f8bc1cbc@valla.it Signed-off-by: Jocelyn Falempe --- drivers/gpu/drm/clients/drm_log.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/clients/drm_log.c b/drivers/gpu/drm/clients/drm_log.c index 4d3005273b27..2bd5189c967e 100644 --- a/drivers/gpu/drm/clients/drm_log.c +++ b/drivers/gpu/drm/clients/drm_log.c @@ -182,7 +182,7 @@ static u32 drm_log_find_usable_format(struct drm_plane *plane) int i; for (i = 0; i < plane->format_count; i++) - if (drm_draw_color_from_xrgb8888(0xffffff, plane->format_types[i]) != 0) + if (drm_draw_can_convert_from_xrgb8888(plane->format_types[i])) return plane->format_types[i]; return DRM_FORMAT_INVALID; } From 4f1b30fe9ef550651ea353714d1119431e76824a Mon Sep 17 00:00:00 2001 From: Francesco Valla Date: Wed, 17 Dec 2025 09:06:56 +0100 Subject: [PATCH 05/48] drm/panic: avoid WARN when checking format support Use drm_draw_can_convert_from_xrgb8888() instead of drm_draw_color_from_xrgb8888() while checking if a color format is usable. This avoids a WARN in case the first format is not usable. Reviewed-by: Jocelyn Falempe Signed-off-by: Francesco Valla Link: https://patch.msgid.link/20251217-drm_draw_conv_check-v3-3-15b6f8bc1cbc@valla.it Signed-off-by: Jocelyn Falempe --- drivers/gpu/drm/drm_panic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/drm_panic.c b/drivers/gpu/drm/drm_panic.c index f42be7f1d8c2..2050094e0518 100644 --- a/drivers/gpu/drm/drm_panic.c +++ b/drivers/gpu/drm/drm_panic.c @@ -872,7 +872,7 @@ static bool drm_panic_is_format_supported(const struct drm_format_info *format) { if (format->num_planes != 1) return false; - return drm_draw_color_from_xrgb8888(0xffffff, format->format) != 0; + return drm_draw_can_convert_from_xrgb8888(format->format); } static void draw_panic_dispatch(struct drm_scanout_buffer *sb) From 38b91e424c1ca0d6b909f8513509e1f6e938b193 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Wed, 3 Dec 2025 12:51:01 +0000 Subject: [PATCH 06/48] drm/panfrost: Add GPU_PM_RT support for RZ/G3E SoC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RZ/G3E SoC is embedded with Mali-G52 GPU system. The system hangs after STR in the following condition: STR -> Wakeup from STR -> Unload panfrost using 'modprobe -r panfrost'. Fix this issue by asserting/deasserting the reset during suspend/resume. Rename the variable allwinner_h616_data->default_pm_rt_data for data reuse and make it as generic GPU PM runtime data. Signed-off-by: Biju Das Reviewed-by: Adrián Larumbe Reviewed-by: Steven Price Link: https://patch.msgid.link/20251203125104.67596-1-biju.das.jz@bp.renesas.com Signed-off-by: Adrian Larumbe --- drivers/gpu/drm/panfrost/panfrost_drv.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/panfrost/panfrost_drv.c b/drivers/gpu/drm/panfrost/panfrost_drv.c index b95120682a72..2f58a9b0773a 100644 --- a/drivers/gpu/drm/panfrost/panfrost_drv.c +++ b/drivers/gpu/drm/panfrost/panfrost_drv.c @@ -1077,7 +1077,7 @@ static const struct panfrost_compatible default_data = { .pm_domain_names = NULL, }; -static const struct panfrost_compatible allwinner_h616_data = { +static const struct panfrost_compatible default_pm_rt_data = { .num_supplies = ARRAY_SIZE(default_supplies) - 1, .supply_names = default_supplies, .num_pm_domains = 1, @@ -1157,6 +1157,7 @@ static const struct of_device_id dt_match[] = { .data = &amlogic_data, }, { .compatible = "amlogic,meson-g12a-mali", .data = &amlogic_data, }, + { .compatible = "renesas,r9a09g047-mali", .data = &default_pm_rt_data }, { .compatible = "arm,mali-t604", .data = &default_data, }, { .compatible = "arm,mali-t624", .data = &default_data, }, { .compatible = "arm,mali-t628", .data = &default_data, }, @@ -1174,7 +1175,7 @@ static const struct of_device_id dt_match[] = { { .compatible = "mediatek,mt8188-mali", .data = &mediatek_mt8188_data }, { .compatible = "mediatek,mt8192-mali", .data = &mediatek_mt8192_data }, { .compatible = "mediatek,mt8370-mali", .data = &mediatek_mt8370_data }, - { .compatible = "allwinner,sun50i-h616-mali", .data = &allwinner_h616_data }, + { .compatible = "allwinner,sun50i-h616-mali", .data = &default_pm_rt_data }, {} }; MODULE_DEVICE_TABLE(of, dt_match); From 89f23d42006630dd94c01a8c916f8c648141ad8e Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Fri, 12 Dec 2025 17:00:32 +0100 Subject: [PATCH 07/48] drm/tests: shmem: Swap names of export tests GEM SHMEM has 2 helpers for exporting S/G tables. Swap the names of the rsp. tests, so that each matches the helper it tests. Signed-off-by: Thomas Zimmermann Fixes: 93032ae634d4 ("drm/test: add a test suite for GEM objects backed by shmem") Cc: dri-devel@lists.freedesktop.org Cc: # v6.8+ Reviewed-by: Boris Brezillon Link: https://patch.msgid.link/20251212160317.287409-2-tzimmermann@suse.de --- drivers/gpu/drm/tests/drm_gem_shmem_test.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/tests/drm_gem_shmem_test.c b/drivers/gpu/drm/tests/drm_gem_shmem_test.c index 68f2c3162354..872881ec9c30 100644 --- a/drivers/gpu/drm/tests/drm_gem_shmem_test.c +++ b/drivers/gpu/drm/tests/drm_gem_shmem_test.c @@ -194,7 +194,7 @@ static void drm_gem_shmem_test_vmap(struct kunit *test) * scatter/gather table large enough to accommodate the backing memory * is successfully exported. */ -static void drm_gem_shmem_test_get_pages_sgt(struct kunit *test) +static void drm_gem_shmem_test_get_sg_table(struct kunit *test) { struct drm_device *drm_dev = test->priv; struct drm_gem_shmem_object *shmem; @@ -236,7 +236,7 @@ static void drm_gem_shmem_test_get_pages_sgt(struct kunit *test) * backing pages are pinned and a scatter/gather table large enough to * accommodate the backing memory is successfully exported. */ -static void drm_gem_shmem_test_get_sg_table(struct kunit *test) +static void drm_gem_shmem_test_get_pages_sgt(struct kunit *test) { struct drm_device *drm_dev = test->priv; struct drm_gem_shmem_object *shmem; @@ -366,8 +366,8 @@ static struct kunit_case drm_gem_shmem_test_cases[] = { KUNIT_CASE(drm_gem_shmem_test_obj_create_private), KUNIT_CASE(drm_gem_shmem_test_pin_pages), KUNIT_CASE(drm_gem_shmem_test_vmap), - KUNIT_CASE(drm_gem_shmem_test_get_pages_sgt), KUNIT_CASE(drm_gem_shmem_test_get_sg_table), + KUNIT_CASE(drm_gem_shmem_test_get_pages_sgt), KUNIT_CASE(drm_gem_shmem_test_madvise), KUNIT_CASE(drm_gem_shmem_test_purge), {} From b47b9ecef309459278eb52f02b50eefdeaac4f6d Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Fri, 12 Dec 2025 17:00:33 +0100 Subject: [PATCH 08/48] drm/tests: shmem: Add clean-up action to unpin pages Automatically unpin pages on cleanup. The test currently fails with the error [ 58.246263] drm-kunit-mock-device drm_gem_shmem_test_get_sg_table.drm-kunit-mock-device: [drm] drm_WARN_ON(refcount_read(&shmem->pages_pin_count)) while cleaning up the GEM object. The pin count has to be zero at this point. Signed-off-by: Thomas Zimmermann Fixes: d586b535f144 ("drm/shmem-helper: Add and use pages_pin_count") Cc: dri-devel@lists.freedesktop.org Cc: # v6.16+ Reviewed-by: Boris Brezillon Link: https://patch.msgid.link/20251212160317.287409-3-tzimmermann@suse.de --- drivers/gpu/drm/tests/drm_gem_shmem_test.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/gpu/drm/tests/drm_gem_shmem_test.c b/drivers/gpu/drm/tests/drm_gem_shmem_test.c index 872881ec9c30..1d50bab51ef3 100644 --- a/drivers/gpu/drm/tests/drm_gem_shmem_test.c +++ b/drivers/gpu/drm/tests/drm_gem_shmem_test.c @@ -34,6 +34,9 @@ KUNIT_DEFINE_ACTION_WRAPPER(sg_free_table_wrapper, sg_free_table, KUNIT_DEFINE_ACTION_WRAPPER(drm_gem_shmem_free_wrapper, drm_gem_shmem_free, struct drm_gem_shmem_object *); +KUNIT_DEFINE_ACTION_WRAPPER(drm_gem_shmem_unpin_wrapper, drm_gem_shmem_unpin, + struct drm_gem_shmem_object *); + /* * Test creating a shmem GEM object backed by shmem buffer. The test * case succeeds if the GEM object is successfully allocated with the @@ -212,6 +215,9 @@ static void drm_gem_shmem_test_get_sg_table(struct kunit *test) ret = drm_gem_shmem_pin(shmem); KUNIT_ASSERT_EQ(test, ret, 0); + ret = kunit_add_action_or_reset(test, drm_gem_shmem_unpin_wrapper, shmem); + KUNIT_ASSERT_EQ(test, ret, 0); + sgt = drm_gem_shmem_get_sg_table(shmem); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sgt); KUNIT_EXPECT_NULL(test, shmem->sgt); From cda83b099f117f2a28a77bf467af934cb39e49cf Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Fri, 12 Dec 2025 17:00:34 +0100 Subject: [PATCH 09/48] drm/tests: shmem: Hold reservation lock around vmap/vunmap Acquire and release the GEM object's reservation lock around vmap and vunmap operations. The tests use vmap_locked, which led to errors such as show below. [ 122.292030] WARNING: CPU: 3 PID: 1413 at drivers/gpu/drm/drm_gem_shmem_helper.c:390 drm_gem_shmem_vmap_locked+0x3a3/0x6f0 [ 122.468066] WARNING: CPU: 3 PID: 1413 at drivers/gpu/drm/drm_gem_shmem_helper.c:293 drm_gem_shmem_pin_locked+0x1fe/0x350 [ 122.563504] WARNING: CPU: 3 PID: 1413 at drivers/gpu/drm/drm_gem_shmem_helper.c:234 drm_gem_shmem_get_pages_locked+0x23c/0x370 [ 122.662248] WARNING: CPU: 2 PID: 1413 at drivers/gpu/drm/drm_gem_shmem_helper.c:452 drm_gem_shmem_vunmap_locked+0x101/0x330 Only export the new vmap/vunmap helpers for Kunit tests. These are not interfaces for regular drivers. Signed-off-by: Thomas Zimmermann Fixes: 954907f7147d ("drm/shmem-helper: Refactor locked/unlocked functions") Cc: dri-devel@lists.freedesktop.org Cc: # v6.16+ Reviewed-by: Boris Brezillon Link: https://patch.msgid.link/20251212160317.287409-4-tzimmermann@suse.de --- drivers/gpu/drm/drm_gem_shmem_helper.c | 33 ++++++++++++++++++++++ drivers/gpu/drm/tests/drm_gem_shmem_test.c | 6 ++-- include/drm/drm_gem_shmem_helper.h | 9 ++++++ 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/drm_gem_shmem_helper.c b/drivers/gpu/drm/drm_gem_shmem_helper.c index fbd1164174b0..de4de594c78b 100644 --- a/drivers/gpu/drm/drm_gem_shmem_helper.c +++ b/drivers/gpu/drm/drm_gem_shmem_helper.c @@ -15,6 +15,8 @@ #include #endif +#include + #include #include #include @@ -901,6 +903,37 @@ fail_detach: } EXPORT_SYMBOL_GPL(drm_gem_shmem_prime_import_no_map); +/* + * Kunit helpers + */ + +#if IS_ENABLED(CONFIG_KUNIT) +int drm_gem_shmem_vmap(struct drm_gem_shmem_object *shmem, struct iosys_map *map) +{ + struct drm_gem_object *obj = &shmem->base; + int ret; + + ret = dma_resv_lock_interruptible(obj->resv, NULL); + if (ret) + return ret; + ret = drm_gem_shmem_vmap_locked(shmem, map); + dma_resv_unlock(obj->resv); + + return ret; +} +EXPORT_SYMBOL_IF_KUNIT(drm_gem_shmem_vmap); + +void drm_gem_shmem_vunmap(struct drm_gem_shmem_object *shmem, struct iosys_map *map) +{ + struct drm_gem_object *obj = &shmem->base; + + dma_resv_lock_interruptible(obj->resv, NULL); + drm_gem_shmem_vunmap_locked(shmem, map); + dma_resv_unlock(obj->resv); +} +EXPORT_SYMBOL_IF_KUNIT(drm_gem_shmem_vunmap); +#endif + MODULE_DESCRIPTION("DRM SHMEM memory-management helpers"); MODULE_IMPORT_NS("DMA_BUF"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/tests/drm_gem_shmem_test.c b/drivers/gpu/drm/tests/drm_gem_shmem_test.c index 1d50bab51ef3..3e7c6f20fbcc 100644 --- a/drivers/gpu/drm/tests/drm_gem_shmem_test.c +++ b/drivers/gpu/drm/tests/drm_gem_shmem_test.c @@ -19,6 +19,8 @@ #include #include +MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); + #define TEST_SIZE SZ_1M #define TEST_BYTE 0xae @@ -176,7 +178,7 @@ static void drm_gem_shmem_test_vmap(struct kunit *test) ret = kunit_add_action_or_reset(test, drm_gem_shmem_free_wrapper, shmem); KUNIT_ASSERT_EQ(test, ret, 0); - ret = drm_gem_shmem_vmap_locked(shmem, &map); + ret = drm_gem_shmem_vmap(shmem, &map); KUNIT_ASSERT_EQ(test, ret, 0); KUNIT_ASSERT_NOT_NULL(test, shmem->vaddr); KUNIT_ASSERT_FALSE(test, iosys_map_is_null(&map)); @@ -186,7 +188,7 @@ static void drm_gem_shmem_test_vmap(struct kunit *test) for (i = 0; i < TEST_SIZE; i++) KUNIT_EXPECT_EQ(test, iosys_map_rd(&map, i, u8), TEST_BYTE); - drm_gem_shmem_vunmap_locked(shmem, &map); + drm_gem_shmem_vunmap(shmem, &map); KUNIT_EXPECT_NULL(test, shmem->vaddr); KUNIT_EXPECT_EQ(test, refcount_read(&shmem->vmap_use_count), 0); } diff --git a/include/drm/drm_gem_shmem_helper.h b/include/drm/drm_gem_shmem_helper.h index 6b6478f5ca24..a80b5bda9271 100644 --- a/include/drm/drm_gem_shmem_helper.h +++ b/include/drm/drm_gem_shmem_helper.h @@ -300,4 +300,13 @@ struct drm_gem_object *drm_gem_shmem_prime_import_no_map(struct drm_device *dev, .gem_prime_import = drm_gem_shmem_prime_import_no_map, \ .dumb_create = drm_gem_shmem_dumb_create +/* + * Kunit helpers + */ + +#if IS_ENABLED(CONFIG_KUNIT) +int drm_gem_shmem_vmap(struct drm_gem_shmem_object *shmem, struct iosys_map *map); +void drm_gem_shmem_vunmap(struct drm_gem_shmem_object *shmem, struct iosys_map *map); +#endif + #endif /* __DRM_GEM_SHMEM_HELPER_H__ */ From 607d07d8cc0b835a8701259f08a03dc149b79b4f Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Fri, 12 Dec 2025 17:00:35 +0100 Subject: [PATCH 10/48] drm/tests: shmem: Hold reservation lock around madvise Acquire and release the GEM object's reservation lock around calls to the object's madvide operation. The tests use drm_gem_shmem_madvise_locked(), which led to errors such as show below. [ 58.339389] WARNING: CPU: 1 PID: 1352 at drivers/gpu/drm/drm_gem_shmem_helper.c:499 drm_gem_shmem_madvise_locked+0xde/0x140 Only export the new helper drm_gem_shmem_madvise() for Kunit tests. This is not an interface for regular drivers. Signed-off-by: Thomas Zimmermann Fixes: 954907f7147d ("drm/shmem-helper: Refactor locked/unlocked functions") Cc: dri-devel@lists.freedesktop.org Cc: # v6.16+ Reviewed-by: Boris Brezillon Link: https://patch.msgid.link/20251212160317.287409-5-tzimmermann@suse.de --- drivers/gpu/drm/drm_gem_shmem_helper.c | 15 +++++++++++++++ drivers/gpu/drm/tests/drm_gem_shmem_test.c | 8 ++++---- include/drm/drm_gem_shmem_helper.h | 1 + 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/drm_gem_shmem_helper.c b/drivers/gpu/drm/drm_gem_shmem_helper.c index de4de594c78b..d377802b9dfd 100644 --- a/drivers/gpu/drm/drm_gem_shmem_helper.c +++ b/drivers/gpu/drm/drm_gem_shmem_helper.c @@ -932,6 +932,21 @@ void drm_gem_shmem_vunmap(struct drm_gem_shmem_object *shmem, struct iosys_map * dma_resv_unlock(obj->resv); } EXPORT_SYMBOL_IF_KUNIT(drm_gem_shmem_vunmap); + +int drm_gem_shmem_madvise(struct drm_gem_shmem_object *shmem, int madv) +{ + struct drm_gem_object *obj = &shmem->base; + int ret; + + ret = dma_resv_lock_interruptible(obj->resv, NULL); + if (ret) + return ret; + ret = drm_gem_shmem_madvise_locked(shmem, madv); + dma_resv_unlock(obj->resv); + + return ret; +} +EXPORT_SYMBOL_IF_KUNIT(drm_gem_shmem_madvise); #endif MODULE_DESCRIPTION("DRM SHMEM memory-management helpers"); diff --git a/drivers/gpu/drm/tests/drm_gem_shmem_test.c b/drivers/gpu/drm/tests/drm_gem_shmem_test.c index 3e7c6f20fbcc..d639848e3c8e 100644 --- a/drivers/gpu/drm/tests/drm_gem_shmem_test.c +++ b/drivers/gpu/drm/tests/drm_gem_shmem_test.c @@ -292,17 +292,17 @@ static void drm_gem_shmem_test_madvise(struct kunit *test) ret = kunit_add_action_or_reset(test, drm_gem_shmem_free_wrapper, shmem); KUNIT_ASSERT_EQ(test, ret, 0); - ret = drm_gem_shmem_madvise_locked(shmem, 1); + ret = drm_gem_shmem_madvise(shmem, 1); KUNIT_EXPECT_TRUE(test, ret); KUNIT_ASSERT_EQ(test, shmem->madv, 1); /* Set madv to a negative value */ - ret = drm_gem_shmem_madvise_locked(shmem, -1); + ret = drm_gem_shmem_madvise(shmem, -1); KUNIT_EXPECT_FALSE(test, ret); KUNIT_ASSERT_EQ(test, shmem->madv, -1); /* Check that madv cannot be set back to a positive value */ - ret = drm_gem_shmem_madvise_locked(shmem, 0); + ret = drm_gem_shmem_madvise(shmem, 0); KUNIT_EXPECT_FALSE(test, ret); KUNIT_ASSERT_EQ(test, shmem->madv, -1); } @@ -330,7 +330,7 @@ static void drm_gem_shmem_test_purge(struct kunit *test) ret = drm_gem_shmem_is_purgeable(shmem); KUNIT_EXPECT_FALSE(test, ret); - ret = drm_gem_shmem_madvise_locked(shmem, 1); + ret = drm_gem_shmem_madvise(shmem, 1); KUNIT_EXPECT_TRUE(test, ret); /* The scatter/gather table will be freed by drm_gem_shmem_free */ diff --git a/include/drm/drm_gem_shmem_helper.h b/include/drm/drm_gem_shmem_helper.h index a80b5bda9271..dd9a7c67195e 100644 --- a/include/drm/drm_gem_shmem_helper.h +++ b/include/drm/drm_gem_shmem_helper.h @@ -307,6 +307,7 @@ struct drm_gem_object *drm_gem_shmem_prime_import_no_map(struct drm_device *dev, #if IS_ENABLED(CONFIG_KUNIT) int drm_gem_shmem_vmap(struct drm_gem_shmem_object *shmem, struct iosys_map *map); void drm_gem_shmem_vunmap(struct drm_gem_shmem_object *shmem, struct iosys_map *map); +int drm_gem_shmem_madvise(struct drm_gem_shmem_object *shmem, int madv); #endif #endif /* __DRM_GEM_SHMEM_HELPER_H__ */ From 3f41307d589c2f25d556d47b165df808124cd0c4 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Fri, 12 Dec 2025 17:00:36 +0100 Subject: [PATCH 11/48] drm/tests: shmem: Hold reservation lock around purge Acquire and release the GEM object's reservation lock around calls to the object's purge operation. The tests use drm_gem_shmem_purge_locked(), which led to errors such as show below. [ 58.709128] WARNING: CPU: 1 PID: 1354 at drivers/gpu/drm/drm_gem_shmem_helper.c:515 drm_gem_shmem_purge_locked+0x51c/0x740 Only export the new helper drm_gem_shmem_purge() for Kunit tests. This is not an interface for regular drivers. Signed-off-by: Thomas Zimmermann Fixes: 954907f7147d ("drm/shmem-helper: Refactor locked/unlocked functions") Cc: dri-devel@lists.freedesktop.org Cc: # v6.16+ Reviewed-by: Boris Brezillon Link: https://patch.msgid.link/20251212160317.287409-6-tzimmermann@suse.de --- drivers/gpu/drm/drm_gem_shmem_helper.c | 15 +++++++++++++++ drivers/gpu/drm/tests/drm_gem_shmem_test.c | 4 +++- include/drm/drm_gem_shmem_helper.h | 1 + 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/drm_gem_shmem_helper.c b/drivers/gpu/drm/drm_gem_shmem_helper.c index d377802b9dfd..de73c8ab8cca 100644 --- a/drivers/gpu/drm/drm_gem_shmem_helper.c +++ b/drivers/gpu/drm/drm_gem_shmem_helper.c @@ -947,6 +947,21 @@ int drm_gem_shmem_madvise(struct drm_gem_shmem_object *shmem, int madv) return ret; } EXPORT_SYMBOL_IF_KUNIT(drm_gem_shmem_madvise); + +int drm_gem_shmem_purge(struct drm_gem_shmem_object *shmem) +{ + struct drm_gem_object *obj = &shmem->base; + int ret; + + ret = dma_resv_lock_interruptible(obj->resv, NULL); + if (ret) + return ret; + drm_gem_shmem_purge_locked(shmem); + dma_resv_unlock(obj->resv); + + return 0; +} +EXPORT_SYMBOL_IF_KUNIT(drm_gem_shmem_purge); #endif MODULE_DESCRIPTION("DRM SHMEM memory-management helpers"); diff --git a/drivers/gpu/drm/tests/drm_gem_shmem_test.c b/drivers/gpu/drm/tests/drm_gem_shmem_test.c index d639848e3c8e..4b459f21acfd 100644 --- a/drivers/gpu/drm/tests/drm_gem_shmem_test.c +++ b/drivers/gpu/drm/tests/drm_gem_shmem_test.c @@ -340,7 +340,9 @@ static void drm_gem_shmem_test_purge(struct kunit *test) ret = drm_gem_shmem_is_purgeable(shmem); KUNIT_EXPECT_TRUE(test, ret); - drm_gem_shmem_purge_locked(shmem); + ret = drm_gem_shmem_purge(shmem); + KUNIT_ASSERT_EQ(test, ret, 0); + KUNIT_EXPECT_NULL(test, shmem->pages); KUNIT_EXPECT_NULL(test, shmem->sgt); KUNIT_EXPECT_EQ(test, shmem->madv, -1); diff --git a/include/drm/drm_gem_shmem_helper.h b/include/drm/drm_gem_shmem_helper.h index dd9a7c67195e..5ccdae21b94a 100644 --- a/include/drm/drm_gem_shmem_helper.h +++ b/include/drm/drm_gem_shmem_helper.h @@ -308,6 +308,7 @@ struct drm_gem_object *drm_gem_shmem_prime_import_no_map(struct drm_device *dev, int drm_gem_shmem_vmap(struct drm_gem_shmem_object *shmem, struct iosys_map *map); void drm_gem_shmem_vunmap(struct drm_gem_shmem_object *shmem, struct iosys_map *map); int drm_gem_shmem_madvise(struct drm_gem_shmem_object *shmem, int madv); +int drm_gem_shmem_purge(struct drm_gem_shmem_object *shmem); #endif #endif /* __DRM_GEM_SHMEM_HELPER_H__ */ From 353e72df138aabc8e6dc60e144eebe806fc64428 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Mon, 8 Dec 2025 11:17:33 +0100 Subject: [PATCH 12/48] vt: Remove trailing whitespace Fix coding style in vt.c Signed-off-by: Thomas Zimmermann Reviewed-by: Greg Kroah-Hartman Link: https://patch.msgid.link/20251208102851.40894-2-tzimmermann@suse.de --- drivers/tty/vt/vt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c index 59b4b5e126ba..d9eb8eae602b 100644 --- a/drivers/tty/vt/vt.c +++ b/drivers/tty/vt/vt.c @@ -38,7 +38,7 @@ * * - Arno Griffioen * - David Carter - * + * * The abstract console driver provides a generic interface for a text * console. It supports VGA text mode, frame buffer based graphical consoles * and special graphics processors that are only accessible through some From c83e42990303c05e60001d636212502ed5a2d48a Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Mon, 8 Dec 2025 11:17:34 +0100 Subject: [PATCH 13/48] vt: Remove con_debug_enter/_leave from struct consw There are no implementations of con_debug_enter and con_debug_leave. Remove the callbacks from struct consw and clean up the caller. This is a functional revert of commit b45cfba4e900 ("vt,console,kdb: implement atomic console enter/leave functions"). Signed-off-by: Thomas Zimmermann Reviewed-by: Greg Kroah-Hartman Link: https://patch.msgid.link/20251208102851.40894-3-tzimmermann@suse.de --- drivers/tty/vt/vt.c | 30 +----------------------------- include/linux/console.h | 8 -------- 2 files changed, 1 insertion(+), 37 deletions(-) diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c index d9eb8eae602b..e987d260e346 100644 --- a/drivers/tty/vt/vt.c +++ b/drivers/tty/vt/vt.c @@ -187,19 +187,12 @@ static DECLARE_WORK(con_driver_unregister_work, con_driver_unregister_callback); * fg_console is the current virtual console, * last_console is the last used one, * want_console is the console we want to switch to, - * saved_* variants are for save/restore around kernel debugger enter/leave */ int fg_console; EXPORT_SYMBOL(fg_console); int last_console; int want_console = -1; -static int saved_fg_console; -static int saved_last_console; -static int saved_want_console; -static int saved_vc_mode; -static int saved_console_blanked; - /* * For each existing display, we have a pointer to console currently visible * on that display, allowing consoles other than fg_console to be refreshed @@ -4287,15 +4280,6 @@ EXPORT_SYMBOL(con_is_visible); */ void con_debug_enter(struct vc_data *vc) { - saved_fg_console = fg_console; - saved_last_console = last_console; - saved_want_console = want_console; - saved_vc_mode = vc->vc_mode; - saved_console_blanked = console_blanked; - vc->vc_mode = KD_TEXT; - console_blanked = 0; - if (vc->vc_sw->con_debug_enter) - vc->vc_sw->con_debug_enter(vc); #ifdef CONFIG_KGDB_KDB /* Set the initial LINES variable if it is not already set */ if (vc->vc_rows < 999) { @@ -4335,19 +4319,7 @@ EXPORT_SYMBOL_GPL(con_debug_enter); * was invoked. */ void con_debug_leave(void) -{ - struct vc_data *vc; - - fg_console = saved_fg_console; - last_console = saved_last_console; - want_console = saved_want_console; - console_blanked = saved_console_blanked; - vc_cons[fg_console].d->vc_mode = saved_vc_mode; - - vc = vc_cons[fg_console].d; - if (vc->vc_sw->con_debug_leave) - vc->vc_sw->con_debug_leave(vc); -} +{ } EXPORT_SYMBOL_GPL(con_debug_leave); static int do_register_con_driver(const struct consw *csw, int first, int last) diff --git a/include/linux/console.h b/include/linux/console.h index fc9f5c5c1b04..e22f5c993e24 100644 --- a/include/linux/console.h +++ b/include/linux/console.h @@ -79,12 +79,6 @@ enum vc_intensity; * characters. (optional) * @con_invert_region: invert a region of length @count on @vc starting at @p. * (optional) - * @con_debug_enter: prepare the console for the debugger. This includes, but - * is not limited to, unblanking the console, loading an - * appropriate palette, and allowing debugger generated output. - * (optional) - * @con_debug_leave: restore the console to its pre-debug state as closely as - * possible. (optional) */ struct consw { struct module *owner; @@ -123,8 +117,6 @@ struct consw { enum vc_intensity intensity, bool blink, bool underline, bool reverse, bool italic); void (*con_invert_region)(struct vc_data *vc, u16 *p, int count); - void (*con_debug_enter)(struct vc_data *vc); - void (*con_debug_leave)(struct vc_data *vc); }; extern const struct consw *conswitchp; From 50c26c301c5176cc8b431044390e10ec862b9b77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Rebe?= Date: Fri, 12 Dec 2025 21:05:04 +0100 Subject: [PATCH 14/48] drm/ast: Swap framebuffer writes on big-endian machines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Swap the pixel data when writing to framebuffer memory on big-endian machines. Fixes incorrect output. Aspeed graphics does not appear to support big-endian framebuffers after AST2400, although the feature has been documented. There's a lengthy discussion at [1]. v5: - avoid restricted cast from __be16 (kernel test robot) Signed-off-by: René Rebe Link: https://lore.kernel.org/dri-devel/20251202.170626.2134482663677806825.rene@exactco.de/ # [1] Reviewed-by: Thomas Zimmermann Signed-off-by: Thomas Zimmermann Link: https://patch.msgid.link/20251212.210504.1355099120650239629.rene@exactco.de --- drivers/gpu/drm/ast/ast_cursor.c | 11 ++++++++--- drivers/gpu/drm/ast/ast_mode.c | 11 +++++++++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/ast/ast_cursor.c b/drivers/gpu/drm/ast/ast_cursor.c index 30b62d3f0151..3fa8e75db9c0 100644 --- a/drivers/gpu/drm/ast/ast_cursor.c +++ b/drivers/gpu/drm/ast/ast_cursor.c @@ -93,12 +93,17 @@ static void ast_set_cursor_image(struct ast_device *ast, const u8 *src, unsigned int width, unsigned int height) { u8 __iomem *dst = ast_plane_vaddr(&ast->cursor_plane.base); - u32 csum; - - csum = ast_cursor_calculate_checksum(src, width, height); + u32 csum = ast_cursor_calculate_checksum(src, width, height); /* write pixel data */ +#if defined(__BIG_ENDIAN) + unsigned int i; + + for (i = 0; i < AST_HWC_SIZE; i += 2) + writew(swab16(*(const __u16 *)&src[i]), &dst[i]); +#else memcpy_toio(dst, src, AST_HWC_SIZE); +#endif /* write checksum + signature */ dst += AST_HWC_SIZE; diff --git a/drivers/gpu/drm/ast/ast_mode.c b/drivers/gpu/drm/ast/ast_mode.c index cd08990a10f9..57c6fbc3232b 100644 --- a/drivers/gpu/drm/ast/ast_mode.c +++ b/drivers/gpu/drm/ast/ast_mode.c @@ -526,12 +526,18 @@ static int ast_primary_plane_helper_atomic_check(struct drm_plane *plane, static void ast_handle_damage(struct ast_plane *ast_plane, struct iosys_map *src, struct drm_framebuffer *fb, - const struct drm_rect *clip) + const struct drm_rect *clip, + struct drm_format_conv_state *fmtcnv_state) { struct iosys_map dst = IOSYS_MAP_INIT_VADDR_IOMEM(ast_plane_vaddr(ast_plane)); iosys_map_incr(&dst, drm_fb_clip_offset(fb->pitches[0], fb->format, clip)); + +#if defined(__BIG_ENDIAN) + drm_fb_swab(&dst, fb->pitches, src, fb, clip, !src[0].is_iomem, fmtcnv_state); +#else drm_fb_memcpy(&dst, fb->pitches, src, fb, clip); +#endif } static void ast_primary_plane_helper_atomic_update(struct drm_plane *plane, @@ -561,7 +567,8 @@ static void ast_primary_plane_helper_atomic_update(struct drm_plane *plane, if (drm_gem_fb_begin_cpu_access(fb, DMA_FROM_DEVICE) == 0) { drm_atomic_helper_damage_iter_init(&iter, old_plane_state, plane_state); drm_atomic_for_each_plane_damage(&iter, &damage) { - ast_handle_damage(ast_plane, shadow_plane_state->data, fb, &damage); + ast_handle_damage(ast_plane, shadow_plane_state->data, fb, &damage, + &shadow_plane_state->fmtcnv_state); } drm_gem_fb_end_cpu_access(fb, DMA_FROM_DEVICE); From e05b08d7d0162cf77fff119367fb1a2d5ab3e669 Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Fri, 19 Dec 2025 13:49:39 +0200 Subject: [PATCH 15/48] drm/atomic: convert drm_atomic_get_{old, new}_colorop_state() into proper functions There is no real reason to include drm_colorop.h from drm_atomic.h, as drm_atomic_get_{old,new}_colorop_state() have no real reason to be static inline. Convert the static inlines to proper functions, and drop the include to reduce the include dependencies and improve data hiding. v2: Fix vkms build failures (Alex) Fixes: cfc27680ee20 ("drm/colorop: Introduce new drm_colorop mode object") Cc: Simon Ser Cc: Alex Hung Cc: Harry Wentland Cc: Daniel Stone Cc: Melissa Wen Cc: Sebastian Wick Cc: Alex Hung Reviewed-by: Alex Hung Link: https://patch.msgid.link/20251219114939.1069851-1-jani.nikula@intel.com Signed-off-by: Jani Nikula --- .../amd/display/amdgpu_dm/amdgpu_dm_color.c | 3 ++ drivers/gpu/drm/drm_atomic.c | 32 +++++++++++++++ drivers/gpu/drm/drm_atomic_helper.c | 1 + .../drm/i915/display/intel_display_types.h | 1 + drivers/gpu/drm/vkms/vkms_composer.c | 1 + drivers/gpu/drm/vkms/vkms_drv.c | 1 + include/drm/drm_atomic.h | 39 ++++--------------- 7 files changed, 47 insertions(+), 31 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c index 1dcc79b35225..20a76d81d532 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c @@ -23,6 +23,9 @@ * Authors: AMD * */ + +#include + #include "amdgpu.h" #include "amdgpu_mode.h" #include "amdgpu_dm.h" diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c index 6d3ea8056b60..52738b80ddbe 100644 --- a/drivers/gpu/drm/drm_atomic.c +++ b/drivers/gpu/drm/drm_atomic.c @@ -641,6 +641,38 @@ drm_atomic_get_colorop_state(struct drm_atomic_state *state, } EXPORT_SYMBOL(drm_atomic_get_colorop_state); +/** + * drm_atomic_get_old_colorop_state - get colorop state, if it exists + * @state: global atomic state object + * @colorop: colorop to grab + * + * This function returns the old colorop state for the given colorop, or + * NULL if the colorop is not part of the global atomic state. + */ +struct drm_colorop_state * +drm_atomic_get_old_colorop_state(struct drm_atomic_state *state, + struct drm_colorop *colorop) +{ + return state->colorops[drm_colorop_index(colorop)].old_state; +} +EXPORT_SYMBOL(drm_atomic_get_old_colorop_state); + +/** + * drm_atomic_get_new_colorop_state - get colorop state, if it exists + * @state: global atomic state object + * @colorop: colorop to grab + * + * This function returns the new colorop state for the given colorop, or + * NULL if the colorop is not part of the global atomic state. + */ +struct drm_colorop_state * +drm_atomic_get_new_colorop_state(struct drm_atomic_state *state, + struct drm_colorop *colorop) +{ + return state->colorops[drm_colorop_index(colorop)].new_state; +} +EXPORT_SYMBOL(drm_atomic_get_new_colorop_state); + static bool plane_switching_crtc(const struct drm_plane_state *old_plane_state, const struct drm_plane_state *new_plane_state) diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c index 10adac9397cf..5840e9cc6f66 100644 --- a/drivers/gpu/drm/drm_atomic_helper.c +++ b/drivers/gpu/drm/drm_atomic_helper.c @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h b/drivers/gpu/drm/i915/display/intel_display_types.h index 06bf8f7c0989..6e26751e8d0e 100644 --- a/drivers/gpu/drm/i915/display/intel_display_types.h +++ b/drivers/gpu/drm/i915/display/intel_display_types.h @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/gpu/drm/vkms/vkms_composer.c b/drivers/gpu/drm/vkms/vkms_composer.c index 3cf3f26e0d8e..cd85de4ffd03 100644 --- a/drivers/gpu/drm/vkms/vkms_composer.c +++ b/drivers/gpu/drm/vkms/vkms_composer.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/gpu/drm/vkms/vkms_drv.c b/drivers/gpu/drm/vkms/vkms_drv.c index dd1402f43773..434c295f44ba 100644 --- a/drivers/gpu/drm/vkms/vkms_drv.c +++ b/drivers/gpu/drm/vkms/vkms_drv.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h index 74ce26fa8838..178f8f62c80f 100644 --- a/include/drm/drm_atomic.h +++ b/include/drm/drm_atomic.h @@ -30,7 +30,6 @@ #include #include -#include /** * struct drm_crtc_commit - track modeset commits on a CRTC @@ -712,6 +711,14 @@ drm_atomic_get_plane_state(struct drm_atomic_state *state, struct drm_colorop_state * drm_atomic_get_colorop_state(struct drm_atomic_state *state, struct drm_colorop *colorop); + +struct drm_colorop_state * +drm_atomic_get_old_colorop_state(struct drm_atomic_state *state, + struct drm_colorop *colorop); +struct drm_colorop_state * +drm_atomic_get_new_colorop_state(struct drm_atomic_state *state, + struct drm_colorop *colorop); + struct drm_connector_state * __must_check drm_atomic_get_connector_state(struct drm_atomic_state *state, struct drm_connector *connector); @@ -808,36 +815,6 @@ drm_atomic_get_new_plane_state(const struct drm_atomic_state *state, return state->planes[drm_plane_index(plane)].new_state; } -/** - * drm_atomic_get_old_colorop_state - get colorop state, if it exists - * @state: global atomic state object - * @colorop: colorop to grab - * - * This function returns the old colorop state for the given colorop, or - * NULL if the colorop is not part of the global atomic state. - */ -static inline struct drm_colorop_state * -drm_atomic_get_old_colorop_state(struct drm_atomic_state *state, - struct drm_colorop *colorop) -{ - return state->colorops[drm_colorop_index(colorop)].old_state; -} - -/** - * drm_atomic_get_new_colorop_state - get colorop state, if it exists - * @state: global atomic state object - * @colorop: colorop to grab - * - * This function returns the new colorop state for the given colorop, or - * NULL if the colorop is not part of the global atomic state. - */ -static inline struct drm_colorop_state * -drm_atomic_get_new_colorop_state(struct drm_atomic_state *state, - struct drm_colorop *colorop) -{ - return state->colorops[drm_colorop_index(colorop)].new_state; -} - /** * drm_atomic_get_old_connector_state - get connector state, if it exists * @state: global atomic state object From 8a717c16ddf261118e9128d7f146d68a2567f087 Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Sun, 3 Aug 2025 14:53:51 +0300 Subject: [PATCH 16/48] drm/bridge: add connector argument to .hpd_notify callback Drivers might need to update DRM connector in the drm_bridge_funcs.hpd_notify callback (e.g. it might be necessary to update EDID before setting ELD). Add corresponding argument to the callback. Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/20250803-lt9611uxc-hdmi-v1-1-cb9ce1793acf@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/display/drm_bridge_connector.c | 2 +- drivers/gpu/drm/meson/meson_encoder_hdmi.c | 1 + drivers/gpu/drm/msm/dp/dp_display.c | 3 ++- drivers/gpu/drm/msm/dp/dp_drm.h | 3 ++- drivers/gpu/drm/omapdrm/dss/hdmi4.c | 1 + include/drm/drm_bridge.h | 1 + 6 files changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c index a2d30cf9e06d..57a0cceabd34 100644 --- a/drivers/gpu/drm/display/drm_bridge_connector.c +++ b/drivers/gpu/drm/display/drm_bridge_connector.c @@ -141,7 +141,7 @@ static void drm_bridge_connector_hpd_notify(struct drm_connector *connector, /* Notify all bridges in the pipeline of hotplug events. */ drm_for_each_bridge_in_chain_scoped(bridge_connector->encoder, bridge) { if (bridge->funcs->hpd_notify) - bridge->funcs->hpd_notify(bridge, status); + bridge->funcs->hpd_notify(bridge, connector, status); } } diff --git a/drivers/gpu/drm/meson/meson_encoder_hdmi.c b/drivers/gpu/drm/meson/meson_encoder_hdmi.c index 8205ee56a691..a665c9036878 100644 --- a/drivers/gpu/drm/meson/meson_encoder_hdmi.c +++ b/drivers/gpu/drm/meson/meson_encoder_hdmi.c @@ -323,6 +323,7 @@ static int meson_encoder_hdmi_atomic_check(struct drm_bridge *bridge, } static void meson_encoder_hdmi_hpd_notify(struct drm_bridge *bridge, + struct drm_connector *connector, enum drm_connector_status status) { struct meson_encoder_hdmi *encoder_hdmi = bridge_to_meson_encoder_hdmi(bridge); diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index 9bd9cd5c1e03..c0569229a1d1 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -1783,7 +1783,8 @@ void msm_dp_bridge_hpd_disable(struct drm_bridge *bridge) } void msm_dp_bridge_hpd_notify(struct drm_bridge *bridge, - enum drm_connector_status status) + struct drm_connector *connector, + enum drm_connector_status status) { struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(bridge); struct msm_dp *msm_dp_display = msm_dp_bridge->msm_dp_display; diff --git a/drivers/gpu/drm/msm/dp/dp_drm.h b/drivers/gpu/drm/msm/dp/dp_drm.h index d8c9b905f8bf..9eb3431dd93a 100644 --- a/drivers/gpu/drm/msm/dp/dp_drm.h +++ b/drivers/gpu/drm/msm/dp/dp_drm.h @@ -40,6 +40,7 @@ void msm_dp_bridge_mode_set(struct drm_bridge *drm_bridge, void msm_dp_bridge_hpd_enable(struct drm_bridge *bridge); void msm_dp_bridge_hpd_disable(struct drm_bridge *bridge); void msm_dp_bridge_hpd_notify(struct drm_bridge *bridge, - enum drm_connector_status status); + struct drm_connector *connector, + enum drm_connector_status status); #endif /* _DP_DRM_H_ */ diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi4.c b/drivers/gpu/drm/omapdrm/dss/hdmi4.c index 3cd612af2449..29b2dfb90b5f 100644 --- a/drivers/gpu/drm/omapdrm/dss/hdmi4.c +++ b/drivers/gpu/drm/omapdrm/dss/hdmi4.c @@ -428,6 +428,7 @@ static void hdmi4_bridge_disable(struct drm_bridge *bridge, } static void hdmi4_bridge_hpd_notify(struct drm_bridge *bridge, + struct drm_connector *connector, enum drm_connector_status status) { struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge); diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h index d2683846cc61..68b9da1eb542 100644 --- a/include/drm/drm_bridge.h +++ b/include/drm/drm_bridge.h @@ -731,6 +731,7 @@ struct drm_bridge_funcs { * controllers for HDMI bridges. */ void (*hpd_notify)(struct drm_bridge *bridge, + struct drm_connector *connector, enum drm_connector_status status); /** From c08c123d4cd6ec3ee482d607e29388d0db2d3f1d Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Sun, 3 Aug 2025 14:53:52 +0300 Subject: [PATCH 17/48] drm/bridge: lontium-lt9611uxc: switch to HDMI audio helpers While LT9611UXC is a DSI-to-HDMI bridge, it implements all HDMI-related functions internally, in the firmware, thus it doesn't make sense to implement DRM_BRIDGE_OP_HDMI. However it is possible to implement DRM_BRIDGE_OP_HDMI_AUDIO, streamlining HDMI audio plumbing (which includes plugged notifications and ELD handling). Implement corresponding callbacks and trigger EDID read / drm_connector_hdmi_audio_plugged_notify() from the hpd_notify callback. Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/20250803-lt9611uxc-hdmi-v1-2-cb9ce1793acf@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/bridge/lontium-lt9611uxc.c | 125 ++++++++------------- 1 file changed, 49 insertions(+), 76 deletions(-) diff --git a/drivers/gpu/drm/bridge/lontium-lt9611uxc.c b/drivers/gpu/drm/bridge/lontium-lt9611uxc.c index 38fb8776c0f4..11aab07d88df 100644 --- a/drivers/gpu/drm/bridge/lontium-lt9611uxc.c +++ b/drivers/gpu/drm/bridge/lontium-lt9611uxc.c @@ -17,8 +17,6 @@ #include #include -#include - #include #include #include @@ -27,6 +25,8 @@ #include #include +#include + #define EDID_BLOCK_SIZE 128 #define EDID_NUM_BLOCKS 2 @@ -48,7 +48,6 @@ struct lt9611uxc { struct device_node *dsi1_node; struct mipi_dsi_device *dsi0; struct mipi_dsi_device *dsi1; - struct platform_device *audio_pdev; struct gpio_desc *reset_gpio; struct gpio_desc *enable_gpio; @@ -429,12 +428,52 @@ static const struct drm_edid *lt9611uxc_bridge_edid_read(struct drm_bridge *brid return drm_edid_read_custom(connector, lt9611uxc_get_edid_block, lt9611uxc); } +static void lt9611uxc_bridge_hpd_notify(struct drm_bridge *bridge, + struct drm_connector *connector, + enum drm_connector_status status) +{ + const struct drm_edid *drm_edid; + + if (status == connector_status_disconnected) { + drm_connector_hdmi_audio_plugged_notify(connector, false); + drm_edid_connector_update(connector, NULL); + return; + } + + drm_edid = lt9611uxc_bridge_edid_read(bridge, connector); + drm_edid_connector_update(connector, drm_edid); + drm_edid_free(drm_edid); + + if (status == connector_status_connected) + drm_connector_hdmi_audio_plugged_notify(connector, true); +} + +static int lt9611uxc_hdmi_audio_prepare(struct drm_bridge *bridge, + struct drm_connector *connector, + struct hdmi_codec_daifmt *fmt, + struct hdmi_codec_params *hparms) +{ + /* + * LT9611UXC will automatically detect rate and sample size, so no need + * to setup anything here. + */ + return 0; +} + +static void lt9611uxc_hdmi_audio_shutdown(struct drm_bridge *bridge, + struct drm_connector *connector) +{ +} + static const struct drm_bridge_funcs lt9611uxc_bridge_funcs = { .attach = lt9611uxc_bridge_attach, .mode_valid = lt9611uxc_bridge_mode_valid, .mode_set = lt9611uxc_bridge_mode_set, .detect = lt9611uxc_bridge_detect, .edid_read = lt9611uxc_bridge_edid_read, + .hpd_notify = lt9611uxc_bridge_hpd_notify, + .hdmi_audio_prepare = lt9611uxc_hdmi_audio_prepare, + .hdmi_audio_shutdown = lt9611uxc_hdmi_audio_shutdown, }; static int lt9611uxc_parse_dt(struct device *dev, @@ -508,73 +547,6 @@ static int lt9611uxc_read_version(struct lt9611uxc *lt9611uxc) return ret < 0 ? ret : rev; } -static int lt9611uxc_hdmi_hw_params(struct device *dev, void *data, - struct hdmi_codec_daifmt *fmt, - struct hdmi_codec_params *hparms) -{ - /* - * LT9611UXC will automatically detect rate and sample size, so no need - * to setup anything here. - */ - return 0; -} - -static void lt9611uxc_audio_shutdown(struct device *dev, void *data) -{ -} - -static int lt9611uxc_hdmi_i2s_get_dai_id(struct snd_soc_component *component, - struct device_node *endpoint, - void *data) -{ - struct of_endpoint of_ep; - int ret; - - ret = of_graph_parse_endpoint(endpoint, &of_ep); - if (ret < 0) - return ret; - - /* - * HDMI sound should be located as reg = <2> - * Then, it is sound port 0 - */ - if (of_ep.port == 2) - return 0; - - return -EINVAL; -} - -static const struct hdmi_codec_ops lt9611uxc_codec_ops = { - .hw_params = lt9611uxc_hdmi_hw_params, - .audio_shutdown = lt9611uxc_audio_shutdown, - .get_dai_id = lt9611uxc_hdmi_i2s_get_dai_id, -}; - -static int lt9611uxc_audio_init(struct device *dev, struct lt9611uxc *lt9611uxc) -{ - struct hdmi_codec_pdata codec_data = { - .ops = <9611uxc_codec_ops, - .max_i2s_channels = 2, - .i2s = 1, - .data = lt9611uxc, - }; - - lt9611uxc->audio_pdev = - platform_device_register_data(dev, HDMI_CODEC_DRV_NAME, - PLATFORM_DEVID_AUTO, - &codec_data, sizeof(codec_data)); - - return PTR_ERR_OR_ZERO(lt9611uxc->audio_pdev); -} - -static void lt9611uxc_audio_exit(struct lt9611uxc *lt9611uxc) -{ - if (lt9611uxc->audio_pdev) { - platform_device_unregister(lt9611uxc->audio_pdev); - lt9611uxc->audio_pdev = NULL; - } -} - #define LT9611UXC_FW_PAGE_SIZE 32 static void lt9611uxc_firmware_write_page(struct lt9611uxc *lt9611uxc, u16 addr, const u8 *buf) { @@ -858,11 +830,17 @@ retry: i2c_set_clientdata(client, lt9611uxc); lt9611uxc->bridge.of_node = client->dev.of_node; - lt9611uxc->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID; + lt9611uxc->bridge.ops = DRM_BRIDGE_OP_DETECT | + DRM_BRIDGE_OP_EDID | + DRM_BRIDGE_OP_HDMI_AUDIO; if (lt9611uxc->hpd_supported) lt9611uxc->bridge.ops |= DRM_BRIDGE_OP_HPD; lt9611uxc->bridge.type = DRM_MODE_CONNECTOR_HDMIA; + lt9611uxc->bridge.hdmi_audio_dev = dev; + lt9611uxc->bridge.hdmi_audio_max_i2s_playback_channels = 2; + lt9611uxc->bridge.hdmi_audio_dai_port = 2; + drm_bridge_add(<9611uxc->bridge); /* Attach primary DSI */ @@ -881,10 +859,6 @@ retry: } } - ret = lt9611uxc_audio_init(dev, lt9611uxc); - if (ret) - goto err_remove_bridge; - return 0; err_remove_bridge: @@ -908,7 +882,6 @@ static void lt9611uxc_remove(struct i2c_client *client) free_irq(client->irq, lt9611uxc); cancel_work_sync(<9611uxc->work); - lt9611uxc_audio_exit(lt9611uxc); drm_bridge_remove(<9611uxc->bridge); mutex_destroy(<9611uxc->ocm_lock); From 969325a2597ebc4cb001a92992f06f698ab2b467 Mon Sep 17 00:00:00 2001 From: Andy Yan Date: Thu, 16 Oct 2025 16:38:31 +0800 Subject: [PATCH 18/48] drm/rockchip: inno-hdmi: Convert to drm bridge Convert it to drm bridge driver, it will be convenient for us to migrate the connector part to the display driver later. Signed-off-by: Andy Yan Reviewed-by: Dmitry Baryshkov Reviewed-by: Heiko Stuebner Link: https://patch.msgid.link/20251016083843.76675-2-andyshrk@163.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/bridge/Kconfig | 7 + drivers/gpu/drm/bridge/Makefile | 1 + .../inno_hdmi.c => bridge/inno-hdmi.c} | 504 +++++------------- drivers/gpu/drm/rockchip/Kconfig | 1 + drivers/gpu/drm/rockchip/Makefile | 2 +- drivers/gpu/drm/rockchip/inno_hdmi-rockchip.c | 189 +++++++ include/drm/bridge/inno_hdmi.h | 33 ++ 7 files changed, 368 insertions(+), 369 deletions(-) rename drivers/gpu/drm/{rockchip/inno_hdmi.c => bridge/inno-hdmi.c} (69%) create mode 100644 drivers/gpu/drm/rockchip/inno_hdmi-rockchip.c create mode 100644 include/drm/bridge/inno_hdmi.h diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index a250afd8d662..39385deafc68 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -100,6 +100,13 @@ config DRM_I2C_NXP_TDA998X help Support for NXP Semiconductors TDA998X HDMI encoders. +config DRM_INNO_HDMI + tristate + select DRM_BRIDGE_CONNECTOR + select DRM_DISPLAY_HDMI_HELPER + select DRM_DISPLAY_HELPER + select DRM_KMS_HELPER + config DRM_ITE_IT6263 tristate "ITE IT6263 LVDS/HDMI bridge" depends on OF diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index c7dc03182e59..909c21cc3acd 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_DRM_FSL_LDB) += fsl-ldb.o tda998x-y := tda998x_drv.o obj-$(CONFIG_DRM_I2C_NXP_TDA998X) += tda998x.o +obj-$(CONFIG_DRM_INNO_HDMI) += inno-hdmi.o obj-$(CONFIG_DRM_ITE_IT6263) += ite-it6263.o obj-$(CONFIG_DRM_ITE_IT6505) += ite-it6505.o obj-$(CONFIG_DRM_LONTIUM_LT8912B) += lontium-lt8912b.o diff --git a/drivers/gpu/drm/rockchip/inno_hdmi.c b/drivers/gpu/drm/bridge/inno-hdmi.c similarity index 69% rename from drivers/gpu/drm/rockchip/inno_hdmi.c rename to drivers/gpu/drm/bridge/inno-hdmi.c index 9f7a8cf0ab44..ab4572eb8395 100644 --- a/drivers/gpu/drm/rockchip/inno_hdmi.c +++ b/drivers/gpu/drm/bridge/inno-hdmi.c @@ -3,14 +3,15 @@ * Copyright (C) Rockchip Electronics Co., Ltd. * Zheng Yang * Yakir Yang + * Andy Yan */ #include #include #include #include +#include #include -#include #include #include #include @@ -18,6 +19,7 @@ #include #include +#include #include #include #include @@ -29,8 +31,6 @@ #include #include -#include "rockchip_drm_drv.h" - #define INNO_HDMI_MIN_TMDS_CLOCK 25000000U #define DDC_SEGMENT_ADDR 0x30 @@ -384,27 +384,6 @@ enum { #define HDMI_CEC_BUSFREETIME_H 0xdd #define HDMI_CEC_LOGICADDR 0xde -#define RK3036_GRF_SOC_CON2 0x148 -#define RK3036_HDMI_PHSYNC BIT(4) -#define RK3036_HDMI_PVSYNC BIT(5) - -enum inno_hdmi_dev_type { - RK3036_HDMI, - RK3128_HDMI, -}; - -struct inno_hdmi_phy_config { - unsigned long pixelclock; - u8 pre_emphasis; - u8 voltage_level_control; -}; - -struct inno_hdmi_variant { - enum inno_hdmi_dev_type dev_type; - struct inno_hdmi_phy_config *phy_configs; - struct inno_hdmi_phy_config *default_phy_config; -}; - struct inno_hdmi_i2c { struct i2c_adapter adap; @@ -417,41 +396,17 @@ struct inno_hdmi_i2c { struct inno_hdmi { struct device *dev; - + struct drm_bridge bridge; struct clk *pclk; struct clk *refclk; void __iomem *regs; struct regmap *grf; - struct drm_connector connector; - struct rockchip_encoder encoder; - struct inno_hdmi_i2c *i2c; struct i2c_adapter *ddc; - - const struct inno_hdmi_variant *variant; + const struct inno_hdmi_plat_data *plat_data; }; -struct inno_hdmi_connector_state { - struct drm_connector_state base; - unsigned int colorimetry; -}; - -static struct inno_hdmi *encoder_to_inno_hdmi(struct drm_encoder *encoder) -{ - struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder); - - return container_of(rkencoder, struct inno_hdmi, encoder); -} - -static struct inno_hdmi *connector_to_inno_hdmi(struct drm_connector *connector) -{ - return container_of(connector, struct inno_hdmi, connector); -} - -#define to_inno_hdmi_conn_state(conn_state) \ - container_of_const(conn_state, struct inno_hdmi_connector_state, base) - enum { CSC_RGB_0_255_TO_ITU601_16_235_8BIT, CSC_RGB_0_255_TO_ITU709_16_235_8BIT, @@ -494,23 +449,15 @@ static const char coeff_csc[][24] = { }, }; -static struct inno_hdmi_phy_config rk3036_hdmi_phy_configs[] = { - { 74250000, 0x3f, 0xbb }, - { 165000000, 0x6f, 0xbb }, - { ~0UL, 0x00, 0x00 } -}; - -static struct inno_hdmi_phy_config rk3128_hdmi_phy_configs[] = { - { 74250000, 0x3f, 0xaa }, - { 165000000, 0x5f, 0xaa }, - { ~0UL, 0x00, 0x00 } -}; +static struct inno_hdmi *bridge_to_inno_hdmi(struct drm_bridge *bridge) +{ + return container_of(bridge, struct inno_hdmi, bridge); +} static int inno_hdmi_find_phy_config(struct inno_hdmi *hdmi, unsigned long pixelclk) { - const struct inno_hdmi_phy_config *phy_configs = - hdmi->variant->phy_configs; + const struct inno_hdmi_phy_config *phy_configs = hdmi->plat_data->phy_configs; int i; for (i = 0; phy_configs[i].pixelclock != ~0UL; i++) { @@ -582,12 +529,12 @@ static void inno_hdmi_power_up(struct inno_hdmi *hdmi, int ret = inno_hdmi_find_phy_config(hdmi, mpixelclock); if (ret < 0) { - phy_config = hdmi->variant->default_phy_config; + phy_config = hdmi->plat_data->default_phy_config; DRM_DEV_ERROR(hdmi->dev, "Using default phy configuration for TMDS rate %lu", mpixelclock); } else { - phy_config = &hdmi->variant->phy_configs[ret]; + phy_config = &hdmi->plat_data->phy_configs[ret]; } inno_hdmi_sys_power(hdmi, false); @@ -637,14 +584,13 @@ static void inno_hdmi_init_hw(struct inno_hdmi *hdmi) hdmi_modb(hdmi, HDMI_STATUS, m_MASK_INT_HOTPLUG, v_MASK_INT_HOTPLUG(1)); } -static int inno_hdmi_disable_frame(struct drm_connector *connector, - enum hdmi_infoframe_type type) +static int inno_hdmi_bridge_clear_infoframe(struct drm_bridge *bridge, + enum hdmi_infoframe_type type) { - struct inno_hdmi *hdmi = connector_to_inno_hdmi(connector); + struct inno_hdmi *hdmi = bridge_to_inno_hdmi(bridge); if (type != HDMI_INFOFRAME_TYPE_AVI) { - drm_err(connector->dev, - "Unsupported infoframe type: %u\n", type); + drm_err(bridge->dev, "Unsupported infoframe type: %u\n", type); return 0; } @@ -653,20 +599,19 @@ static int inno_hdmi_disable_frame(struct drm_connector *connector, return 0; } -static int inno_hdmi_upload_frame(struct drm_connector *connector, - enum hdmi_infoframe_type type, - const u8 *buffer, size_t len) +static int inno_hdmi_bridge_write_infoframe(struct drm_bridge *bridge, + enum hdmi_infoframe_type type, + const u8 *buffer, size_t len) { - struct inno_hdmi *hdmi = connector_to_inno_hdmi(connector); + struct inno_hdmi *hdmi = bridge_to_inno_hdmi(bridge); ssize_t i; if (type != HDMI_INFOFRAME_TYPE_AVI) { - drm_err(connector->dev, - "Unsupported infoframe type: %u\n", type); + drm_err(bridge->dev, "Unsupported infoframe type: %u\n", type); return 0; } - inno_hdmi_disable_frame(connector, type); + inno_hdmi_bridge_clear_infoframe(bridge, type); for (i = 0; i < len; i++) hdmi_writeb(hdmi, HDMI_CONTROL_PACKET_ADDR + i, buffer[i]); @@ -674,23 +619,26 @@ static int inno_hdmi_upload_frame(struct drm_connector *connector, return 0; } -static const struct drm_connector_hdmi_funcs inno_hdmi_hdmi_connector_funcs = { - .clear_infoframe = inno_hdmi_disable_frame, - .write_infoframe = inno_hdmi_upload_frame, -}; - -static int inno_hdmi_config_video_csc(struct inno_hdmi *hdmi) +static int inno_hdmi_config_video_csc(struct inno_hdmi *hdmi, + struct drm_connector *connector, + struct drm_display_mode *mode) { - struct drm_connector *connector = &hdmi->connector; struct drm_connector_state *conn_state = connector->state; - struct inno_hdmi_connector_state *inno_conn_state = - to_inno_hdmi_conn_state(conn_state); int c0_c2_change = 0; int csc_enable = 0; int csc_mode = 0; int auto_csc = 0; int value; int i; + int colorimetry; + u8 vic = drm_match_cea_mode(mode); + + if (vic == 6 || vic == 7 || vic == 21 || vic == 22 || + vic == 2 || vic == 3 || vic == 17 || vic == 18) + colorimetry = HDMI_COLORIMETRY_ITU_601; + else + colorimetry = HDMI_COLORIMETRY_ITU_709; + /* Input video mode is SDR RGB24bit, data enable signal from external */ hdmi_writeb(hdmi, HDMI_VIDEO_CONTRL1, v_DE_EXTERNAL | @@ -720,7 +668,7 @@ static int inno_hdmi_config_video_csc(struct inno_hdmi *hdmi) return 0; } } else { - if (inno_conn_state->colorimetry == HDMI_COLORIMETRY_ITU_601) { + if (colorimetry == HDMI_COLORIMETRY_ITU_601) { if (conn_state->hdmi.output_format == HDMI_COLORSPACE_YUV444) { csc_mode = CSC_RGB_0_255_TO_ITU601_16_235_8BIT; auto_csc = AUTO_CSC_DISABLE; @@ -738,8 +686,7 @@ static int inno_hdmi_config_video_csc(struct inno_hdmi *hdmi) } for (i = 0; i < 24; i++) - hdmi_writeb(hdmi, HDMI_VIDEO_CSC_COEF + i, - coeff_csc[csc_mode][i]); + hdmi_writeb(hdmi, HDMI_VIDEO_CSC_COEF + i, coeff_csc[csc_mode][i]); value = v_SOF_DISABLE | csc_enable | v_COLOR_DEPTH_NOT_INDICATED(1); hdmi_writeb(hdmi, HDMI_VIDEO_CONTRL3, value); @@ -753,15 +700,11 @@ static int inno_hdmi_config_video_csc(struct inno_hdmi *hdmi) static int inno_hdmi_config_video_timing(struct inno_hdmi *hdmi, struct drm_display_mode *mode) { - int value, psync; + const struct inno_hdmi_plat_ops *plat_ops = hdmi->plat_data->ops; + u32 value; - if (hdmi->variant->dev_type == RK3036_HDMI) { - psync = mode->flags & DRM_MODE_FLAG_PHSYNC ? 1 : 0; - value = FIELD_PREP_WM16(RK3036_HDMI_PHSYNC, psync); - psync = mode->flags & DRM_MODE_FLAG_PVSYNC ? 1 : 0; - value |= FIELD_PREP_WM16(RK3036_HDMI_PVSYNC, psync); - regmap_write(hdmi->grf, RK3036_GRF_SOC_CON2, value); - } + if (plat_ops && plat_ops->enable) + plat_ops->enable(hdmi->dev, mode); /* Set detail external video timing polarity and interlace mode */ value = v_EXTERANL_VIDEO(1); @@ -810,14 +753,16 @@ static int inno_hdmi_config_video_timing(struct inno_hdmi *hdmi, return 0; } -static int inno_hdmi_setup(struct inno_hdmi *hdmi, - struct drm_atomic_state *state) +static int inno_hdmi_setup(struct inno_hdmi *hdmi, struct drm_atomic_state *state) { - struct drm_connector *connector = &hdmi->connector; - struct drm_display_info *display = &connector->display_info; + struct drm_bridge *bridge = &hdmi->bridge; + struct drm_connector *connector; + struct drm_display_info *info; struct drm_connector_state *new_conn_state; struct drm_crtc_state *new_crtc_state; + connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder); + new_conn_state = drm_atomic_get_new_connector_state(state, connector); if (WARN_ON(!new_conn_state)) return -EINVAL; @@ -826,17 +771,18 @@ static int inno_hdmi_setup(struct inno_hdmi *hdmi, if (WARN_ON(!new_crtc_state)) return -EINVAL; + info = &connector->display_info; + /* Mute video and audio output */ hdmi_modb(hdmi, HDMI_AV_MUTE, m_AUDIO_MUTE | m_VIDEO_BLACK, v_AUDIO_MUTE(1) | v_VIDEO_MUTE(1)); /* Set HDMI Mode */ - hdmi_writeb(hdmi, HDMI_HDCP_CTRL, - v_HDMI_DVI(display->is_hdmi)); + hdmi_writeb(hdmi, HDMI_HDCP_CTRL, v_HDMI_DVI(info->is_hdmi)); inno_hdmi_config_video_timing(hdmi, &new_crtc_state->adjusted_mode); - inno_hdmi_config_video_csc(hdmi); + inno_hdmi_config_video_csc(hdmi, connector, &new_crtc_state->adjusted_mode); drm_atomic_helper_connector_hdmi_update_infoframes(connector, state); @@ -857,9 +803,11 @@ static int inno_hdmi_setup(struct inno_hdmi *hdmi, return 0; } -static enum drm_mode_status inno_hdmi_display_mode_valid(struct inno_hdmi *hdmi, - const struct drm_display_mode *mode) +static enum drm_mode_status inno_hdmi_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) { + struct inno_hdmi *hdmi = bridge_to_inno_hdmi(bridge); unsigned long mpixelclk, max_tolerance; long rounded_refclk; @@ -889,189 +837,57 @@ static enum drm_mode_status inno_hdmi_display_mode_valid(struct inno_hdmi *hdmi, return MODE_OK; } -static void inno_hdmi_encoder_enable(struct drm_encoder *encoder, - struct drm_atomic_state *state) -{ - struct inno_hdmi *hdmi = encoder_to_inno_hdmi(encoder); - - inno_hdmi_setup(hdmi, state); -} - -static void inno_hdmi_encoder_disable(struct drm_encoder *encoder, - struct drm_atomic_state *state) -{ - struct inno_hdmi *hdmi = encoder_to_inno_hdmi(encoder); - - inno_hdmi_standby(hdmi); -} - -static int -inno_hdmi_encoder_atomic_check(struct drm_encoder *encoder, - struct drm_crtc_state *crtc_state, - struct drm_connector_state *conn_state) -{ - struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state); - struct drm_display_mode *mode = &crtc_state->adjusted_mode; - u8 vic = drm_match_cea_mode(mode); - struct inno_hdmi_connector_state *inno_conn_state = - to_inno_hdmi_conn_state(conn_state); - - s->output_mode = ROCKCHIP_OUT_MODE_P888; - s->output_type = DRM_MODE_CONNECTOR_HDMIA; - - if (vic == 6 || vic == 7 || - vic == 21 || vic == 22 || - vic == 2 || vic == 3 || - vic == 17 || vic == 18) - inno_conn_state->colorimetry = HDMI_COLORIMETRY_ITU_601; - else - inno_conn_state->colorimetry = HDMI_COLORIMETRY_ITU_709; - - return 0; -} - -static const struct drm_encoder_helper_funcs inno_hdmi_encoder_helper_funcs = { - .atomic_check = inno_hdmi_encoder_atomic_check, - .atomic_enable = inno_hdmi_encoder_enable, - .atomic_disable = inno_hdmi_encoder_disable, -}; - static enum drm_connector_status -inno_hdmi_connector_detect(struct drm_connector *connector, bool force) +inno_hdmi_bridge_detect(struct drm_bridge *bridge, struct drm_connector *connector) { - struct inno_hdmi *hdmi = connector_to_inno_hdmi(connector); + struct inno_hdmi *hdmi = bridge_to_inno_hdmi(bridge); return (hdmi_readb(hdmi, HDMI_STATUS) & m_HOTPLUG) ? connector_status_connected : connector_status_disconnected; } -static int inno_hdmi_connector_get_modes(struct drm_connector *connector) +static const struct drm_edid * +inno_hdmi_bridge_edid_read(struct drm_bridge *bridge, struct drm_connector *connector) { - struct inno_hdmi *hdmi = connector_to_inno_hdmi(connector); + struct inno_hdmi *hdmi = bridge_to_inno_hdmi(bridge); const struct drm_edid *drm_edid; - int ret = 0; - if (!hdmi->ddc) - return 0; + drm_edid = drm_edid_read_ddc(connector, bridge->ddc); + if (!drm_edid) + dev_dbg(hdmi->dev, "failed to get edid\n"); - drm_edid = drm_edid_read_ddc(connector, hdmi->ddc); - drm_edid_connector_update(connector, drm_edid); - ret = drm_edid_connector_add_modes(connector); - drm_edid_free(drm_edid); - - return ret; + return drm_edid; } -static enum drm_mode_status -inno_hdmi_connector_mode_valid(struct drm_connector *connector, - const struct drm_display_mode *mode) +static void inno_hdmi_bridge_atomic_enable(struct drm_bridge *bridge, + struct drm_atomic_state *state) { - struct inno_hdmi *hdmi = connector_to_inno_hdmi(connector); + struct inno_hdmi *hdmi = bridge_to_inno_hdmi(bridge); - return inno_hdmi_display_mode_valid(hdmi, mode); + inno_hdmi_setup(hdmi, state); } -static void -inno_hdmi_connector_destroy_state(struct drm_connector *connector, - struct drm_connector_state *state) +static void inno_hdmi_bridge_atomic_disable(struct drm_bridge *bridge, + struct drm_atomic_state *state) { - struct inno_hdmi_connector_state *inno_conn_state = - to_inno_hdmi_conn_state(state); + struct inno_hdmi *hdmi = bridge_to_inno_hdmi(bridge); - __drm_atomic_helper_connector_destroy_state(&inno_conn_state->base); - kfree(inno_conn_state); + inno_hdmi_standby(hdmi); } -static void inno_hdmi_connector_reset(struct drm_connector *connector) -{ - struct inno_hdmi_connector_state *inno_conn_state; - - if (connector->state) { - inno_hdmi_connector_destroy_state(connector, connector->state); - connector->state = NULL; - } - - inno_conn_state = kzalloc(sizeof(*inno_conn_state), GFP_KERNEL); - if (!inno_conn_state) - return; - - __drm_atomic_helper_connector_reset(connector, &inno_conn_state->base); - __drm_atomic_helper_connector_hdmi_reset(connector, connector->state); - - inno_conn_state->colorimetry = HDMI_COLORIMETRY_ITU_709; -} - -static struct drm_connector_state * -inno_hdmi_connector_duplicate_state(struct drm_connector *connector) -{ - struct inno_hdmi_connector_state *inno_conn_state; - - if (WARN_ON(!connector->state)) - return NULL; - - inno_conn_state = kmemdup(to_inno_hdmi_conn_state(connector->state), - sizeof(*inno_conn_state), GFP_KERNEL); - - if (!inno_conn_state) - return NULL; - - __drm_atomic_helper_connector_duplicate_state(connector, - &inno_conn_state->base); - - return &inno_conn_state->base; -} - -static const struct drm_connector_funcs inno_hdmi_connector_funcs = { - .fill_modes = drm_helper_probe_single_connector_modes, - .detect = inno_hdmi_connector_detect, - .reset = inno_hdmi_connector_reset, - .atomic_duplicate_state = inno_hdmi_connector_duplicate_state, - .atomic_destroy_state = inno_hdmi_connector_destroy_state, +static const struct drm_bridge_funcs inno_hdmi_bridge_funcs = { + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_reset = drm_atomic_helper_bridge_reset, + .atomic_enable = inno_hdmi_bridge_atomic_enable, + .atomic_disable = inno_hdmi_bridge_atomic_disable, + .detect = inno_hdmi_bridge_detect, + .edid_read = inno_hdmi_bridge_edid_read, + .hdmi_clear_infoframe = inno_hdmi_bridge_clear_infoframe, + .hdmi_write_infoframe = inno_hdmi_bridge_write_infoframe, + .mode_valid = inno_hdmi_bridge_mode_valid, }; -static struct drm_connector_helper_funcs inno_hdmi_connector_helper_funcs = { - .atomic_check = drm_atomic_helper_connector_hdmi_check, - .get_modes = inno_hdmi_connector_get_modes, - .mode_valid = inno_hdmi_connector_mode_valid, -}; - -static int inno_hdmi_register(struct drm_device *drm, struct inno_hdmi *hdmi) -{ - struct drm_encoder *encoder = &hdmi->encoder.encoder; - struct device *dev = hdmi->dev; - - encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node); - - /* - * If we failed to find the CRTC(s) which this encoder is - * supposed to be connected to, it's because the CRTC has - * not been registered yet. Defer probing, and hope that - * the required CRTC is added later. - */ - if (encoder->possible_crtcs == 0) - return -EPROBE_DEFER; - - drm_encoder_helper_add(encoder, &inno_hdmi_encoder_helper_funcs); - drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS); - - hdmi->connector.polled = DRM_CONNECTOR_POLL_HPD; - - drm_connector_helper_add(&hdmi->connector, - &inno_hdmi_connector_helper_funcs); - drmm_connector_hdmi_init(drm, &hdmi->connector, - "Rockchip", "Inno HDMI", - &inno_hdmi_connector_funcs, - &inno_hdmi_hdmi_connector_funcs, - DRM_MODE_CONNECTOR_HDMIA, - hdmi->ddc, - BIT(HDMI_COLORSPACE_RGB), - 8); - - drm_connector_attach_encoder(&hdmi->connector, encoder); - - return 0; -} - static irqreturn_t inno_hdmi_i2c_irq(struct inno_hdmi *hdmi) { struct inno_hdmi_i2c *i2c = hdmi->i2c; @@ -1111,7 +927,7 @@ static irqreturn_t inno_hdmi_irq(int irq, void *dev_id) { struct inno_hdmi *hdmi = dev_id; - drm_helper_hpd_irq_event(hdmi->connector.dev); + drm_helper_hpd_irq_event(hdmi->bridge.dev); return IRQ_HANDLED; } @@ -1243,128 +1059,80 @@ static struct i2c_adapter *inno_hdmi_i2c_adapter(struct inno_hdmi *hdmi) return adap; } -static int inno_hdmi_bind(struct device *dev, struct device *master, - void *data) +struct inno_hdmi *inno_hdmi_bind(struct device *dev, + struct drm_encoder *encoder, + const struct inno_hdmi_plat_data *plat_data) { struct platform_device *pdev = to_platform_device(dev); - struct drm_device *drm = data; struct inno_hdmi *hdmi; - const struct inno_hdmi_variant *variant; int irq; int ret; - hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL); - if (!hdmi) - return -ENOMEM; + if (!plat_data->phy_configs || !plat_data->default_phy_config) { + dev_err(dev, "Missing platform PHY ops\n"); + return ERR_PTR(-ENODEV); + } + + hdmi = devm_drm_bridge_alloc(dev, struct inno_hdmi, bridge, &inno_hdmi_bridge_funcs); + if (IS_ERR(hdmi)) + return ERR_CAST(hdmi); hdmi->dev = dev; - - variant = of_device_get_match_data(hdmi->dev); - if (!variant) - return -EINVAL; - - hdmi->variant = variant; + hdmi->plat_data = plat_data; hdmi->regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(hdmi->regs)) - return PTR_ERR(hdmi->regs); + return ERR_CAST(hdmi->regs); hdmi->pclk = devm_clk_get_enabled(hdmi->dev, "pclk"); - if (IS_ERR(hdmi->pclk)) - return dev_err_probe(dev, PTR_ERR(hdmi->pclk), "Unable to get HDMI pclk\n"); - - hdmi->refclk = devm_clk_get_optional_enabled(hdmi->dev, "ref"); - if (IS_ERR(hdmi->refclk)) - return dev_err_probe(dev, PTR_ERR(hdmi->refclk), "Unable to get HDMI refclk\n"); - - if (hdmi->variant->dev_type == RK3036_HDMI) { - hdmi->grf = syscon_regmap_lookup_by_phandle(dev->of_node, "rockchip,grf"); - if (IS_ERR(hdmi->grf)) - return dev_err_probe(dev, - PTR_ERR(hdmi->grf), "Unable to get rockchip,grf\n"); + if (IS_ERR(hdmi->pclk)) { + dev_err_probe(dev, PTR_ERR(hdmi->pclk), "Unable to get HDMI pclk\n"); + return ERR_CAST(hdmi->pclk); } - irq = platform_get_irq(pdev, 0); - if (irq < 0) - return irq; + hdmi->refclk = devm_clk_get_optional_enabled(hdmi->dev, "ref"); + if (IS_ERR(hdmi->refclk)) { + dev_err_probe(dev, PTR_ERR(hdmi->refclk), "Unable to get HDMI refclk\n"); + return ERR_CAST(hdmi->refclk); + } inno_hdmi_init_hw(hdmi); - hdmi->ddc = inno_hdmi_i2c_adapter(hdmi); - if (IS_ERR(hdmi->ddc)) - return PTR_ERR(hdmi->ddc); - - ret = inno_hdmi_register(drm, hdmi); - if (ret) - return ret; - - dev_set_drvdata(dev, hdmi); + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return ERR_PTR(irq); ret = devm_request_threaded_irq(dev, irq, inno_hdmi_hardirq, inno_hdmi_irq, IRQF_SHARED, dev_name(dev), hdmi); - if (ret < 0) - goto err_cleanup_hdmi; + if (ret) + return ERR_PTR(ret); - return 0; -err_cleanup_hdmi: - hdmi->connector.funcs->destroy(&hdmi->connector); - hdmi->encoder.encoder.funcs->destroy(&hdmi->encoder.encoder); - return ret; + hdmi->bridge.driver_private = hdmi; + hdmi->bridge.ops = DRM_BRIDGE_OP_DETECT | + DRM_BRIDGE_OP_EDID | + DRM_BRIDGE_OP_HDMI | + DRM_BRIDGE_OP_HPD; + hdmi->bridge.of_node = pdev->dev.of_node; + hdmi->bridge.type = DRM_MODE_CONNECTOR_HDMIA; + hdmi->bridge.vendor = "Inno"; + hdmi->bridge.product = "Inno HDMI"; + + hdmi->bridge.ddc = inno_hdmi_i2c_adapter(hdmi); + if (IS_ERR(hdmi->bridge.ddc)) + return ERR_CAST(hdmi->bridge.ddc); + + ret = devm_drm_bridge_add(dev, &hdmi->bridge); + if (ret) + return ERR_PTR(ret); + + ret = drm_bridge_attach(encoder, &hdmi->bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR); + if (ret) + return ERR_PTR(ret); + + return hdmi; } - -static void inno_hdmi_unbind(struct device *dev, struct device *master, - void *data) -{ - struct inno_hdmi *hdmi = dev_get_drvdata(dev); - - hdmi->connector.funcs->destroy(&hdmi->connector); - hdmi->encoder.encoder.funcs->destroy(&hdmi->encoder.encoder); -} - -static const struct component_ops inno_hdmi_ops = { - .bind = inno_hdmi_bind, - .unbind = inno_hdmi_unbind, -}; - -static int inno_hdmi_probe(struct platform_device *pdev) -{ - return component_add(&pdev->dev, &inno_hdmi_ops); -} - -static void inno_hdmi_remove(struct platform_device *pdev) -{ - component_del(&pdev->dev, &inno_hdmi_ops); -} - -static const struct inno_hdmi_variant rk3036_inno_hdmi_variant = { - .dev_type = RK3036_HDMI, - .phy_configs = rk3036_hdmi_phy_configs, - .default_phy_config = &rk3036_hdmi_phy_configs[1], -}; - -static const struct inno_hdmi_variant rk3128_inno_hdmi_variant = { - .dev_type = RK3128_HDMI, - .phy_configs = rk3128_hdmi_phy_configs, - .default_phy_config = &rk3128_hdmi_phy_configs[1], -}; - -static const struct of_device_id inno_hdmi_dt_ids[] = { - { .compatible = "rockchip,rk3036-inno-hdmi", - .data = &rk3036_inno_hdmi_variant, - }, - { .compatible = "rockchip,rk3128-inno-hdmi", - .data = &rk3128_inno_hdmi_variant, - }, - {}, -}; -MODULE_DEVICE_TABLE(of, inno_hdmi_dt_ids); - -struct platform_driver inno_hdmi_driver = { - .probe = inno_hdmi_probe, - .remove = inno_hdmi_remove, - .driver = { - .name = "innohdmi-rockchip", - .of_match_table = inno_hdmi_dt_ids, - }, -}; +EXPORT_SYMBOL_GPL(inno_hdmi_bind); +MODULE_AUTHOR("Andy Yan "); +MODULE_DESCRIPTION("INNOSILICON HDMI transmitter library"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig index b7b025814e72..6f068ce1021d 100644 --- a/drivers/gpu/drm/rockchip/Kconfig +++ b/drivers/gpu/drm/rockchip/Kconfig @@ -15,6 +15,7 @@ config DRM_ROCKCHIP select DRM_DW_HDMI_QP if ROCKCHIP_DW_HDMI_QP select DRM_DW_MIPI_DSI if ROCKCHIP_DW_MIPI_DSI select DRM_DW_MIPI_DSI2 if ROCKCHIP_DW_MIPI_DSI2 + select DRM_INNO_HDMI if ROCKCHIP_INNO_HDMI select GENERIC_PHY if ROCKCHIP_DW_MIPI_DSI select GENERIC_PHY_MIPI_DPHY if ROCKCHIP_DW_MIPI_DSI select SND_SOC_HDMI_CODEC if ROCKCHIP_CDN_DP && SND_SOC diff --git a/drivers/gpu/drm/rockchip/Makefile b/drivers/gpu/drm/rockchip/Makefile index 097f062399c7..948b0f906d3d 100644 --- a/drivers/gpu/drm/rockchip/Makefile +++ b/drivers/gpu/drm/rockchip/Makefile @@ -15,7 +15,7 @@ rockchipdrm-$(CONFIG_ROCKCHIP_DW_HDMI_QP) += dw_hdmi_qp-rockchip.o rockchipdrm-$(CONFIG_ROCKCHIP_DW_MIPI_DSI) += dw-mipi-dsi-rockchip.o rockchipdrm-$(CONFIG_ROCKCHIP_DW_MIPI_DSI2) += dw-mipi-dsi2-rockchip.o rockchipdrm-$(CONFIG_ROCKCHIP_DW_DP) += dw_dp-rockchip.o -rockchipdrm-$(CONFIG_ROCKCHIP_INNO_HDMI) += inno_hdmi.o +rockchipdrm-$(CONFIG_ROCKCHIP_INNO_HDMI) += inno_hdmi-rockchip.o rockchipdrm-$(CONFIG_ROCKCHIP_LVDS) += rockchip_lvds.o rockchipdrm-$(CONFIG_ROCKCHIP_RGB) += rockchip_rgb.o rockchipdrm-$(CONFIG_ROCKCHIP_RK3066_HDMI) += rk3066_hdmi.o diff --git a/drivers/gpu/drm/rockchip/inno_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/inno_hdmi-rockchip.c new file mode 100644 index 000000000000..97c20500f790 --- /dev/null +++ b/drivers/gpu/drm/rockchip/inno_hdmi-rockchip.c @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) Rockchip Electronics Co., Ltd. + * Zheng Yang + * Andy Yan + */ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "rockchip_drm_drv.h" + +#define HIWORD_UPDATE(val, mask) ((val) | (mask) << 16) + +#define RK3036_GRF_SOC_CON2 0x148 +#define RK3036_HDMI_PHSYNC BIT(4) +#define RK3036_HDMI_PVSYNC BIT(5) + +enum inno_hdmi_dev_type { + RK3036_HDMI, + RK3128_HDMI, +}; + +struct inno_hdmi_connector_state { + struct drm_connector_state base; + unsigned int colorimetry; +}; + +struct rockchip_inno_hdmi { + struct inno_hdmi *base; + struct device *dev; + struct regmap *grf; + struct rockchip_encoder encoder; +}; + +static struct inno_hdmi_phy_config rk3036_hdmi_phy_configs[] = { + { 74250000, 0x3f, 0xbb }, + { 165000000, 0x6f, 0xbb }, + { ~0UL, 0x00, 0x00 } +}; + +static struct inno_hdmi_phy_config rk3128_hdmi_phy_configs[] = { + { 74250000, 0x3f, 0xaa }, + { 165000000, 0x5f, 0xaa }, + { ~0UL, 0x00, 0x00 } +}; + +static void inno_hdmi_rk3036_enable(struct device *dev, struct drm_display_mode *mode) +{ + struct rockchip_inno_hdmi *hdmi = dev_get_drvdata(dev); + int value, psync; + + psync = mode->flags & DRM_MODE_FLAG_PHSYNC ? 1 : 0; + value = FIELD_PREP_WM16(RK3036_HDMI_PHSYNC, psync); + psync = mode->flags & DRM_MODE_FLAG_PVSYNC ? 1 : 0; + value |= FIELD_PREP_WM16(RK3036_HDMI_PVSYNC, psync); + regmap_write(hdmi->grf, RK3036_GRF_SOC_CON2, value); +} + +static int inno_hdmi_encoder_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state); + + s->output_mode = ROCKCHIP_OUT_MODE_P888; + s->output_type = DRM_MODE_CONNECTOR_HDMIA; + + return 0; +} + +static const struct drm_encoder_helper_funcs inno_hdmi_rockchip_encoder_helper_funcs = { + .atomic_check = inno_hdmi_encoder_atomic_check, +}; + +static int inno_hdmi_rockchip_bind(struct device *dev, struct device *master, void *data) +{ + struct drm_device *drm = data; + struct drm_connector *connector; + struct drm_encoder *encoder; + struct rockchip_inno_hdmi *hdmi; + const struct inno_hdmi_plat_data *plat_data; + int ret; + + hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL); + if (!hdmi) + return -ENOMEM; + + hdmi->dev = dev; + + plat_data = of_device_get_match_data(hdmi->dev); + if (!plat_data) + return -EINVAL; + + if (of_device_is_compatible(dev->of_node, "rockchip,rk3036-inno-hdmi")) { + hdmi->grf = syscon_regmap_lookup_by_phandle(dev->of_node, "rockchip,grf"); + if (IS_ERR(hdmi->grf)) + return dev_err_probe(dev, + PTR_ERR(hdmi->grf), "Unable to get rockchip,grf\n"); + } + + encoder = &hdmi->encoder.encoder; + encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node); + + /* + * If we failed to find the CRTC(s) which this encoder is + * supposed to be connected to, it's because the CRTC has + * not been registered yet. Defer probing, and hope that + * the required CRTC is added later. + */ + if (encoder->possible_crtcs == 0) + return -EPROBE_DEFER; + + ret = drmm_encoder_init(drm, encoder, NULL, DRM_MODE_ENCODER_TMDS, NULL); + if (ret) + return ret; + + drm_encoder_helper_add(encoder, &inno_hdmi_rockchip_encoder_helper_funcs); + + dev_set_drvdata(dev, hdmi); + + hdmi->base = inno_hdmi_bind(dev, encoder, plat_data); + + connector = drm_bridge_connector_init(drm, encoder); + if (IS_ERR(connector)) { + ret = PTR_ERR(connector); + dev_err(hdmi->dev, "failed to init bridge connector: %d\n", ret); + return ret; + } + + return drm_connector_attach_encoder(connector, encoder); +} + +static const struct component_ops inno_hdmi_rockchip_ops = { + .bind = inno_hdmi_rockchip_bind, +}; + +static int inno_hdmi_rockchip_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &inno_hdmi_rockchip_ops); +} + +static void inno_hdmi_rockchip_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &inno_hdmi_rockchip_ops); +} + +static const struct inno_hdmi_plat_ops rk3036_inno_hdmi_plat_ops = { + .enable = inno_hdmi_rk3036_enable, +}; + +static const struct inno_hdmi_plat_data rk3036_inno_hdmi_plat_data = { + .ops = &rk3036_inno_hdmi_plat_ops, + .phy_configs = rk3036_hdmi_phy_configs, + .default_phy_config = &rk3036_hdmi_phy_configs[1], +}; + +static const struct inno_hdmi_plat_data rk3128_inno_hdmi_plat_data = { + .phy_configs = rk3128_hdmi_phy_configs, + .default_phy_config = &rk3128_hdmi_phy_configs[1], +}; + +static const struct of_device_id inno_hdmi_rockchip_dt_ids[] = { + { .compatible = "rockchip,rk3036-inno-hdmi", + .data = &rk3036_inno_hdmi_plat_data, + }, + { .compatible = "rockchip,rk3128-inno-hdmi", + .data = &rk3128_inno_hdmi_plat_data, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, inno_hdmi_rockchip_dt_ids); + +struct platform_driver inno_hdmi_driver = { + .probe = inno_hdmi_rockchip_probe, + .remove = inno_hdmi_rockchip_remove, + .driver = { + .name = "innohdmi-rockchip", + .of_match_table = inno_hdmi_rockchip_dt_ids, + }, +}; diff --git a/include/drm/bridge/inno_hdmi.h b/include/drm/bridge/inno_hdmi.h new file mode 100644 index 000000000000..8b39655212e2 --- /dev/null +++ b/include/drm/bridge/inno_hdmi.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2025 Rockchip Electronics Co., Ltd. + */ + +#ifndef __INNO_HDMI__ +#define __INNO_HDMI__ + +struct device; +struct drm_encoder; +struct drm_display_mode; +struct inno_hdmi; + +struct inno_hdmi_plat_ops { + void (*enable)(struct device *pdev, struct drm_display_mode *mode); +}; + +struct inno_hdmi_phy_config { + unsigned long pixelclock; + u8 pre_emphasis; + u8 voltage_level_control; +}; + +struct inno_hdmi_plat_data { + const struct inno_hdmi_plat_ops *ops; + struct inno_hdmi_phy_config *phy_configs; + struct inno_hdmi_phy_config *default_phy_config; +}; + +struct inno_hdmi *inno_hdmi_bind(struct device *pdev, + struct drm_encoder *encoder, + const struct inno_hdmi_plat_data *plat_data); +#endif /* __INNO_HDMI__ */ From 130343ee6bca9895c47d314467db7dd3dcc8bc35 Mon Sep 17 00:00:00 2001 From: Andy Yan Date: Thu, 16 Oct 2025 16:38:32 +0800 Subject: [PATCH 19/48] MAINTAINERS: Add entry for Innosilicon hdmi bridge library Add entry for Innosilicon hdmi bridge library Signed-off-by: Andy Yan Link: https://patch.msgid.link/20251016083843.76675-3-andyshrk@163.com Signed-off-by: Dmitry Baryshkov --- MAINTAINERS | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index f980623f1479..d28c51323d36 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12457,6 +12457,14 @@ M: Samuel Holland S: Maintained F: drivers/power/supply/ip5xxx_power.c +INNOSILICON HDMI BRIDGE DRIVER +M: Andy Yan +L: dri-devel@lists.freedesktop.org +S: Maintained +T: git https://gitlab.freedesktop.org/drm/misc/kernel.git +F: drivers/gpu/drm/bridge/inno-hdmi.c +F: include/drm/bridge/inno_hdmi.h + INOTIFY M: Jan Kara R: Amir Goldstein From 293a8fd7721a90987d9bf149feab60e756dac269 Mon Sep 17 00:00:00 2001 From: Luca Ceresoli Date: Tue, 16 Dec 2025 18:58:34 +0100 Subject: [PATCH 20/48] drm/bridge: add of_drm_find_and_get_bridge() of_drm_find_bridge() does not increment the refcount for the returned bridge, but that is required now. However converting it and all its users is not realistically doable at once given the large amount of (direct and indirect) callers and the complexity of some. Solve this issue by creating a new of_drm_find_and_get_bridge() function that is identical to of_drm_find_bridge() except also it takes a reference. Then of_drm_find_bridge() will be deprecated to be eventually removed. Suggested-by: Maxime Ripard Link: https://lore.kernel.org/dri-devel/20250319-stylish-lime-mongoose-0a18ad@houat/ Reviewed-by: Maxime Ripard Link: https://patch.msgid.link/20251216-drm-bridge-alloc-getput-drm_of_find_bridge-v3-1-b5165fab8058@bootlin.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/drm_bridge.c | 25 +++++++++++++++++++++++++ include/drm/drm_bridge.h | 5 +++++ 2 files changed, 30 insertions(+) diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c index db40c26d1cb3..0dbc8b59c3be 100644 --- a/drivers/gpu/drm/drm_bridge.c +++ b/drivers/gpu/drm/drm_bridge.c @@ -1479,6 +1479,31 @@ void drm_bridge_hpd_notify(struct drm_bridge *bridge, EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify); #ifdef CONFIG_OF +/** + * of_drm_find_and_get_bridge - find the bridge corresponding to the device + * node in the global bridge list + * @np: device node + * + * The refcount of the returned bridge is incremented. Use drm_bridge_put() + * when done with it. + * + * RETURNS: + * drm_bridge control struct on success, NULL on failure + */ +struct drm_bridge *of_drm_find_and_get_bridge(struct device_node *np) +{ + struct drm_bridge *bridge; + + scoped_guard(mutex, &bridge_lock) { + list_for_each_entry(bridge, &bridge_list, list) + if (bridge->of_node == np) + return drm_bridge_get(bridge); + } + + return NULL; +} +EXPORT_SYMBOL(of_drm_find_and_get_bridge); + /** * of_drm_find_bridge - find the bridge corresponding to the device node in * the global bridge list diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h index 68b9da1eb542..42ec33116d18 100644 --- a/include/drm/drm_bridge.h +++ b/include/drm/drm_bridge.h @@ -1326,8 +1326,13 @@ int drm_bridge_attach(struct drm_encoder *encoder, struct drm_bridge *bridge, enum drm_bridge_attach_flags flags); #ifdef CONFIG_OF +struct drm_bridge *of_drm_find_and_get_bridge(struct device_node *np); struct drm_bridge *of_drm_find_bridge(struct device_node *np); #else +static inline struct drm_bridge *of_drm_find_and_get_bridge(struct device_node *np) +{ + return NULL; +} static inline struct drm_bridge *of_drm_find_bridge(struct device_node *np) { return NULL; From 9da0e06abda87b1f97014113d5231fdeb4700749 Mon Sep 17 00:00:00 2001 From: Luca Ceresoli Date: Tue, 16 Dec 2025 18:58:35 +0100 Subject: [PATCH 21/48] drm/bridge: deprecate of_drm_find_bridge() of_drm_find_bridge() does not increment the returned bridge refcount. of_drm_find_and_get_bridge() is to be used as a replacement. Suggested-by: Maxime Ripard Link: https://lore.kernel.org/dri-devel/20250319-stylish-lime-mongoose-0a18ad@houat/ Reviewed-by: Maxime Ripard Link: https://patch.msgid.link/20251216-drm-bridge-alloc-getput-drm_of_find_bridge-v3-2-b5165fab8058@bootlin.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/drm_bridge.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c index 0dbc8b59c3be..f612d486cad0 100644 --- a/drivers/gpu/drm/drm_bridge.c +++ b/drivers/gpu/drm/drm_bridge.c @@ -361,7 +361,7 @@ EXPORT_SYMBOL(__devm_drm_bridge_alloc); * @bridge: bridge control structure * * Add the given bridge to the global list of bridges, where they can be - * found by users via of_drm_find_bridge(). + * found by users via of_drm_find_and_get_bridge(). * * The bridge to be added must have been allocated by * devm_drm_bridge_alloc(). @@ -422,9 +422,9 @@ EXPORT_SYMBOL(devm_drm_bridge_add); * @bridge: bridge control structure * * Remove the given bridge from the global list of registered bridges, so - * it won't be found by users via of_drm_find_bridge(), and add it to the - * lingering bridge list, to keep track of it until its allocated memory is - * eventually freed. + * it won't be found by users via of_drm_find_and_get_bridge(), and add it + * to the lingering bridge list, to keep track of it until its allocated + * memory is eventually freed. */ void drm_bridge_remove(struct drm_bridge *bridge) { @@ -1510,6 +1510,20 @@ EXPORT_SYMBOL(of_drm_find_and_get_bridge); * * @np: device node * + * This function is deprecated. Convert to of_drm_find_and_get_bridge() + * instead for proper refcounting. + * + * The bridge returned by this function is not refcounted. This is + * dangerous because the bridge might be deallocated even before the caller + * has a chance to use it. To use this function you have to do one of: + * - get a reference with drm_bridge_get() as soon as possible to + * minimize the race window, and then drm_bridge_put() when no longer + * using the pointer + * - not call drm_bridge_get() or drm_bridge_put() at all, which used to + * be the correct practice before dynamic bridge lifetime was introduced + * - again, convert to of_drm_find_and_get_bridge(), which is the only safe + * thing to do + * * RETURNS: * drm_bridge control struct on success, NULL on failure */ From c637217efb892ac5d0d5f9aea3df92147c2e24db Mon Sep 17 00:00:00 2001 From: Luca Ceresoli Date: Tue, 16 Dec 2025 18:58:36 +0100 Subject: [PATCH 22/48] drm/todo: add entry about converting to of_drm_find_and_get_bridge() of_drm_find_bridge() is deprecated, but converting some users is very complex and should be reasonably doable only after the DRM panel bridge lifetime rework. Add a TODO to track this. Suggested-by: Maxime Ripard Link: https://lore.kernel.org/dri-devel/20250319-stylish-lime-mongoose-0a18ad@houat/ Reviewed-by: Maxime Ripard Link: https://patch.msgid.link/20251216-drm-bridge-alloc-getput-drm_of_find_bridge-v3-3-b5165fab8058@bootlin.com Signed-off-by: Luca Ceresoli --- Documentation/gpu/todo.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Documentation/gpu/todo.rst b/Documentation/gpu/todo.rst index 22487ac1b011..520da44a04a6 100644 --- a/Documentation/gpu/todo.rst +++ b/Documentation/gpu/todo.rst @@ -506,6 +506,22 @@ Contact: Maxime Ripard , Level: Intermediate +Convert users of of_drm_find_bridge() to of_drm_find_and_get_bridge() +--------------------------------------------------------------------- + +Taking a struct drm_bridge pointer requires getting a reference and putting +it after disposing of the pointer. Most functions returning a struct +drm_bridge pointer already call drm_bridge_get() to increment the refcount +and their users have been updated to call drm_bridge_put() when +appropriate. of_drm_find_bridge() does not get a reference and it has been +deprecated in favor of of_drm_find_and_get_bridge() which does, but some +users still need to be converted. + +Contact: Maxime Ripard , + Luca Ceresoli + +Level: Intermediate + Core refactorings ================= From 5d7cb36254b5facd2a349331c1a47102d4e66e40 Mon Sep 17 00:00:00 2001 From: Luca Ceresoli Date: Tue, 16 Dec 2025 18:58:37 +0100 Subject: [PATCH 23/48] drm/bridge: make of_drm_find_bridge() a wrapper of of_drm_find_and_get_bridge() of_drm_find_bridge() is identical to of_drm_find_and_get_bridge() except it does not increment the refcount. Rewrite it as a wrapper and put the bridge being returned so the behaviour is still the same. Reviewed-by: Maxime Ripard Link: https://patch.msgid.link/20251216-drm-bridge-alloc-getput-drm_of_find_bridge-v3-4-b5165fab8058@bootlin.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/drm_bridge.c | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c index f612d486cad0..64aa69dcf46f 100644 --- a/drivers/gpu/drm/drm_bridge.c +++ b/drivers/gpu/drm/drm_bridge.c @@ -1529,19 +1529,17 @@ EXPORT_SYMBOL(of_drm_find_and_get_bridge); */ struct drm_bridge *of_drm_find_bridge(struct device_node *np) { - struct drm_bridge *bridge; + struct drm_bridge *bridge = of_drm_find_and_get_bridge(np); - mutex_lock(&bridge_lock); + /* + * We need to emulate the original semantics of + * of_drm_find_bridge(), which was not getting any bridge + * reference. Being now based on of_drm_find_and_get_bridge() which + * gets a reference, put it before returning. + */ + drm_bridge_put(bridge); - list_for_each_entry(bridge, &bridge_list, list) { - if (bridge->of_node == np) { - mutex_unlock(&bridge_lock); - return bridge; - } - } - - mutex_unlock(&bridge_lock); - return NULL; + return bridge; } EXPORT_SYMBOL(of_drm_find_bridge); #endif From 7282066e55347b26cb31784059ab62c2de8a3e01 Mon Sep 17 00:00:00 2001 From: Luca Ceresoli Date: Tue, 16 Dec 2025 18:58:38 +0100 Subject: [PATCH 24/48] drm/arcpgu: convert to of_drm_find_and_get_bridge() of_drm_find_bridge() is deprecated. Move to its replacement of_drm_find_and_get_bridge() which gets a bridge reference, and ensure it is put it when done. Reviewed-by: Maxime Ripard Link: https://patch.msgid.link/20251216-drm-bridge-alloc-getput-drm_of_find_bridge-v3-5-b5165fab8058@bootlin.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/tiny/arcpgu.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/tiny/arcpgu.c b/drivers/gpu/drm/tiny/arcpgu.c index 7cf0f0ea1bfe..505888497482 100644 --- a/drivers/gpu/drm/tiny/arcpgu.c +++ b/drivers/gpu/drm/tiny/arcpgu.c @@ -308,10 +308,9 @@ static int arcpgu_load(struct arcpgu_drm_private *arcpgu) return ret; if (encoder_node) { - struct drm_bridge *bridge; - /* Locate drm bridge from the hdmi encoder DT node */ - bridge = of_drm_find_bridge(encoder_node); + struct drm_bridge *bridge __free(drm_bridge_put) = + of_drm_find_and_get_bridge(encoder_node); if (!bridge) return -EPROBE_DEFER; From 3fdeae134ba956aacbd87d5532c025913c98fc49 Mon Sep 17 00:00:00 2001 From: Luca Ceresoli Date: Tue, 16 Dec 2025 18:58:39 +0100 Subject: [PATCH 25/48] drm/bridge: add next_bridge pointer to struct drm_bridge Many bridge drivers store a next_bridge pointer in their private data and use it for attach and sometimes other purposes. This is going to be risky when bridge hot-unplug is used. Considering this example scenario: 1. pipeline: encoder --> bridge A --> bridge B --> bridge C 2. encoder takes a reference to bridge B 3. bridge B takes a next_bridge reference to bridge C 4. encoder calls (bridge B)->b_foo(), which in turns references next_bridge, e.g.: b_foo() { bar(b->next_bridge); } If bridges B and C are removed, bridge C can be freed but B is still allocated because the encoder holds a reference to B. So when step 4 happens, 'b->next-bridge' would be a use-after-free. Calling drm_bridge_put() in the B bridge .remove function does not solve the problem as it leaves a (potentially long) risk window between B removal and the final deallocation of B. A safe moment to put the B reference is in __drm_bridge_free(), when the last reference has been put. This can be done by drivers in the .destroy func. However to avoid the need for so many drivers to implement a .destroy func, just offer a next_bridge pointer to all bridges that is automatically put it in __drm_bridge_free(), exactly when the .destroy func is called. Suggested-by: Maxime Ripard Link: https://lore.kernel.org/all/20251201-thick-jasmine-oarfish-1eceb0@houat/ Reviewed-by: Maxime Ripard Link: https://patch.msgid.link/20251216-drm-bridge-alloc-getput-drm_of_find_bridge-v3-6-b5165fab8058@bootlin.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/drm_bridge.c | 2 ++ include/drm/drm_bridge.h | 11 +++++++++++ 2 files changed, 13 insertions(+) diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c index 64aa69dcf46f..6dcf8f6d3ecf 100644 --- a/drivers/gpu/drm/drm_bridge.c +++ b/drivers/gpu/drm/drm_bridge.c @@ -275,6 +275,8 @@ static void __drm_bridge_free(struct kref *kref) if (bridge->funcs->destroy) bridge->funcs->destroy(bridge); + drm_bridge_put(bridge->next_bridge); + kfree(bridge->container); } diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h index 42ec33116d18..3e6cbfa9dc44 100644 --- a/include/drm/drm_bridge.h +++ b/include/drm/drm_bridge.h @@ -1279,6 +1279,17 @@ struct drm_bridge { * @hpd_cb. */ void *hpd_data; + + /** + * @next_bridge: Pointer to the following bridge, automatically put + * when this bridge is freed (i.e. at destroy time). This is for + * drivers needing to store a pointer to the next bridge in the + * chain, and ensures any code still holding a reference to this + * bridge after its removal cannot use-after-free the next + * bridge. Any other bridge pointers stored by the driver must be + * put in the .destroy callback by driver code. + */ + struct drm_bridge *next_bridge; }; static inline struct drm_bridge * From 8f92a5fcbfe33f86b08f5f74dcc58a41425ea8c0 Mon Sep 17 00:00:00 2001 From: Luca Ceresoli Date: Tue, 16 Dec 2025 18:58:40 +0100 Subject: [PATCH 26/48] drm/bridge: ite-it66121: get/put the next bridge This driver obtains a bridge pointer from of_drm_find_bridge() in the probe function and stores it until driver removal. of_drm_find_bridge() is deprecated. Move to of_drm_find_and_get_bridge() for the bridge to be refcounted and use bridge->next_bridge to put the reference on deallocation. Reviewed-by: Maxime Ripard Link: https://patch.msgid.link/20251216-drm-bridge-alloc-getput-drm_of_find_bridge-v3-7-b5165fab8058@bootlin.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/ite-it66121.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/bridge/ite-it66121.c b/drivers/gpu/drm/bridge/ite-it66121.c index 0185f61e6e59..9246e9c15a6e 100644 --- a/drivers/gpu/drm/bridge/ite-it66121.c +++ b/drivers/gpu/drm/bridge/ite-it66121.c @@ -298,7 +298,6 @@ struct it66121_chip_info { struct it66121_ctx { struct regmap *regmap; struct drm_bridge bridge; - struct drm_bridge *next_bridge; struct drm_connector *connector; struct device *dev; struct gpio_desc *gpio_reset; @@ -596,7 +595,7 @@ static int it66121_bridge_attach(struct drm_bridge *bridge, if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) return -EINVAL; - ret = drm_bridge_attach(encoder, ctx->next_bridge, bridge, flags); + ret = drm_bridge_attach(encoder, ctx->bridge.next_bridge, bridge, flags); if (ret) return ret; @@ -1543,9 +1542,9 @@ static int it66121_probe(struct i2c_client *client) return -EINVAL; } - ctx->next_bridge = of_drm_find_bridge(ep); + ctx->bridge.next_bridge = of_drm_find_and_get_bridge(ep); of_node_put(ep); - if (!ctx->next_bridge) { + if (!ctx->bridge.next_bridge) { dev_dbg(ctx->dev, "Next bridge not found, deferring probe\n"); return -EPROBE_DEFER; } From ae754f049ce1c01f09d175f80265970f0d5b4489 Mon Sep 17 00:00:00 2001 From: Luca Ceresoli Date: Tue, 16 Dec 2025 18:58:41 +0100 Subject: [PATCH 27/48] drm/bridge: imx8qxp-pixel-combiner: get/put the next bridge This driver obtains a bridge pointer from of_drm_find_bridge() in the probe function and stores it until driver removal. of_drm_find_bridge() is deprecated. Move to of_drm_find_and_get_bridge() for the bridge to be refcounted and use bridge->next_bridge to put the reference on deallocation. Reviewed-by: Maxime Ripard Link: https://patch.msgid.link/20251216-drm-bridge-alloc-getput-drm_of_find_bridge-v3-8-b5165fab8058@bootlin.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/imx/imx8qxp-pixel-combiner.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/bridge/imx/imx8qxp-pixel-combiner.c b/drivers/gpu/drm/bridge/imx/imx8qxp-pixel-combiner.c index 8517b1c953d4..74eda8b54023 100644 --- a/drivers/gpu/drm/bridge/imx/imx8qxp-pixel-combiner.c +++ b/drivers/gpu/drm/bridge/imx/imx8qxp-pixel-combiner.c @@ -60,7 +60,6 @@ enum imx8qxp_pc_pix_data_format { struct imx8qxp_pc_channel { struct drm_bridge bridge; - struct drm_bridge *next_bridge; struct imx8qxp_pc *pc; unsigned int stream_id; }; @@ -120,7 +119,7 @@ static int imx8qxp_pc_bridge_attach(struct drm_bridge *bridge, } return drm_bridge_attach(encoder, - ch->next_bridge, bridge, + ch->bridge.next_bridge, bridge, DRM_BRIDGE_ATTACH_NO_CONNECTOR); } @@ -326,8 +325,8 @@ static int imx8qxp_pc_bridge_probe(struct platform_device *pdev) goto free_child; } - ch->next_bridge = of_drm_find_bridge(remote); - if (!ch->next_bridge) { + ch->bridge.next_bridge = of_drm_find_and_get_bridge(remote); + if (!ch->bridge.next_bridge) { of_node_put(remote); ret = -EPROBE_DEFER; DRM_DEV_DEBUG_DRIVER(dev, @@ -349,7 +348,7 @@ static int imx8qxp_pc_bridge_probe(struct platform_device *pdev) free_child: of_node_put(child); - if (i == 1 && pc->ch[0]->next_bridge) + if (i == 1 && pc->ch[0]->bridge.next_bridge) drm_bridge_remove(&pc->ch[0]->bridge); pm_runtime_disable(dev); From b9a7d5918bd436d64699b80af061f21c168df699 Mon Sep 17 00:00:00 2001 From: Luca Ceresoli Date: Tue, 16 Dec 2025 18:58:42 +0100 Subject: [PATCH 28/48] drm/bridge: simple-bridge: get/put the next bridge This driver obtains a bridge pointer from of_drm_find_bridge() in the probe function and stores it until driver removal. of_drm_find_bridge() is deprecated. Move to of_drm_find_and_get_bridge() for the bridge to be refcounted and use bridge->next_bridge to put the reference on deallocation. Reviewed-by: Maxime Ripard Link: https://patch.msgid.link/20251216-drm-bridge-alloc-getput-drm_of_find_bridge-v3-9-b5165fab8058@bootlin.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/simple-bridge.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/bridge/simple-bridge.c b/drivers/gpu/drm/bridge/simple-bridge.c index 2cd1847ba776..873b32cfb508 100644 --- a/drivers/gpu/drm/bridge/simple-bridge.c +++ b/drivers/gpu/drm/bridge/simple-bridge.c @@ -31,7 +31,6 @@ struct simple_bridge { const struct simple_bridge_info *info; - struct drm_bridge *next_bridge; struct regulator *vdd; struct gpio_desc *enable; }; @@ -54,8 +53,8 @@ static int simple_bridge_get_modes(struct drm_connector *connector) const struct drm_edid *drm_edid; int ret; - if (sbridge->next_bridge->ops & DRM_BRIDGE_OP_EDID) { - drm_edid = drm_bridge_edid_read(sbridge->next_bridge, connector); + if (sbridge->bridge.next_bridge->ops & DRM_BRIDGE_OP_EDID) { + drm_edid = drm_bridge_edid_read(sbridge->bridge.next_bridge, connector); if (!drm_edid) DRM_INFO("EDID read failed. Fallback to standard modes\n"); } else { @@ -90,7 +89,7 @@ simple_bridge_connector_detect(struct drm_connector *connector, bool force) { struct simple_bridge *sbridge = drm_connector_to_simple_bridge(connector); - return drm_bridge_detect(sbridge->next_bridge, connector); + return drm_bridge_detect(sbridge->bridge.next_bridge, connector); } static const struct drm_connector_funcs simple_bridge_con_funcs = { @@ -109,7 +108,7 @@ static int simple_bridge_attach(struct drm_bridge *bridge, struct simple_bridge *sbridge = drm_bridge_to_simple_bridge(bridge); int ret; - ret = drm_bridge_attach(encoder, sbridge->next_bridge, bridge, + ret = drm_bridge_attach(encoder, sbridge->bridge.next_bridge, bridge, DRM_BRIDGE_ATTACH_NO_CONNECTOR); if (ret < 0) return ret; @@ -122,7 +121,7 @@ static int simple_bridge_attach(struct drm_bridge *bridge, ret = drm_connector_init_with_ddc(bridge->dev, &sbridge->connector, &simple_bridge_con_funcs, sbridge->info->connector_type, - sbridge->next_bridge->ddc); + sbridge->bridge.next_bridge->ddc); if (ret) { DRM_ERROR("Failed to initialize connector\n"); return ret; @@ -180,10 +179,10 @@ static int simple_bridge_probe(struct platform_device *pdev) if (!remote) return -EINVAL; - sbridge->next_bridge = of_drm_find_bridge(remote); + sbridge->bridge.next_bridge = of_drm_find_and_get_bridge(remote); of_node_put(remote); - if (!sbridge->next_bridge) { + if (!sbridge->bridge.next_bridge) { dev_dbg(&pdev->dev, "Next bridge not found, deferring probe\n"); return -EPROBE_DEFER; } From 47fa48b3faa7cae925c9b14b18376cdc4622bae0 Mon Sep 17 00:00:00 2001 From: Luca Ceresoli Date: Tue, 16 Dec 2025 18:58:43 +0100 Subject: [PATCH 29/48] drm/meson: encoder_cvbs: get/put the next bridge This driver obtains a bridge pointer from of_drm_find_bridge() in the probe function and stores it until driver removal. of_drm_find_bridge() is deprecated. Move to of_drm_find_and_get_bridge() for the bridge to be refcounted and use bridge->next_bridge to put the reference on deallocation. Reviewed-by: Martin Blumenstingl Reviewed-by: Maxime Ripard Link: https://patch.msgid.link/20251216-drm-bridge-alloc-getput-drm_of_find_bridge-v3-10-b5165fab8058@bootlin.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/meson/meson_encoder_cvbs.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/meson/meson_encoder_cvbs.c b/drivers/gpu/drm/meson/meson_encoder_cvbs.c index dc374bfc5951..41071d6e05e5 100644 --- a/drivers/gpu/drm/meson/meson_encoder_cvbs.c +++ b/drivers/gpu/drm/meson/meson_encoder_cvbs.c @@ -33,7 +33,6 @@ struct meson_encoder_cvbs { struct drm_encoder encoder; struct drm_bridge bridge; - struct drm_bridge *next_bridge; struct meson_drm *priv; }; @@ -89,7 +88,7 @@ static int meson_encoder_cvbs_attach(struct drm_bridge *bridge, struct meson_encoder_cvbs *meson_encoder_cvbs = bridge_to_meson_encoder_cvbs(bridge); - return drm_bridge_attach(encoder, meson_encoder_cvbs->next_bridge, + return drm_bridge_attach(encoder, meson_encoder_cvbs->bridge.next_bridge, &meson_encoder_cvbs->bridge, flags); } @@ -241,9 +240,9 @@ int meson_encoder_cvbs_probe(struct meson_drm *priv) return 0; } - meson_encoder_cvbs->next_bridge = of_drm_find_bridge(remote); + meson_encoder_cvbs->bridge.next_bridge = of_drm_find_and_get_bridge(remote); of_node_put(remote); - if (!meson_encoder_cvbs->next_bridge) + if (!meson_encoder_cvbs->bridge.next_bridge) return dev_err_probe(priv->dev, -EPROBE_DEFER, "Failed to find CVBS Connector bridge\n"); From bfb8f5d0a9e756c57d4fc0f7966e4873bbfb34a5 Mon Sep 17 00:00:00 2001 From: Luca Ceresoli Date: Tue, 16 Dec 2025 18:58:44 +0100 Subject: [PATCH 30/48] drm/meson: encoder_dsi: get/put the next bridge This driver obtains a bridge pointer from of_drm_find_bridge() in the probe function and stores it until driver removal. of_drm_find_bridge() is deprecated. Move to of_drm_find_and_get_bridge() for the bridge to be refcounted and use bridge->next_bridge to put the reference on deallocation. Reviewed-by: Martin Blumenstingl Reviewed-by: Maxime Ripard Link: https://patch.msgid.link/20251216-drm-bridge-alloc-getput-drm_of_find_bridge-v3-11-b5165fab8058@bootlin.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/meson/meson_encoder_dsi.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/meson/meson_encoder_dsi.c b/drivers/gpu/drm/meson/meson_encoder_dsi.c index 6c6624f9ba24..eba246791c68 100644 --- a/drivers/gpu/drm/meson/meson_encoder_dsi.c +++ b/drivers/gpu/drm/meson/meson_encoder_dsi.c @@ -25,7 +25,6 @@ struct meson_encoder_dsi { struct drm_encoder encoder; struct drm_bridge bridge; - struct drm_bridge *next_bridge; struct meson_drm *priv; }; @@ -38,7 +37,7 @@ static int meson_encoder_dsi_attach(struct drm_bridge *bridge, { struct meson_encoder_dsi *encoder_dsi = bridge_to_meson_encoder_dsi(bridge); - return drm_bridge_attach(encoder, encoder_dsi->next_bridge, + return drm_bridge_attach(encoder, encoder_dsi->bridge.next_bridge, &encoder_dsi->bridge, flags); } @@ -120,8 +119,8 @@ int meson_encoder_dsi_probe(struct meson_drm *priv) return 0; } - meson_encoder_dsi->next_bridge = of_drm_find_bridge(remote); - if (!meson_encoder_dsi->next_bridge) + meson_encoder_dsi->bridge.next_bridge = of_drm_find_and_get_bridge(remote); + if (!meson_encoder_dsi->bridge.next_bridge) return dev_err_probe(priv->dev, -EPROBE_DEFER, "Failed to find DSI transceiver bridge\n"); From c87ad784aacb89cf88c00cde78970543010a6d67 Mon Sep 17 00:00:00 2001 From: Luca Ceresoli Date: Tue, 16 Dec 2025 18:58:45 +0100 Subject: [PATCH 31/48] drm/meson: encoder_hdmi: get/put the next bridge This driver obtains a bridge pointer from of_drm_find_bridge() in the probe function and stores it until driver removal. of_drm_find_bridge() is deprecated. Move to of_drm_find_and_get_bridge() for the bridge to be refcounted and use bridge->next_bridge to put the reference on deallocation. Reviewed-by: Martin Blumenstingl Tested-by: Martin Blumenstingl Reviewed-by: Maxime Ripard Link: https://patch.msgid.link/20251216-drm-bridge-alloc-getput-drm_of_find_bridge-v3-12-b5165fab8058@bootlin.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/meson/meson_encoder_hdmi.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/meson/meson_encoder_hdmi.c b/drivers/gpu/drm/meson/meson_encoder_hdmi.c index a665c9036878..1abb0572bb5f 100644 --- a/drivers/gpu/drm/meson/meson_encoder_hdmi.c +++ b/drivers/gpu/drm/meson/meson_encoder_hdmi.c @@ -38,7 +38,6 @@ struct meson_encoder_hdmi { struct drm_encoder encoder; struct drm_bridge bridge; - struct drm_bridge *next_bridge; struct drm_connector *connector; struct meson_drm *priv; unsigned long output_bus_fmt; @@ -54,7 +53,7 @@ static int meson_encoder_hdmi_attach(struct drm_bridge *bridge, { struct meson_encoder_hdmi *encoder_hdmi = bridge_to_meson_encoder_hdmi(bridge); - return drm_bridge_attach(encoder, encoder_hdmi->next_bridge, + return drm_bridge_attach(encoder, encoder_hdmi->bridge.next_bridge, &encoder_hdmi->bridge, flags); } @@ -335,7 +334,7 @@ static void meson_encoder_hdmi_hpd_notify(struct drm_bridge *bridge, const struct drm_edid *drm_edid; const struct edid *edid; - drm_edid = drm_bridge_edid_read(encoder_hdmi->next_bridge, + drm_edid = drm_bridge_edid_read(encoder_hdmi->bridge.next_bridge, encoder_hdmi->connector); if (!drm_edid) return; @@ -391,8 +390,8 @@ int meson_encoder_hdmi_probe(struct meson_drm *priv) return 0; } - meson_encoder_hdmi->next_bridge = of_drm_find_bridge(remote); - if (!meson_encoder_hdmi->next_bridge) { + meson_encoder_hdmi->bridge.next_bridge = of_drm_find_and_get_bridge(remote); + if (!meson_encoder_hdmi->bridge.next_bridge) { ret = dev_err_probe(priv->dev, -EPROBE_DEFER, "Failed to find HDMI transceiver bridge\n"); goto err_put_node; From ceea3f7806a109baba4a23c9066eb9e86659408b Mon Sep 17 00:00:00 2001 From: Luca Ceresoli Date: Tue, 16 Dec 2025 18:58:46 +0100 Subject: [PATCH 32/48] drm/bridge: imx8qxp-pxl2dpi: simplify put of device_node pointers Simplify the error-management code in imx8qxp_pxl2dpi_get_available_ep_from_port() by using a release action for the struct device_node pointers. Reviewed-by: Maxime Ripard Link: https://patch.msgid.link/20251216-drm-bridge-alloc-getput-drm_of_find_bridge-v3-13-b5165fab8058@bootlin.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/imx/imx8qxp-pxl2dpi.c | 21 +++++++------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/drivers/gpu/drm/bridge/imx/imx8qxp-pxl2dpi.c b/drivers/gpu/drm/bridge/imx/imx8qxp-pxl2dpi.c index 111310acab2c..a4941f39a2e6 100644 --- a/drivers/gpu/drm/bridge/imx/imx8qxp-pxl2dpi.c +++ b/drivers/gpu/drm/bridge/imx/imx8qxp-pxl2dpi.c @@ -258,35 +258,28 @@ out: static struct drm_bridge * imx8qxp_pxl2dpi_find_next_bridge(struct imx8qxp_pxl2dpi *p2d) { - struct device_node *ep, *remote; struct drm_bridge *next_bridge; int ret; - ep = imx8qxp_pxl2dpi_get_available_ep_from_port(p2d, 1); + struct device_node *ep __free(device_node) = + imx8qxp_pxl2dpi_get_available_ep_from_port(p2d, 1); if (IS_ERR(ep)) { ret = PTR_ERR(ep); return ERR_PTR(ret); } - remote = of_graph_get_remote_port_parent(ep); + struct device_node *remote __free(device_node) = of_graph_get_remote_port_parent(ep); if (!remote || !of_device_is_available(remote)) { DRM_DEV_ERROR(p2d->dev, "no available remote\n"); - next_bridge = ERR_PTR(-ENODEV); - goto out; + return ERR_PTR(-ENODEV); } else if (!of_device_is_available(remote->parent)) { DRM_DEV_ERROR(p2d->dev, "remote parent is not available\n"); - next_bridge = ERR_PTR(-ENODEV); - goto out; + return ERR_PTR(-ENODEV); } next_bridge = of_drm_find_bridge(remote); - if (!next_bridge) { - next_bridge = ERR_PTR(-EPROBE_DEFER); - goto out; - } -out: - of_node_put(remote); - of_node_put(ep); + if (!next_bridge) + return ERR_PTR(-EPROBE_DEFER); return next_bridge; } From 54af17788629fbf343c5a9ebc55454d6a5486ff8 Mon Sep 17 00:00:00 2001 From: Luca Ceresoli Date: Tue, 16 Dec 2025 18:58:47 +0100 Subject: [PATCH 33/48] drm/bridge: imx8qxp-pxl2dpi: remove excess error message imx8qxp_pxl2dpi_find_next_bridge() already emits a DRM_DEV_ERROR() for every error except -EPROBE_DEFER. The caller emits another one, which is redundant. Remove the message in the caller and keep the two in imx8qxp_pxl2dpi_find_next_bridge() as they are more informative about the error cause. Reviewed-by: Maxime Ripard Link: https://patch.msgid.link/20251216-drm-bridge-alloc-getput-drm_of_find_bridge-v3-14-b5165fab8058@bootlin.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/imx/imx8qxp-pxl2dpi.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/bridge/imx/imx8qxp-pxl2dpi.c b/drivers/gpu/drm/bridge/imx/imx8qxp-pxl2dpi.c index a4941f39a2e6..e03a411cb9db 100644 --- a/drivers/gpu/drm/bridge/imx/imx8qxp-pxl2dpi.c +++ b/drivers/gpu/drm/bridge/imx/imx8qxp-pxl2dpi.c @@ -415,13 +415,8 @@ static int imx8qxp_pxl2dpi_bridge_probe(struct platform_device *pdev) } p2d->next_bridge = imx8qxp_pxl2dpi_find_next_bridge(p2d); - if (IS_ERR(p2d->next_bridge)) { - ret = PTR_ERR(p2d->next_bridge); - if (ret != -EPROBE_DEFER) - DRM_DEV_ERROR(dev, "failed to find next bridge: %d\n", - ret); - return ret; - } + if (IS_ERR(p2d->next_bridge)) + return PTR_ERR(p2d->next_bridge); ret = imx8qxp_pxl2dpi_set_pixel_link_sel(p2d); if (ret) From 0dc4a8d6d096f97a00b10919e8ffe2466666f03c Mon Sep 17 00:00:00 2001 From: Luca Ceresoli Date: Tue, 16 Dec 2025 18:58:48 +0100 Subject: [PATCH 34/48] drm/bridge: imx8qxp-pxl2dpi: imx8qxp_pxl2dpi_find_next_bridge: return int, not ERR_PTR In preparation for using bridge->next_bridge, we need to ensure that it will never contain anything but NULL or a valid bridge pointer. Current code stores an ERR_PTR when imx8qxp_pxl2dpi_find_next_bridge() errors out. Instead of fixing that after the facts in the caller, change the function to internally set the next_pointer and just return an int error value. No functional changes. Reviewed-by: Maxime Ripard Link: https://patch.msgid.link/20251216-drm-bridge-alloc-getput-drm_of_find_bridge-v3-15-b5165fab8058@bootlin.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/imx/imx8qxp-pxl2dpi.c | 30 ++++++++------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/drivers/gpu/drm/bridge/imx/imx8qxp-pxl2dpi.c b/drivers/gpu/drm/bridge/imx/imx8qxp-pxl2dpi.c index e03a411cb9db..2e66b003386e 100644 --- a/drivers/gpu/drm/bridge/imx/imx8qxp-pxl2dpi.c +++ b/drivers/gpu/drm/bridge/imx/imx8qxp-pxl2dpi.c @@ -255,33 +255,27 @@ out: return ep; } -static struct drm_bridge * -imx8qxp_pxl2dpi_find_next_bridge(struct imx8qxp_pxl2dpi *p2d) +static int imx8qxp_pxl2dpi_find_next_bridge(struct imx8qxp_pxl2dpi *p2d) { - struct drm_bridge *next_bridge; - int ret; - struct device_node *ep __free(device_node) = imx8qxp_pxl2dpi_get_available_ep_from_port(p2d, 1); - if (IS_ERR(ep)) { - ret = PTR_ERR(ep); - return ERR_PTR(ret); - } + if (IS_ERR(ep)) + return PTR_ERR(ep); struct device_node *remote __free(device_node) = of_graph_get_remote_port_parent(ep); if (!remote || !of_device_is_available(remote)) { DRM_DEV_ERROR(p2d->dev, "no available remote\n"); - return ERR_PTR(-ENODEV); + return -ENODEV; } else if (!of_device_is_available(remote->parent)) { DRM_DEV_ERROR(p2d->dev, "remote parent is not available\n"); - return ERR_PTR(-ENODEV); + return -ENODEV; } - next_bridge = of_drm_find_bridge(remote); - if (!next_bridge) - return ERR_PTR(-EPROBE_DEFER); + p2d->next_bridge = of_drm_find_bridge(remote); + if (!p2d->next_bridge) + return -EPROBE_DEFER; - return next_bridge; + return 0; } static int imx8qxp_pxl2dpi_set_pixel_link_sel(struct imx8qxp_pxl2dpi *p2d) @@ -414,9 +408,9 @@ static int imx8qxp_pxl2dpi_bridge_probe(struct platform_device *pdev) return ret; } - p2d->next_bridge = imx8qxp_pxl2dpi_find_next_bridge(p2d); - if (IS_ERR(p2d->next_bridge)) - return PTR_ERR(p2d->next_bridge); + ret = imx8qxp_pxl2dpi_find_next_bridge(p2d); + if (ret) + return ret; ret = imx8qxp_pxl2dpi_set_pixel_link_sel(p2d); if (ret) From 6802c7ee360d98a018962adb8412d42dcca28272 Mon Sep 17 00:00:00 2001 From: Luca Ceresoli Date: Tue, 16 Dec 2025 18:58:49 +0100 Subject: [PATCH 35/48] drm/bridge: imx8qxp-pxl2dpi: get/put the next bridge This driver obtains a bridge pointer from of_drm_find_bridge() in the probe function and stores it until driver removal. of_drm_find_bridge() is deprecated. Move to of_drm_find_and_get_bridge() for the bridge to be refcounted and use bridge->next_bridge to put the reference on deallocation. Reviewed-by: Maxime Ripard Link: https://patch.msgid.link/20251216-drm-bridge-alloc-getput-drm_of_find_bridge-v3-16-b5165fab8058@bootlin.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/imx/imx8qxp-pxl2dpi.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/bridge/imx/imx8qxp-pxl2dpi.c b/drivers/gpu/drm/bridge/imx/imx8qxp-pxl2dpi.c index 2e66b003386e..556aec3b3734 100644 --- a/drivers/gpu/drm/bridge/imx/imx8qxp-pxl2dpi.c +++ b/drivers/gpu/drm/bridge/imx/imx8qxp-pxl2dpi.c @@ -35,7 +35,6 @@ struct imx8qxp_pxl2dpi { struct regmap *regmap; struct drm_bridge bridge; - struct drm_bridge *next_bridge; struct drm_bridge *companion; struct device *dev; struct imx_sc_ipc *ipc_handle; @@ -60,7 +59,7 @@ static int imx8qxp_pxl2dpi_bridge_attach(struct drm_bridge *bridge, } return drm_bridge_attach(encoder, - p2d->next_bridge, bridge, + p2d->bridge.next_bridge, bridge, DRM_BRIDGE_ATTACH_NO_CONNECTOR); } @@ -271,8 +270,8 @@ static int imx8qxp_pxl2dpi_find_next_bridge(struct imx8qxp_pxl2dpi *p2d) return -ENODEV; } - p2d->next_bridge = of_drm_find_bridge(remote); - if (!p2d->next_bridge) + p2d->bridge.next_bridge = of_drm_find_and_get_bridge(remote); + if (!p2d->bridge.next_bridge) return -EPROBE_DEFER; return 0; @@ -351,8 +350,8 @@ static int imx8qxp_pxl2dpi_parse_dt_companion(struct imx8qxp_pxl2dpi *p2d) * the next bridges are connected to. If they are marked as expecting * even pixels and odd pixels than we need to use the companion PXL2DPI. */ - port1 = of_graph_get_port_by_id(p2d->next_bridge->of_node, 1); - port2 = of_graph_get_port_by_id(companion_p2d->next_bridge->of_node, 1); + port1 = of_graph_get_port_by_id(p2d->bridge.next_bridge->of_node, 1); + port2 = of_graph_get_port_by_id(companion_p2d->bridge.next_bridge->of_node, 1); dual_link = drm_of_lvds_get_dual_link_pixel_order(port1, port2); of_node_put(port1); of_node_put(port2); From 900699ba830fd3558ba00f55fbf014022dc7ecdb Mon Sep 17 00:00:00 2001 From: Luca Ceresoli Date: Tue, 16 Dec 2025 18:58:50 +0100 Subject: [PATCH 36/48] drm/bridge: imx8qxp-pxl2dpi: get/put the companion bridge This driver obtains a bridge pointer from of_drm_find_bridge() in the probe function and stores it until driver removal. of_drm_find_bridge() is deprecated. Move to of_drm_find_and_get_bridge() for the bridge to be refcounted and use the destroy hook to put the reference on deallocation. Reviewed-by: Maxime Ripard Link: https://patch.msgid.link/20251216-drm-bridge-alloc-getput-drm_of_find_bridge-v3-17-b5165fab8058@bootlin.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/imx/imx8qxp-pxl2dpi.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/bridge/imx/imx8qxp-pxl2dpi.c b/drivers/gpu/drm/bridge/imx/imx8qxp-pxl2dpi.c index 556aec3b3734..2c40ca86e319 100644 --- a/drivers/gpu/drm/bridge/imx/imx8qxp-pxl2dpi.c +++ b/drivers/gpu/drm/bridge/imx/imx8qxp-pxl2dpi.c @@ -63,6 +63,13 @@ static int imx8qxp_pxl2dpi_bridge_attach(struct drm_bridge *bridge, DRM_BRIDGE_ATTACH_NO_CONNECTOR); } +static void imx8qxp_pxl2dpi_bridge_destroy(struct drm_bridge *bridge) +{ + struct imx8qxp_pxl2dpi *p2d = bridge->driver_private; + + drm_bridge_put(p2d->companion); +} + static int imx8qxp_pxl2dpi_bridge_atomic_check(struct drm_bridge *bridge, struct drm_bridge_state *bridge_state, @@ -205,6 +212,7 @@ static const struct drm_bridge_funcs imx8qxp_pxl2dpi_bridge_funcs = { .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, .atomic_reset = drm_atomic_helper_bridge_reset, .attach = imx8qxp_pxl2dpi_bridge_attach, + .destroy = imx8qxp_pxl2dpi_bridge_destroy, .atomic_check = imx8qxp_pxl2dpi_bridge_atomic_check, .mode_set = imx8qxp_pxl2dpi_bridge_mode_set, .atomic_disable = imx8qxp_pxl2dpi_bridge_atomic_disable, @@ -333,7 +341,7 @@ static int imx8qxp_pxl2dpi_parse_dt_companion(struct imx8qxp_pxl2dpi *p2d) goto out; } - p2d->companion = of_drm_find_bridge(companion); + p2d->companion = of_drm_find_and_get_bridge(companion); if (!p2d->companion) { ret = -EPROBE_DEFER; DRM_DEV_DEBUG_DRIVER(p2d->dev, From 40630210211a34f0714fe976530ccf1291e5ef78 Mon Sep 17 00:00:00 2001 From: Luca Ceresoli Date: Tue, 16 Dec 2025 18:58:53 +0100 Subject: [PATCH 37/48] drm/bridge: imx8qxp-pixel-link: remove excess error message imx8qxp_pixel_link_find_next_bridge() already emits a DRM_DEV_ERROR() on error except for -EPROBE_DEFER. The caller emits another one, which is redundant. Remove the message in the caller and keep the one in imx8qxp_pixel_link_find_next_bridge() as it is more informative about the error cause. Reviewed-by: Maxime Ripard Link: https://patch.msgid.link/20251216-drm-bridge-alloc-getput-drm_of_find_bridge-v3-20-b5165fab8058@bootlin.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/imx/imx8qxp-pixel-link.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/bridge/imx/imx8qxp-pixel-link.c b/drivers/gpu/drm/bridge/imx/imx8qxp-pixel-link.c index e5943506981d..433c080197a2 100644 --- a/drivers/gpu/drm/bridge/imx/imx8qxp-pixel-link.c +++ b/drivers/gpu/drm/bridge/imx/imx8qxp-pixel-link.c @@ -374,13 +374,8 @@ static int imx8qxp_pixel_link_bridge_probe(struct platform_device *pdev) return ret; pl->next_bridge = imx8qxp_pixel_link_find_next_bridge(pl); - if (IS_ERR(pl->next_bridge)) { - ret = PTR_ERR(pl->next_bridge); - if (ret != -EPROBE_DEFER) - DRM_DEV_ERROR(dev, "failed to find next bridge: %d\n", - ret); - return ret; - } + if (IS_ERR(pl->next_bridge)) + return PTR_ERR(pl->next_bridge); platform_set_drvdata(pdev, pl); From 9eb018828b1b30dfba689c060735c50fc5b9f704 Mon Sep 17 00:00:00 2001 From: Xiaolei Wang Date: Wed, 3 Dec 2025 21:03:23 +0800 Subject: [PATCH 38/48] drm/v3d: Set DMA segment size to avoid debug warnings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When using V3D rendering with CONFIG_DMA_API_DEBUG enabled, the kernel occasionally reports a segment size mismatch. This is because 'max_seg_size' is not set. The kernel defaults to 64K. setting 'max_seg_size' to the maximum will prevent 'debug_dma_map_sg()' from complaining about the over-mapping of the V3D segment length. DMA-API: v3d 1002000000.v3d: mapping sg segment longer than device claims to support [len=8290304] [max=65536] WARNING: CPU: 0 PID: 493 at kernel/dma/debug.c:1179 debug_dma_map_sg+0x330/0x388 CPU: 0 UID: 0 PID: 493 Comm: Xorg Not tainted 6.12.53-yocto-standard #1 Hardware name: Raspberry Pi 5 Model B Rev 1.0 (DT) pstate: 60400009 (nZCv daif +PAN -UAO -TCO -DIT -SSBS BTYPE=--) pc : debug_dma_map_sg+0x330/0x388 lr : debug_dma_map_sg+0x330/0x388 sp : ffff8000829a3ac0 x29: ffff8000829a3ac0 x28: 0000000000000001 x27: ffff8000813fe000 x26: ffffc1ffc0000000 x25: ffff00010fdeb760 x24: 0000000000000000 x23: ffff8000816a9bf0 x22: 0000000000000001 x21: 0000000000000002 x20: 0000000000000002 x19: ffff00010185e810 x18: ffffffffffffffff x17: 69766564206e6168 x16: 74207265676e6f6c x15: 20746e656d676573 x14: 20677320676e6970 x13: 5d34303334393134 x12: 0000000000000000 x11: 00000000000000c0 x10: 00000000000009c0 x9 : ffff8000800e0b7c x8 : ffff00010a315ca0 x7 : ffff8000816a5110 x6 : 0000000000000001 x5 : 000000000000002b x4 : 0000000000000002 x3 : 0000000000000008 x2 : 0000000000000000 x1 : 0000000000000000 x0 : ffff00010a315280 Call trace: debug_dma_map_sg+0x330/0x388 __dma_map_sg_attrs+0xc0/0x278 dma_map_sgtable+0x30/0x58 drm_gem_shmem_get_pages_sgt+0xb4/0x140 v3d_bo_create_finish+0x28/0x130 [v3d] v3d_create_bo_ioctl+0x54/0x180 [v3d] drm_ioctl_kernel+0xc8/0x140 drm_ioctl+0x2d4/0x4d8 Signed-off-by: Xiaolei Wang Link: https://patch.msgid.link/20251203130323.2247072-1-xiaolei.wang@windriver.com Signed-off-by: Maíra Canal --- drivers/gpu/drm/v3d/v3d_drv.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/v3d/v3d_drv.c b/drivers/gpu/drm/v3d/v3d_drv.c index 8faa9382846f..a11ca276061a 100644 --- a/drivers/gpu/drm/v3d/v3d_drv.c +++ b/drivers/gpu/drm/v3d/v3d_drv.c @@ -378,6 +378,8 @@ static int v3d_platform_drm_probe(struct platform_device *pdev) if (ret) goto clk_disable; + dma_set_max_seg_size(&pdev->dev, UINT_MAX); + v3d->va_width = 30 + V3D_GET_FIELD(mmu_debug, V3D_MMU_VA_WIDTH); ident1 = V3D_READ(V3D_HUB_IDENT1); From a7c2f143f878ab7a5e60ca68ed71d427fe78ce19 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Tue, 23 Dec 2025 22:49:09 +0100 Subject: [PATCH 39/48] drm: pl111: fix build regression The drm_info() function requires the drm/drm_print.h header to be included first: In file included from drivers/gpu/drm/pl111/pl111_nomadik.c:7: drivers/gpu/drm/pl111/pl111_nomadik.h:11:32: error: 'struct drm_device' declared inside parameter list will not be visible outside of this definition or declaration [-Werror] 11 | void pl111_nomadik_init(struct drm_device *dev); | ^~~~~~~~~~ drivers/gpu/drm/pl111/pl111_nomadik.c: In function 'pl111_nomadik_init': drivers/gpu/drm/pl111/pl111_nomadik.c:34:9: error: implicit declaration of function 'drm_info'; did you mean 'pr_info'? [-Wimplicit-function-declaration] 34 | drm_info(dev, "set Nomadik PMU mux to CLCD mode\n"); | ^~~~~~~~ | pr_info Fixes: a1542b8ca6ed ("drm: pl111: replace dev_* print functions with drm_* variants") Signed-off-by: Arnd Bergmann Reviewed-by: Eslam Khafagy Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20251223214915.503913-1-arnd@kernel.org --- drivers/gpu/drm/pl111/pl111_nomadik.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/pl111/pl111_nomadik.c b/drivers/gpu/drm/pl111/pl111_nomadik.c index f3218d59c5f1..0e5153deb1ac 100644 --- a/drivers/gpu/drm/pl111/pl111_nomadik.c +++ b/drivers/gpu/drm/pl111/pl111_nomadik.c @@ -4,6 +4,7 @@ #include #include #include +#include #include "pl111_nomadik.h" #define PMU_CTRL_OFFSET 0x0000 From 5555a3074921285f8ba0346208db29f335e45b95 Mon Sep 17 00:00:00 2001 From: Jocelyn Falempe Date: Tue, 16 Dec 2025 09:20:38 +0100 Subject: [PATCH 40/48] drm/panic: Rename draw_panic_static_* to draw_panic_screen_* I called them "static" because the panic screen is drawn only once, but this can be confused with the static meaning in C. Also remove some unnecessary braces in draw_panic_dispatch(). No functionnal change. Reviewed-by: Javier Martinez Canillas Link: https://patch.msgid.link/20251216082524.115980-2-jfalempe@redhat.com Signed-off-by: Jocelyn Falempe --- drivers/gpu/drm/drm_panic.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/drm_panic.c b/drivers/gpu/drm/drm_panic.c index 2050094e0518..ebe253ad4060 100644 --- a/drivers/gpu/drm/drm_panic.c +++ b/drivers/gpu/drm/drm_panic.c @@ -476,7 +476,7 @@ static void drm_panic_logo_draw(struct drm_scanout_buffer *sb, struct drm_rect * fg_color); } -static void draw_panic_static_user(struct drm_scanout_buffer *sb) +static void draw_panic_screen_user(struct drm_scanout_buffer *sb) { u32 fg_color = drm_draw_color_from_xrgb8888(CONFIG_DRM_PANIC_FOREGROUND_COLOR, sb->format->format); @@ -545,7 +545,7 @@ static int draw_line_with_wrap(struct drm_scanout_buffer *sb, const struct font_ * Draw the kmsg buffer to the screen, starting from the youngest message at the bottom, * and going up until reaching the top of the screen. */ -static void draw_panic_static_kmsg(struct drm_scanout_buffer *sb) +static void draw_panic_screen_kmsg(struct drm_scanout_buffer *sb) { u32 fg_color = drm_draw_color_from_xrgb8888(CONFIG_DRM_PANIC_FOREGROUND_COLOR, sb->format->format); @@ -733,7 +733,7 @@ static int drm_panic_get_qr_code(u8 **qr_image) /* * Draw the panic message at the center of the screen, with a QR Code */ -static int _draw_panic_static_qr_code(struct drm_scanout_buffer *sb) +static int _draw_panic_screen_qr_code(struct drm_scanout_buffer *sb) { u32 fg_color = drm_draw_color_from_xrgb8888(CONFIG_DRM_PANIC_FOREGROUND_COLOR, sb->format->format); @@ -801,10 +801,10 @@ static int _draw_panic_static_qr_code(struct drm_scanout_buffer *sb) return 0; } -static void draw_panic_static_qr_code(struct drm_scanout_buffer *sb) +static void draw_panic_screen_qr_code(struct drm_scanout_buffer *sb) { - if (_draw_panic_static_qr_code(sb)) - draw_panic_static_user(sb); + if (_draw_panic_screen_qr_code(sb)) + draw_panic_screen_user(sb); } #else static void drm_panic_qr_init(void) {}; @@ -879,18 +879,18 @@ static void draw_panic_dispatch(struct drm_scanout_buffer *sb) { switch (drm_panic_type) { case DRM_PANIC_TYPE_KMSG: - draw_panic_static_kmsg(sb); + draw_panic_screen_kmsg(sb); break; #if IS_ENABLED(CONFIG_DRM_PANIC_SCREEN_QR_CODE) case DRM_PANIC_TYPE_QR: - draw_panic_static_qr_code(sb); + draw_panic_screen_qr_code(sb); break; #endif case DRM_PANIC_TYPE_USER: default: - draw_panic_static_user(sb); + draw_panic_screen_user(sb); } } From 91ce41427d468cd103a08970b448a42430f9e52f Mon Sep 17 00:00:00 2001 From: Jocelyn Falempe Date: Tue, 16 Dec 2025 09:20:39 +0100 Subject: [PATCH 41/48] drm/panic: Add kunit tests for drm_panic Add kunit tests for drm_panic. They check that drawing the panic screen doesn't crash, but they don't check the correctness of the resulting image. Reviewed-by: Maxime Ripard Link: https://patch.msgid.link/20251216082524.115980-3-jfalempe@redhat.com Signed-off-by: Jocelyn Falempe --- MAINTAINERS | 1 + drivers/gpu/drm/drm_panic.c | 4 + drivers/gpu/drm/tests/drm_panic_test.c | 221 +++++++++++++++++++++++++ 3 files changed, 226 insertions(+) create mode 100644 drivers/gpu/drm/tests/drm_panic_test.c diff --git a/MAINTAINERS b/MAINTAINERS index d28c51323d36..6169bd4d7bac 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8749,6 +8749,7 @@ T: git https://gitlab.freedesktop.org/drm/misc/kernel.git F: drivers/gpu/drm/drm_draw.c F: drivers/gpu/drm/drm_draw_internal.h F: drivers/gpu/drm/drm_panic*.c +F: drivers/gpu/drm/tests/drm_panic_test.c F: include/drm/drm_panic* DRM PANIC QR CODE diff --git a/drivers/gpu/drm/drm_panic.c b/drivers/gpu/drm/drm_panic.c index ebe253ad4060..eb7ef17b9c71 100644 --- a/drivers/gpu/drm/drm_panic.c +++ b/drivers/gpu/drm/drm_panic.c @@ -1084,3 +1084,7 @@ void drm_panic_exit(void) { drm_panic_qr_exit(); } + +#ifdef CONFIG_DRM_KUNIT_TEST +#include "tests/drm_panic_test.c" +#endif diff --git a/drivers/gpu/drm/tests/drm_panic_test.c b/drivers/gpu/drm/tests/drm_panic_test.c new file mode 100644 index 000000000000..d60150877df8 --- /dev/null +++ b/drivers/gpu/drm/tests/drm_panic_test.c @@ -0,0 +1,221 @@ +// SPDX-License-Identifier: GPL-2.0 or MIT +/* + * Copyright (c) 2025 Red Hat. + * Author: Jocelyn Falempe + * + * KUNIT tests for drm panic + */ + +#include +#include + +#include + +#include +#include + +/* Check the framebuffer color only if the panic colors are the default */ +#if (CONFIG_DRM_PANIC_BACKGROUND_COLOR == 0 && \ + CONFIG_DRM_PANIC_FOREGROUND_COLOR == 0xffffff) + +static void drm_panic_check_color_byte(struct kunit *test, u8 b) +{ + KUNIT_EXPECT_TRUE(test, (b == 0 || b == 0xff)); +} +#else +static void drm_panic_check_color_byte(struct kunit *test, u8 b) {} +#endif + +struct drm_test_mode { + const int width; + const int height; + const u32 format; + void (*draw_screen)(struct drm_scanout_buffer *sb); + const char *fname; +}; + +/* + * Run all tests for the 3 panic screens: user, kmsg and qr_code + */ +#define DRM_TEST_MODE_LIST(func) \ + DRM_PANIC_TEST_MODE(1024, 768, DRM_FORMAT_XRGB8888, func) \ + DRM_PANIC_TEST_MODE(300, 200, DRM_FORMAT_XRGB8888, func) \ + DRM_PANIC_TEST_MODE(1920, 1080, DRM_FORMAT_XRGB8888, func) \ + DRM_PANIC_TEST_MODE(1024, 768, DRM_FORMAT_RGB565, func) \ + DRM_PANIC_TEST_MODE(1024, 768, DRM_FORMAT_RGB888, func) \ + +#define DRM_PANIC_TEST_MODE(w, h, f, name) { \ + .width = w, \ + .height = h, \ + .format = f, \ + .draw_screen = draw_panic_screen_##name, \ + .fname = #name, \ + }, \ + +static const struct drm_test_mode drm_test_modes_cases[] = { + DRM_TEST_MODE_LIST(user) + DRM_TEST_MODE_LIST(kmsg) +#if IS_ENABLED(CONFIG_DRM_PANIC_SCREEN_QR_CODE) + DRM_TEST_MODE_LIST(qr_code) +#endif +}; + +#undef DRM_PANIC_TEST_MODE + +static int drm_test_panic_init(struct kunit *test) +{ + struct drm_scanout_buffer *priv; + + priv = kunit_kzalloc(test, sizeof(*priv), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, priv); + + test->priv = priv; + + drm_panic_set_description("Kunit testing"); + + return 0; +} + +/* + * Test drawing the panic screen, using a memory mapped framebuffer + * Set the whole buffer to 0xa5, and then check that all pixels have been + * written. + */ +static void drm_test_panic_screen_user_map(struct kunit *test) +{ + struct drm_scanout_buffer *sb = test->priv; + const struct drm_test_mode *params = test->param_value; + char *fb; + int fb_size; + int i; + + sb->format = drm_format_info(params->format); + fb_size = params->width * params->height * sb->format->cpp[0]; + + fb = vmalloc(fb_size); + KUNIT_ASSERT_NOT_NULL(test, fb); + + memset(fb, 0xa5, fb_size); + + iosys_map_set_vaddr(&sb->map[0], fb); + sb->width = params->width; + sb->height = params->height; + sb->pitch[0] = params->width * sb->format->cpp[0]; + + params->draw_screen(sb); + + for (i = 0; i < fb_size; i++) + drm_panic_check_color_byte(test, fb[i]); + + vfree(fb); +} + +/* + * Test drawing the panic screen, using a list of pages framebuffer + * Set the whole buffer to 0xa5, and then check that all pixels have been + * written. + */ +static void drm_test_panic_screen_user_page(struct kunit *test) +{ + struct drm_scanout_buffer *sb = test->priv; + const struct drm_test_mode *params = test->param_value; + int fb_size, p, i, npages; + struct page **pages; + u8 *vaddr; + + sb->format = drm_format_info(params->format); + fb_size = params->width * params->height * sb->format->cpp[0]; + npages = DIV_ROUND_UP(fb_size, PAGE_SIZE); + + pages = kmalloc_array(npages, sizeof(struct page *), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, pages); + + for (p = 0; p < npages; p++) { + pages[p] = alloc_page(GFP_KERNEL); + if (!pages[p]) { + npages = p - 1; + KUNIT_FAIL(test, "Can't allocate page\n"); + goto free_pages; + } + vaddr = kmap_local_page(pages[p]); + memset(vaddr, 0xa5, PAGE_SIZE); + kunmap_local(vaddr); + } + sb->pages = pages; + sb->width = params->width; + sb->height = params->height; + sb->pitch[0] = params->width * sb->format->cpp[0]; + + params->draw_screen(sb); + + for (p = 0; p < npages; p++) { + int bytes_in_page = (p == npages - 1) ? fb_size - p * PAGE_SIZE : PAGE_SIZE; + + vaddr = kmap_local_page(pages[p]); + for (i = 0; i < bytes_in_page; i++) + drm_panic_check_color_byte(test, vaddr[i]); + + kunmap_local(vaddr); + } + +free_pages: + for (p = 0; p < npages; p++) + __free_page(pages[p]); + kfree(pages); +} + +static void drm_test_panic_set_pixel(struct drm_scanout_buffer *sb, + unsigned int x, + unsigned int y, + u32 color) +{ + struct kunit *test = (struct kunit *)sb->private; + + KUNIT_ASSERT_TRUE(test, x < sb->width && y < sb->height); +} + +/* + * Test drawing the panic screen, using the set_pixel callback + * Check that all calls to set_pixel() are within the framebuffer + */ +static void drm_test_panic_screen_user_set_pixel(struct kunit *test) +{ + struct drm_scanout_buffer *sb = test->priv; + const struct drm_test_mode *params = test->param_value; + + sb->format = drm_format_info(params->format); + sb->set_pixel = drm_test_panic_set_pixel; + sb->width = params->width; + sb->height = params->height; + sb->private = test; + + params->draw_screen(sb); +} + +static void drm_test_panic_desc(const struct drm_test_mode *t, char *desc) +{ + sprintf(desc, "Panic screen %s, mode: %d x %d \t%p4cc", + t->fname, t->width, t->height, &t->format); +} + +KUNIT_ARRAY_PARAM(drm_test_panic_screen_user_map, drm_test_modes_cases, drm_test_panic_desc); +KUNIT_ARRAY_PARAM(drm_test_panic_screen_user_page, drm_test_modes_cases, drm_test_panic_desc); +KUNIT_ARRAY_PARAM(drm_test_panic_screen_user_set_pixel, drm_test_modes_cases, drm_test_panic_desc); + +static struct kunit_case drm_panic_screen_user_test[] = { + KUNIT_CASE_PARAM(drm_test_panic_screen_user_map, + drm_test_panic_screen_user_map_gen_params), + KUNIT_CASE_PARAM(drm_test_panic_screen_user_page, + drm_test_panic_screen_user_page_gen_params), + KUNIT_CASE_PARAM(drm_test_panic_screen_user_set_pixel, + drm_test_panic_screen_user_set_pixel_gen_params), + { } +}; + +static struct kunit_suite drm_panic_suite = { + .name = "drm_panic", + .init = drm_test_panic_init, + .test_cases = drm_panic_screen_user_test, +}; + +kunit_test_suite(drm_panic_suite); From 26ce0b87a815c71d24fd193a81c06daefb60959f Mon Sep 17 00:00:00 2001 From: Martin Blumenstingl Date: Sat, 8 Nov 2025 14:42:36 +0100 Subject: [PATCH 42/48] drm/meson: venc: add support for HDMI DMT modes up to 3840x2160 Commit 5d0bfe448481 ("drm/meson: Add HDMI 1.4 4k modes") added support for HDMI 1.4 4k modes, which is what TVs need. For computer monitors the code is using the DMT code-path, which ends up in meson_venc_hdmi_supported_mode(), which does not allow the 4k modes yet. The datasheet for all supported SoCs mentions "4Kx2K@60". It's not clear whether "4K" here means 3840 or 4096 pixels. Allow resolutions up to 3840x2160 pixels (including middle steps, such as WQHD at 2560x1440 pixels) so they can be used with computer monitors (using the DMT code-path in the driver). Signed-off-by: Martin Blumenstingl Acked-by: Neil Armstrong Signed-off-by: Neil Armstrong Link: https://patch.msgid.link/20251108134236.1299630-1-martin.blumenstingl@googlemail.com --- drivers/gpu/drm/meson/meson_venc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/meson/meson_venc.c b/drivers/gpu/drm/meson/meson_venc.c index 3bf0d6e4fc30..4abd0c09b442 100644 --- a/drivers/gpu/drm/meson/meson_venc.c +++ b/drivers/gpu/drm/meson/meson_venc.c @@ -868,10 +868,10 @@ meson_venc_hdmi_supported_mode(const struct drm_display_mode *mode) DRM_MODE_FLAG_PVSYNC | DRM_MODE_FLAG_NVSYNC)) return MODE_BAD; - if (mode->hdisplay < 400 || mode->hdisplay > 1920) + if (mode->hdisplay < 400 || mode->hdisplay > 3840) return MODE_BAD_HVALUE; - if (mode->vdisplay < 480 || mode->vdisplay > 1920) + if (mode->vdisplay < 480 || mode->vdisplay > 2160) return MODE_BAD_VVALUE; return MODE_OK; From 349d4efadc1f831ebc0b872ba1e3a2b7dd58b72b Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Mon, 5 Jan 2026 16:51:34 +0100 Subject: [PATCH 43/48] drm/panel: edp: add BOE NV140WUM-T08 panel Add powerseq timing info for the BOE NV140WUM-T08 panel used on Lenovo Thinkpad T14s gen 6 (Snapdragon X1 Elite) laptops. edid-decode (hex): 00 ff ff ff ff ff ff 00 09 e5 26 0c 00 00 00 00 0a 21 01 04 a5 1e 13 78 03 d6 62 99 5e 5a 8e 27 25 53 58 00 00 00 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 33 3f 80 dc 70 b0 3c 40 30 20 36 00 2e bc 10 00 00 1a 00 00 00 fd 00 28 3c 4c 4c 10 01 0a 20 20 20 20 20 20 00 00 00 fe 00 42 4f 45 20 43 51 0a 20 20 20 20 20 20 00 00 00 fe 00 4e 56 31 34 30 57 55 4d 2d 54 30 38 0a 00 fa Signed-off-by: Hans de Goede Reviewed-by: Douglas Anderson Signed-off-by: Douglas Anderson Link: https://patch.msgid.link/20260105155134.83266-1-johannes.goede@oss.qualcomm.com --- drivers/gpu/drm/panel/panel-edp.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/gpu/drm/panel/panel-edp.c b/drivers/gpu/drm/panel/panel-edp.c index 85dd3f4cb8e1..679f4af5246d 100644 --- a/drivers/gpu/drm/panel/panel-edp.c +++ b/drivers/gpu/drm/panel/panel-edp.c @@ -1730,6 +1730,12 @@ static const struct panel_delay delay_200_500_p2e100 = { .prepare_to_enable = 100, }; +static const struct panel_delay delay_200_500_p2e200 = { + .hpd_absent = 200, + .unprepare = 500, + .prepare_to_enable = 200, +}; + static const struct panel_delay delay_200_500_e50 = { .hpd_absent = 200, .unprepare = 500, @@ -1977,6 +1983,7 @@ static const struct edp_panel_entry edp_panels[] = { EDP_PANEL_ENTRY('B', 'O', 'E', 0x0b56, &delay_200_500_e80, "NT140FHM-N47"), EDP_PANEL_ENTRY('B', 'O', 'E', 0x0b66, &delay_200_500_e80, "NE140WUM-N6G"), EDP_PANEL_ENTRY('B', 'O', 'E', 0x0c20, &delay_200_500_e80, "NT140FHM-N47"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0c26, &delay_200_500_p2e200, "NV140WUM-T08"), EDP_PANEL_ENTRY('B', 'O', 'E', 0x0c93, &delay_200_500_e200, "Unknown"), EDP_PANEL_ENTRY('B', 'O', 'E', 0x0cb6, &delay_200_500_e200, "NT116WHM-N44"), EDP_PANEL_ENTRY('B', 'O', 'E', 0x0cf2, &delay_200_500_e200, "NV156FHM-N4S"), From a8fffbe7dec7d2dda19ed8c7cc13bed744546c05 Mon Sep 17 00:00:00 2001 From: Gert Wollny Date: Wed, 19 Nov 2025 17:45:47 +0100 Subject: [PATCH 44/48] drm/etnaviv: Add command stream definitions required for a PPU flop reset v2: move some defines that resided in etnaviv_flop_reset.c into the header as well v3: fix spacing/tab stops [cgmeiner: remove use of multiple blank lines] Signed-off-by: Gert Wollny Reviewed-by: Christian Gmeiner Tested-by: Marek Vasut # STM32MP255C DHCOS DHSBC Link: https://patch.msgid.link/20251119164624.9297-2-gert.wollny@collabora.com Signed-off-by: Christian Gmeiner --- drivers/gpu/drm/etnaviv/state_3d.xml.h | 95 ++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/drivers/gpu/drm/etnaviv/state_3d.xml.h b/drivers/gpu/drm/etnaviv/state_3d.xml.h index ebbd4fcf3096..a654c05414ec 100644 --- a/drivers/gpu/drm/etnaviv/state_3d.xml.h +++ b/drivers/gpu/drm/etnaviv/state_3d.xml.h @@ -4,6 +4,101 @@ /* This is a cut-down version of the state_3d.xml.h file */ +#define VIVS_CL_CONFIG 0x00000900 +#define VIVS_CL_CONFIG_DIMENSIONS__MASK 0x00000003 +#define VIVS_CL_CONFIG_DIMENSIONS__SHIFT 0 +#define VIVS_CL_CONFIG_DIMENSIONS(x) (((x) << VIVS_CL_CONFIG_DIMENSIONS__SHIFT) & VIVS_CL_CONFIG_DIMENSIONS__MASK) +#define VIVS_CL_CONFIG_TRAVERSE_ORDER__MASK 0x00000070 +#define VIVS_CL_CONFIG_TRAVERSE_ORDER__SHIFT 4 +#define VIVS_CL_CONFIG_TRAVERSE_ORDER(x) (((x) << VIVS_CL_CONFIG_TRAVERSE_ORDER__SHIFT) & VIVS_CL_CONFIG_TRAVERSE_ORDER__MASK) +#define VIVS_CL_CONFIG_ENABLE_SWATH_X 0x00000100 +#define VIVS_CL_CONFIG_ENABLE_SWATH_Y 0x00000200 +#define VIVS_CL_CONFIG_ENABLE_SWATH_Z 0x00000400 +#define VIVS_CL_CONFIG_SWATH_SIZE_X__MASK 0x0000f000 +#define VIVS_CL_CONFIG_SWATH_SIZE_X__SHIFT 12 +#define VIVS_CL_CONFIG_SWATH_SIZE_X(x) (((x) << VIVS_CL_CONFIG_SWATH_SIZE_X__SHIFT) & VIVS_CL_CONFIG_SWATH_SIZE_X__MASK) +#define VIVS_CL_CONFIG_SWATH_SIZE_Y__MASK 0x000f0000 +#define VIVS_CL_CONFIG_SWATH_SIZE_Y__SHIFT 16 +#define VIVS_CL_CONFIG_SWATH_SIZE_Y(x) (((x) << VIVS_CL_CONFIG_SWATH_SIZE_Y__SHIFT) & VIVS_CL_CONFIG_SWATH_SIZE_Y__MASK) +#define VIVS_CL_CONFIG_SWATH_SIZE_Z__MASK 0x00f00000 +#define VIVS_CL_CONFIG_SWATH_SIZE_Z__SHIFT 20 +#define VIVS_CL_CONFIG_SWATH_SIZE_Z(x) (((x) << VIVS_CL_CONFIG_SWATH_SIZE_Z__SHIFT) & VIVS_CL_CONFIG_SWATH_SIZE_Z__MASK) + +#define VIVS_CL_CONFIG_DIMENSIONS__MASK 0x00000003 +#define VIVS_CL_CONFIG_DIMENSIONS__SHIFT 0 +#define VIVS_CL_CONFIG_DIMENSIONS(x) (((x) << VIVS_CL_CONFIG_DIMENSIONS__SHIFT) & VIVS_CL_CONFIG_DIMENSIONS__MASK) + +#define VIVS_CL_CONFIG_VALUE_ORDER__MASK 0x07000000 +#define VIVS_CL_CONFIG_VALUE_ORDER__SHIFT 24 +#define VIVS_CL_CONFIG_VALUE_ORDER(x) (((x) << VIVS_CL_CONFIG_VALUE_ORDER__SHIFT) & VIVS_CL_CONFIG_VALUE_ORDER__MASK) + +#define VIVS_CL_GLOBAL_WORK_OFFSET_X 0x0000092c +#define VIVS_CL_GLOBAL_WORK_OFFSET_Y 0x00000934 +#define VIVS_CL_GLOBAL_WORK_OFFSET_Z 0x0000093c + +#define VIVS_CL_KICKER 0x00000920 +#define VIVS_CL_THREAD_ALLOCATION 0x0000091c +#define VIVS_CL_UNK00924 0x00000924 + +#define VIVS_CL_WORKGROUP_COUNT_X 0x00000940 +#define VIVS_CL_WORKGROUP_COUNT_Y 0x00000944 +#define VIVS_CL_WORKGROUP_COUNT_Z 0x00000948 +#define VIVS_CL_WORKGROUP_SIZE_X 0x0000094c +#define VIVS_CL_WORKGROUP_SIZE_Y 0x00000950 +#define VIVS_CL_WORKGROUP_SIZE_Z 0x00000954 + +#define VIVS_CL_GLOBAL_SCALE_X 0x00000958 +#define VIVS_CL_GLOBAL_SCALE_Y 0x0000095c +#define VIVS_CL_GLOBAL_SCALE_Z 0x00000960 + +#define VIVS_PA_VS_OUTPUT_COUNT 0x00000aa8 +#define VIVS_PS_CONTROL_EXT 0x00001030 +#define VIVS_PS_ICACHE_COUNT 0x00001094 +#define VIVS_PS_ICACHE_PREFETCH 0x00001048 + +#define VIVS_PS_INPUT_COUNT 0x00001008 +#define VIVS_PS_INPUT_COUNT_COUNT__MASK 0x0000001f +#define VIVS_PS_INPUT_COUNT_COUNT__SHIFT 0 +#define VIVS_PS_INPUT_COUNT_COUNT(x) (((x) << VIVS_PS_INPUT_COUNT_COUNT__SHIFT) & VIVS_PS_INPUT_COUNT_COUNT__MASK) + +#define VIVS_PS_NEWRANGE_LOW 0x0000087c +#define VIVS_PS_NEWRANGE_HIGH 0x00001090 +#define VIVS_PS_SAMPLER_BASE 0x00001058 + +#define VIVS_PS_UNIFORM_BASE 0x00001024 +#define VIVS_PS_INST_ADDR 0x00001028 + +#define VIVS_PS_TEMP_REGISTER_CONTROL 0x0000100c +#define VIVS_PS_TEMP_REGISTER_CONTROL_NUM_TEMPS__MASK 0x0000003f +#define VIVS_PS_TEMP_REGISTER_CONTROL_NUM_TEMPS__SHIFT 0 +#define VIVS_PS_TEMP_REGISTER_CONTROL_NUM_TEMPS(x) (((x) << VIVS_PS_TEMP_REGISTER_CONTROL_NUM_TEMPS__SHIFT) & VIVS_PS_TEMP_REGISTER_CONTROL_NUM_TEMPS__MASK) + +#define VIVS_PS_VARYING_NUM_COMPONENTS(i0) (0x00001080 + 0x4*(i0)) +#define VIVS_PS_VARYING_NUM_COMPONENTS__ESIZE 0x00000004 +#define VIVS_PS_VARYING_NUM_COMPONENTS__LEN 0x00000004 + +#define VIVS_SH_CONFIG 0x00015600 +#define VIVS_SH_CONFIG_RTNE_ROUNDING 0x00000002 + +#define VIVS_SH_HALTI5_UNIFORMS(i0) (0x00036000 + 0x4*(i0)) +#define VIVS_SH_HALTI5_UNIFORMS__ESIZE 0x00000004 +#define VIVS_SH_HALTI5_UNIFORMS__LEN 0x00000800 + +#define VIVS_VS_HALTI5_UNK008A0 0x000008a0 +#define VIVS_VS_HALTI5_UNK008A0_A__MASK 0x0000003f +#define VIVS_VS_HALTI5_UNK008A0_A__SHIFT 0 +#define VIVS_VS_HALTI5_UNK008A0_A(x) (((x) << VIVS_VS_HALTI5_UNK008A0_A__SHIFT) & VIVS_VS_HALTI5_UNK008A0_A__MASK) + +#define VIVS_VS_ICACHE_CONTROL 0x00000868 +#define VIVS_VS_ICACHE_CONTROL_ENABLE 0x00000001 + +#define VIVS_VS_ICACHE_INVALIDATE 0x000008b0 + +#define VIVS_VS_OUTPUT_COUNT 0x00000804 +#define VIVS_VS_OUTPUT_COUNT_COUNT__MASK 0x000000ff +#define VIVS_VS_OUTPUT_COUNT_COUNT__SHIFT 0 +#define VIVS_VS_OUTPUT_COUNT_COUNT(x) (((x) << VIVS_VS_OUTPUT_COUNT_COUNT__SHIFT) & VIVS_VS_OUTPUT_COUNT_COUNT__MASK) + #define VIVS_TS_FLUSH_CACHE 0x00001650 #define VIVS_TS_FLUSH_CACHE_FLUSH 0x00000001 From 9934873be03c781e0be7b91168fb6a929b140cd1 Mon Sep 17 00:00:00 2001 From: Gert Wollny Date: Wed, 19 Nov 2025 17:45:48 +0100 Subject: [PATCH 45/48] drm/etnaviv: move some functions to a header to be able to use them externally v2: Add license info to header v3: remove unused headers (Christian Gmainer) [cgmeiner: improve include guard] Signed-off-by: Gert Wollny Reviewed-by: Christian Gmeiner Tested-by: Marek Vasut # STM32MP255C DHCOS DHSBC Link: https://patch.msgid.link/20251119164624.9297-3-gert.wollny@collabora.com Signed-off-by: Christian Gmeiner --- drivers/gpu/drm/etnaviv/etnaviv_buffer.c | 71 +-------------------- drivers/gpu/drm/etnaviv/etnaviv_buffer.h | 79 ++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 70 deletions(-) create mode 100644 drivers/gpu/drm/etnaviv/etnaviv_buffer.h diff --git a/drivers/gpu/drm/etnaviv/etnaviv_buffer.c b/drivers/gpu/drm/etnaviv/etnaviv_buffer.c index ad5e6f7b23f9..e99b1c9e66e4 100644 --- a/drivers/gpu/drm/etnaviv/etnaviv_buffer.c +++ b/drivers/gpu/drm/etnaviv/etnaviv_buffer.c @@ -10,6 +10,7 @@ #include "etnaviv_gpu.h" #include "etnaviv_gem.h" #include "etnaviv_mmu.h" +#include "etnaviv_buffer.h" #include "common.xml.h" #include "state.xml.h" @@ -18,76 +19,6 @@ #include "state_3d.xml.h" #include "cmdstream.xml.h" -/* - * Command Buffer helper: - */ - - -static inline void OUT(struct etnaviv_cmdbuf *buffer, u32 data) -{ - u32 *vaddr = (u32 *)buffer->vaddr; - - BUG_ON(buffer->user_size >= buffer->size); - - vaddr[buffer->user_size / 4] = data; - buffer->user_size += 4; -} - -static inline void CMD_LOAD_STATE(struct etnaviv_cmdbuf *buffer, - u32 reg, u32 value) -{ - u32 index = reg >> VIV_FE_LOAD_STATE_HEADER_OFFSET__SHR; - - buffer->user_size = ALIGN(buffer->user_size, 8); - - /* write a register via cmd stream */ - OUT(buffer, VIV_FE_LOAD_STATE_HEADER_OP_LOAD_STATE | - VIV_FE_LOAD_STATE_HEADER_COUNT(1) | - VIV_FE_LOAD_STATE_HEADER_OFFSET(index)); - OUT(buffer, value); -} - -static inline void CMD_END(struct etnaviv_cmdbuf *buffer) -{ - buffer->user_size = ALIGN(buffer->user_size, 8); - - OUT(buffer, VIV_FE_END_HEADER_OP_END); -} - -static inline void CMD_WAIT(struct etnaviv_cmdbuf *buffer, - unsigned int waitcycles) -{ - buffer->user_size = ALIGN(buffer->user_size, 8); - - OUT(buffer, VIV_FE_WAIT_HEADER_OP_WAIT | waitcycles); -} - -static inline void CMD_LINK(struct etnaviv_cmdbuf *buffer, - u16 prefetch, u32 address) -{ - buffer->user_size = ALIGN(buffer->user_size, 8); - - OUT(buffer, VIV_FE_LINK_HEADER_OP_LINK | - VIV_FE_LINK_HEADER_PREFETCH(prefetch)); - OUT(buffer, address); -} - -static inline void CMD_STALL(struct etnaviv_cmdbuf *buffer, - u32 from, u32 to) -{ - buffer->user_size = ALIGN(buffer->user_size, 8); - - OUT(buffer, VIV_FE_STALL_HEADER_OP_STALL); - OUT(buffer, VIV_FE_STALL_TOKEN_FROM(from) | VIV_FE_STALL_TOKEN_TO(to)); -} - -static inline void CMD_SEM(struct etnaviv_cmdbuf *buffer, u32 from, u32 to) -{ - CMD_LOAD_STATE(buffer, VIVS_GL_SEMAPHORE_TOKEN, - VIVS_GL_SEMAPHORE_TOKEN_FROM(from) | - VIVS_GL_SEMAPHORE_TOKEN_TO(to)); -} - static void etnaviv_cmd_select_pipe(struct etnaviv_gpu *gpu, struct etnaviv_cmdbuf *buffer, u8 pipe) { diff --git a/drivers/gpu/drm/etnaviv/etnaviv_buffer.h b/drivers/gpu/drm/etnaviv/etnaviv_buffer.h new file mode 100644 index 000000000000..a5c347c778c1 --- /dev/null +++ b/drivers/gpu/drm/etnaviv/etnaviv_buffer.h @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2014-2025 Etnaviv Project + */ + +#ifndef __ETNAVIV_BUFFER_H__ +#define __ETNAVIV_BUFFER_H__ + +#include "etnaviv_cmdbuf.h" + +#include "common.xml.h" +#include "state.xml.h" +#include "cmdstream.xml.h" + +static inline void OUT(struct etnaviv_cmdbuf *buffer, u32 data) +{ + u32 *vaddr = (u32 *)buffer->vaddr; + + BUG_ON(buffer->user_size >= buffer->size); + + vaddr[buffer->user_size / 4] = data; + buffer->user_size += 4; +} + +static inline void CMD_LOAD_STATE(struct etnaviv_cmdbuf *buffer, u32 reg, + u32 value) +{ + u32 index = reg >> VIV_FE_LOAD_STATE_HEADER_OFFSET__SHR; + + buffer->user_size = ALIGN(buffer->user_size, 8); + + /* write a register via cmd stream */ + OUT(buffer, VIV_FE_LOAD_STATE_HEADER_OP_LOAD_STATE | + VIV_FE_LOAD_STATE_HEADER_COUNT(1) | + VIV_FE_LOAD_STATE_HEADER_OFFSET(index)); + OUT(buffer, value); +} + +static inline void CMD_END(struct etnaviv_cmdbuf *buffer) +{ + buffer->user_size = ALIGN(buffer->user_size, 8); + + OUT(buffer, VIV_FE_END_HEADER_OP_END); +} + +static inline void CMD_WAIT(struct etnaviv_cmdbuf *buffer, + unsigned int waitcycles) +{ + buffer->user_size = ALIGN(buffer->user_size, 8); + + OUT(buffer, VIV_FE_WAIT_HEADER_OP_WAIT | waitcycles); +} + +static inline void CMD_LINK(struct etnaviv_cmdbuf *buffer, u16 prefetch, + u32 address) +{ + buffer->user_size = ALIGN(buffer->user_size, 8); + + OUT(buffer, + VIV_FE_LINK_HEADER_OP_LINK | VIV_FE_LINK_HEADER_PREFETCH(prefetch)); + OUT(buffer, address); +} + +static inline void CMD_STALL(struct etnaviv_cmdbuf *buffer, u32 from, u32 to) +{ + buffer->user_size = ALIGN(buffer->user_size, 8); + + OUT(buffer, VIV_FE_STALL_HEADER_OP_STALL); + OUT(buffer, VIV_FE_STALL_TOKEN_FROM(from) | VIV_FE_STALL_TOKEN_TO(to)); +} + +static inline void CMD_SEM(struct etnaviv_cmdbuf *buffer, u32 from, u32 to) +{ + CMD_LOAD_STATE(buffer, VIVS_GL_SEMAPHORE_TOKEN, + VIVS_GL_SEMAPHORE_TOKEN_FROM(from) | + VIVS_GL_SEMAPHORE_TOKEN_TO(to)); +} + +#endif /* __ETNAVIV_BUFFER_H__ */ From 9fcdece1a734bc71d2d9f9e3dd301cc9fff23327 Mon Sep 17 00:00:00 2001 From: Gert Wollny Date: Wed, 19 Nov 2025 17:45:49 +0100 Subject: [PATCH 46/48] drm/etnaviv: Add a new function to emit a series of states to cmd stream v2: fix formatting and remove superfluous masking (Lucas) Signed-off-by: Gert Wollny Reviewed-by: Christian Gmeiner Tested-by: Marek Vasut # STM32MP255C DHCOS DHSBC Link: https://patch.msgid.link/20251119164624.9297-4-gert.wollny@collabora.com Signed-off-by: Christian Gmeiner --- drivers/gpu/drm/etnaviv/etnaviv_buffer.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/gpu/drm/etnaviv/etnaviv_buffer.h b/drivers/gpu/drm/etnaviv/etnaviv_buffer.h index a5c347c778c1..9f9eb4d545a2 100644 --- a/drivers/gpu/drm/etnaviv/etnaviv_buffer.h +++ b/drivers/gpu/drm/etnaviv/etnaviv_buffer.h @@ -36,6 +36,19 @@ static inline void CMD_LOAD_STATE(struct etnaviv_cmdbuf *buffer, u32 reg, OUT(buffer, value); } +static inline void CMD_LOAD_STATES_START(struct etnaviv_cmdbuf *buffer, u32 reg, + u32 nvalues) +{ + u32 index = reg >> VIV_FE_LOAD_STATE_HEADER_OFFSET__SHR; + + buffer->user_size = ALIGN(buffer->user_size, 8); + + /* write a register via cmd stream */ + OUT(buffer, VIV_FE_LOAD_STATE_HEADER_OP_LOAD_STATE | + VIV_FE_LOAD_STATE_HEADER_OFFSET(index) | + VIV_FE_LOAD_STATE_HEADER_COUNT(nvalues)); +} + static inline void CMD_END(struct etnaviv_cmdbuf *buffer) { buffer->user_size = ALIGN(buffer->user_size, 8); From 85ba57ad88cf96b2fb4cf6c81639c7907bf3cd94 Mon Sep 17 00:00:00 2001 From: Gert Wollny Date: Wed, 19 Nov 2025 17:45:50 +0100 Subject: [PATCH 47/48] drm/etnaviv: Add PPU flop reset The PPU flop reset is required on some hardware to clear the temporary registers. This code follows the implementation of the PPU flop reset as found in the public galcore kernel module. Compared to that code some superfluous parts were removed and only the code path for SoC chip_model = 0x8000 and revision = 0x6205 is implemented and tested. v2: - Move flop reset data to etnaviv_drm_private and initialize it from etnaviv_gpu_bind (Lucas) - Prepare code for more chip IDs and other flop reset types - Do some cleanups and rename some functions v3: - Move initialization of flop reset data to etnaviv_gpu_init (Lucas) - Free PPU data suballocation (Lucas) v4: As suggested by - replace "asm-generic/int-ll64.h" with "linux/types.h" - drop flop reset type enum since we only support one type here - move function return parameters on same line with function name - replace open coded for loop with memset32 - add cnost to local static values - add a return value to etnaviv_flop_reset_ppu_init; handle and pass errors on to the caller - handle etnaviv_flop_reset_ppu_init return value - use dev_err for flop reset error message - fix include guard to be consistent with the other driver code - fix license header and formatting v5: As suggested by Christian Gmeiner: - add required header that is no longer pulled in by etnaviv_buffer.h - fix include style of linux headers - free flop_reset_data_ppu when command buffer initialization fails - fix typo in error message [cgmeiner: fix SPDX comment style, fix line end with a '(' and fix typo] Signed-off-by: Gert Wollny Reviewed-by: Christian Gmeiner Tested-by: Marek Vasut # STM32MP255C DHCOS DHSBC Link: https://patch.msgid.link/20251119164624.9297-5-gert.wollny@collabora.com Signed-off-by: Christian Gmeiner --- drivers/gpu/drm/etnaviv/Makefile | 1 + drivers/gpu/drm/etnaviv/etnaviv_buffer.c | 6 + drivers/gpu/drm/etnaviv/etnaviv_buffer.h | 7 + drivers/gpu/drm/etnaviv/etnaviv_drv.c | 3 + drivers/gpu/drm/etnaviv/etnaviv_drv.h | 3 + drivers/gpu/drm/etnaviv/etnaviv_flop_reset.c | 208 +++++++++++++++++++ drivers/gpu/drm/etnaviv/etnaviv_flop_reset.h | 21 ++ drivers/gpu/drm/etnaviv/etnaviv_gpu.c | 11 + 8 files changed, 260 insertions(+) create mode 100644 drivers/gpu/drm/etnaviv/etnaviv_flop_reset.c create mode 100644 drivers/gpu/drm/etnaviv/etnaviv_flop_reset.h diff --git a/drivers/gpu/drm/etnaviv/Makefile b/drivers/gpu/drm/etnaviv/Makefile index 46e5ffad69a6..903101e8751a 100644 --- a/drivers/gpu/drm/etnaviv/Makefile +++ b/drivers/gpu/drm/etnaviv/Makefile @@ -14,6 +14,7 @@ etnaviv-y := \ etnaviv_iommu.o \ etnaviv_mmu.o \ etnaviv_perfmon.o \ + etnaviv_flop_reset.o \ etnaviv_sched.o obj-$(CONFIG_DRM_ETNAVIV) += etnaviv.o diff --git a/drivers/gpu/drm/etnaviv/etnaviv_buffer.c b/drivers/gpu/drm/etnaviv/etnaviv_buffer.c index e99b1c9e66e4..03b2576af67f 100644 --- a/drivers/gpu/drm/etnaviv/etnaviv_buffer.c +++ b/drivers/gpu/drm/etnaviv/etnaviv_buffer.c @@ -19,6 +19,8 @@ #include "state_3d.xml.h" #include "cmdstream.xml.h" +#include "etnaviv_flop_reset.h" + static void etnaviv_cmd_select_pipe(struct etnaviv_gpu *gpu, struct etnaviv_cmdbuf *buffer, u8 pipe) { @@ -101,6 +103,10 @@ u16 etnaviv_buffer_init(struct etnaviv_gpu *gpu) /* initialize buffer */ buffer->user_size = 0; + /* Queue in PPU flop reset */ + if (etnaviv_flop_reset_ppu_require(&gpu->identity)) + etnaviv_flop_reset_ppu_run(gpu); + CMD_WAIT(buffer, gpu->fe_waitcycles); CMD_LINK(buffer, 2, etnaviv_cmdbuf_get_va(buffer, &gpu->mmu_context->cmdbuf_mapping) diff --git a/drivers/gpu/drm/etnaviv/etnaviv_buffer.h b/drivers/gpu/drm/etnaviv/etnaviv_buffer.h index 9f9eb4d545a2..4331ca8aac47 100644 --- a/drivers/gpu/drm/etnaviv/etnaviv_buffer.h +++ b/drivers/gpu/drm/etnaviv/etnaviv_buffer.h @@ -7,9 +7,16 @@ #define __ETNAVIV_BUFFER_H__ #include "etnaviv_cmdbuf.h" +#include "etnaviv_gpu.h" +#include "etnaviv_gem.h" +#include "etnaviv_mmu.h" #include "common.xml.h" +#include "linux/printk.h" #include "state.xml.h" +#include "state_blt.xml.h" +#include "state_hi.xml.h" +#include "state_3d.xml.h" #include "cmdstream.xml.h" static inline void OUT(struct etnaviv_cmdbuf *buffer, u32 data) diff --git a/drivers/gpu/drm/etnaviv/etnaviv_drv.c b/drivers/gpu/drm/etnaviv/etnaviv_drv.c index 54ceae87b401..bb1b84eecec8 100644 --- a/drivers/gpu/drm/etnaviv/etnaviv_drv.c +++ b/drivers/gpu/drm/etnaviv/etnaviv_drv.c @@ -601,6 +601,9 @@ static void etnaviv_unbind(struct device *dev) component_unbind_all(dev, drm); + etnaviv_cmdbuf_free(priv->flop_reset_data_ppu); + kfree(priv->flop_reset_data_ppu); + etnaviv_cmdbuf_suballoc_destroy(priv->cmdbuf_suballoc); xa_destroy(&priv->active_contexts); diff --git a/drivers/gpu/drm/etnaviv/etnaviv_drv.h b/drivers/gpu/drm/etnaviv/etnaviv_drv.h index b3eb1662e90c..20dad16fd554 100644 --- a/drivers/gpu/drm/etnaviv/etnaviv_drv.h +++ b/drivers/gpu/drm/etnaviv/etnaviv_drv.h @@ -48,6 +48,9 @@ struct etnaviv_drm_private { /* list of GEM objects: */ struct mutex gem_lock; struct list_head gem_list; + + /* ppu flop reset data */ + struct etnaviv_cmdbuf *flop_reset_data_ppu; }; int etnaviv_ioctl_gem_submit(struct drm_device *dev, void *data, diff --git a/drivers/gpu/drm/etnaviv/etnaviv_flop_reset.c b/drivers/gpu/drm/etnaviv/etnaviv_flop_reset.c new file mode 100644 index 000000000000..2ae6532a51ab --- /dev/null +++ b/drivers/gpu/drm/etnaviv/etnaviv_flop_reset.c @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2025 Etnaviv Project + */ + +#include +#include +#include +#include + +#include "etnaviv_buffer.h" +#include "etnaviv_cmdbuf.h" +#include "etnaviv_gpu.h" +#include "state_3d.xml.h" + +#include "etnaviv_flop_reset.h" + +#define PPU_IMAGE_STRIDE 64 +#define PPU_IMAGE_XSIZE 64 +#define PPU_IMAGE_YSIZE 6 + +#define PPU_FLOP_RESET_INSTR_DWORD_COUNT 16 + +static void etnaviv_emit_flop_reset_state_ppu(struct etnaviv_cmdbuf *cmdbuf, + u32 buffer_base, u32 input_offset, + u32 output_offset, + u32 shader_offset, + u32 shader_size, + u32 shader_register_count) +{ + CMD_LOAD_STATE(cmdbuf, VIVS_GL_API_MODE, VIVS_GL_API_MODE_OPENCL); + CMD_SEM(cmdbuf, SYNC_RECIPIENT_FE, SYNC_RECIPIENT_PE); + CMD_STALL(cmdbuf, SYNC_RECIPIENT_FE, SYNC_RECIPIENT_PE); + + CMD_LOAD_STATES_START(cmdbuf, VIVS_SH_HALTI5_UNIFORMS(0), 4); + + OUT(cmdbuf, buffer_base + input_offset); + OUT(cmdbuf, PPU_IMAGE_STRIDE); + OUT(cmdbuf, PPU_IMAGE_XSIZE | (PPU_IMAGE_YSIZE << 16)); + OUT(cmdbuf, 0x444051f0); + OUT(cmdbuf, 0xffffffff); + + CMD_LOAD_STATES_START(cmdbuf, VIVS_SH_HALTI5_UNIFORMS(4), 4); + OUT(cmdbuf, buffer_base + output_offset); + OUT(cmdbuf, PPU_IMAGE_STRIDE); + OUT(cmdbuf, PPU_IMAGE_XSIZE | (PPU_IMAGE_YSIZE << 16)); + OUT(cmdbuf, 0x444051f0); + OUT(cmdbuf, 0xffffffff); + + CMD_LOAD_STATE(cmdbuf, VIVS_CL_CONFIG, + VIVS_CL_CONFIG_DIMENSIONS(2) | + VIVS_CL_CONFIG_VALUE_ORDER(3)); + CMD_LOAD_STATE(cmdbuf, VIVS_VS_ICACHE_INVALIDATE, 0x1f); + CMD_LOAD_STATE(cmdbuf, VIVS_PS_VARYING_NUM_COMPONENTS(0), 0); + CMD_LOAD_STATE(cmdbuf, VIVS_PS_TEMP_REGISTER_CONTROL, + shader_register_count); + CMD_LOAD_STATE(cmdbuf, VIVS_PS_SAMPLER_BASE, 0x0); + CMD_LOAD_STATE(cmdbuf, VIVS_PS_UNIFORM_BASE, 0x0); + CMD_LOAD_STATE(cmdbuf, VIVS_PS_NEWRANGE_LOW, 0x0); + CMD_LOAD_STATE(cmdbuf, VIVS_PS_NEWRANGE_HIGH, shader_size / 16); + CMD_LOAD_STATE(cmdbuf, VIVS_PS_INST_ADDR, buffer_base + shader_offset); + CMD_LOAD_STATE(cmdbuf, VIVS_SH_CONFIG, VIVS_SH_CONFIG_RTNE_ROUNDING); + CMD_LOAD_STATE(cmdbuf, VIVS_VS_ICACHE_CONTROL, + VIVS_VS_ICACHE_CONTROL_ENABLE); + CMD_LOAD_STATE(cmdbuf, VIVS_PS_ICACHE_COUNT, shader_size / 16 - 1); + CMD_LOAD_STATE(cmdbuf, VIVS_PS_INPUT_COUNT, 0x1f01); + CMD_LOAD_STATE(cmdbuf, VIVS_VS_HALTI5_UNK008A0, 0x0); + CMD_LOAD_STATE(cmdbuf, VIVS_PA_VS_OUTPUT_COUNT, 0x0); + CMD_LOAD_STATE(cmdbuf, VIVS_GL_VARYING_TOTAL_COMPONENTS, 0x0); + CMD_LOAD_STATE(cmdbuf, VIVS_PS_CONTROL_EXT, 0x0); + CMD_LOAD_STATE(cmdbuf, VIVS_VS_OUTPUT_COUNT, 0x1); + CMD_LOAD_STATE(cmdbuf, VIVS_GL_HALTI5_SH_SPECIALS, 0x0); + CMD_LOAD_STATE(cmdbuf, VIVS_PS_ICACHE_PREFETCH, 0x0); + CMD_LOAD_STATE(cmdbuf, VIVS_CL_UNK00924, 0x0); + CMD_LOAD_STATE(cmdbuf, VIVS_CL_THREAD_ALLOCATION, 0x1); + + CMD_LOAD_STATE(cmdbuf, VIVS_CL_GLOBAL_WORK_OFFSET_X, 0x0); + CMD_LOAD_STATE(cmdbuf, VIVS_CL_GLOBAL_WORK_OFFSET_Y, 0x0); + CMD_LOAD_STATE(cmdbuf, VIVS_CL_GLOBAL_WORK_OFFSET_Z, 0x0); + + CMD_LOAD_STATES_START(cmdbuf, VIVS_CL_WORKGROUP_COUNT_X, 9); + OUT(cmdbuf, 0xf); + OUT(cmdbuf, 0x5); + OUT(cmdbuf, 0xffffffff); + OUT(cmdbuf, 0x0); + OUT(cmdbuf, 0x0); + OUT(cmdbuf, 0x3ff); + OUT(cmdbuf, 0x0); + OUT(cmdbuf, 0x4); + OUT(cmdbuf, 0x1); + OUT(cmdbuf, 0x0); + + CMD_LOAD_STATE(cmdbuf, VIVS_CL_KICKER, 0xbadabeeb); + CMD_LOAD_STATE(cmdbuf, VIVS_GL_FLUSH_CACHE, + VIVS_GL_FLUSH_CACHE_SHADER_L1 | + VIVS_GL_FLUSH_CACHE_UNK10 | + VIVS_GL_FLUSH_CACHE_UNK11); +} + +static void etnaviv_flop_reset_ppu_fill_input(u32 *buffer, u32 size) +{ + memset32(buffer, 0x01010101, size / 4); +} + +static void etnaviv_flop_reset_ppu_set_shader(u8 *dest) +{ + static const u32 inst[PPU_FLOP_RESET_INSTR_DWORD_COUNT] = { + /* img_load.u8 r1, c0, r0.xy */ + 0x78011779, + 0x39000804, + 0x00A90050, + 0x00000000, + /* img_load.u8 r2, c0, r0.xy */ + 0x78021779, + 0x39000804, + 0x00A90050, + 0x00000000, + /* dp2x8 r1, r1, r2, c3_512 */ + 0xB8017145, + 0x390018FC, + 0x01C90140, + 0x40390028, + /* img_store.u8 r1, c2, r0.xy, r1 */ + 0x380007BA, + 0x39001804, + 0x00A90050, + 0x00390018, + }; + memcpy(dest, inst, sizeof(inst)); +} + +static const struct etnaviv_flop_reset_entry { + u16 chip_model; + u16 revision; + u32 flags; +} etnaviv_flop_reset_db[] = { + { + .chip_model = 0x8000, + .revision = 0x6205, + }, +}; + +bool etnaviv_flop_reset_ppu_require(const struct etnaviv_chip_identity *chip_id) +{ + const struct etnaviv_flop_reset_entry *e = etnaviv_flop_reset_db; + + for (int i = 0; i < ARRAY_SIZE(etnaviv_flop_reset_db); ++i, ++e) { + if (chip_id->model == e->chip_model && + chip_id->revision == e->revision) + return true; + } + + return false; +} + +static const u32 image_data_size = PPU_IMAGE_STRIDE * PPU_IMAGE_YSIZE; +static const u32 output_offset = ALIGN(image_data_size, 64); +static const u32 shader_offset = ALIGN(output_offset + image_data_size, 64); +static const u32 shader_size = PPU_FLOP_RESET_INSTR_DWORD_COUNT * sizeof(u32); +static const u32 shader_register_count = 3; +static const u32 buffer_size = shader_offset + shader_size; + +int etnaviv_flop_reset_ppu_init(struct etnaviv_drm_private *priv) +{ + /* Get some space from the ring buffer to put the payload + * (input and output image, and shader), we keep this buffer + * for the whole life time the driver is bound + */ + priv->flop_reset_data_ppu = + kzalloc(sizeof(*priv->flop_reset_data_ppu), GFP_KERNEL); + + if (!priv->flop_reset_data_ppu) + return -ENOMEM; + + int ret = etnaviv_cmdbuf_init(priv->cmdbuf_suballoc, + priv->flop_reset_data_ppu, buffer_size); + if (ret) { + kfree(priv->flop_reset_data_ppu); + return ret; + } + + void *buffer_base = priv->flop_reset_data_ppu->vaddr; + u32 *input_data = (u32 *)buffer_base; + u8 *shader_data = (u8 *)buffer_base + shader_offset; + + etnaviv_flop_reset_ppu_fill_input(input_data, image_data_size); + etnaviv_flop_reset_ppu_set_shader(shader_data); + + return 0; +} + +void etnaviv_flop_reset_ppu_run(struct etnaviv_gpu *gpu) +{ + struct etnaviv_drm_private *priv = gpu->drm->dev_private; + + if (!priv->flop_reset_data_ppu) { + dev_err(gpu->dev, + "Oops: Flop reset data was not initialized, skipping\n"); + return; + } + + u32 buffer_base = etnaviv_cmdbuf_get_va(priv->flop_reset_data_ppu, + &gpu->mmu_context->cmdbuf_mapping); + + etnaviv_emit_flop_reset_state_ppu(&gpu->buffer, buffer_base, 0, + output_offset, shader_offset, + shader_size, shader_register_count); +} diff --git a/drivers/gpu/drm/etnaviv/etnaviv_flop_reset.h b/drivers/gpu/drm/etnaviv/etnaviv_flop_reset.h new file mode 100644 index 000000000000..e6dee1db788f --- /dev/null +++ b/drivers/gpu/drm/etnaviv/etnaviv_flop_reset.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2025 Etnaviv Project + */ + +#ifndef _ETNAVIV_FLOP_RESET_H_ +#define _ETNAVIV_FLOP_RESET_H_ + +#include + +struct etnaviv_chip_identity; +struct etnaviv_drm_private; +struct etnaviv_gpu; + +bool etnaviv_flop_reset_ppu_require(const struct etnaviv_chip_identity *chip_id); + +int etnaviv_flop_reset_ppu_init(struct etnaviv_drm_private *priv); + +void etnaviv_flop_reset_ppu_run(struct etnaviv_gpu *gpu); + +#endif diff --git a/drivers/gpu/drm/etnaviv/etnaviv_gpu.c b/drivers/gpu/drm/etnaviv/etnaviv_gpu.c index ca0be293f5fe..94326dddaf6f 100644 --- a/drivers/gpu/drm/etnaviv/etnaviv_gpu.c +++ b/drivers/gpu/drm/etnaviv/etnaviv_gpu.c @@ -20,6 +20,7 @@ #include "etnaviv_cmdbuf.h" #include "etnaviv_dump.h" +#include "etnaviv_flop_reset.h" #include "etnaviv_gpu.h" #include "etnaviv_gem.h" #include "etnaviv_mmu.h" @@ -839,6 +840,16 @@ int etnaviv_gpu_init(struct etnaviv_gpu *gpu) goto fail; } + if (etnaviv_flop_reset_ppu_require(&gpu->identity) && + !priv->flop_reset_data_ppu) { + ret = etnaviv_flop_reset_ppu_init(priv); + if (ret) { + dev_err(gpu->dev, + "Unable to initialize PPU flop reset data\n"); + goto fail; + } + } + if (gpu->identity.nn_core_count > 0) dev_warn(gpu->dev, "etnaviv has been instantiated on a NPU, " "for which the UAPI is still experimental\n"); From 6a0b99e9fb45f403c3097a9047963d2dd5b0fab2 Mon Sep 17 00:00:00 2001 From: Gert Wollny Date: Wed, 19 Nov 2025 17:45:51 +0100 Subject: [PATCH 48/48] drm/etnaviv: Add module parameter to force PPU flop reset v2: Check for feature PIPE_3D when forcing PPU flop reset (Lucas) v3: - drop use of ppu_flop_reset enum (Christian Gmeiner) - don't initialize module parameter to zero (checkpatch) - avoid multi-line string in warning message (checkpatch) Signed-off-by: Gert Wollny Reviewed-by: Christian Gmeiner Tested-by: Marek Vasut # STM32MP255C DHCOS DHSBC Link: https://patch.msgid.link/20251119164624.9297-6-gert.wollny@collabora.com Signed-off-by: Christian Gmeiner --- drivers/gpu/drm/etnaviv/etnaviv_flop_reset.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/gpu/drm/etnaviv/etnaviv_flop_reset.c b/drivers/gpu/drm/etnaviv/etnaviv_flop_reset.c index 2ae6532a51ab..58d957ee861d 100644 --- a/drivers/gpu/drm/etnaviv/etnaviv_flop_reset.c +++ b/drivers/gpu/drm/etnaviv/etnaviv_flop_reset.c @@ -15,6 +15,9 @@ #include "etnaviv_flop_reset.h" +static int etnaviv_force_flop_reset; +module_param_named(force_flop_reset, etnaviv_force_flop_reset, int, 0); + #define PPU_IMAGE_STRIDE 64 #define PPU_IMAGE_XSIZE 64 #define PPU_IMAGE_YSIZE 6 @@ -150,6 +153,19 @@ bool etnaviv_flop_reset_ppu_require(const struct etnaviv_chip_identity *chip_id) return true; } + if (etnaviv_force_flop_reset) { + if (!(chip_id->features & chipFeatures_PIPE_3D)) { + pr_warn("Etnaviv: model: 0x%04x, revision: 0x%04x does not support PIPE_3D\n", + chip_id->model, chip_id->revision); + pr_warn("Request to force PPU flop reset ignored.\n"); + return false; + } + + pr_info("Force PPU flop reset for model: 0x%04x, revision: 0x%04x\n", + chip_id->model, chip_id->revision); + return true; + } + return false; }