From e5320be8a585a9286f231305d0d443d59c24e46c Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Thu, 9 Jul 2026 12:53:24 +0200 Subject: [PATCH 01/46] drm/solomon: remove unneeded variables in blit functions Remove unneeded 'ret' variables in ssd130x_fb_blit_rect(), ssd132x_fb_blit_rect(), and ssd133x_fb_blit_rect() functions. These functions initialize ret to 0 and return it unchanged, so return 0 directly instead. Fixes: 2258f03989af ("drm/solomon: Move calls to drm_gem_fb_end_cpu*()") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202606301409.I0ctsf41-lkp@intel.com/ Signed-off-by: Iker Pedrosa Reviewed-by: Javier Martinez Canillas Link: https://patch.msgid.link/20260709-fix-ssd130x-v1-1-1272cb3dc85e@gmail.com Signed-off-by: Javier Martinez Canillas --- drivers/gpu/drm/solomon/ssd130x.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/solomon/ssd130x.c b/drivers/gpu/drm/solomon/ssd130x.c index 4907be694aec..0940eae7a2e4 100644 --- a/drivers/gpu/drm/solomon/ssd130x.c +++ b/drivers/gpu/drm/solomon/ssd130x.c @@ -897,7 +897,6 @@ static int ssd130x_fb_blit_rect(struct drm_framebuffer *fb, struct ssd130x_device *ssd130x = drm_to_ssd130x(fb->dev); struct iosys_map dst; unsigned int dst_pitch; - int ret = 0; /* Align y to display page boundaries */ rect->y1 = round_down(rect->y1, SSD130X_PAGE_HEIGHT); @@ -910,7 +909,7 @@ static int ssd130x_fb_blit_rect(struct drm_framebuffer *fb, ssd130x_update_rect(ssd130x, rect, buf, data_array); - return ret; + return 0; } static int ssd132x_fb_blit_rect(struct drm_framebuffer *fb, @@ -922,7 +921,6 @@ static int ssd132x_fb_blit_rect(struct drm_framebuffer *fb, struct ssd130x_device *ssd130x = drm_to_ssd130x(fb->dev); unsigned int dst_pitch; struct iosys_map dst; - int ret = 0; /* Align x to display segment boundaries */ rect->x1 = round_down(rect->x1, SSD132X_SEGMENT_WIDTH); @@ -936,7 +934,7 @@ static int ssd132x_fb_blit_rect(struct drm_framebuffer *fb, ssd132x_update_rect(ssd130x, rect, buf, data_array); - return ret; + return 0; } static int ssd133x_fb_blit_rect(struct drm_framebuffer *fb, @@ -948,7 +946,6 @@ static int ssd133x_fb_blit_rect(struct drm_framebuffer *fb, const struct drm_format_info *fi = drm_format_info(DRM_FORMAT_RGB332); unsigned int dst_pitch; struct iosys_map dst; - int ret = 0; if (!fi) return -EINVAL; @@ -960,7 +957,7 @@ static int ssd133x_fb_blit_rect(struct drm_framebuffer *fb, ssd133x_update_rect(ssd130x, rect, data_array, dst_pitch); - return ret; + return 0; } static int ssd130x_primary_plane_atomic_check(struct drm_plane *plane, From 7a39b9856acccc8e9b05845ca2bf62494fcd3dfa Mon Sep 17 00:00:00 2001 From: Mikko Perttunen Date: Tue, 9 Jun 2026 17:09:17 +0900 Subject: [PATCH 02/46] gpu: host1x: Wait for timeout worker completion on channel free cdma_timeout_destroy() used cancel_delayed_work() to cancel pending timeout work when destroying the CDMA. Usually this is fine, but there is a narrow race condition where the timeout handler has started execution but has not taken cdma->lock; the channel is freed causing cdma_stop to take cdma->lock and flush the channel; host1x_cdma_deinit then proceeds with deinitializing cdma while the handler is waiting to take cdma->lock. Therefore change cdma_timeout_destroy to use cancel_delayed_work_sync instead to ensure any pending timeout work completes before proceeding. Signed-off-by: Mikko Perttunen Signed-off-by: Thierry Reding Link: https://patch.msgid.link/20260609-b4-host1x-small-fixes-a-v1-1-7c1131c0b3ad@nvidia.com --- drivers/gpu/host1x/hw/cdma_hw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/host1x/hw/cdma_hw.c b/drivers/gpu/host1x/hw/cdma_hw.c index 3f3f0018eee0..ab714d221120 100644 --- a/drivers/gpu/host1x/hw/cdma_hw.c +++ b/drivers/gpu/host1x/hw/cdma_hw.c @@ -355,7 +355,7 @@ static int cdma_timeout_init(struct host1x_cdma *cdma) static void cdma_timeout_destroy(struct host1x_cdma *cdma) { if (cdma->timeout.initialized) - cancel_delayed_work(&cdma->timeout.wq); + cancel_delayed_work_sync(&cdma->timeout.wq); cdma->timeout.initialized = false; } From 254290869fd234b85119c48dbb98efa5fcd41a31 Mon Sep 17 00:00:00 2001 From: Mikko Perttunen Date: Tue, 9 Jun 2026 17:09:18 +0900 Subject: [PATCH 03/46] gpu: host1x: Avoid double device_add when clients already present host1x_device_add looks through the idle clients list to populate subdevs, and any matches entries are moved from the subdevs list to the active list. If all subdevs are populated, device_add will be called on the device. The secondary "subdevs list empty" check will then incorrectly again call device_add. However, this would require a convoluted scenario since clients don't typically end up on the idle clients list. Fix by checking whether the device was already added before adding again. Fixes: fab823d82ee5 ("gpu: host1x: Allow loading tegra-drm without enabled engines") Signed-off-by: Mikko Perttunen Signed-off-by: Thierry Reding Link: https://patch.msgid.link/20260609-b4-host1x-small-fixes-a-v1-2-7c1131c0b3ad@nvidia.com --- drivers/gpu/host1x/bus.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/host1x/bus.c b/drivers/gpu/host1x/bus.c index e3884096c2fe..825a943c64dd 100644 --- a/drivers/gpu/host1x/bus.c +++ b/drivers/gpu/host1x/bus.c @@ -508,7 +508,7 @@ static int host1x_device_add(struct host1x *host1x, * Add device even if there are no subdevs to ensure syncpoint functionality * is available regardless of whether any engine subdevices are present */ - if (list_empty(&device->subdevs)) { + if (list_empty(&device->subdevs) && !device->registered) { err = device_add(&device->dev); if (err < 0) dev_err(&device->dev, "failed to add device: %d\n", err); From eb896850964d3dfce291b4fdff9c2d42d85e564b Mon Sep 17 00:00:00 2001 From: Mikko Perttunen Date: Tue, 9 Jun 2026 17:09:19 +0900 Subject: [PATCH 04/46] gpu: host1x: Fix offset calculation in trace_write_gather When a gather longer than 2*TRACE_MAX_LENGTH (256) words is traced through host1x_cdma_push_gather, the reported BO offset drifts from the third iteration onward. Fix the calculation by properly calculating the value on each loop rather than accumulating. In reality, gathers tend to be pretty short so this is unlikely to ever have been observed. Fixes: b40d02bf96e0 ("gpu: host1x: Use struct host1x_bo pointers in traces") Signed-off-by: Mikko Perttunen Signed-off-by: Thierry Reding Link: https://patch.msgid.link/20260609-b4-host1x-small-fixes-a-v1-3-7c1131c0b3ad@nvidia.com --- drivers/gpu/host1x/hw/channel_hw.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/host1x/hw/channel_hw.c b/drivers/gpu/host1x/hw/channel_hw.c index 2df6a16d484e..9dda73199889 100644 --- a/drivers/gpu/host1x/hw/channel_hw.c +++ b/drivers/gpu/host1x/hw/channel_hw.c @@ -36,10 +36,9 @@ static void trace_write_gather(struct host1x_cdma *cdma, struct host1x_bo *bo, for (i = 0; i < words; i += TRACE_MAX_LENGTH) { u32 num_words = min(words - i, TRACE_MAX_LENGTH); - offset += i * sizeof(u32); - trace_host1x_cdma_push_gather(dev_name(dev), bo, - num_words, offset, + num_words, + offset + i * sizeof(u32), mem); } From bc17ac285fb708f22a8fa2c0ed32eceb1d37e6d6 Mon Sep 17 00:00:00 2001 From: Mikko Perttunen Date: Tue, 9 Jun 2026 17:09:20 +0900 Subject: [PATCH 05/46] gpu: host1x: Avoid stack over-read in debug output helpers host1x_debug_output() and host1x_debug_cont() used vsnprintf(), which returns the length the formatted string would have reached with an unbounded buffer. That return value was passed straight to o->fn as the number of bytes to emit. This could cause a read past end of the output buffer if a call to host1x_debug_* produced a string longer than 256 bytes. This only affected the debugfs files as the printk debug sink ignores the number of bytes. In practice, this is very unlikely to occur. Fix by switching to vscnprintf(), which returns the number of bytes actually written. Fixes: 6236451d83a7 ("gpu: host1x: Add debug support") Signed-off-by: Mikko Perttunen Signed-off-by: Thierry Reding Link: https://patch.msgid.link/20260609-b4-host1x-small-fixes-a-v1-4-7c1131c0b3ad@nvidia.com --- drivers/gpu/host1x/debug.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/host1x/debug.c b/drivers/gpu/host1x/debug.c index 6433c00d5d7e..b828f773fc06 100644 --- a/drivers/gpu/host1x/debug.c +++ b/drivers/gpu/host1x/debug.c @@ -31,7 +31,7 @@ void host1x_debug_output(struct output *o, const char *fmt, ...) int len; va_start(args, fmt); - len = vsnprintf(o->buf, sizeof(o->buf), fmt, args); + len = vscnprintf(o->buf, sizeof(o->buf), fmt, args); va_end(args); o->fn(o->ctx, o->buf, len, false); @@ -43,7 +43,7 @@ void host1x_debug_cont(struct output *o, const char *fmt, ...) int len; va_start(args, fmt); - len = vsnprintf(o->buf, sizeof(o->buf), fmt, args); + len = vscnprintf(o->buf, sizeof(o->buf), fmt, args); va_end(args); o->fn(o->ctx, o->buf, len, true); From 5c53592e3bc4f473b7fb94e4d6a4fa5aee4f7e3e Mon Sep 17 00:00:00 2001 From: Mikko Perttunen Date: Tue, 9 Jun 2026 17:09:21 +0900 Subject: [PATCH 06/46] gpu: host1x: Change pin_job() return type to int pin_job() returns negative errno values on error paths (-EINVAL, -ENOMEM, PTR_ERR() of mapping) but was declared as unsigned int. The caller would immediately cast back to int, so there was no functional issue, but it still warrants fixing. Signed-off-by: Mikko Perttunen Signed-off-by: Thierry Reding Link: https://patch.msgid.link/20260609-b4-host1x-small-fixes-a-v1-5-7c1131c0b3ad@nvidia.com --- drivers/gpu/host1x/job.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/host1x/job.c b/drivers/gpu/host1x/job.c index 70bda32f1ff4..71411bc344bb 100644 --- a/drivers/gpu/host1x/job.c +++ b/drivers/gpu/host1x/job.c @@ -138,7 +138,7 @@ void host1x_job_add_wait(struct host1x_job *job, u32 id, u32 thresh, } EXPORT_SYMBOL(host1x_job_add_wait); -static unsigned int pin_job(struct host1x *host, struct host1x_job *job) +static int pin_job(struct host1x *host, struct host1x_job *job) { unsigned long mask = HOST1X_RELOC_READ | HOST1X_RELOC_WRITE; struct host1x_client *client = job->client; From 5d4f4018274069d6dd9ec760a29eceb88fc6bd49 Mon Sep 17 00:00:00 2001 From: Mikko Perttunen Date: Tue, 9 Jun 2026 17:09:22 +0900 Subject: [PATCH 07/46] gpu: host1x: Annotate intentional syncpoint wrap-around Host1x syncpoints are 32-bit counters that roll over by design. To make that explicit in the code, use wrapping_* functions whenever arithmetic is done on syncpoint values. Atomic operations cannot be updated but a comment is added. Signed-off-by: Mikko Perttunen Signed-off-by: Thierry Reding Link: https://patch.msgid.link/20260609-b4-host1x-small-fixes-a-v1-6-7c1131c0b3ad@nvidia.com --- drivers/gpu/host1x/cdma.c | 3 ++- drivers/gpu/host1x/hw/channel_hw.c | 10 +++++++--- drivers/gpu/host1x/intr.c | 5 +++-- drivers/gpu/host1x/syncpt.c | 7 ++++++- drivers/gpu/host1x/syncpt.h | 3 ++- 5 files changed, 20 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/host1x/cdma.c b/drivers/gpu/host1x/cdma.c index ba2e572567c0..f6d3db2c8c39 100644 --- a/drivers/gpu/host1x/cdma.c +++ b/drivers/gpu/host1x/cdma.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -419,7 +420,7 @@ void host1x_cdma_update_sync_queue(struct host1x_cdma *cdma, /* won't need a timeout when replayed */ job->timeout = 0; - syncpt_incrs = job->syncpt_end - syncpt_val; + syncpt_incrs = wrapping_sub(u32, job->syncpt_end, syncpt_val); dev_dbg(dev, "%s: CPU incr (%d)\n", __func__, syncpt_incrs); host1x_job_dump(dev, job); diff --git a/drivers/gpu/host1x/hw/channel_hw.c b/drivers/gpu/host1x/hw/channel_hw.c index 9dda73199889..a8251ec0810c 100644 --- a/drivers/gpu/host1x/hw/channel_hw.c +++ b/drivers/gpu/host1x/hw/channel_hw.c @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -120,7 +121,8 @@ static void submit_gathers(struct host1x_job *job, struct host1x_job_cmd *cmds, if (cmd->is_wait) { if (cmd->wait.relative) - threshold = job_syncpt_base + cmd->wait.threshold; + threshold = wrapping_add(u32, job_syncpt_base, + cmd->wait.threshold); else threshold = cmd->wait.threshold; @@ -259,7 +261,8 @@ static void channel_program_cdma(struct host1x_job *job) /* Submit work. */ job->syncpt_end = host1x_syncpt_incr_max(sp, job->syncpt_incrs); - submit_gathers(job, job->cmds + i, job->num_cmds - i, job->syncpt_end - job->syncpt_incrs); + submit_gathers(job, job->cmds + i, job->num_cmds - i, + wrapping_sub(u32, job->syncpt_end, job->syncpt_incrs)); /* Before releasing MLOCK, ensure engine is idle again. */ fence = host1x_syncpt_incr_max(sp, 1); @@ -297,7 +300,8 @@ static void channel_program_cdma(struct host1x_job *job) job->syncpt_end = host1x_syncpt_incr_max(sp, job->syncpt_incrs); - submit_gathers(job, job->cmds, job->num_cmds, job->syncpt_end - job->syncpt_incrs); + submit_gathers(job, job->cmds, job->num_cmds, + wrapping_sub(u32, job->syncpt_end, job->syncpt_incrs)); #endif } diff --git a/drivers/gpu/host1x/intr.c b/drivers/gpu/host1x/intr.c index 723297250768..6a383e0c6eb9 100644 --- a/drivers/gpu/host1x/intr.c +++ b/drivers/gpu/host1x/intr.c @@ -7,6 +7,7 @@ #include #include +#include #include "dev.h" #include "fence.h" #include "intr.h" @@ -17,7 +18,7 @@ static void host1x_intr_add_fence_to_list(struct host1x_fence_list *list, struct host1x_syncpt_fence *fence_in_list; list_for_each_entry_reverse(fence_in_list, &list->list, list) { - if ((s32)(fence_in_list->threshold - fence->threshold) <= 0) { + if ((s32)wrapping_sub(u32, fence_in_list->threshold, fence->threshold) <= 0) { /* Fence in list is before us, we can insert here */ list_add(&fence->list, &fence_in_list->list); return; @@ -83,7 +84,7 @@ void host1x_intr_handle_interrupt(struct host1x *host, unsigned int id) spin_lock(&sp->fences.lock); list_for_each_entry_safe(fence, tmp, &sp->fences.list, list) { - if (((value - fence->threshold) & 0x80000000U) != 0U) { + if ((wrapping_sub(u32, value, fence->threshold) & 0x80000000U) != 0U) { /* Fence is not yet expired, we are done */ break; } diff --git a/drivers/gpu/host1x/syncpt.c b/drivers/gpu/host1x/syncpt.c index 807c74fc6a0a..2c111710f3bf 100644 --- a/drivers/gpu/host1x/syncpt.c +++ b/drivers/gpu/host1x/syncpt.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -126,6 +127,10 @@ EXPORT_SYMBOL(host1x_syncpt_id); */ u32 host1x_syncpt_incr_max(struct host1x_syncpt *sp, u32 incrs) { + /* + * Syncpoint values are intended to be modulo 2^32, so overflow + * here is intended. + */ return (u32)atomic_add_return(incrs, &sp->max_val); } EXPORT_SYMBOL(host1x_syncpt_incr_max); @@ -279,7 +284,7 @@ bool host1x_syncpt_is_expired(struct host1x_syncpt *sp, u32 thresh) current_val = (u32)atomic_read(&sp->min_val); - return ((current_val - thresh) & 0x80000000U) == 0U; + return (wrapping_sub(u32, current_val, thresh) & 0x80000000U) == 0U; } int host1x_syncpt_init(struct host1x *host) diff --git a/drivers/gpu/host1x/syncpt.h b/drivers/gpu/host1x/syncpt.h index 4c3f3b2f0e9c..9eff42efc445 100644 --- a/drivers/gpu/host1x/syncpt.h +++ b/drivers/gpu/host1x/syncpt.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include "fence.h" @@ -77,7 +78,7 @@ static inline bool host1x_syncpt_check_max(struct host1x_syncpt *sp, u32 real) if (sp->client_managed) return true; max = host1x_syncpt_read_max(sp); - return (s32)(max - real) >= 0; + return (s32)wrapping_sub(u32, max, real) >= 0; } /* Return true if sync point is client managed. */ From 450793a9762aa7ef7ec534c20e54b666f6807ea7 Mon Sep 17 00:00:00 2001 From: Mikko Perttunen Date: Mon, 22 Jun 2026 15:57:38 +0900 Subject: [PATCH 08/46] dt-bindings: display: tegra: Changes to support Tegra264 Add nvidia,tegra264-host1x compatible string. The Tegra264 host1x is similar to Tegra234, but with a different set of engines and layout. The engine register range is no longer continuous, so two range entries are also needed. Signed-off-by: Mikko Perttunen Reviewed-by: Rob Herring (Arm) Signed-off-by: Thierry Reding Link: https://patch.msgid.link/20260622-t264-host1x-v2-1-ff7364d9ff7b@nvidia.com --- .../display/tegra/nvidia,tegra20-host1x.yaml | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.yaml b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.yaml index 3563378a01af..8312b7699cbe 100644 --- a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.yaml +++ b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.yaml @@ -25,6 +25,7 @@ properties: - nvidia,tegra186-host1x - nvidia,tegra194-host1x - nvidia,tegra234-host1x + - nvidia,tegra264-host1x - items: - const: nvidia,tegra132-host1x @@ -57,7 +58,8 @@ properties: enum: [1, 2] ranges: - maxItems: 1 + minItems: 1 + maxItems: 2 clocks: description: Must contain one entry, for the module clock. See @@ -192,6 +194,7 @@ allOf: contains: enum: - nvidia,tegra234-host1x + - nvidia,tegra264-host1x then: properties: reg-names: @@ -239,6 +242,21 @@ allOf: required: - reg-names + - if: + properties: + compatible: + contains: + enum: + - nvidia,tegra264-host1x + then: + properties: + ranges: + minItems: 2 + maxItems: 2 + else: + properties: + ranges: + maxItems: 1 examples: - | From fd58cd8974a3e36e677d59baeae4a5a2913d76d4 Mon Sep 17 00:00:00 2001 From: Mikko Perttunen Date: Mon, 22 Jun 2026 15:57:39 +0900 Subject: [PATCH 09/46] dt-bindings: display: tegra: Add Tegra264 compatible for VIC Add nvidia,tegra264-vic compatible string for the VIC on Tegra264. VIC on Tegra264 has a new RISC-V based microcontroller and improved image processing capabilities. Acked-by: Conor Dooley Signed-off-by: Mikko Perttunen Signed-off-by: Thierry Reding Link: https://patch.msgid.link/20260622-t264-host1x-v2-2-ff7364d9ff7b@nvidia.com --- .../devicetree/bindings/display/tegra/nvidia,tegra124-vic.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra124-vic.yaml b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra124-vic.yaml index 7200095ef19e..bdf981781bd5 100644 --- a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra124-vic.yaml +++ b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra124-vic.yaml @@ -22,6 +22,7 @@ properties: - nvidia,tegra186-vic - nvidia,tegra194-vic - nvidia,tegra234-vic + - nvidia,tegra264-vic - items: - const: nvidia,tegra132-vic From 5db6378b32ec133b32bdf8eff01e1bb54a4e2e2a Mon Sep 17 00:00:00 2001 From: Mikko Perttunen Date: Mon, 22 Jun 2026 15:57:40 +0900 Subject: [PATCH 10/46] gpu: host1x: Correctly parse linear ranges of context devices The previous parsing of the iommu-map property assumed each context device has its own one-length entry in the device tree. This has worked fine so far, but on Tegra264 larger numbers of context devices are usable, so it's better to support linear ranges as well. Signed-off-by: Mikko Perttunen Signed-off-by: Thierry Reding Link: https://patch.msgid.link/20260622-t264-host1x-v2-3-ff7364d9ff7b@nvidia.com --- drivers/gpu/host1x/context.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/host1x/context.c b/drivers/gpu/host1x/context.c index 512f3d189ebf..94ecc8769c93 100644 --- a/drivers/gpu/host1x/context.c +++ b/drivers/gpu/host1x/context.c @@ -24,7 +24,7 @@ int host1x_memory_context_list_init(struct host1x *host1x) struct host1x_memory_context_list *cdl = &host1x->context_list; struct device_node *node = host1x->dev->of_node; struct host1x_memory_context *ctx; - unsigned int i; + unsigned int devs, i; int err; cdl->devs = NULL; @@ -35,7 +35,16 @@ int host1x_memory_context_list_init(struct host1x *host1x) if (err < 0) return 0; - cdl->len = err / 4; + devs = 0; + + for (i = 0; i < err / 4; i++) { + u32 length; + + of_property_read_u32_index(node, "iommu-map", i * 4 + 3, &length); + devs += length; + } + + cdl->len = devs; cdl->devs = kzalloc_objs(*cdl->devs, cdl->len); if (!cdl->devs) return -ENOMEM; From 60bca008dd78907eff49e38f3c40884c31bc83fb Mon Sep 17 00:00:00 2001 From: Santosh BS Date: Mon, 22 Jun 2026 15:57:41 +0900 Subject: [PATCH 11/46] gpu: host1x: Add Tegra264 support Add device data and chip headers for Tegra264. Signed-off-by: Santosh BS Co-developed-by: Mikko Perttunen Signed-off-by: Mikko Perttunen Signed-off-by: Thierry Reding Link: https://patch.msgid.link/20260622-t264-host1x-v2-4-ff7364d9ff7b@nvidia.com --- drivers/gpu/host1x/Makefile | 3 +- drivers/gpu/host1x/dev.c | 41 ++++ drivers/gpu/host1x/hw/cdma_hw.c | 12 +- drivers/gpu/host1x/hw/host1x10.c | 33 ++++ drivers/gpu/host1x/hw/host1x10.h | 15 ++ drivers/gpu/host1x/hw/host1x10_hardware.h | 21 ++ drivers/gpu/host1x/hw/hw_host1x10_common.h | 6 + .../gpu/host1x/hw/hw_host1x10_hypervisor.h | 10 + drivers/gpu/host1x/hw/hw_host1x10_uclass.h | 181 ++++++++++++++++++ drivers/gpu/host1x/hw/hw_host1x10_vm.h | 36 ++++ 10 files changed, 352 insertions(+), 6 deletions(-) create mode 100644 drivers/gpu/host1x/hw/host1x10.c create mode 100644 drivers/gpu/host1x/hw/host1x10.h create mode 100644 drivers/gpu/host1x/hw/host1x10_hardware.h create mode 100644 drivers/gpu/host1x/hw/hw_host1x10_common.h create mode 100644 drivers/gpu/host1x/hw/hw_host1x10_hypervisor.h create mode 100644 drivers/gpu/host1x/hw/hw_host1x10_uclass.h create mode 100644 drivers/gpu/host1x/hw/hw_host1x10_vm.h diff --git a/drivers/gpu/host1x/Makefile b/drivers/gpu/host1x/Makefile index fead483af0b4..b684fbf73841 100644 --- a/drivers/gpu/host1x/Makefile +++ b/drivers/gpu/host1x/Makefile @@ -17,7 +17,8 @@ host1x-y = \ hw/host1x05.o \ hw/host1x06.o \ hw/host1x07.o \ - hw/host1x08.o + hw/host1x08.o \ + hw/host1x10.o host1x-$(CONFIG_IOMMU_API) += \ context.o diff --git a/drivers/gpu/host1x/dev.c b/drivers/gpu/host1x/dev.c index 3f475f0e6545..d2c64728f804 100644 --- a/drivers/gpu/host1x/dev.c +++ b/drivers/gpu/host1x/dev.c @@ -41,6 +41,7 @@ #include "hw/host1x06.h" #include "hw/host1x07.h" #include "hw/host1x08.h" +#include "hw/host1x10.h" void host1x_common_writel(struct host1x *host1x, u32 v, u32 r) { @@ -287,7 +288,47 @@ static const struct host1x_info host1x08_info = { .reserve_vblank_syncpts = false, }; +static const struct host1x_sid_entry tegra264_sid_table[] = { + { /* SE1 MMIO */ .base = 0x1650, .offset = 0x90, .limit = 0x90 }, + { /* SE2 MMIO */ .base = 0x1658, .offset = 0x90, .limit = 0x90 }, + { /* SE4 MMIO */ .base = 0x1660, .offset = 0x90, .limit = 0x90 }, + { /* SE1 ch */ .base = 0x1738, .offset = 0x90, .limit = 0x90 }, + { /* SE2 ch */ .base = 0x1740, .offset = 0x90, .limit = 0x90 }, + { /* SE4 ch */ .base = 0x1748, .offset = 0x90, .limit = 0x90 }, + { /* VIC ch */ .base = 0x1790, .offset = 0x30, .limit = 0x30 }, + { /* VIC MMIO */ .base = 0x1688, .offset = 0x34, .limit = 0x34 }, + { /* TSEC MMIO */ .base = 0x1690, .offset = 0x30, .limit = 0x34 }, + { /* VI MMIO */ .base = 0x1698, .offset = 0x800, .limit = 0x800 }, + { /* VI_THI MMIO */ .base = 0x16a0, .offset = 0x30, .limit = 0x34 }, + { /* ISP MMIO */ .base = 0x1680, .offset = 0x800, .limit = 0x800 }, + { /* ISP_THI MMIO */ .base = 0x16a8, .offset = 0x30, .limit = 0x34 }, + { /* VI2 MMIO */ .base = 0x16b8, .offset = 0x800, .limit = 0x800 }, + { /* VI2_THI MMIO */ .base = 0x16c0, .offset = 0x30, .limit = 0x34 }, + { /* ISP1 MMIO */ .base = 0x16c8, .offset = 0x800, .limit = 0x800 }, + { /* ISP1_THI MMIO */ .base = 0x16d0, .offset = 0x30, .limit = 0x34 }, +}; + +static const struct host1x_info host1x10_info = { + .nb_channels = 63, + .nb_pts = 1024, + .nb_mlocks = 24, + .nb_bases = 0, + .init = host1x10_init, + .sync_offset = 0x0, + .dma_mask = DMA_BIT_MASK(40), + .has_wide_gather = true, + .has_hypervisor = true, + .has_common = true, + .num_sid_entries = ARRAY_SIZE(tegra264_sid_table), + .sid_table = tegra264_sid_table, + .streamid_vm_table = { 0x1004, 128 }, + .classid_vm_table = { 0x1404, 25 }, + .mmio_vm_table = { 0x1504, 25 }, + .reserve_vblank_syncpts = false, +}; + static const struct of_device_id host1x_of_match[] = { + { .compatible = "nvidia,tegra264-host1x", .data = &host1x10_info, }, { .compatible = "nvidia,tegra234-host1x", .data = &host1x08_info, }, { .compatible = "nvidia,tegra194-host1x", .data = &host1x07_info, }, { .compatible = "nvidia,tegra186-host1x", .data = &host1x06_info, }, diff --git a/drivers/gpu/host1x/hw/cdma_hw.c b/drivers/gpu/host1x/hw/cdma_hw.c index ab714d221120..08a3cf2b11a9 100644 --- a/drivers/gpu/host1x/hw/cdma_hw.c +++ b/drivers/gpu/host1x/hw/cdma_hw.c @@ -246,23 +246,24 @@ static void timeout_release_mlock(struct host1x_cdma *cdma) * so it turns out that if we don't /actually/ need MLOCKs, we can just * ignore them. * - * As such, for now just implement this on Tegra234 where things are - * stricter but also easy to implement. + * As such, for now just implement this on Tegra234 and above where things + * are stricter but also easy to implement. */ struct host1x_channel *ch = cdma_to_channel(cdma); struct host1x *host1x = cdma_to_host1x(cdma); u32 offset; switch (ch->client->class) { + case HOST1X_CLASS_VIC: + offset = HOST1X_COMMON_VIC_MLOCK; + break; +#if HOST1X_HW == 8 case HOST1X_CLASS_NVJPG1: offset = HOST1X_COMMON_NVJPG1_MLOCK; break; case HOST1X_CLASS_NVENC: offset = HOST1X_COMMON_NVENC_MLOCK; break; - case HOST1X_CLASS_VIC: - offset = HOST1X_COMMON_VIC_MLOCK; - break; case HOST1X_CLASS_NVJPG: offset = HOST1X_COMMON_NVJPG_MLOCK; break; @@ -272,6 +273,7 @@ static void timeout_release_mlock(struct host1x_cdma *cdma) case HOST1X_CLASS_OFA: offset = HOST1X_COMMON_OFA_MLOCK; break; +#endif default: WARN(1, "%s was not updated for class %u", __func__, ch->client->class); return; diff --git a/drivers/gpu/host1x/hw/host1x10.c b/drivers/gpu/host1x/hw/host1x10.c new file mode 100644 index 000000000000..2800f309bf6f --- /dev/null +++ b/drivers/gpu/host1x/hw/host1x10.c @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Host1x init for Tegra264 SoCs + * + * Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + */ + +/* include hw specification */ +#include "host1x10.h" +#include "host1x10_hardware.h" + +/* include code */ +#define HOST1X_HW 10 + +#include "cdma_hw.c" +#include "channel_hw.c" +#include "debug_hw.c" +#include "intr_hw.c" +#include "syncpt_hw.c" + +#include "../dev.h" + +int host1x10_init(struct host1x *host) +{ + host->channel_op = &host1x_channel_ops; + host->cdma_op = &host1x_cdma_ops; + host->cdma_pb_op = &host1x_pushbuffer_ops; + host->syncpt_op = &host1x_syncpt_ops; + host->intr_op = &host1x_intr_ops; + host->debug_op = &host1x_debug_ops; + + return 0; +} diff --git a/drivers/gpu/host1x/hw/host1x10.h b/drivers/gpu/host1x/hw/host1x10.h new file mode 100644 index 000000000000..577f6ff3dff5 --- /dev/null +++ b/drivers/gpu/host1x/hw/host1x10.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Host1x init for Tegra264 SoCs + * + * Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + */ + +#ifndef HOST1X_HOST1X10_H +#define HOST1X_HOST1X10_H + +struct host1x; + +int host1x10_init(struct host1x *host); + +#endif diff --git a/drivers/gpu/host1x/hw/host1x10_hardware.h b/drivers/gpu/host1x/hw/host1x10_hardware.h new file mode 100644 index 000000000000..abbead8190b1 --- /dev/null +++ b/drivers/gpu/host1x/hw/host1x10_hardware.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Tegra host1x Register Offsets for Tegra264 + * + * Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + */ + +#ifndef __HOST1X_HOST1X10_HARDWARE_H +#define __HOST1X_HOST1X10_HARDWARE_H + +#include +#include + +#include "hw_host1x10_uclass.h" +#include "hw_host1x10_vm.h" +#include "hw_host1x10_hypervisor.h" +#include "hw_host1x10_common.h" + +#include "opcodes.h" + +#endif diff --git a/drivers/gpu/host1x/hw/hw_host1x10_common.h b/drivers/gpu/host1x/hw/hw_host1x10_common.h new file mode 100644 index 000000000000..48a632672a47 --- /dev/null +++ b/drivers/gpu/host1x/hw/hw_host1x10_common.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + */ + +#define HOST1X_COMMON_VIC_MLOCK 0x4060 diff --git a/drivers/gpu/host1x/hw/hw_host1x10_hypervisor.h b/drivers/gpu/host1x/hw/hw_host1x10_hypervisor.h new file mode 100644 index 000000000000..8c9069caffa8 --- /dev/null +++ b/drivers/gpu/host1x/hw/hw_host1x10_hypervisor.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + */ + +#define HOST1X_HV_SYNCPT_PROT_EN 0x172c +#define HOST1X_HV_SYNCPT_PROT_EN_CH_EN BIT(1) +#define HOST1X_HV_CH_MLOCK_EN(x) (0x1708 + (x * 4)) +#define HOST1X_HV_CH_KERNEL_FILTER_GBUFFER(x) (0x1718 + (x * 4)) +#define HOST1X_HV_SYNCPT_VM(x) (0x0 + 4 * (x)) diff --git a/drivers/gpu/host1x/hw/hw_host1x10_uclass.h b/drivers/gpu/host1x/hw/hw_host1x10_uclass.h new file mode 100644 index 000000000000..abe83e67fa83 --- /dev/null +++ b/drivers/gpu/host1x/hw/hw_host1x10_uclass.h @@ -0,0 +1,181 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + */ + + /* + * Function naming determines intended use: + * + * _r(void) : Returns the offset for register . + * + * _w(void) : Returns the word offset for word (4 byte) element . + * + * __s(void) : Returns size of field of register in bits. + * + * __f(u32 v) : Returns a value based on 'v' which has been shifted + * and masked to place it at field of register . This value + * can be |'d with others to produce a full register value for + * register . + * + * __m(void) : Returns a mask for field of register . This + * value can be ~'d and then &'d to clear the value of field for + * register . + * + * ___f(void) : Returns the constant value after being shifted + * to place it at field of register . This value can be |'d + * with others to produce a full register value for . + * + * __v(u32 r) : Returns the value of field from a full register + * value 'r' after being shifted to place its LSB at bit 0. + * This value is suitable for direct comparison with other unshifted + * values appropriate for use in field of register . + * + * ___v(void) : Returns the constant value for defined for + * field of register . This value is suitable for direct + * comparison with unshifted values appropriate for use in field + * of register . + */ + +#ifndef HOST1X_HW_HOST1X10_UCLASS_H +#define HOST1X_HW_HOST1X10_UCLASS_H + +static inline u32 host1x_uclass_incr_syncpt_r(void) +{ + return 0x0; +} +#define HOST1X_UCLASS_INCR_SYNCPT \ + host1x_uclass_incr_syncpt_r() +static inline u32 host1x_uclass_incr_syncpt_cond_f(u32 v) +{ + return (v & 0xff) << 10; +} +#define HOST1X_UCLASS_INCR_SYNCPT_COND_F(v) \ + host1x_uclass_incr_syncpt_cond_f(v) +static inline u32 host1x_uclass_incr_syncpt_indx_f(u32 v) +{ + return (v & 0x3ff) << 0; +} +#define HOST1X_UCLASS_INCR_SYNCPT_INDX_F(v) \ + host1x_uclass_incr_syncpt_indx_f(v) +static inline u32 host1x_uclass_wait_syncpt_r(void) +{ + return 0x8; +} +#define HOST1X_UCLASS_WAIT_SYNCPT \ + host1x_uclass_wait_syncpt_r() +static inline u32 host1x_uclass_wait_syncpt_indx_f(u32 v) +{ + return (v & 0xff) << 24; +} +#define HOST1X_UCLASS_WAIT_SYNCPT_INDX_F(v) \ + host1x_uclass_wait_syncpt_indx_f(v) +static inline u32 host1x_uclass_wait_syncpt_thresh_f(u32 v) +{ + return (v & 0xffffff) << 0; +} +#define HOST1X_UCLASS_WAIT_SYNCPT_THRESH_F(v) \ + host1x_uclass_wait_syncpt_thresh_f(v) +static inline u32 host1x_uclass_wait_syncpt_base_r(void) +{ + return 0x9; +} +#define HOST1X_UCLASS_WAIT_SYNCPT_BASE \ + host1x_uclass_wait_syncpt_base_r() +static inline u32 host1x_uclass_wait_syncpt_base_indx_f(u32 v) +{ + return (v & 0xff) << 24; +} +#define HOST1X_UCLASS_WAIT_SYNCPT_BASE_INDX_F(v) \ + host1x_uclass_wait_syncpt_base_indx_f(v) +static inline u32 host1x_uclass_wait_syncpt_base_base_indx_f(u32 v) +{ + return (v & 0xff) << 16; +} +#define HOST1X_UCLASS_WAIT_SYNCPT_BASE_BASE_INDX_F(v) \ + host1x_uclass_wait_syncpt_base_base_indx_f(v) +static inline u32 host1x_uclass_wait_syncpt_base_offset_f(u32 v) +{ + return (v & 0xffff) << 0; +} +#define HOST1X_UCLASS_WAIT_SYNCPT_BASE_OFFSET_F(v) \ + host1x_uclass_wait_syncpt_base_offset_f(v) +static inline u32 host1x_uclass_load_syncpt_base_r(void) +{ + return 0xb; +} +#define HOST1X_UCLASS_LOAD_SYNCPT_BASE \ + host1x_uclass_load_syncpt_base_r() +static inline u32 host1x_uclass_load_syncpt_base_base_indx_f(u32 v) +{ + return (v & 0xff) << 24; +} +#define HOST1X_UCLASS_LOAD_SYNCPT_BASE_BASE_INDX_F(v) \ + host1x_uclass_load_syncpt_base_base_indx_f(v) +static inline u32 host1x_uclass_load_syncpt_base_value_f(u32 v) +{ + return (v & 0xffffff) << 0; +} +#define HOST1X_UCLASS_LOAD_SYNCPT_BASE_VALUE_F(v) \ + host1x_uclass_load_syncpt_base_value_f(v) +static inline u32 host1x_uclass_incr_syncpt_base_base_indx_f(u32 v) +{ + return (v & 0xff) << 24; +} +#define HOST1X_UCLASS_INCR_SYNCPT_BASE_BASE_INDX_F(v) \ + host1x_uclass_incr_syncpt_base_base_indx_f(v) +static inline u32 host1x_uclass_incr_syncpt_base_offset_f(u32 v) +{ + return (v & 0xffffff) << 0; +} +#define HOST1X_UCLASS_INCR_SYNCPT_BASE_OFFSET_F(v) \ + host1x_uclass_incr_syncpt_base_offset_f(v) +static inline u32 host1x_uclass_indoff_r(void) +{ + return 0x2d; +} +#define HOST1X_UCLASS_INDOFF \ + host1x_uclass_indoff_r() +static inline u32 host1x_uclass_indoff_indbe_f(u32 v) +{ + return (v & 0xf) << 28; +} +#define HOST1X_UCLASS_INDOFF_INDBE_F(v) \ + host1x_uclass_indoff_indbe_f(v) +static inline u32 host1x_uclass_indoff_autoinc_f(u32 v) +{ + return (v & 0x1) << 27; +} +#define HOST1X_UCLASS_INDOFF_AUTOINC_F(v) \ + host1x_uclass_indoff_autoinc_f(v) +static inline u32 host1x_uclass_indoff_indmodid_f(u32 v) +{ + return (v & 0xff) << 18; +} +#define HOST1X_UCLASS_INDOFF_INDMODID_F(v) \ + host1x_uclass_indoff_indmodid_f(v) +static inline u32 host1x_uclass_indoff_indroffset_f(u32 v) +{ + return (v & 0xffff) << 2; +} +#define HOST1X_UCLASS_INDOFF_INDROFFSET_F(v) \ + host1x_uclass_indoff_indroffset_f(v) +static inline u32 host1x_uclass_indoff_rwn_read_v(void) +{ + return 1; +} +#define HOST1X_UCLASS_INDOFF_INDROFFSET_F(v) \ + host1x_uclass_indoff_indroffset_f(v) +static inline u32 host1x_uclass_load_syncpt_payload_32_r(void) +{ + return 0x4e; +} +#define HOST1X_UCLASS_LOAD_SYNCPT_PAYLOAD_32 \ + host1x_uclass_load_syncpt_payload_32_r() +static inline u32 host1x_uclass_wait_syncpt_32_r(void) +{ + return 0x50; +} +#define HOST1X_UCLASS_WAIT_SYNCPT_32 \ + host1x_uclass_wait_syncpt_32_r() + +#endif diff --git a/drivers/gpu/host1x/hw/hw_host1x10_vm.h b/drivers/gpu/host1x/hw/hw_host1x10_vm.h new file mode 100644 index 000000000000..75f5b881c561 --- /dev/null +++ b/drivers/gpu/host1x/hw/hw_host1x10_vm.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + */ + +#define HOST1X_CHANNEL_DMASTART 0x0000 +#define HOST1X_CHANNEL_DMASTART_HI 0x0004 +#define HOST1X_CHANNEL_DMAPUT 0x0008 +#define HOST1X_CHANNEL_DMAPUT_HI 0x000c +#define HOST1X_CHANNEL_DMAGET 0x0010 +#define HOST1X_CHANNEL_DMAGET_HI 0x0014 +#define HOST1X_CHANNEL_DMAEND 0x0018 +#define HOST1X_CHANNEL_DMAEND_HI 0x001c +#define HOST1X_CHANNEL_DMACTRL 0x0020 +#define HOST1X_CHANNEL_DMACTRL_DMASTOP BIT(0) +#define HOST1X_CHANNEL_DMACTRL_DMAGETRST BIT(1) +#define HOST1X_CHANNEL_DMACTRL_DMAINITGET BIT(2) +#define HOST1X_CHANNEL_CMDFIFO_STAT 0x0024 +#define HOST1X_CHANNEL_CMDFIFO_STAT_EMPTY BIT(13) +#define HOST1X_CHANNEL_CMDFIFO_RDATA 0x0028 +#define HOST1X_CHANNEL_CMDP_OFFSET 0x0030 +#define HOST1X_CHANNEL_CMDP_CLASS 0x0034 +#define HOST1X_CHANNEL_CHANNELSTAT 0x0038 +#define HOST1X_CHANNEL_CMDPROC_STOP 0x0048 +#define HOST1X_CHANNEL_TEARDOWN 0x004c +#define HOST1X_CHANNEL_SMMU_STREAMID 0x0084 + +#define HOST1X_SYNC_SYNCPT_CPU_INCR(x) (0x6400 + 4 * (x)) +#define HOST1X_SYNC_SYNCPT_THRESH_CPU0_INT_STATUS(x) (0x6600 + 4 * (x)) +#define HOST1X_SYNC_SYNCPT_INTR_DEST(x) (0x6684 + 4 * (x)) +#define HOST1X_SYNC_SYNCPT_THRESH_INT_ENABLE_CPU0(x) (0x770c + 4 * (x)) +#define HOST1X_SYNC_SYNCPT_THRESH_INT_DISABLE(x) (0x7790 + 4 * (x)) +#define HOST1X_SYNC_SYNCPT(x) (0x8080 + 4 * (x)) +#define HOST1X_SYNC_SYNCPT_INT_THRESH(x) (0xa088 + 4 * (x)) +#define HOST1X_SYNC_SYNCPT_CH_APP(x) (0xb090 + 4 * (x)) +#define HOST1X_SYNC_SYNCPT_CH_APP_CH(v) (((v) & 0x3f) << 8) From c2cc9998ea52f953fcff8420b2164eece781bcf9 Mon Sep 17 00:00:00 2001 From: Mikko Perttunen Date: Mon, 22 Jun 2026 15:57:42 +0900 Subject: [PATCH 12/46] drm/tegra: falcon: Add support for RISC-V external boot Add support for loading and booting RISC-V firmwares on Falcons with RISC-V hardware. The flow is mostly the same as for traditional Falcons, with a few different registers and different firmware layout. Signed-off-by: Mikko Perttunen Signed-off-by: Thierry Reding Link: https://patch.msgid.link/20260622-t264-host1x-v2-5-ff7364d9ff7b@nvidia.com --- drivers/gpu/drm/tegra/falcon.c | 66 ++++++++++++++++++++++++++++------ drivers/gpu/drm/tegra/falcon.h | 23 ++++++++++++ 2 files changed, 79 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/tegra/falcon.c b/drivers/gpu/drm/tegra/falcon.c index 17f616bbcb45..1172356b6af3 100644 --- a/drivers/gpu/drm/tegra/falcon.c +++ b/drivers/gpu/drm/tegra/falcon.c @@ -26,8 +26,12 @@ int falcon_wait_idle(struct falcon *falcon) { u32 value; - return readl_poll_timeout(falcon->regs + FALCON_IDLESTATE, value, - (value == 0), 10, 100000); + if (falcon->riscv) + return readl_poll_timeout(falcon->regs + RISCV_CPUCTL, value, + (value & RISCV_CPUCTL_ACTIVE_STAT_ACTIVE), 10, 100000); + else + return readl_poll_timeout(falcon->regs + FALCON_IDLESTATE, value, + (value == 0), 10, 100000); } static int falcon_dma_wait_not_full(struct falcon *falcon) @@ -122,6 +126,17 @@ static int falcon_parse_firmware_image(struct falcon *falcon) return 0; } +static void falcon_parse_firmware_desc(struct falcon *falcon) +{ + struct falcon_fw_riscv_desc *desc = + (struct falcon_fw_riscv_desc *)falcon->firmware.desc_firmware->data; + + falcon->firmware.code.offset = desc->code_offset; + falcon->firmware.code.size = desc->code_size; + falcon->firmware.data.offset = desc->data_offset; + falcon->firmware.data.size = desc->data_size; +} + int falcon_read_firmware(struct falcon *falcon, const char *name) { int err; @@ -133,7 +148,23 @@ int falcon_read_firmware(struct falcon *falcon, const char *name) falcon->firmware.size = falcon->firmware.firmware->size; + if (falcon->riscv) { + /* Load separate descriptor */ + char desc_name[128]; + + scnprintf(desc_name, sizeof(desc_name), "%s.desc", name); + err = request_firmware(&falcon->firmware.desc_firmware, desc_name, falcon->dev); + if (err < 0) + goto release_firmware; + } + return 0; + +release_firmware: + release_firmware(falcon->firmware.firmware); + falcon->firmware.firmware = NULL; + + return err; } int falcon_load_firmware(struct falcon *falcon) @@ -144,16 +175,22 @@ int falcon_load_firmware(struct falcon *falcon) /* copy firmware image into local area. this also ensures endianness */ falcon_copy_firmware_image(falcon, firmware); - /* parse the image data */ - err = falcon_parse_firmware_image(falcon); - if (err < 0) { - dev_err(falcon->dev, "failed to parse firmware image\n"); - return err; + if (falcon->riscv) { + falcon_parse_firmware_desc(falcon); + } else { + err = falcon_parse_firmware_image(falcon); + if (err < 0) { + dev_err(falcon->dev, "failed to parse firmware image\n"); + return err; + } } release_firmware(firmware); falcon->firmware.firmware = NULL; + release_firmware(falcon->firmware.desc_firmware); + falcon->firmware.desc_firmware = NULL; + return 0; } @@ -168,6 +205,9 @@ void falcon_exit(struct falcon *falcon) { if (falcon->firmware.firmware) release_firmware(falcon->firmware.firmware); + + if (falcon->firmware.desc_firmware) + release_firmware(falcon->firmware.desc_firmware); } int falcon_boot(struct falcon *falcon) @@ -229,9 +269,15 @@ int falcon_boot(struct falcon *falcon) FALCON_ITFEN_CTXEN, FALCON_ITFEN); - /* boot falcon */ - falcon_writel(falcon, 0x00000000, FALCON_BOOTVEC); - falcon_writel(falcon, FALCON_CPUCTL_STARTCPU, FALCON_CPUCTL); + if (falcon->riscv) { + falcon_writel(falcon, RISCV_BCR_CTRL_CORE_SELECT_RISCV, RISCV_BCR_CTRL); + falcon_writel(falcon, 0x0, RISCV_BOOT_VECTOR_HI); + falcon_writel(falcon, 0x100000, RISCV_BOOT_VECTOR_LO); + falcon_writel(falcon, RISCV_CPUCTL_STARTCPU, RISCV_CPUCTL); + } else { + falcon_writel(falcon, 0x00000000, FALCON_BOOTVEC); + falcon_writel(falcon, FALCON_CPUCTL_STARTCPU, FALCON_CPUCTL); + } err = falcon_wait_idle(falcon); if (err < 0) { diff --git a/drivers/gpu/drm/tegra/falcon.h b/drivers/gpu/drm/tegra/falcon.h index 902bb7e4fd0f..37a17c6136b3 100644 --- a/drivers/gpu/drm/tegra/falcon.h +++ b/drivers/gpu/drm/tegra/falcon.h @@ -55,6 +55,16 @@ #define FALCON_DMATRFFBOFFS 0x0000111c +#define RISCV_BOOT_VECTOR_LO 0x00001780 +#define RISCV_BOOT_VECTOR_HI 0x00001784 + +#define RISCV_CPUCTL 0x00001788 +#define RISCV_CPUCTL_STARTCPU (1 << 0) +#define RISCV_CPUCTL_ACTIVE_STAT_ACTIVE (1 << 7) + +#define RISCV_BCR_CTRL 0x00001a68 +#define RISCV_BCR_CTRL_CORE_SELECT_RISCV (1 << 4) + struct falcon_fw_bin_header_v1 { u32 magic; /* 0x10de */ u32 version; /* version of bin format (1) */ @@ -76,6 +86,14 @@ struct falcon_fw_os_header_v1 { u32 data_size; }; +struct falcon_fw_riscv_desc { + u32 reserved[74]; + u32 data_offset; + u32 data_size; + u32 code_offset; + u32 code_size; +}; + struct falcon_firmware_section { unsigned long offset; size_t size; @@ -84,6 +102,8 @@ struct falcon_firmware_section { struct falcon_firmware { /* Firmware after it is read but not loaded */ const struct firmware *firmware; + /* RISC-V firmware descriptor */ + const struct firmware *desc_firmware; /* Raw firmware data */ dma_addr_t iova; @@ -102,6 +122,9 @@ struct falcon { struct device *dev; void __iomem *regs; + /* Peregrine falcon, external boot */ + bool riscv; + struct falcon_firmware firmware; }; From 262110be925ac1f992633c34780a1c4ea21422e7 Mon Sep 17 00:00:00 2001 From: Mikko Perttunen Date: Mon, 22 Jun 2026 15:57:43 +0900 Subject: [PATCH 13/46] drm/tegra: vic: Add Tegra264 support Add support for VIC on Tegra264. The Tegra264 VIC uses a RISC-V based Falcon microcontroller instead of the traditional Falcon previously, and has the TRANSCFG register in a different place. The .version field is set to 0x264 rather than 0x26 to allow distinguishing between different VIC capabilities between minor version variations of some chips. Signed-off-by: Mikko Perttunen [treding@nvidia.com: fix checkpatch warnings] Signed-off-by: Thierry Reding Link: https://patch.msgid.link/20260622-t264-host1x-v2-6-ff7364d9ff7b@nvidia.com --- drivers/gpu/drm/tegra/drm.c | 1 + drivers/gpu/drm/tegra/vic.c | 92 ++++++++++++++++++++++++++++--------- drivers/gpu/drm/tegra/vic.h | 9 ++-- 3 files changed, 75 insertions(+), 27 deletions(-) diff --git a/drivers/gpu/drm/tegra/drm.c b/drivers/gpu/drm/tegra/drm.c index 24e8ef261836..324480c87fb1 100644 --- a/drivers/gpu/drm/tegra/drm.c +++ b/drivers/gpu/drm/tegra/drm.c @@ -1398,6 +1398,7 @@ static const struct of_device_id host1x_drm_subdevs[] = { { .compatible = "nvidia,tegra194-nvdec", }, { .compatible = "nvidia,tegra234-vic", }, { .compatible = "nvidia,tegra234-nvdec", }, + { .compatible = "nvidia,tegra264-vic", }, { /* sentinel */ } }; diff --git a/drivers/gpu/drm/tegra/vic.c b/drivers/gpu/drm/tegra/vic.c index 332c9b563d3f..fe097e967551 100644 --- a/drivers/gpu/drm/tegra/vic.c +++ b/drivers/gpu/drm/tegra/vic.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -20,10 +21,16 @@ #include "falcon.h" #include "vic.h" +#define VIC_FALCON_DEBUGINFO 0x1094 +#define VIC_DEBUGINFO_DUMMY 0xabcd1234 +#define VIC_DEBUGINFO_CLEAR 0x0 + struct vic_config { const char *firmware; unsigned int version; bool supports_sid; + bool has_riscv; + unsigned int transcfg_offset; }; struct vic { @@ -54,8 +61,8 @@ static void vic_writel(struct vic *vic, u32 value, unsigned int offset) static int vic_boot(struct vic *vic) { - u32 fce_ucode_size, fce_bin_data_offset, stream_id; - void *hdr; + u32 stream_id; + u32 val; int err = 0; if (vic->config->supports_sid && tegra_dev_iommu_get_stream_id(vic->dev, &stream_id)) { @@ -63,7 +70,7 @@ static int vic_boot(struct vic *vic) value = TRANSCFG_ATT(1, TRANSCFG_SID_FALCON) | TRANSCFG_ATT(0, TRANSCFG_SID_HW); - vic_writel(vic, value, VIC_TFBIF_TRANSCFG); + vic_writel(vic, value, vic->config->transcfg_offset); /* * STREAMID0 is used for input/output buffers. Initialize it to SID_VIC in case @@ -85,31 +92,51 @@ static int vic_boot(struct vic *vic) CG_WAKEUP_DLY_CNT(4), NV_PVIC_MISC_PRI_VIC_CG); + if (vic->config->has_riscv) { + /* Write a known pattern into DEBUGINFO register */ + vic_writel(vic, VIC_DEBUGINFO_DUMMY, VIC_FALCON_DEBUGINFO); + } + err = falcon_boot(&vic->falcon); if (err < 0) return err; - hdr = vic->falcon.firmware.virt; - fce_bin_data_offset = *(u32 *)(hdr + VIC_UCODE_FCE_DATA_OFFSET); + if (vic->config->has_riscv) { + /* Check VIC has reached a proper initialized state */ + err = readl_poll_timeout(vic->regs + VIC_FALCON_DEBUGINFO, val, + val == VIC_DEBUGINFO_CLEAR, + 1000, 2000000); + if (err) { + dev_err(vic->dev, "VIC not initialized, timeout, val=0x%x\n", val); + return err; + } + } else { + u32 fce_ucode_size, fce_bin_data_offset; + dma_addr_t iova; + void *hdr; - /* Old VIC firmware needs kernel help with setting up FCE microcode. */ - if (fce_bin_data_offset != 0x0 && fce_bin_data_offset != 0xa5a5a5a5) { - hdr = vic->falcon.firmware.virt + - *(u32 *)(hdr + VIC_UCODE_FCE_HEADER_OFFSET); - fce_ucode_size = *(u32 *)(hdr + FCE_UCODE_SIZE_OFFSET); + iova = vic->falcon.firmware.iova; + hdr = vic->falcon.firmware.virt; + fce_bin_data_offset = *(u32 *)(hdr + VIC_UCODE_FCE_DATA_OFFSET); - falcon_execute_method(&vic->falcon, VIC_SET_FCE_UCODE_SIZE, - fce_ucode_size); - falcon_execute_method( - &vic->falcon, VIC_SET_FCE_UCODE_OFFSET, - (vic->falcon.firmware.iova + fce_bin_data_offset) >> 8); - } + /* Old VIC firmware needs kernel help with setting up FCE microcode. */ + if (fce_bin_data_offset != 0x0 && fce_bin_data_offset != 0xa5a5a5a5) { + hdr = vic->falcon.firmware.virt + + *(u32 *)(hdr + VIC_UCODE_FCE_HEADER_OFFSET); + fce_ucode_size = *(u32 *)(hdr + FCE_UCODE_SIZE_OFFSET); - err = falcon_wait_idle(&vic->falcon); - if (err < 0) { - dev_err(vic->dev, - "failed to set application ID and FCE base\n"); - return err; + falcon_execute_method(&vic->falcon, VIC_SET_FCE_UCODE_SIZE, + fce_ucode_size); + falcon_execute_method(&vic->falcon, VIC_SET_FCE_UCODE_OFFSET, + (iova + fce_bin_data_offset) >> 8); + } + + err = falcon_wait_idle(&vic->falcon); + if (err < 0) { + dev_err(vic->dev, + "failed to set application ID and FCE base\n"); + return err; + } } return 0; @@ -277,6 +304,8 @@ static int vic_load_firmware(struct vic *vic) if (!vic->config->supports_sid) { vic->can_use_context = false; + } else if (vic->config->has_riscv) { + vic->can_use_context = true; } else if (fce_bin_data_offset != 0x0 && fce_bin_data_offset != 0xa5a5a5a5) { /* * Firmware will access FCE through STREAMID0, so context @@ -302,7 +331,6 @@ static int vic_load_firmware(struct vic *vic) return err; } - static int __maybe_unused vic_runtime_resume(struct device *dev) { struct vic *vic = dev_get_drvdata(dev); @@ -417,6 +445,7 @@ static const struct vic_config vic_t186_config = { .firmware = NVIDIA_TEGRA_186_VIC_FIRMWARE, .version = 0x18, .supports_sid = true, + .transcfg_offset = 0x2044, }; #define NVIDIA_TEGRA_194_VIC_FIRMWARE "nvidia/tegra194/vic.bin" @@ -425,6 +454,7 @@ static const struct vic_config vic_t194_config = { .firmware = NVIDIA_TEGRA_194_VIC_FIRMWARE, .version = 0x19, .supports_sid = true, + .transcfg_offset = 0x2044, }; #define NVIDIA_TEGRA_234_VIC_FIRMWARE "nvidia/tegra234/vic.bin" @@ -433,6 +463,18 @@ static const struct vic_config vic_t234_config = { .firmware = NVIDIA_TEGRA_234_VIC_FIRMWARE, .version = 0x23, .supports_sid = true, + .transcfg_offset = 0x2044, +}; + +#define NVIDIA_TEGRA_264_VIC_FIRMWARE "nvidia/tegra264/vic.bin" +#define NVIDIA_TEGRA_264_VIC_DESC "nvidia/tegra264/vic.bin.desc" + +static const struct vic_config vic_t264_config = { + .firmware = NVIDIA_TEGRA_264_VIC_FIRMWARE, + .version = 0x264, + .supports_sid = true, + .has_riscv = true, + .transcfg_offset = 0x2244, }; static const struct of_device_id tegra_vic_of_match[] = { @@ -441,6 +483,7 @@ static const struct of_device_id tegra_vic_of_match[] = { { .compatible = "nvidia,tegra186-vic", .data = &vic_t186_config }, { .compatible = "nvidia,tegra194-vic", .data = &vic_t194_config }, { .compatible = "nvidia,tegra234-vic", .data = &vic_t234_config }, + { .compatible = "nvidia,tegra264-vic", .data = &vic_t264_config }, { }, }; MODULE_DEVICE_TABLE(of, tegra_vic_of_match); @@ -495,6 +538,7 @@ static int vic_probe(struct platform_device *pdev) vic->falcon.dev = dev; vic->falcon.regs = vic->regs; + vic->falcon.riscv = vic->config->has_riscv; err = falcon_init(&vic->falcon); if (err < 0) @@ -571,3 +615,7 @@ MODULE_FIRMWARE(NVIDIA_TEGRA_194_VIC_FIRMWARE); #if IS_ENABLED(CONFIG_ARCH_TEGRA_234_SOC) MODULE_FIRMWARE(NVIDIA_TEGRA_234_VIC_FIRMWARE); #endif +#if IS_ENABLED(CONFIG_ARCH_TEGRA_264_SOC) +MODULE_FIRMWARE(NVIDIA_TEGRA_264_VIC_FIRMWARE); +MODULE_FIRMWARE(NVIDIA_TEGRA_264_VIC_DESC); +#endif diff --git a/drivers/gpu/drm/tegra/vic.h b/drivers/gpu/drm/tegra/vic.h index acf35aac948b..aca98a09c9fb 100644 --- a/drivers/gpu/drm/tegra/vic.h +++ b/drivers/gpu/drm/tegra/vic.h @@ -21,11 +21,10 @@ #define CG_IDLE_CG_EN (1 << 6) #define CG_WAKEUP_DLY_CNT(val) ((val & 0xf) << 16) -#define VIC_TFBIF_TRANSCFG 0x00002044 -#define TRANSCFG_ATT(i, v) (((v) & 0x3) << (i * 4)) -#define TRANSCFG_SID_HW 0 -#define TRANSCFG_SID_PHY 1 -#define TRANSCFG_SID_FALCON 2 +#define TRANSCFG_ATT(i, v) (((v) & 0x3) << ((i) * 4)) +#define TRANSCFG_SID_HW 0 +#define TRANSCFG_SID_PHY 1 +#define TRANSCFG_SID_FALCON 2 /* Firmware offsets */ From e52916643c1f6be6004d377367b6e7240392d50f Mon Sep 17 00:00:00 2001 From: Diogo Silva Date: Tue, 23 Jun 2026 21:56:48 +0200 Subject: [PATCH 14/46] drm/gma500: cdv_intel_dp: fix indentation to use tabs instead of spaces The kernel coding style specifies that tabs should be used instead of spaces, which the cdv_intel_dp driver was not using in some places. This patch replaces the space indentations with tabs. Signed-off-by: Diogo Silva Reviewed-by: Thomas Zimmermann Signed-off-by: Thomas Zimmermann Link: https://patch.msgid.link/20260623-cdv_intel_dp_indentation-v1-1-bcb61da38f17@gmail.com --- drivers/gpu/drm/gma500/cdv_intel_dp.c | 111 +++++++++++++------------- 1 file changed, 56 insertions(+), 55 deletions(-) diff --git a/drivers/gpu/drm/gma500/cdv_intel_dp.c b/drivers/gpu/drm/gma500/cdv_intel_dp.c index fbed35cf7603..7d08011d9663 100644 --- a/drivers/gpu/drm/gma500/cdv_intel_dp.c +++ b/drivers/gpu/drm/gma500/cdv_intel_dp.c @@ -232,16 +232,17 @@ i2c_dp_aux_add_bus(struct i2c_adapter *adapter) } #define _wait_for(COND, MS, W) ({ \ - unsigned long timeout__ = jiffies + msecs_to_jiffies(MS); \ - int ret__ = 0; \ - while (! (COND)) { \ - if (time_after(jiffies, timeout__)) { \ - ret__ = -ETIMEDOUT; \ - break; \ - } \ - if (W && !in_dbg_master()) msleep(W); \ - } \ - ret__; \ + unsigned long timeout__ = jiffies + msecs_to_jiffies(MS); \ + int ret__ = 0; \ + while (!(COND)) { \ + if (time_after(jiffies, timeout__)) { \ + ret__ = -ETIMEDOUT; \ + break; \ + } \ + if (W && !in_dbg_master()) \ + msleep(W); \ + } \ + ret__; \ }) #define wait_for(COND, MS) _wait_for(COND, MS, 1) @@ -296,10 +297,10 @@ static struct ddi_regoff ddi_DP_train_table[] = { }; static uint32_t dp_vswing_premph_table[] = { - 0x55338954, 0x4000, - 0x554d8954, 0x2000, - 0x55668954, 0, - 0x559ac0d4, 0x6000, + 0x55338954, 0x4000, + 0x554d8954, 0x2000, + 0x55668954, 0, + 0x559ac0d4, 0x6000, }; /** * is_edp - is the given port attached to an eDP panel (either CPU or PCH) @@ -1141,7 +1142,7 @@ static void cdv_intel_dp_prepare(struct drm_encoder *encoder) cdv_intel_edp_backlight_off(intel_encoder); cdv_intel_edp_panel_off(intel_encoder); cdv_intel_edp_panel_vdd_on(intel_encoder); - } + } /* Wake up the sink first */ cdv_intel_dp_sink_dpms(intel_encoder, DRM_MODE_DPMS_ON); cdv_intel_dp_link_down(intel_encoder); @@ -1183,7 +1184,7 @@ cdv_intel_dp_dpms(struct drm_encoder *encoder, int mode) cdv_intel_edp_panel_off(intel_encoder); } } else { - if (edp) + if (edp) cdv_intel_edp_panel_on(intel_encoder); cdv_intel_dp_sink_dpms(intel_encoder, mode); if (!(dp_reg & DP_PORT_EN)) { @@ -1191,7 +1192,7 @@ cdv_intel_dp_dpms(struct drm_encoder *encoder, int mode) cdv_intel_dp_complete_link_train(intel_encoder); } if (edp) - cdv_intel_edp_backlight_on(intel_encoder); + cdv_intel_edp_backlight_on(intel_encoder); } } @@ -1419,8 +1420,8 @@ cdv_intel_dp_set_vswing_premph(struct gma_encoder *encoder, uint8_t signal_level DRM_DEBUG_KMS("Test2\n"); //return ; cdv_sb_reset(dev); - /* ;Swing voltage programming - ;gfx_dpio_set_reg(0xc058, 0x0505313A) */ + /* ;Swing voltage programming */ + /* ;gfx_dpio_set_reg(0xc058, 0x0505313A) */ cdv_sb_write(dev, ddi_reg->VSwing5, 0x0505313A); /* ;gfx_dpio_set_reg(0x8154, 0x43406055) */ @@ -1575,7 +1576,7 @@ cdv_intel_dp_complete_link_train(struct gma_encoder *encoder) intel_dp->train_set[0], intel_dp->link_configuration[0], intel_dp->link_configuration[1]); - /* channel eq pattern */ + /* channel eq pattern */ if (!cdv_intel_dp_set_link_train(encoder, reg, DP_TRAINING_PATTERN_2)) { @@ -1704,7 +1705,7 @@ cdv_intel_dp_detect(struct drm_connector *connector, bool force) if (edp) cdv_intel_edp_panel_vdd_off(encoder); return status; - } + } if (intel_dp->force_audio) { intel_dp->has_audio = intel_dp->force_audio > 0; @@ -1956,12 +1957,12 @@ cdv_intel_dp_init(struct drm_device *dev, struct psb_intel_mode_device *mode_dev gma_encoder = kzalloc_obj(struct gma_encoder); if (!gma_encoder) return; - gma_connector = kzalloc_obj(struct gma_connector); - if (!gma_connector) - goto err_connector; + gma_connector = kzalloc_obj(struct gma_connector); + if (!gma_connector) + goto err_connector; intel_dp = kzalloc_obj(struct cdv_intel_dp); if (!intel_dp) - goto err_priv; + goto err_priv; if ((output_reg == DP_C) && cdv_intel_dpc_is_edp(dev)) type = DRM_MODE_CONNECTOR_eDP; @@ -1976,7 +1977,7 @@ cdv_intel_dp_init(struct drm_device *dev, struct psb_intel_mode_device *mode_dev if (type == DRM_MODE_CONNECTOR_DisplayPort) gma_encoder->type = INTEL_OUTPUT_DISPLAYPORT; - else + else gma_encoder->type = INTEL_OUTPUT_EDP; @@ -2006,18 +2007,18 @@ cdv_intel_dp_init(struct drm_device *dev, struct psb_intel_mode_device *mode_dev cdv_disable_intel_clock_gating(dev); cdv_intel_dp_i2c_init(gma_connector, gma_encoder, name); - /* FIXME:fail check */ + /* FIXME:fail check */ cdv_intel_dp_add_properties(connector); if (is_edp(gma_encoder)) { int ret; struct edp_power_seq cur; - u32 pp_on, pp_off, pp_div; + u32 pp_on, pp_off, pp_div; u32 pwm_ctrl; pp_on = REG_READ(PP_CONTROL); pp_on &= ~PANEL_UNLOCK_MASK; - pp_on |= PANEL_UNLOCK_REGS; + pp_on |= PANEL_UNLOCK_REGS; REG_WRITE(PP_CONTROL, pp_on); @@ -2025,42 +2026,42 @@ cdv_intel_dp_init(struct drm_device *dev, struct psb_intel_mode_device *mode_dev pwm_ctrl |= PWM_PIPE_B; REG_WRITE(BLC_PWM_CTL2, pwm_ctrl); - pp_on = REG_READ(PP_ON_DELAYS); - pp_off = REG_READ(PP_OFF_DELAYS); - pp_div = REG_READ(PP_DIVISOR); + pp_on = REG_READ(PP_ON_DELAYS); + pp_off = REG_READ(PP_OFF_DELAYS); + pp_div = REG_READ(PP_DIVISOR); /* Pull timing values out of registers */ - cur.t1_t3 = (pp_on & PANEL_POWER_UP_DELAY_MASK) >> - PANEL_POWER_UP_DELAY_SHIFT; + cur.t1_t3 = (pp_on & PANEL_POWER_UP_DELAY_MASK) >> + PANEL_POWER_UP_DELAY_SHIFT; - cur.t8 = (pp_on & PANEL_LIGHT_ON_DELAY_MASK) >> - PANEL_LIGHT_ON_DELAY_SHIFT; + cur.t8 = (pp_on & PANEL_LIGHT_ON_DELAY_MASK) >> + PANEL_LIGHT_ON_DELAY_SHIFT; - cur.t9 = (pp_off & PANEL_LIGHT_OFF_DELAY_MASK) >> - PANEL_LIGHT_OFF_DELAY_SHIFT; + cur.t9 = (pp_off & PANEL_LIGHT_OFF_DELAY_MASK) >> + PANEL_LIGHT_OFF_DELAY_SHIFT; - cur.t10 = (pp_off & PANEL_POWER_DOWN_DELAY_MASK) >> - PANEL_POWER_DOWN_DELAY_SHIFT; + cur.t10 = (pp_off & PANEL_POWER_DOWN_DELAY_MASK) >> + PANEL_POWER_DOWN_DELAY_SHIFT; - cur.t11_t12 = ((pp_div & PANEL_POWER_CYCLE_DELAY_MASK) >> - PANEL_POWER_CYCLE_DELAY_SHIFT); + cur.t11_t12 = ((pp_div & PANEL_POWER_CYCLE_DELAY_MASK) >> + PANEL_POWER_CYCLE_DELAY_SHIFT); - DRM_DEBUG_KMS("cur t1_t3 %d t8 %d t9 %d t10 %d t11_t12 %d\n", - cur.t1_t3, cur.t8, cur.t9, cur.t10, cur.t11_t12); + DRM_DEBUG_KMS("cur t1_t3 %d t8 %d t9 %d t10 %d t11_t12 %d\n", + cur.t1_t3, cur.t8, cur.t9, cur.t10, cur.t11_t12); intel_dp->panel_power_up_delay = cur.t1_t3 / 10; - intel_dp->backlight_on_delay = cur.t8 / 10; - intel_dp->backlight_off_delay = cur.t9 / 10; - intel_dp->panel_power_down_delay = cur.t10 / 10; - intel_dp->panel_power_cycle_delay = (cur.t11_t12 - 1) * 100; + intel_dp->backlight_on_delay = cur.t8 / 10; + intel_dp->backlight_off_delay = cur.t9 / 10; + intel_dp->panel_power_down_delay = cur.t10 / 10; + intel_dp->panel_power_cycle_delay = (cur.t11_t12 - 1) * 100; - DRM_DEBUG_KMS("panel power up delay %d, power down delay %d, power cycle delay %d\n", - intel_dp->panel_power_up_delay, intel_dp->panel_power_down_delay, - intel_dp->panel_power_cycle_delay); + DRM_DEBUG_KMS("panel power up delay %d, power down delay %d, power cycle delay %d\n", + intel_dp->panel_power_up_delay, intel_dp->panel_power_down_delay, + intel_dp->panel_power_cycle_delay); - DRM_DEBUG_KMS("backlight on delay %d, off delay %d\n", - intel_dp->backlight_on_delay, intel_dp->backlight_off_delay); + DRM_DEBUG_KMS("backlight on delay %d, off delay %d\n", + intel_dp->backlight_on_delay, intel_dp->backlight_off_delay); cdv_intel_edp_panel_vdd_on(gma_encoder); @@ -2075,7 +2076,7 @@ cdv_intel_dp_init(struct drm_device *dev, struct psb_intel_mode_device *mode_dev cdv_intel_dp_destroy(connector); goto err_connector; } else { - DRM_DEBUG_KMS("DPCD: Rev=%x LN_Rate=%x LN_CNT=%x LN_DOWNSP=%x\n", + DRM_DEBUG_KMS("DPCD: Rev=%x LN_Rate=%x LN_CNT=%x LN_DOWNSP=%x\n", intel_dp->dpcd[0], intel_dp->dpcd[1], intel_dp->dpcd[2], intel_dp->dpcd[3]); @@ -2083,7 +2084,7 @@ cdv_intel_dp_init(struct drm_device *dev, struct psb_intel_mode_device *mode_dev /* The CDV reference driver moves pnale backlight setup into the displays that have a backlight: this is a good idea and one we should probably adopt, however we need to migrate all the drivers before we can do that */ - /*cdv_intel_panel_setup_backlight(dev); */ + /*cdv_intel_panel_setup_backlight(dev); */ } return; From 6143359440d41d4c571435c9cd2fbaf9f8dc2788 Mon Sep 17 00:00:00 2001 From: Diogo Silva Date: Tue, 23 Jun 2026 21:38:33 +0200 Subject: [PATCH 15/46] drm/gma500: Remove dependency on DRM simple helpers Simple KMS helper are deprecated since they only add an intermediate layer between drivers and the atomic modesetting. This patch removes the dependency on drm simple helpers from gma500 DRM drivers. Signed-off-by: Diogo Silva Reviewed-by: Thomas Zimmermann Signed-off-by: Thomas Zimmermann Link: https://patch.msgid.link/20260623-gma-drm-simple-v1-1-c404a5e62aab@gmail.com --- drivers/gpu/drm/gma500/cdv_intel_crt.c | 9 +++++++-- drivers/gpu/drm/gma500/cdv_intel_dp.c | 9 +++++++-- drivers/gpu/drm/gma500/cdv_intel_hdmi.c | 10 +++++++--- drivers/gpu/drm/gma500/cdv_intel_lvds.c | 9 +++++++-- drivers/gpu/drm/gma500/oaktrail_hdmi.c | 9 +++++++-- drivers/gpu/drm/gma500/oaktrail_lvds.c | 9 +++++++-- drivers/gpu/drm/gma500/psb_intel_lvds.c | 9 +++++++-- 7 files changed, 49 insertions(+), 15 deletions(-) diff --git a/drivers/gpu/drm/gma500/cdv_intel_crt.c b/drivers/gpu/drm/gma500/cdv_intel_crt.c index 342a57c82846..e51a965ddeb8 100644 --- a/drivers/gpu/drm/gma500/cdv_intel_crt.c +++ b/drivers/gpu/drm/gma500/cdv_intel_crt.c @@ -29,8 +29,8 @@ #include #include +#include #include -#include #include "cdv_device.h" #include "intel_bios.h" @@ -217,6 +217,10 @@ static int cdv_intel_crt_set_property(struct drm_connector *connector, * Routines for controlling stuff on the analog port */ +static const struct drm_encoder_funcs cdv_intel_crt_funcs = { + .destroy = drm_encoder_cleanup, +}; + static const struct drm_encoder_helper_funcs cdv_intel_crt_helper_funcs = { .dpms = cdv_intel_crt_dpms, .prepare = gma_encoder_prepare, @@ -275,7 +279,8 @@ void cdv_intel_crt_init(struct drm_device *dev, goto err_ddc_destroy; encoder = &gma_encoder->base; - ret = drm_simple_encoder_init(dev, encoder, DRM_MODE_ENCODER_DAC); + ret = drm_encoder_init(dev, encoder, &cdv_intel_crt_funcs, + DRM_MODE_ENCODER_DAC, NULL); if (ret) goto err_connector_cleanup; diff --git a/drivers/gpu/drm/gma500/cdv_intel_dp.c b/drivers/gpu/drm/gma500/cdv_intel_dp.c index 7d08011d9663..8d0626ddf1c3 100644 --- a/drivers/gpu/drm/gma500/cdv_intel_dp.c +++ b/drivers/gpu/drm/gma500/cdv_intel_dp.c @@ -33,9 +33,9 @@ #include #include #include +#include #include #include -#include #include "gma_display.h" #include "psb_drv.h" @@ -1873,6 +1873,10 @@ cdv_intel_dp_destroy(struct drm_connector *connector) kfree(gma_connector); } +static const struct drm_encoder_funcs cdv_intel_dp_funcs = { + .destroy = drm_encoder_cleanup, +}; + static const struct drm_encoder_helper_funcs cdv_intel_dp_helper_funcs = { .dpms = cdv_intel_dp_dpms, .mode_fixup = cdv_intel_dp_mode_fixup, @@ -1971,7 +1975,8 @@ cdv_intel_dp_init(struct drm_device *dev, struct psb_intel_mode_device *mode_dev encoder = &gma_encoder->base; drm_connector_init(dev, connector, &cdv_intel_dp_connector_funcs, type); - drm_simple_encoder_init(dev, encoder, DRM_MODE_ENCODER_TMDS); + drm_encoder_init(dev, encoder, &cdv_intel_dp_funcs, + DRM_MODE_ENCODER_TMDS, NULL); gma_connector_attach_encoder(gma_connector, gma_encoder); diff --git a/drivers/gpu/drm/gma500/cdv_intel_hdmi.c b/drivers/gpu/drm/gma500/cdv_intel_hdmi.c index ce7850647778..757ff9408250 100644 --- a/drivers/gpu/drm/gma500/cdv_intel_hdmi.c +++ b/drivers/gpu/drm/gma500/cdv_intel_hdmi.c @@ -30,9 +30,9 @@ #include #include #include +#include #include #include -#include #include "cdv_device.h" #include "psb_drv.h" @@ -251,6 +251,10 @@ static void cdv_hdmi_destroy(struct drm_connector *connector) kfree(gma_connector); } +static const struct drm_encoder_funcs cdv_hdmi_funcs = { + .destroy = drm_encoder_cleanup, +}; + static const struct drm_encoder_helper_funcs cdv_hdmi_helper_funcs = { .dpms = cdv_hdmi_dpms, .prepare = gma_encoder_prepare, @@ -329,8 +333,8 @@ void cdv_hdmi_init(struct drm_device *dev, if (ret) goto err_ddc_destroy; - ret = drm_simple_encoder_init(dev, &gma_encoder->base, - DRM_MODE_ENCODER_TMDS); + ret = drm_encoder_init(dev, &gma_encoder->base, &cdv_hdmi_funcs, + DRM_MODE_ENCODER_TMDS, NULL); if (ret) goto err_connector_cleanup; diff --git a/drivers/gpu/drm/gma500/cdv_intel_lvds.c b/drivers/gpu/drm/gma500/cdv_intel_lvds.c index d7fd9a783cde..336ab411d699 100644 --- a/drivers/gpu/drm/gma500/cdv_intel_lvds.c +++ b/drivers/gpu/drm/gma500/cdv_intel_lvds.c @@ -13,9 +13,9 @@ #include #include +#include #include #include -#include #include "cdv_device.h" #include "intel_bios.h" @@ -394,6 +394,10 @@ static int cdv_intel_lvds_set_property(struct drm_connector *connector, return 0; } +static const struct drm_encoder_funcs cdv_intel_lvds_funcs = { + .destroy = drm_encoder_cleanup, +}; + static const struct drm_encoder_helper_funcs cdv_intel_lvds_helper_funcs = { .dpms = cdv_intel_lvds_encoder_dpms, @@ -535,7 +539,8 @@ void cdv_intel_lvds_init(struct drm_device *dev, if (ret) goto err_destroy_ddc; - ret = drm_simple_encoder_init(dev, encoder, DRM_MODE_ENCODER_LVDS); + ret = drm_encoder_init(dev, encoder, &cdv_intel_lvds_funcs, + DRM_MODE_ENCODER_LVDS, NULL); if (ret) goto err_connector_cleanup; diff --git a/drivers/gpu/drm/gma500/oaktrail_hdmi.c b/drivers/gpu/drm/gma500/oaktrail_hdmi.c index 403d21cbb3a2..5a6132176086 100644 --- a/drivers/gpu/drm/gma500/oaktrail_hdmi.c +++ b/drivers/gpu/drm/gma500/oaktrail_hdmi.c @@ -29,9 +29,9 @@ #include #include #include +#include #include #include -#include #include "psb_drv.h" #include "psb_intel_drv.h" @@ -605,6 +605,10 @@ static void oaktrail_hdmi_destroy(struct drm_connector *connector) return; } +static const struct drm_encoder_funcs oaktrail_hdmi_funcs = { + .destroy = drm_encoder_cleanup, +}; + static const struct drm_encoder_helper_funcs oaktrail_hdmi_helper_funcs = { .dpms = oaktrail_hdmi_dpms, .prepare = gma_encoder_prepare, @@ -648,7 +652,8 @@ void oaktrail_hdmi_init(struct drm_device *dev, &oaktrail_hdmi_connector_funcs, DRM_MODE_CONNECTOR_DVID); - drm_simple_encoder_init(dev, encoder, DRM_MODE_ENCODER_TMDS); + drm_encoder_init(dev, encoder, &oaktrail_hdmi_funcs, + DRM_MODE_ENCODER_TMDS, NULL); gma_connector_attach_encoder(gma_connector, gma_encoder); diff --git a/drivers/gpu/drm/gma500/oaktrail_lvds.c b/drivers/gpu/drm/gma500/oaktrail_lvds.c index e194d0cce067..425796d8d6c3 100644 --- a/drivers/gpu/drm/gma500/oaktrail_lvds.c +++ b/drivers/gpu/drm/gma500/oaktrail_lvds.c @@ -12,9 +12,9 @@ #include #include +#include #include #include -#include #include "intel_bios.h" #include "power.h" @@ -202,6 +202,10 @@ static void oaktrail_lvds_commit(struct drm_encoder *encoder) oaktrail_lvds_set_power(dev, gma_encoder, true); } +static const struct drm_encoder_funcs oaktrail_lvds_funcs = { + .destroy = drm_encoder_cleanup, +}; + static const struct drm_encoder_helper_funcs oaktrail_lvds_helper_funcs = { .dpms = oaktrail_lvds_dpms, .mode_fixup = psb_intel_lvds_mode_fixup, @@ -319,7 +323,8 @@ void oaktrail_lvds_init(struct drm_device *dev, if (ret) goto err_free_connector; - ret = drm_simple_encoder_init(dev, encoder, DRM_MODE_ENCODER_LVDS); + ret = drm_encoder_init(dev, encoder, &oaktrail_lvds_funcs, + DRM_MODE_ENCODER_LVDS, NULL); if (ret) goto err_connector_cleanup; diff --git a/drivers/gpu/drm/gma500/psb_intel_lvds.c b/drivers/gpu/drm/gma500/psb_intel_lvds.c index 2ca164b21293..cd882786d946 100644 --- a/drivers/gpu/drm/gma500/psb_intel_lvds.c +++ b/drivers/gpu/drm/gma500/psb_intel_lvds.c @@ -12,9 +12,9 @@ #include #include +#include #include #include -#include #include "intel_bios.h" #include "power.h" @@ -593,6 +593,10 @@ int psb_intel_lvds_set_property(struct drm_connector *connector, return -1; } +static const struct drm_encoder_funcs psb_intel_lvds_funcs = { + .destroy = drm_encoder_cleanup, +}; + static const struct drm_encoder_helper_funcs psb_intel_lvds_helper_funcs = { .dpms = psb_intel_lvds_encoder_dpms, .mode_fixup = psb_intel_lvds_mode_fixup, @@ -679,7 +683,8 @@ void psb_intel_lvds_init(struct drm_device *dev, if (ret) goto err_ddc_destroy; - ret = drm_simple_encoder_init(dev, encoder, DRM_MODE_ENCODER_LVDS); + ret = drm_encoder_init(dev, encoder, &psb_intel_lvds_funcs, + DRM_MODE_ENCODER_LVDS, NULL); if (ret) goto err_connector_cleanup; From 5b9af25f2d0b574b4058c9f8c84486a597168116 Mon Sep 17 00:00:00 2001 From: Andrzej Kacprowski Date: Thu, 16 Jul 2026 10:33:50 +0200 Subject: [PATCH 16/46] accel/ivpu: Remove unnecessary min_t()/max_t() usage Remove unnecessary min_t()/max_t() usage in ivpu_fw.c and ivpu_mmu_context.c. The min()/max() macros are sufficient as the types are compatible and there is no risk of overflow. Signed-off-by: Andrzej Kacprowski Reviewed-by: Karol Wachowski Signed-off-by: Karol Wachowski Link: https://patch.msgid.link/20260716083350.248419-1-andrzej.kacprowski@linux.intel.com --- drivers/accel/ivpu/ivpu_fw.c | 2 +- drivers/accel/ivpu/ivpu_mmu_context.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/accel/ivpu/ivpu_fw.c b/drivers/accel/ivpu/ivpu_fw.c index 5a8f7dfb54e6..b78b7c35cd9b 100644 --- a/drivers/accel/ivpu/ivpu_fw.c +++ b/drivers/accel/ivpu/ivpu_fw.c @@ -318,7 +318,7 @@ static int ivpu_fw_parse(struct ivpu_device *vdev) fw->shave_nn_size = PAGE_ALIGN(fw_hdr->shave_nn_fw_size); fw->cold_boot_entry_point = fw_hdr->entry_point; - fw->trace_level = min_t(u32, ivpu_fw_log_level, IVPU_FW_LOG_FATAL); + fw->trace_level = min(ivpu_fw_log_level, IVPU_FW_LOG_FATAL); fw->trace_destination_mask = VPU_TRACE_DESTINATION_VERBOSE_TRACING; fw->trace_hw_component_mask = -1; diff --git a/drivers/accel/ivpu/ivpu_mmu_context.c b/drivers/accel/ivpu/ivpu_mmu_context.c index c4014c83e727..bb3ca81838e3 100644 --- a/drivers/accel/ivpu/ivpu_mmu_context.c +++ b/drivers/accel/ivpu/ivpu_mmu_context.c @@ -588,8 +588,8 @@ void ivpu_mmu_context_init(struct ivpu_device *vdev, struct ivpu_mmu_context *ct start = vdev->hw->ranges.runtime.start; end = vdev->hw->ranges.shave.end; } else { - start = min_t(u64, vdev->hw->ranges.user.start, vdev->hw->ranges.shave.start); - end = max_t(u64, vdev->hw->ranges.user.end, vdev->hw->ranges.dma.end); + start = min(vdev->hw->ranges.user.start, vdev->hw->ranges.shave.start); + end = max(vdev->hw->ranges.user.end, vdev->hw->ranges.dma.end); } drm_mm_init(&ctx->mm, start, end - start); From b0a652436b892eb9a036a031b33099dca036faaa Mon Sep 17 00:00:00 2001 From: Markus Elfring Date: Wed, 25 Mar 2026 14:55:01 +0100 Subject: [PATCH 17/46] drm/nouveau: Omit a redundant pm_runtime_mark_last_busy() call in nouveau_pmops_runtime_idle() The device's last busy timestamp was set in a wrapper function since the commit 08071e64cb642ae19ebd6ffeb13b4f3d130b5860 ("PM: runtime: Mark last busy stamp in pm_runtime_autosuspend()"). Thus delete a pm_runtime_mark_last_busy() call before a pm_runtime_autosuspend() call. The source code was transformed by using the Coccinelle software. Signed-off-by: Markus Elfring Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/e3631a8e-b4a4-4e14-822d-2199f1576cc9@web.de --- drivers/gpu/drm/nouveau/nouveau_drm.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nouveau_drm.c b/drivers/gpu/drm/nouveau/nouveau_drm.c index 7ea95ab960a0..4d1ad718e09b 100644 --- a/drivers/gpu/drm/nouveau/nouveau_drm.c +++ b/drivers/gpu/drm/nouveau/nouveau_drm.c @@ -1209,7 +1209,6 @@ nouveau_pmops_runtime_idle(struct device *dev) return -EBUSY; } - pm_runtime_mark_last_busy(dev); pm_runtime_autosuspend(dev); /* we don't want the main rpm_idle to call suspend - we want to autosuspend */ return 1; From fa98563ab00dbe62fcedfef6bdd34ded7a860d9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ADra=20Canal?= Date: Fri, 10 Jul 2026 08:43:27 -0300 Subject: [PATCH 18/46] drm/v3d: Associate BOs with every job that accesses them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A submission can expand into a chain of jobs (e.g. bin + render + cache clean). Implicit synchronization in v3d_submit_lock_reservations() is gated on each job's bo[], but the BO list was only ever attached to the last job of the chain. When that last job is a trailing CACHE_CLEAN job, the job that actually consumes the BOs (that is, a RENDER or CSD job) was left with bo_count == 0 and picked up no implicit dependencies. It could therefore be dispatched to the hardware and read a BO while another context was still writing it, leading to data corruption. Attach the BOs to the job that consumes them, so (1) it acquires the correct implicit dependencies during reservation locking and (2) they are kept mapped until the end of the submission. Give it references to all consuming job's BOs through v3d_job_reference_bos() instead of looking the handles up a second time; that avoids a redundant lookup and guarantees both jobs reference the exact same objects. As the CACHE_CLEAN job now carries a BO array as well, add a per-job `has_implicit_dep` flag so that only the consuming jobs take implicit dependencies. The CACHE_CLEAN job (a global flush) and the BIN job (binning waiting on another context is not a realistic scenario) are excluded. Fixes: dffa9b7a78c4 ("drm/v3d: Add missing implicit synchronization.") Reviewed-by: Iago Toral Quiroga Link: https://patch.msgid.link/20260710114734.2731000-1-mcanal@igalia.com Signed-off-by: Maíra Canal --- drivers/gpu/drm/v3d/v3d_drv.h | 5 ++ drivers/gpu/drm/v3d/v3d_submit.c | 92 +++++++++++++++++++++++--------- 2 files changed, 72 insertions(+), 25 deletions(-) diff --git a/drivers/gpu/drm/v3d/v3d_drv.h b/drivers/gpu/drm/v3d/v3d_drv.h index 1b88b3dff757..8c46ed09f5c4 100644 --- a/drivers/gpu/drm/v3d/v3d_drv.h +++ b/drivers/gpu/drm/v3d/v3d_drv.h @@ -375,6 +375,11 @@ struct v3d_job { void (*free)(struct kref *ref); bool has_pm_ref; + + /* Whether the job needs implicit dependencies, i.e. must wait for + * other contexts still writing its BOs. + */ + bool has_implicit_dep; }; struct v3d_bin_job { diff --git a/drivers/gpu/drm/v3d/v3d_submit.c b/drivers/gpu/drm/v3d/v3d_submit.c index 5c05f1ea24bc..623df9d5bbee 100644 --- a/drivers/gpu/drm/v3d/v3d_submit.c +++ b/drivers/gpu/drm/v3d/v3d_submit.c @@ -46,6 +46,9 @@ v3d_submit_lock_reservations(struct v3d_submit *submit) for (i = 0; i < submit->job_count; i++) { struct v3d_job *job = submit->jobs[i]; + if (!job->has_implicit_dep) + continue; + for (j = 0; j < job->bo_count; j++) { ret = drm_sched_job_add_implicit_dependencies(&job->base, job->bo[j], @@ -71,7 +74,6 @@ v3d_submit_unlock_reservations(struct v3d_submit *submit) /** * v3d_lookup_bos() - Sets up job->bo[] with the GEM objects * referenced by the job. - * @dev: DRM device * @file_priv: DRM file for this fd * @job: V3D job being set up * @bo_handles: GEM handles @@ -85,23 +87,44 @@ v3d_submit_unlock_reservations(struct v3d_submit *submit) * failure, because that will happen at `v3d_job_free()`. */ static int -v3d_lookup_bos(struct v3d_submit *submit, u64 bo_handles, u32 bo_count) +v3d_lookup_bos(struct drm_file *file_priv, struct v3d_job *job, + u64 bo_handles, u32 bo_count) { - struct v3d_job *last_job = submit->jobs[submit->job_count - 1]; - - last_job->bo_count = bo_count; - - if (!last_job->bo_count) { - /* See comment on bo_index for why we have to check - * this. - */ - drm_warn(&submit->v3d->drm, "Rendering requires BOs\n"); + if (!bo_count) { + drm_warn(&job->v3d->drm, "Rendering requires BOs\n"); return -EINVAL; } - return drm_gem_objects_lookup(submit->file_priv, + job->bo_count = bo_count; + + return drm_gem_objects_lookup(file_priv, (void __user *)(uintptr_t)bo_handles, - last_job->bo_count, &last_job->bo); + job->bo_count, &job->bo); +} + +/** + * v3d_job_reference_bos() - Share another job's BOs with @dst + * @dst: job that acquires references to the BOs + * @src: job whose already-resolved BO list is shared + * + * For submissions with multiple jobs that use the same BOs, a trailing job + * shouldn't look the handles up again, as it could cause inconsistencies. + * Instead, it should reference the previous job's BOs. + */ +static int +v3d_job_reference_bos(struct v3d_job *dst, struct v3d_job *src) +{ + dst->bo = kvmalloc_objs(*dst->bo, src->bo_count); + if (!dst->bo) + return -ENOMEM; + + dst->bo_count = src->bo_count; + for (int i = 0; i < dst->bo_count; i++) { + dst->bo[i] = src->bo[i]; + drm_gem_object_get(dst->bo[i]); + } + + return 0; } static void @@ -223,13 +246,14 @@ v3d_job_add_syncobjs(struct v3d_job *job, struct drm_file *file_priv, static const struct { size_t size; void (*free)(struct kref *ref); + bool has_implicit_dep; } v3d_job_types[] = { - [V3D_BIN] = { sizeof(struct v3d_bin_job), v3d_job_free }, - [V3D_RENDER] = { sizeof(struct v3d_render_job), v3d_render_job_free }, - [V3D_TFU] = { sizeof(struct v3d_tfu_job), v3d_job_free }, - [V3D_CSD] = { sizeof(struct v3d_csd_job), v3d_job_free }, - [V3D_CACHE_CLEAN] = { sizeof(struct v3d_job), v3d_job_free }, - [V3D_CPU] = { sizeof(struct v3d_cpu_job), v3d_cpu_job_free }, + [V3D_BIN] = { sizeof(struct v3d_bin_job), v3d_job_free, false }, + [V3D_RENDER] = { sizeof(struct v3d_render_job), v3d_render_job_free, true }, + [V3D_TFU] = { sizeof(struct v3d_tfu_job), v3d_job_free, true }, + [V3D_CSD] = { sizeof(struct v3d_csd_job), v3d_job_free, true }, + [V3D_CACHE_CLEAN] = { sizeof(struct v3d_job), v3d_job_free, false }, + [V3D_CPU] = { sizeof(struct v3d_cpu_job), v3d_cpu_job_free, true }, }; static struct v3d_job * @@ -251,6 +275,7 @@ v3d_submit_add_job(struct v3d_submit *submit, enum v3d_queue queue) job->queue = queue; job->file_priv = v3d_priv; job->free = v3d_job_types[queue].free; + job->has_implicit_dep = v3d_job_types[queue].has_implicit_dep; ret = drm_sched_job_init(&job->base, &v3d_priv->sched_entity[queue], 1, v3d_priv, submit->file_priv->client_id); @@ -518,13 +543,18 @@ v3d_setup_csd_jobs_and_bos(struct v3d_submit *submit, if (ret) return ret; + ret = v3d_lookup_bos(submit->file_priv, &job->base, args->bo_handles, + args->bo_handle_count); + if (ret) + return ret; + job->args = *args; clean_job = v3d_submit_add_job(submit, V3D_CACHE_CLEAN); if (IS_ERR(clean_job)) return PTR_ERR(clean_job); - return v3d_lookup_bos(submit, args->bo_handles, args->bo_handle_count); + return v3d_job_reference_bos(clean_job, &job->base); } static void @@ -1163,22 +1193,33 @@ v3d_submit_cl_ioctl(struct drm_device *dev, void *data, if (ret) goto fail; + /* + * We don't associate the BOs with the BIN job. Fences are only + * attached to the last job in the submission chain, and BIN jobs + * don't need implicit dependencies since depending on results from + * another context is not a realistic scenario for binning. + */ + ret = v3d_lookup_bos(submit.file_priv, &render->base, + args->bo_handles, args->bo_handle_count); + if (ret) + goto fail; + if (args->flags & DRM_V3D_SUBMIT_CL_FLUSH_CACHE) { clean_job = v3d_submit_add_job(&submit, V3D_CACHE_CLEAN); if (IS_ERR(clean_job)) { ret = PTR_ERR(clean_job); goto fail; } + + ret = v3d_job_reference_bos(clean_job, &render->base); + if (ret) + goto fail; } ret = v3d_attach_perfmon_to_jobs(&submit, args->perfmon_id); if (ret) goto fail; - ret = v3d_lookup_bos(&submit, args->bo_handles, args->bo_handle_count); - if (ret) - goto fail; - ret = v3d_submit_lock_reservations(&submit); if (ret) goto fail; @@ -1557,7 +1598,8 @@ v3d_submit_cpu_ioctl(struct drm_device *dev, void *data, * the CSD and clean jobs in the case of indirect CSD job. */ if (args->bo_handle_count) { - ret = v3d_lookup_bos(&submit, args->bo_handles, args->bo_handle_count); + ret = v3d_lookup_bos(submit.file_priv, &cpu_job->base, + args->bo_handles, args->bo_handle_count); if (ret) goto fail; From 7a4b7122a623e3d57fc15cf843a9d45fbd72c6ab Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 16 Jul 2026 15:24:48 +0200 Subject: [PATCH 19/46] drm/vc4: hdmi: take i2c adapter module reference MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The i2c subsystem currently blocks during adapter deregistration whenever there are consumers holding a reference. Switch to using of_get_i2c_adapter_by_node() which also takes a reference to the adapter module so that an attempt to unload the module while in use fails gracefully instead of blocking uninterruptibly. Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20260716132448.1565278-1-johan@kernel.org Reviewed-by: Maíra Canal Signed-off-by: Maíra Canal --- drivers/gpu/drm/vc4/vc4_hdmi.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index 74dce4be0c00..17c8635c5afa 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -3208,11 +3208,11 @@ static int vc4_hdmi_runtime_resume(struct device *dev) return ret; } -static void vc4_hdmi_put_ddc_device(void *ptr) +static void vc4_hdmi_put_ddc(void *ptr) { struct vc4_hdmi *vc4_hdmi = ptr; - put_device(&vc4_hdmi->ddc->dev); + i2c_put_adapter(vc4_hdmi->ddc); } static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) @@ -3266,14 +3266,14 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) return -ENODEV; } - vc4_hdmi->ddc = of_find_i2c_adapter_by_node(ddc_node); + vc4_hdmi->ddc = of_get_i2c_adapter_by_node(ddc_node); of_node_put(ddc_node); if (!vc4_hdmi->ddc) { drm_err(drm, "Failed to get ddc i2c adapter by node\n"); return -EPROBE_DEFER; } - ret = devm_add_action_or_reset(dev, vc4_hdmi_put_ddc_device, vc4_hdmi); + ret = devm_add_action_or_reset(dev, vc4_hdmi_put_ddc, vc4_hdmi); if (ret) return ret; From 61aebeadff40cded27168e53406e0120ad66e114 Mon Sep 17 00:00:00 2001 From: Albert Esteve Date: Fri, 17 Jul 2026 16:02:04 +0200 Subject: [PATCH 20/46] drm/panel: have drm_panel_add/remove manage a list reference The global panel_list holds raw pointers to drm_panel objects. Nothing prevents a panel from being freed while it is still linked in the list: if a driver's probe calls drm_panel_add() and then fails at a later step, panel->list remains in panel_list. Any subsequent call to of_drm_find_panel() that iterates the list will dereference freed memory. Have drm_panel_add() acquire a reference via drm_panel_get() before inserting the panel into the list, and have drm_panel_remove() drop it via drm_panel_put() after removing the panel from the list. The global registry now holds a counted reference for as long as the panel is listed, ensuring the object outlives any concurrent lookup. Reviewed-by: Maxime Ripard Signed-off-by: Albert Esteve Reviewed-by: Luca Ceresoli Signed-off-by: Neil Armstrong Link: https://patch.msgid.link/20260717-drm_refcount_wiring-v3-1-023900c32e01@redhat.com --- drivers/gpu/drm/drm_panel.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/drm_panel.c b/drivers/gpu/drm/drm_panel.c index d7c6f4824b2d..68b5a2b7694c 100644 --- a/drivers/gpu/drm/drm_panel.c +++ b/drivers/gpu/drm/drm_panel.c @@ -82,6 +82,7 @@ static void drm_panel_init(struct drm_panel *panel, struct device *dev, */ void drm_panel_add(struct drm_panel *panel) { + drm_panel_get(panel); mutex_lock(&panel_lock); list_add_tail(&panel->list, &panel_list); mutex_unlock(&panel_lock); @@ -99,6 +100,7 @@ void drm_panel_remove(struct drm_panel *panel) mutex_lock(&panel_lock); list_del_init(&panel->list); mutex_unlock(&panel_lock); + drm_panel_put(panel); } EXPORT_SYMBOL(drm_panel_remove); From 8f79d34ea8950fd825e0d6ecd713705a42f06417 Mon Sep 17 00:00:00 2001 From: Albert Esteve Date: Fri, 17 Jul 2026 16:02:05 +0200 Subject: [PATCH 21/46] drm/bridge/panel: hold a reference to the wrapped panel drm_panel_bridge_add_typed() stores a pointer to the drm_panel it wraps, but never acquires a reference to it. If the panel device goes away while a panel_bridge still exists, the dangling pointer can be dereferenced through panel_bridge->panel. Acquire a reference in drm_panel_bridge_add_typed() with drm_panel_get() and release it in each teardown path. Reviewed-by: Maxime Ripard Signed-off-by: Albert Esteve Signed-off-by: Neil Armstrong Link: https://patch.msgid.link/20260717-drm_refcount_wiring-v3-2-023900c32e01@redhat.com --- drivers/gpu/drm/bridge/panel.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/bridge/panel.c b/drivers/gpu/drm/bridge/panel.c index 62af499f1f5c..01e342a9e0b5 100644 --- a/drivers/gpu/drm/bridge/panel.c +++ b/drivers/gpu/drm/bridge/panel.c @@ -294,7 +294,7 @@ struct drm_bridge *drm_panel_bridge_add_typed(struct drm_panel *panel, return (void *)panel_bridge; panel_bridge->connector_type = connector_type; - panel_bridge->panel = panel; + panel_bridge->panel = drm_panel_get(panel); panel_bridge->bridge.of_node = panel->dev->of_node; panel_bridge->bridge.ops = DRM_BRIDGE_OP_MODES; @@ -316,6 +316,7 @@ EXPORT_SYMBOL(drm_panel_bridge_add_typed); void drm_panel_bridge_remove(struct drm_bridge *bridge) { struct panel_bridge *panel_bridge; + struct drm_panel *panel; if (!bridge) return; @@ -326,10 +327,12 @@ void drm_panel_bridge_remove(struct drm_bridge *bridge) } panel_bridge = drm_bridge_to_panel_bridge(bridge); + panel = panel_bridge->panel; drm_bridge_remove(bridge); /* TODO remove this after reworking panel_bridge lifetime */ - devm_drm_put_bridge(panel_bridge->panel->dev, bridge); + devm_drm_put_bridge(panel->dev, bridge); + drm_panel_put(panel); } EXPORT_SYMBOL(drm_panel_bridge_remove); @@ -357,11 +360,16 @@ EXPORT_SYMBOL(drm_panel_bridge_set_orientation); static void devm_drm_panel_bridge_release(struct device *dev, void *res) { struct drm_bridge *bridge = *(struct drm_bridge **)res; + struct panel_bridge *panel_bridge; + struct drm_panel *panel; if (!bridge) return; + panel_bridge = drm_bridge_to_panel_bridge(bridge); + panel = panel_bridge->panel; drm_bridge_remove(bridge); + drm_panel_put(panel); } /** From b71a623598d319cae2acd9df1b297cc948c58bc7 Mon Sep 17 00:00:00 2001 From: Albert Esteve Date: Fri, 17 Jul 2026 16:02:06 +0200 Subject: [PATCH 22/46] drm/panel: of_drm_find_panel() return a counted reference Callers of of_drm_find_panel() and drm_of_find_panel_or_bridge() receive a pointer with no reference held, creating a window where the panel device can be unregistered and freed between the lookup and first use (e.g., drm_panel_prepare()). Fix the lookup function by acquiring a reference with drm_panel_get() before returning, under panel_lock. Callers are now responsible for calling drm_panel_put() when they no longer need the pointer. For bridge drivers that immediately wrap the panel in a panel_bridge (which acquires its own reference), release the lookup reference right after the bridge creation call. For analogix-anx6345, which stores the panel for direct use, release the reference in the i2c remove path. For platform drivers using analogix_dp_core with a component lifecycle (exynos_dp, rockchip analogix_dp), release the lookup reference in the platform remove() function. The panel_bridge created during bind() holds a separate reference that devm cleanup releases after remove() returns. Also fix devm_drm_of_get_bridge() and drmm_of_get_bridge() in bridge/panel.c itself, update a second batch of drivers calling of_drm_find_panel() or drm_of_find_panel_or_bridge() to release the lookup reference after wrapping the panel in a bridge, and handle the cases where a panel is found but cannot be used, dropping the reference immediately in those paths. Assisted-by: Claude:claude-opus-4-6 Acked-by: Maxime Ripard Signed-off-by: Albert Esteve Signed-off-by: Neil Armstrong Link: https://patch.msgid.link/20260717-drm_refcount_wiring-v3-3-023900c32e01@redhat.com --- .../drm/bridge/analogix/analogix-anx6345.c | 12 ++++++++++ drivers/gpu/drm/bridge/fsl-ldb.c | 1 + drivers/gpu/drm/bridge/lontium-lt9211.c | 1 + drivers/gpu/drm/bridge/lvds-codec.c | 1 + drivers/gpu/drm/bridge/panel.c | 8 +++++-- drivers/gpu/drm/bridge/samsung-dsim.c | 1 + drivers/gpu/drm/bridge/ssd2825.c | 1 + drivers/gpu/drm/bridge/tc358767.c | 2 ++ drivers/gpu/drm/bridge/tc358768.c | 1 + drivers/gpu/drm/bridge/waveshare-dsi.c | 1 + drivers/gpu/drm/drm_of.c | 3 ++- drivers/gpu/drm/drm_panel.c | 12 ++++++---- drivers/gpu/drm/exynos/exynos_dp.c | 19 ++++++++++++++- drivers/gpu/drm/exynos/exynos_drm_dpi.c | 3 +++ drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c | 18 ++++++++++++++ drivers/gpu/drm/imx/dcss/dcss-kms.c | 3 +++ drivers/gpu/drm/ingenic/ingenic-drm-drv.c | 4 +++- drivers/gpu/drm/logicvc/logicvc_interface.c | 12 ++++++++++ drivers/gpu/drm/mcde/mcde_drv.c | 1 + drivers/gpu/drm/mcde/mcde_dsi.c | 1 + drivers/gpu/drm/mxsfb/mxsfb_drv.c | 1 + drivers/gpu/drm/omapdrm/dss/output.c | 1 + drivers/gpu/drm/pl111/pl111_drv.c | 1 + .../gpu/drm/renesas/rcar-du/rcar_du_encoder.c | 1 + drivers/gpu/drm/renesas/rcar-du/rcar_lvds.c | 1 + .../gpu/drm/renesas/rz-du/rzg2l_du_encoder.c | 1 + .../gpu/drm/rockchip/analogix_dp-rockchip.c | 11 +++++++++ drivers/gpu/drm/rockchip/rockchip_lvds.c | 4 ++++ drivers/gpu/drm/rockchip/rockchip_rgb.c | 3 +++ drivers/gpu/drm/sti/sti_dvo.c | 3 +++ drivers/gpu/drm/stm/ltdc.c | 1 + drivers/gpu/drm/stm/lvds.c | 12 +++++++--- drivers/gpu/drm/sun4i/sun4i_lvds.c | 13 ++++++++++ drivers/gpu/drm/sun4i/sun4i_rgb.c | 13 ++++++++++ drivers/gpu/drm/sun4i/sun4i_tcon.c | 2 ++ drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c | 6 ++++- drivers/gpu/drm/tegra/dsi.c | 5 ++++ drivers/gpu/drm/tegra/output.c | 24 ++++++++++++++++--- drivers/gpu/drm/tidss/tidss_kms.c | 16 +++++++++---- drivers/gpu/drm/tve200/tve200_drv.c | 1 + 40 files changed, 204 insertions(+), 21 deletions(-) diff --git a/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c b/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c index eba5c6dcb5ad..d152c02419f5 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c +++ b/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c @@ -663,6 +663,11 @@ static bool anx6345_get_chip_id(struct anx6345 *anx6345) return false; } +static void anx6345_panel_put_action(void *data) +{ + drm_panel_put(data); +} + static int anx6345_i2c_probe(struct i2c_client *client) { struct anx6345 *anx6345; @@ -691,6 +696,13 @@ static int anx6345_i2c_probe(struct i2c_client *client) if (err) DRM_DEBUG("No panel found\n"); + if (anx6345->panel) { + err = devm_add_action_or_reset(dev, anx6345_panel_put_action, + anx6345->panel); + if (err) + return err; + } + /* 1.2V digital core power regulator */ anx6345->dvdd12 = devm_regulator_get(dev, "dvdd12"); if (IS_ERR(anx6345->dvdd12)) { diff --git a/drivers/gpu/drm/bridge/fsl-ldb.c b/drivers/gpu/drm/bridge/fsl-ldb.c index cc1f88e7873e..26cc72948f31 100644 --- a/drivers/gpu/drm/bridge/fsl-ldb.c +++ b/drivers/gpu/drm/bridge/fsl-ldb.c @@ -348,6 +348,7 @@ static int fsl_ldb_probe(struct platform_device *pdev) fsl_ldb->use_termination_resistor = true; fsl_ldb->panel_bridge = devm_drm_panel_bridge_add(dev, panel); + drm_panel_put(panel); if (IS_ERR(fsl_ldb->panel_bridge)) return PTR_ERR(fsl_ldb->panel_bridge); diff --git a/drivers/gpu/drm/bridge/lontium-lt9211.c b/drivers/gpu/drm/bridge/lontium-lt9211.c index f39d83a5ae37..6d97ae5640c4 100644 --- a/drivers/gpu/drm/bridge/lontium-lt9211.c +++ b/drivers/gpu/drm/bridge/lontium-lt9211.c @@ -660,6 +660,7 @@ static int lt9211_parse_dt(struct lt9211 *ctx) return ret; if (panel) { panel_bridge = devm_drm_panel_bridge_add(dev, panel); + drm_panel_put(panel); if (IS_ERR(panel_bridge)) return PTR_ERR(panel_bridge); } diff --git a/drivers/gpu/drm/bridge/lvds-codec.c b/drivers/gpu/drm/bridge/lvds-codec.c index a82ea0c944eb..f97c8586273d 100644 --- a/drivers/gpu/drm/bridge/lvds-codec.c +++ b/drivers/gpu/drm/bridge/lvds-codec.c @@ -156,6 +156,7 @@ static int lvds_codec_probe(struct platform_device *pdev) lvds_codec->panel_bridge = devm_drm_panel_bridge_add_typed(dev, panel, lvds_codec->connector_type); + drm_panel_put(panel); if (IS_ERR(lvds_codec->panel_bridge)) return PTR_ERR(lvds_codec->panel_bridge); diff --git a/drivers/gpu/drm/bridge/panel.c b/drivers/gpu/drm/bridge/panel.c index 01e342a9e0b5..02388a3de626 100644 --- a/drivers/gpu/drm/bridge/panel.c +++ b/drivers/gpu/drm/bridge/panel.c @@ -515,8 +515,10 @@ struct drm_bridge *devm_drm_of_get_bridge(struct device *dev, if (ret) return ERR_PTR(ret); - if (panel) + if (panel) { bridge = devm_drm_panel_bridge_add(dev, panel); + drm_panel_put(panel); + } return bridge; } @@ -549,8 +551,10 @@ struct drm_bridge *drmm_of_get_bridge(struct drm_device *drm, if (ret) return ERR_PTR(ret); - if (panel) + if (panel) { bridge = drmm_panel_bridge_add(drm, panel); + drm_panel_put(panel); + } return bridge; } diff --git a/drivers/gpu/drm/bridge/samsung-dsim.c b/drivers/gpu/drm/bridge/samsung-dsim.c index 25ab475309e8..e2fc69fc51b6 100644 --- a/drivers/gpu/drm/bridge/samsung-dsim.c +++ b/drivers/gpu/drm/bridge/samsung-dsim.c @@ -1934,6 +1934,7 @@ static int samsung_dsim_host_attach(struct mipi_dsi_host *host, panel = of_drm_find_panel(remote); if (!IS_ERR(panel)) { next_bridge = devm_drm_panel_bridge_add(dev, panel); + drm_panel_put(panel); if (IS_ERR(next_bridge)) { ret = PTR_ERR(next_bridge); next_bridge = NULL; // Inhibit the cleanup action on an ERR_PTR diff --git a/drivers/gpu/drm/bridge/ssd2825.c b/drivers/gpu/drm/bridge/ssd2825.c index 00a4ed4a0700..b0142d6d40dc 100644 --- a/drivers/gpu/drm/bridge/ssd2825.c +++ b/drivers/gpu/drm/bridge/ssd2825.c @@ -301,6 +301,7 @@ static int ssd2825_dsi_host_attach(struct mipi_dsi_host *host, struct mipi_dsi_d if (panel) { bridge = drm_panel_bridge_add_typed(panel, DRM_MODE_CONNECTOR_DSI); + drm_panel_put(panel); if (IS_ERR(bridge)) return PTR_ERR(bridge); } diff --git a/drivers/gpu/drm/bridge/tc358767.c b/drivers/gpu/drm/bridge/tc358767.c index ce1b73214f26..8f1648f799a7 100644 --- a/drivers/gpu/drm/bridge/tc358767.c +++ b/drivers/gpu/drm/bridge/tc358767.c @@ -2329,6 +2329,7 @@ static int tc_probe_dpi_bridge_endpoint(struct tc_data *tc) if (panel) { bridge = devm_drm_panel_bridge_add(dev, panel); + drm_panel_put(panel); if (IS_ERR(bridge)) return PTR_ERR(bridge); } @@ -2359,6 +2360,7 @@ static int tc_probe_edp_bridge_endpoint(struct tc_data *tc) struct drm_bridge *panel_bridge; panel_bridge = devm_drm_panel_bridge_add(dev, panel); + drm_panel_put(panel); if (IS_ERR(panel_bridge)) return PTR_ERR(panel_bridge); diff --git a/drivers/gpu/drm/bridge/tc358768.c b/drivers/gpu/drm/bridge/tc358768.c index 0d85120fcc7a..112a421f99b5 100644 --- a/drivers/gpu/drm/bridge/tc358768.c +++ b/drivers/gpu/drm/bridge/tc358768.c @@ -479,6 +479,7 @@ static int tc358768_dsi_host_attach(struct mipi_dsi_host *host, if (panel) { bridge = drm_panel_bridge_add_typed(panel, DRM_MODE_CONNECTOR_DSI); + drm_panel_put(panel); if (IS_ERR(bridge)) return PTR_ERR(bridge); diff --git a/drivers/gpu/drm/bridge/waveshare-dsi.c b/drivers/gpu/drm/bridge/waveshare-dsi.c index 9c9825c9b7c5..420f3b870a74 100644 --- a/drivers/gpu/drm/bridge/waveshare-dsi.c +++ b/drivers/gpu/drm/bridge/waveshare-dsi.c @@ -169,6 +169,7 @@ static int ws_bridge_probe(struct i2c_client *i2c) return dev_err_probe(dev, ret, "Failed to find remote panel\n"); ws->next_bridge = devm_drm_panel_bridge_add(dev, panel); + drm_panel_put(panel); if (IS_ERR(ws->next_bridge)) return PTR_ERR(ws->next_bridge); diff --git a/drivers/gpu/drm/drm_of.c b/drivers/gpu/drm/drm_of.c index 1f4cff6bb4a7..e495117735e1 100644 --- a/drivers/gpu/drm/drm_of.c +++ b/drivers/gpu/drm/drm_of.c @@ -269,7 +269,8 @@ EXPORT_SYMBOL_GPL(drm_of_get_panel_orientation); * @np: device tree node containing encoder output ports * @port: port in the device tree node * @endpoint: endpoint in the device tree node - * @panel: pointer to hold returned drm_panel, must not be NULL + * @panel: pointer to hold returned drm_panel, must not be NULL. On success + * the caller must call drm_panel_put() when done with the panel * @bridge: pointer to hold returned drm_bridge * * Given a DT node's port and endpoint number, find the connected node and diff --git a/drivers/gpu/drm/drm_panel.c b/drivers/gpu/drm/drm_panel.c index 68b5a2b7694c..32fba1a562fb 100644 --- a/drivers/gpu/drm/drm_panel.c +++ b/drivers/gpu/drm/drm_panel.c @@ -459,14 +459,17 @@ EXPORT_SYMBOL(__devm_drm_panel_alloc); #ifdef CONFIG_OF /** - * of_drm_find_panel - look up a panel using a device tree node + * of_drm_find_panel - look up and reference a panel by device tree node * @np: device tree node of the panel * * Searches the set of registered panels for one that matches the given device - * tree node. If a matching panel is found, return a pointer to it. + * tree node. If a matching panel is found, the panel's reference count is + * incremented before returning a pointer to it. The caller must call + * drm_panel_put() when it no longer needs the panel pointer. * - * Return: A pointer to the panel registered for the specified device tree - * node or an ERR_PTR() if no panel matching the device tree node can be found. + * Return: A reference-counted pointer to the panel registered for the specified + * device tree node or an ERR_PTR() if no panel matching the device tree node + * can be found. * * Possible error codes returned by this function: * @@ -485,6 +488,7 @@ struct drm_panel *of_drm_find_panel(const struct device_node *np) list_for_each_entry(panel, &panel_list, list) { if (panel->dev->of_node == np) { + drm_panel_get(panel); mutex_unlock(&panel_lock); return panel; } diff --git a/drivers/gpu/drm/exynos/exynos_dp.c b/drivers/gpu/drm/exynos/exynos_dp.c index b80540328150..a418011f7d4d 100644 --- a/drivers/gpu/drm/exynos/exynos_dp.c +++ b/drivers/gpu/drm/exynos/exynos_dp.c @@ -182,8 +182,15 @@ static int exynos_dp_probe(struct platform_device *pdev) out: dp->adp = analogix_dp_probe(dev, &dp->plat_data); - if (IS_ERR(dp->adp)) + if (IS_ERR(dp->adp)) { + /* + * The driver core does not invoke remove() for failed probes, + * so release the probe-time panel reference here. + */ + if (dp->plat_data.panel) + drm_panel_put(dp->plat_data.panel); return PTR_ERR(dp->adp); + } if (dp->plat_data.panel || dp->plat_data.next_bridge) return component_add(&pdev->dev, &exynos_dp_ops); @@ -193,6 +200,16 @@ static int exynos_dp_probe(struct platform_device *pdev) static void exynos_dp_remove(struct platform_device *pdev) { + struct exynos_dp_device *dp = platform_get_drvdata(pdev); + + /* + * Release the probe-time reference from of_drm_find_panel(). If bind + * ran, the panel_bridge holds a second reference that devm cleanup + * will release when the bridge is destroyed after remove() returns. + */ + if (dp->plat_data.panel) + drm_panel_put(dp->plat_data.panel); + component_del(&pdev->dev, &exynos_dp_ops); } diff --git a/drivers/gpu/drm/exynos/exynos_drm_dpi.c b/drivers/gpu/drm/exynos/exynos_drm_dpi.c index 0dc36df6ada3..9d15a0035ea9 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_dpi.c +++ b/drivers/gpu/drm/exynos/exynos_drm_dpi.c @@ -245,5 +245,8 @@ int exynos_dpi_remove(struct drm_encoder *encoder) exynos_dpi_disable(&ctx->encoder); + if (ctx->panel) + drm_panel_put(ctx->panel); + return 0; } diff --git a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c index 84eff7519e32..ec71fbbb0eb8 100644 --- a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c +++ b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c @@ -109,6 +109,13 @@ static int fsl_dcu_attach_panel(struct fsl_dcu_drm_device *fsl_dev, return ret; } +static void fsl_dcu_panel_put_action(void *data) +{ + struct drm_panel *panel = data; + + drm_panel_put(panel); +} + int fsl_dcu_create_outputs(struct fsl_dcu_drm_device *fsl_dev) { struct device_node *panel_node; @@ -124,6 +131,12 @@ int fsl_dcu_create_outputs(struct fsl_dcu_drm_device *fsl_dev) if (IS_ERR(fsl_dev->connector.panel)) return PTR_ERR(fsl_dev->connector.panel); + ret = devm_add_action_or_reset(fsl_dev->dev, + fsl_dcu_panel_put_action, + fsl_dev->connector.panel); + if (ret) + return ret; + return fsl_dcu_attach_panel(fsl_dev, fsl_dev->connector.panel); } @@ -132,6 +145,11 @@ int fsl_dcu_create_outputs(struct fsl_dcu_drm_device *fsl_dev) return ret; if (panel) { + ret = devm_add_action_or_reset(fsl_dev->dev, + fsl_dcu_panel_put_action, panel); + if (ret) + return ret; + fsl_dev->connector.panel = panel; return fsl_dcu_attach_panel(fsl_dev, panel); } diff --git a/drivers/gpu/drm/imx/dcss/dcss-kms.c b/drivers/gpu/drm/imx/dcss/dcss-kms.c index 50bd7f36d36d..01e0c10b6ea1 100644 --- a/drivers/gpu/drm/imx/dcss/dcss-kms.c +++ b/drivers/gpu/drm/imx/dcss/dcss-kms.c @@ -77,6 +77,9 @@ static int dcss_kms_bridge_connector_init(struct dcss_kms_dev *kms) if (ret) return ret; + if (panel) + drm_panel_put(panel); + if (!bridge) { dev_err(ddev->dev, "No bridge found %d.\n", ret); return -ENODEV; diff --git a/drivers/gpu/drm/ingenic/ingenic-drm-drv.c b/drivers/gpu/drm/ingenic/ingenic-drm-drv.c index 7e569af22391..738a80b2550f 100644 --- a/drivers/gpu/drm/ingenic/ingenic-drm-drv.c +++ b/drivers/gpu/drm/ingenic/ingenic-drm-drv.c @@ -1297,9 +1297,11 @@ static int ingenic_drm_bind(struct device *dev, bool has_components) goto err_drvdata; } - if (panel) + if (panel) { bridge = devm_drm_panel_bridge_add_typed(dev, panel, DRM_MODE_CONNECTOR_DPI); + drm_panel_put(panel); + } ib = drmm_encoder_alloc(drm, struct ingenic_drm_bridge, encoder, NULL, DRM_MODE_ENCODER_DPI, NULL); diff --git a/drivers/gpu/drm/logicvc/logicvc_interface.c b/drivers/gpu/drm/logicvc/logicvc_interface.c index 689049d395c0..81f760dc07f8 100644 --- a/drivers/gpu/drm/logicvc/logicvc_interface.c +++ b/drivers/gpu/drm/logicvc/logicvc_interface.c @@ -28,6 +28,11 @@ #define logicvc_interface_from_drm_connector(c) \ container_of(c, struct logicvc_interface, drm_connector) +static void logicvc_panel_put_action(void *data) +{ + drm_panel_put(data); +} + static void logicvc_encoder_enable(struct drm_encoder *drm_encoder) { struct logicvc_drm *logicvc = logicvc_drm(drm_encoder->dev); @@ -160,6 +165,13 @@ int logicvc_interface_init(struct logicvc_drm *logicvc) if (ret == -EPROBE_DEFER) goto error_early; + if (interface->drm_panel) { + ret = devm_add_action_or_reset(dev, logicvc_panel_put_action, + interface->drm_panel); + if (ret) + goto error_early; + } + ret = drm_encoder_init(drm_dev, &interface->drm_encoder, &logicvc_encoder_funcs, encoder_type, NULL); if (ret) { diff --git a/drivers/gpu/drm/mcde/mcde_drv.c b/drivers/gpu/drm/mcde/mcde_drv.c index 5f2c462bad7e..53275b575f0c 100644 --- a/drivers/gpu/drm/mcde/mcde_drv.c +++ b/drivers/gpu/drm/mcde/mcde_drv.c @@ -153,6 +153,7 @@ static int mcde_modeset_init(struct drm_device *drm) if (panel) { bridge = drm_panel_bridge_add_typed(panel, DRM_MODE_CONNECTOR_DPI); + drm_panel_put(panel); if (IS_ERR(bridge)) { dev_err(drm->dev, "Could not connect panel bridge\n"); diff --git a/drivers/gpu/drm/mcde/mcde_dsi.c b/drivers/gpu/drm/mcde/mcde_dsi.c index 5cf44ccb02cf..694372581840 100644 --- a/drivers/gpu/drm/mcde/mcde_dsi.c +++ b/drivers/gpu/drm/mcde/mcde_dsi.c @@ -1127,6 +1127,7 @@ static int mcde_dsi_bind(struct device *dev, struct device *master, if (panel) { bridge = drm_panel_bridge_add_typed(panel, DRM_MODE_CONNECTOR_DSI); + drm_panel_put(panel); if (IS_ERR(bridge)) { dev_err(dev, "error adding panel bridge\n"); return PTR_ERR(bridge); diff --git a/drivers/gpu/drm/mxsfb/mxsfb_drv.c b/drivers/gpu/drm/mxsfb/mxsfb_drv.c index 9b8fbda85d28..7cb19c15bf39 100644 --- a/drivers/gpu/drm/mxsfb/mxsfb_drv.c +++ b/drivers/gpu/drm/mxsfb/mxsfb_drv.c @@ -127,6 +127,7 @@ static int mxsfb_attach_bridge(struct mxsfb_drm_private *mxsfb) if (panel) { bridge = devm_drm_panel_bridge_add_typed(drm->dev, panel, DRM_MODE_CONNECTOR_DPI); + drm_panel_put(panel); if (IS_ERR(bridge)) return PTR_ERR(bridge); } diff --git a/drivers/gpu/drm/omapdrm/dss/output.c b/drivers/gpu/drm/omapdrm/dss/output.c index ca891aba3820..6e9bc605ee22 100644 --- a/drivers/gpu/drm/omapdrm/dss/output.c +++ b/drivers/gpu/drm/omapdrm/dss/output.c @@ -43,6 +43,7 @@ int omapdss_device_init_output(struct omap_dss_device *out, struct drm_bridge *bridge; bridge = drm_panel_bridge_add(out->panel); + drm_panel_put(out->panel); if (IS_ERR(bridge)) { dev_err(out->dev, "unable to create panel bridge (%ld)\n", diff --git a/drivers/gpu/drm/pl111/pl111_drv.c b/drivers/gpu/drm/pl111/pl111_drv.c index ac7b1d12a0f5..8ec659b3c08e 100644 --- a/drivers/gpu/drm/pl111/pl111_drv.c +++ b/drivers/gpu/drm/pl111/pl111_drv.c @@ -145,6 +145,7 @@ static int pl111_modeset_init(struct drm_device *dev) if (panel) { bridge = drm_panel_bridge_add_typed(panel, DRM_MODE_CONNECTOR_Unknown); + drm_panel_put(panel); if (IS_ERR(bridge)) { ret = PTR_ERR(bridge); goto finish; diff --git a/drivers/gpu/drm/renesas/rcar-du/rcar_du_encoder.c b/drivers/gpu/drm/renesas/rcar-du/rcar_du_encoder.c index db2088529b48..d8e7e9877ba8 100644 --- a/drivers/gpu/drm/renesas/rcar-du/rcar_du_encoder.c +++ b/drivers/gpu/drm/renesas/rcar-du/rcar_du_encoder.c @@ -69,6 +69,7 @@ int rcar_du_encoder_init(struct rcar_du_device *rcdu, bridge = devm_drm_panel_bridge_add_typed(rcdu->dev, panel, DRM_MODE_CONNECTOR_DPI); + drm_panel_put(panel); if (IS_ERR(bridge)) return PTR_ERR(no_free_ptr(bridge)); diff --git a/drivers/gpu/drm/renesas/rcar-du/rcar_lvds.c b/drivers/gpu/drm/renesas/rcar-du/rcar_lvds.c index e433ce61d431..9527d3637e6c 100644 --- a/drivers/gpu/drm/renesas/rcar-du/rcar_lvds.c +++ b/drivers/gpu/drm/renesas/rcar-du/rcar_lvds.c @@ -791,6 +791,7 @@ static int rcar_lvds_parse_dt(struct rcar_lvds *lvds) if (lvds->panel) { lvds->next_bridge = devm_drm_panel_bridge_add(lvds->dev, lvds->panel); + drm_panel_put(lvds->panel); if (IS_ERR_OR_NULL(lvds->next_bridge)) { ret = -EINVAL; goto done; diff --git a/drivers/gpu/drm/renesas/rz-du/rzg2l_du_encoder.c b/drivers/gpu/drm/renesas/rz-du/rzg2l_du_encoder.c index f50d166b764f..3d0999e4fcfd 100644 --- a/drivers/gpu/drm/renesas/rz-du/rzg2l_du_encoder.c +++ b/drivers/gpu/drm/renesas/rz-du/rzg2l_du_encoder.c @@ -90,6 +90,7 @@ int rzg2l_du_encoder_init(struct rzg2l_du_device *rcdu, bridge = devm_drm_panel_bridge_add_typed(rcdu->dev, panel, DRM_MODE_CONNECTOR_DPI); + drm_panel_put(panel); if (IS_ERR(bridge)) return PTR_ERR(no_free_ptr(bridge)); diff --git a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c index 46c245e35d21..587e60232ec7 100644 --- a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c +++ b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -485,6 +486,16 @@ static int rockchip_dp_probe(struct platform_device *pdev) static void rockchip_dp_remove(struct platform_device *pdev) { + struct rockchip_dp_device *dp = platform_get_drvdata(pdev); + + /* + * Release the probe-time reference from of_drm_find_panel(). If bind + * ran, the panel_bridge holds a second reference that devm cleanup + * will release when the bridge is destroyed after remove() returns. + */ + if (dp->plat_data.panel) + drm_panel_put(dp->plat_data.panel); + component_del(&pdev->dev, &rockchip_dp_component_ops); } diff --git a/drivers/gpu/drm/rockchip/rockchip_lvds.c b/drivers/gpu/drm/rockchip/rockchip_lvds.c index 262f81875278..505c6c56736d 100644 --- a/drivers/gpu/drm/rockchip/rockchip_lvds.c +++ b/drivers/gpu/drm/rockchip/rockchip_lvds.c @@ -610,6 +610,8 @@ static int rockchip_lvds_bind(struct device *dev, struct device *master, if (lvds->panel) { lvds->bridge = drm_panel_bridge_add_typed(lvds->panel, DRM_MODE_CONNECTOR_LVDS); + drm_panel_put(lvds->panel); + lvds->panel = NULL; if (IS_ERR(lvds->bridge)) { ret = PTR_ERR(lvds->bridge); goto err_free_encoder; @@ -646,6 +648,8 @@ static int rockchip_lvds_bind(struct device *dev, struct device *master, err_free_encoder: drm_encoder_cleanup(encoder); err_put_remote: + if (lvds->panel) + drm_panel_put(lvds->panel); of_node_put(remote); err_put_port: of_node_put(port); diff --git a/drivers/gpu/drm/rockchip/rockchip_rgb.c b/drivers/gpu/drm/rockchip/rockchip_rgb.c index 2ad24b914989..0e1830bc40ee 100644 --- a/drivers/gpu/drm/rockchip/rockchip_rgb.c +++ b/drivers/gpu/drm/rockchip/rockchip_rgb.c @@ -135,6 +135,8 @@ struct rockchip_rgb *rockchip_rgb_init(struct device *dev, if (ret < 0) { DRM_DEV_ERROR(drm_dev->dev, "failed to initialize encoder: %d\n", ret); + if (panel) + drm_panel_put(panel); return ERR_PTR(ret); } @@ -143,6 +145,7 @@ struct rockchip_rgb *rockchip_rgb_init(struct device *dev, if (panel) { bridge = drm_panel_bridge_add_typed(panel, DRM_MODE_CONNECTOR_LVDS); + drm_panel_put(panel); if (IS_ERR(bridge)) return ERR_CAST(bridge); } diff --git a/drivers/gpu/drm/sti/sti_dvo.c b/drivers/gpu/drm/sti/sti_dvo.c index b48099468eb9..cdb19b75f5e1 100644 --- a/drivers/gpu/drm/sti/sti_dvo.c +++ b/drivers/gpu/drm/sti/sti_dvo.c @@ -498,6 +498,9 @@ static void sti_dvo_unbind(struct device *dev, { struct sti_dvo *dvo = dev_get_drvdata(dev); + if (dvo->panel) + drm_panel_put(dvo->panel); + drm_bridge_remove(&dvo->bridge); } diff --git a/drivers/gpu/drm/stm/ltdc.c b/drivers/gpu/drm/stm/ltdc.c index 95fcfa48d8be..daf198edb42f 100644 --- a/drivers/gpu/drm/stm/ltdc.c +++ b/drivers/gpu/drm/stm/ltdc.c @@ -1982,6 +1982,7 @@ int ltdc_load(struct drm_device *ddev) if (panel) { bridge = drmm_panel_bridge_add(ddev, panel); + drm_panel_put(panel); if (IS_ERR(bridge)) { drm_err(ddev, "panel-bridge endpoint %d\n", i); ret = PTR_ERR(bridge); diff --git a/drivers/gpu/drm/stm/lvds.c b/drivers/gpu/drm/stm/lvds.c index 90a44e722057..1072dbe7bd6a 100644 --- a/drivers/gpu/drm/stm/lvds.c +++ b/drivers/gpu/drm/stm/lvds.c @@ -1068,20 +1068,20 @@ static int lvds_probe(struct platform_device *pdev) if (IS_ERR(lvds->base)) { ret = PTR_ERR(lvds->base); dev_err(dev, "Unable to get regs %d\n", ret); - return ret; + goto err_put_panel; } lvds->pclk = devm_clk_get(dev, "pclk"); if (IS_ERR(lvds->pclk)) { ret = PTR_ERR(lvds->pclk); dev_err(dev, "Unable to get peripheral clock: %d\n", ret); - return ret; + goto err_put_panel; } ret = clk_prepare_enable(lvds->pclk); if (ret) { dev_err(dev, "%s: Failed to enable peripheral clk\n", __func__); - return ret; + goto err_put_panel; } rstc = devm_reset_control_get_exclusive(dev, NULL); @@ -1181,6 +1181,9 @@ static int lvds_probe(struct platform_device *pdev) err_lvds_probe: clk_disable_unprepare(lvds->pclk); +err_put_panel: + if (lvds->panel) + drm_panel_put(lvds->panel); return ret; } @@ -1189,6 +1192,9 @@ static void lvds_remove(struct platform_device *pdev) { struct stm_lvds *lvds = platform_get_drvdata(pdev); + if (lvds->panel) + drm_panel_put(lvds->panel); + lvds_pixel_clk_unregister(lvds); drm_bridge_remove(&lvds->lvds_bridge); diff --git a/drivers/gpu/drm/sun4i/sun4i_lvds.c b/drivers/gpu/drm/sun4i/sun4i_lvds.c index 35a3f987c37a..6b98d20a8890 100644 --- a/drivers/gpu/drm/sun4i/sun4i_lvds.c +++ b/drivers/gpu/drm/sun4i/sun4i_lvds.c @@ -18,6 +18,11 @@ #include "sun4i_tcon.h" #include "sun4i_lvds.h" +static void sun4i_panel_put_action(void *data) +{ + drm_panel_put(data); +} + struct sun4i_lvds { struct drm_connector connector; struct drm_encoder encoder; @@ -117,6 +122,14 @@ int sun4i_lvds_init(struct drm_device *drm, struct sun4i_tcon *tcon) return 0; } + if (lvds->panel) { + ret = devm_add_action_or_reset(tcon->dev, + sun4i_panel_put_action, + lvds->panel); + if (ret) + return ret; + } + drm_encoder_helper_add(&lvds->encoder, &sun4i_lvds_enc_helper_funcs); ret = drm_encoder_init(drm, &lvds->encoder, &sun4i_lvds_enc_funcs, diff --git a/drivers/gpu/drm/sun4i/sun4i_rgb.c b/drivers/gpu/drm/sun4i/sun4i_rgb.c index 9c3fbf1b949e..3da9a9283c6e 100644 --- a/drivers/gpu/drm/sun4i/sun4i_rgb.c +++ b/drivers/gpu/drm/sun4i/sun4i_rgb.c @@ -43,6 +43,11 @@ drm_encoder_to_sun4i_rgb(struct drm_encoder *encoder) encoder); } +static void sun4i_panel_put_action(void *data) +{ + drm_panel_put(data); +} + static int sun4i_rgb_get_modes(struct drm_connector *connector) { struct sun4i_rgb *rgb = @@ -209,6 +214,14 @@ int sun4i_rgb_init(struct drm_device *drm, struct sun4i_tcon *tcon) return 0; } + if (rgb->panel) { + ret = devm_add_action_or_reset(tcon->dev, + sun4i_panel_put_action, + rgb->panel); + if (ret) + return ret; + } + drm_encoder_helper_add(&rgb->encoder, &sun4i_rgb_enc_helper_funcs); ret = drm_encoder_init(drm, &rgb->encoder, &sun4i_rgb_enc_funcs, diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c index 960e83c8291d..d4c1723c5e3d 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c @@ -1326,6 +1326,8 @@ static int sun4i_tcon_probe(struct platform_device *pdev) ret = drm_of_find_panel_or_bridge(node, 1, 0, &panel, &bridge); if (ret == -EPROBE_DEFER) return ret; + if (panel) + drm_panel_put(panel); } return component_add(&pdev->dev, &sun4i_tcon_ops); diff --git a/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c b/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c index f0c9f0e573d2..d504ae583294 100644 --- a/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c +++ b/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c @@ -971,8 +971,10 @@ static int sun6i_dsi_attach(struct mipi_dsi_host *host, if (IS_ERR(panel)) return PTR_ERR(panel); - if (!dsi->drm || !dsi->drm->registered) + if (!dsi->drm || !dsi->drm->registered) { + drm_panel_put(panel); return -EPROBE_DEFER; + } dsi->panel = panel; dsi->device = device; @@ -989,6 +991,8 @@ static int sun6i_dsi_detach(struct mipi_dsi_host *host, { struct sun6i_dsi *dsi = host_to_sun6i_dsi(host); + if (dsi->panel) + drm_panel_put(dsi->panel); dsi->panel = NULL; dsi->device = NULL; diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index e7fdd8c7ac12..cb88aafbd36f 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -1527,6 +1527,10 @@ static int tegra_dsi_host_attach(struct mipi_dsi_host *host, if (!dsi->master) { struct tegra_output *output = &dsi->output; + /* + * tegra_output_probe() never populates a panel for DSI + * outputs, so output->panel is always NULL here. + */ output->panel = of_drm_find_panel(device->dev.of_node); if (IS_ERR(output->panel)) output->panel = NULL; @@ -1545,6 +1549,7 @@ static int tegra_dsi_host_detach(struct mipi_dsi_host *host, struct tegra_output *output = &dsi->output; if (output->panel && &device->dev == output->panel->dev) { + drm_panel_put(output->panel); output->panel = NULL; if (output->connector.dev) diff --git a/drivers/gpu/drm/tegra/output.c b/drivers/gpu/drm/tegra/output.c index 49e4f63a5550..d0ba3bd0aab8 100644 --- a/drivers/gpu/drm/tegra/output.c +++ b/drivers/gpu/drm/tegra/output.c @@ -117,11 +117,19 @@ int tegra_output_probe(struct tegra_output *output) */ WARN_ON(output->panel || output->bridge); + if (output->panel) { + drm_panel_put(output->panel); + output->panel = NULL; + } + output->panel = of_drm_find_panel(panel); of_node_put(panel); - if (IS_ERR(output->panel)) - return PTR_ERR(output->panel); + if (IS_ERR(output->panel)) { + err = PTR_ERR(output->panel); + output->panel = NULL; + return err; + } } ddc = of_parse_phandle(output->of_node, "nvidia,ddc-i2c-bus", 0); @@ -131,7 +139,7 @@ int tegra_output_probe(struct tegra_output *output) if (!output->ddc) { err = -EPROBE_DEFER; - return err; + goto put_i2c; } } @@ -185,6 +193,11 @@ int tegra_output_probe(struct tegra_output *output) return 0; put_i2c: + if (output->panel) { + drm_panel_put(output->panel); + output->panel = NULL; + } + if (output->ddc) i2c_put_adapter(output->ddc); @@ -195,6 +208,11 @@ int tegra_output_probe(struct tegra_output *output) void tegra_output_remove(struct tegra_output *output) { + if (output->panel) { + drm_panel_put(output->panel); + output->panel = NULL; + } + if (output->hpd_gpio) free_irq(output->hpd_irq, output); diff --git a/drivers/gpu/drm/tidss/tidss_kms.c b/drivers/gpu/drm/tidss/tidss_kms.c index 1512ee2574b6..70c14c3be10d 100644 --- a/drivers/gpu/drm/tidss/tidss_kms.c +++ b/drivers/gpu/drm/tidss/tidss_kms.c @@ -162,6 +162,7 @@ static int tidss_dispc_modeset_init(struct tidss_device *tidss) if (panel) { u32 conn_type; + int ret; dev_dbg(dev, "Setting up panel for port %d\n", i); @@ -176,7 +177,8 @@ static int tidss_dispc_modeset_init(struct tidss_device *tidss) break; default: WARN_ON(1); - return -EINVAL; + ret = -EINVAL; + goto put_panel; } if (panel->connector_type != conn_type) { @@ -184,16 +186,20 @@ static int tidss_dispc_modeset_init(struct tidss_device *tidss) "%s: Panel %s has incompatible connector type for vp%d (%d != %d)\n", __func__, dev_name(panel->dev), i, panel->connector_type, conn_type); - return -EINVAL; + ret = -EINVAL; + goto put_panel; } bridge = devm_drm_panel_bridge_add(dev, panel); - if (IS_ERR(bridge)) { + ret = PTR_ERR_OR_ZERO(bridge); + if (ret) dev_err(dev, "failed to set up panel bridge for port %d\n", i); - return PTR_ERR(bridge); - } +put_panel: + drm_panel_put(panel); + if (ret) + return ret; } pipes[num_pipes].hw_videoport = i; diff --git a/drivers/gpu/drm/tve200/tve200_drv.c b/drivers/gpu/drm/tve200/tve200_drv.c index 562f3f11812a..f858c58ccb99 100644 --- a/drivers/gpu/drm/tve200/tve200_drv.c +++ b/drivers/gpu/drm/tve200/tve200_drv.c @@ -84,6 +84,7 @@ static int tve200_modeset_init(struct drm_device *dev) if (panel) { bridge = drm_panel_bridge_add_typed(panel, DRM_MODE_CONNECTOR_Unknown); + drm_panel_put(panel); if (IS_ERR(bridge)) { ret = PTR_ERR(bridge); goto out_bridge; From ac3baea883dab989178dd4186090d6bcf52b8d55 Mon Sep 17 00:00:00 2001 From: Albert Esteve Date: Fri, 17 Jul 2026 16:02:07 +0200 Subject: [PATCH 23/46] drm/panel: find_panel_by_fwnode() return a counted reference find_panel_by_fwnode() is the fwnode-based counterpart to of_drm_find_panel(), used internally by drm_panel_add_follower(). Like of_drm_find_panel(), it returned an unrefcounted pointer, leaving a window where the panel could be freed between the lookup and first use. drm_panel_add_follower() worked around the missing panel kref by calling get_device() on the panel's underlying struct device. However, get_device() only prevents the device kobject from being freed. It does not prevent the panel's kzalloc()'d container memory from being released when the kref reaches zero. Apply the same fix: call drm_panel_get() under panel_lock before returning. Since find_panel_by_fwnode() now transfers a counted reference to drm_panel_add_follower(), drm_panel_remove_follower() must balance it with a matching drm_panel_put(). Acked-by: Maxime Ripard Signed-off-by: Albert Esteve Signed-off-by: Neil Armstrong Link: https://patch.msgid.link/20260717-drm_refcount_wiring-v3-4-023900c32e01@redhat.com --- drivers/gpu/drm/drm_panel.c | 10 +- drivers/gpu/drm/drm_panel.c.orig | 722 +++++++++++++++++++++++++++++++ 2 files changed, 731 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/drm_panel.c.orig diff --git a/drivers/gpu/drm/drm_panel.c b/drivers/gpu/drm/drm_panel.c index 32fba1a562fb..f8f6082e637f 100644 --- a/drivers/gpu/drm/drm_panel.c +++ b/drivers/gpu/drm/drm_panel.c @@ -500,7 +500,13 @@ struct drm_panel *of_drm_find_panel(const struct device_node *np) EXPORT_SYMBOL(of_drm_find_panel); #endif -/* Find panel by fwnode. This should be identical to of_drm_find_panel(). */ +/* + * Find panel by fwnode, returning a counted reference. + * + * Behaves identically to of_drm_find_panel(). On success the returned + * pointer has been passed through drm_panel_get(); the caller must call + * drm_panel_put() when done with it. + */ static struct drm_panel *find_panel_by_fwnode(const struct fwnode_handle *fwnode) { struct drm_panel *panel; @@ -512,6 +518,7 @@ static struct drm_panel *find_panel_by_fwnode(const struct fwnode_handle *fwnode list_for_each_entry(panel, &panel_list, list) { if (dev_fwnode(panel->dev) == fwnode) { + drm_panel_get(panel); mutex_unlock(&panel_lock); return panel; } @@ -648,6 +655,7 @@ void drm_panel_remove_follower(struct drm_panel_follower *follower) mutex_unlock(&panel->follower_lock); put_device(panel->dev); + drm_panel_put(panel); } EXPORT_SYMBOL(drm_panel_remove_follower); diff --git a/drivers/gpu/drm/drm_panel.c.orig b/drivers/gpu/drm/drm_panel.c.orig new file mode 100644 index 000000000000..32fba1a562fb --- /dev/null +++ b/drivers/gpu/drm/drm_panel.c.orig @@ -0,0 +1,722 @@ +/* + * Copyright (C) 2013, NVIDIA Corporation. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sub license, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial portions + * of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +static DEFINE_MUTEX(panel_lock); +static LIST_HEAD(panel_list); + +/** + * DOC: drm panel + * + * The DRM panel helpers allow drivers to register panel objects with a + * central registry and provide functions to retrieve those panels in display + * drivers. + * + * For easy integration into drivers using the &drm_bridge infrastructure please + * take look at drm_panel_bridge_add() and devm_drm_panel_bridge_add(). + */ + +/** + * drm_panel_init - initialize a panel + * @panel: DRM panel + * @dev: parent device of the panel + * @funcs: panel operations + * @connector_type: the connector type (DRM_MODE_CONNECTOR_*) corresponding to + * the panel interface (must NOT be DRM_MODE_CONNECTOR_Unknown) + * + * Initialize the panel structure for subsequent registration with + * drm_panel_add(). + */ +static void drm_panel_init(struct drm_panel *panel, struct device *dev, + const struct drm_panel_funcs *funcs, + int connector_type) +{ + if (connector_type == DRM_MODE_CONNECTOR_Unknown) + DRM_WARN("%s: %s: a valid connector type is required!\n", __func__, dev_name(dev)); + + INIT_LIST_HEAD(&panel->list); + INIT_LIST_HEAD(&panel->followers); + mutex_init(&panel->follower_lock); + panel->dev = dev; + panel->funcs = funcs; + panel->connector_type = connector_type; +} + +/** + * drm_panel_add - add a panel to the global registry + * @panel: panel to add + * + * Add a panel to the global registry so that it can be looked + * up by display drivers. The panel to be added must have been + * allocated by devm_drm_panel_alloc(). + */ +void drm_panel_add(struct drm_panel *panel) +{ + drm_panel_get(panel); + mutex_lock(&panel_lock); + list_add_tail(&panel->list, &panel_list); + mutex_unlock(&panel_lock); +} +EXPORT_SYMBOL(drm_panel_add); + +/** + * drm_panel_remove - remove a panel from the global registry + * @panel: DRM panel + * + * Removes a panel from the global registry. + */ +void drm_panel_remove(struct drm_panel *panel) +{ + mutex_lock(&panel_lock); + list_del_init(&panel->list); + mutex_unlock(&panel_lock); + drm_panel_put(panel); +} +EXPORT_SYMBOL(drm_panel_remove); + +static void drm_panel_add_release(void *data) +{ + drm_panel_remove(data); +} + +/** + * devm_drm_panel_add - add a panel to the global registry using devres + * @dev: device to which the panel is attached + * @panel: panel to add + * + * Add a panel to the global registry so that it can be looked + * up by display drivers. The panel to be added must have been + * allocated by devm_drm_panel_alloc(). Unlike drm_panel_add() with this + * function there is no need to call drm_panel_remove(), it will be called + * automatically. + */ +int devm_drm_panel_add(struct device *dev, struct drm_panel *panel) +{ + drm_panel_add(panel); + + return devm_add_action_or_reset(dev, drm_panel_add_release, panel); +} +EXPORT_SYMBOL(devm_drm_panel_add); + +/** + * drm_panel_prepare - power on a panel + * @panel: DRM panel + * + * Calling this function will enable power and deassert any reset signals to + * the panel. After this has completed it is possible to communicate with any + * integrated circuitry via a command bus. This function cannot fail (as it is + * called from the pre_enable call chain). There will always be a call to + * drm_panel_disable() afterwards. + */ +void drm_panel_prepare(struct drm_panel *panel) +{ + struct drm_panel_follower *follower; + int ret; + + if (!panel) + return; + + if (panel->prepared) { + dev_warn(panel->dev, "Skipping prepare of already prepared panel\n"); + return; + } + + mutex_lock(&panel->follower_lock); + + if (panel->funcs && panel->funcs->prepare) { + ret = panel->funcs->prepare(panel); + if (ret < 0) + goto exit; + } + panel->prepared = true; + + list_for_each_entry(follower, &panel->followers, list) { + if (!follower->funcs->panel_prepared) + continue; + + ret = follower->funcs->panel_prepared(follower); + if (ret < 0) + dev_info(panel->dev, "%ps failed: %d\n", + follower->funcs->panel_prepared, ret); + } + +exit: + mutex_unlock(&panel->follower_lock); +} +EXPORT_SYMBOL(drm_panel_prepare); + +/** + * drm_panel_unprepare - power off a panel + * @panel: DRM panel + * + * Calling this function will completely power off a panel (assert the panel's + * reset, turn off power supplies, ...). After this function has completed, it + * is usually no longer possible to communicate with the panel until another + * call to drm_panel_prepare(). + */ +void drm_panel_unprepare(struct drm_panel *panel) +{ + struct drm_panel_follower *follower; + int ret; + + if (!panel) + return; + + /* + * If you are seeing the warning below it likely means one of two things: + * - Your panel driver incorrectly calls drm_panel_unprepare() in its + * shutdown routine. You should delete this. + * - You are using panel-edp or panel-simple and your DRM modeset + * driver's shutdown() callback happened after the panel's shutdown(). + * In this case the warning is harmless though ideally you should + * figure out how to reverse the order of the shutdown() callbacks. + */ + if (!panel->prepared) { + dev_warn(panel->dev, "Skipping unprepare of already unprepared panel\n"); + return; + } + + mutex_lock(&panel->follower_lock); + + list_for_each_entry(follower, &panel->followers, list) { + if (!follower->funcs->panel_unpreparing) + continue; + + ret = follower->funcs->panel_unpreparing(follower); + if (ret < 0) + dev_info(panel->dev, "%ps failed: %d\n", + follower->funcs->panel_unpreparing, ret); + } + + if (panel->funcs && panel->funcs->unprepare) { + ret = panel->funcs->unprepare(panel); + if (ret < 0) + goto exit; + } + panel->prepared = false; + +exit: + mutex_unlock(&panel->follower_lock); +} +EXPORT_SYMBOL(drm_panel_unprepare); + +/** + * drm_panel_enable - enable a panel + * @panel: DRM panel + * + * Calling this function will cause the panel display drivers to be turned on + * and the backlight to be enabled. Content will be visible on screen after + * this call completes. This function cannot fail (as it is called from the + * enable call chain). There will always be a call to drm_panel_disable() + * afterwards. + */ +void drm_panel_enable(struct drm_panel *panel) +{ + struct drm_panel_follower *follower; + int ret; + + if (!panel) + return; + + if (panel->enabled) { + dev_warn(panel->dev, "Skipping enable of already enabled panel\n"); + return; + } + + mutex_lock(&panel->follower_lock); + + if (panel->funcs && panel->funcs->enable) { + ret = panel->funcs->enable(panel); + if (ret < 0) + goto exit; + } + panel->enabled = true; + + ret = backlight_enable(panel->backlight); + if (ret < 0) + DRM_DEV_INFO(panel->dev, "failed to enable backlight: %d\n", + ret); + + list_for_each_entry(follower, &panel->followers, list) { + if (!follower->funcs->panel_enabled) + continue; + + ret = follower->funcs->panel_enabled(follower); + if (ret < 0) + dev_info(panel->dev, "%ps failed: %d\n", + follower->funcs->panel_enabled, ret); + } + +exit: + mutex_unlock(&panel->follower_lock); +} +EXPORT_SYMBOL(drm_panel_enable); + +/** + * drm_panel_disable - disable a panel + * @panel: DRM panel + * + * This will typically turn off the panel's backlight or disable the display + * drivers. For smart panels it should still be possible to communicate with + * the integrated circuitry via any command bus after this call. + */ +void drm_panel_disable(struct drm_panel *panel) +{ + struct drm_panel_follower *follower; + int ret; + + if (!panel) + return; + + /* + * If you are seeing the warning below it likely means one of two things: + * - Your panel driver incorrectly calls drm_panel_disable() in its + * shutdown routine. You should delete this. + * - You are using panel-edp or panel-simple and your DRM modeset + * driver's shutdown() callback happened after the panel's shutdown(). + * In this case the warning is harmless though ideally you should + * figure out how to reverse the order of the shutdown() callbacks. + */ + if (!panel->enabled) { + dev_warn(panel->dev, "Skipping disable of already disabled panel\n"); + return; + } + + mutex_lock(&panel->follower_lock); + + list_for_each_entry(follower, &panel->followers, list) { + if (!follower->funcs->panel_disabling) + continue; + + ret = follower->funcs->panel_disabling(follower); + if (ret < 0) + dev_info(panel->dev, "%ps failed: %d\n", + follower->funcs->panel_disabling, ret); + } + + ret = backlight_disable(panel->backlight); + if (ret < 0) + DRM_DEV_INFO(panel->dev, "failed to disable backlight: %d\n", + ret); + + if (panel->funcs && panel->funcs->disable) { + ret = panel->funcs->disable(panel); + if (ret < 0) + goto exit; + } + panel->enabled = false; + +exit: + mutex_unlock(&panel->follower_lock); +} +EXPORT_SYMBOL(drm_panel_disable); + +/** + * drm_panel_get_modes - probe the available display modes of a panel + * @panel: DRM panel + * @connector: DRM connector + * + * The modes probed from the panel are automatically added to the connector + * that the panel is attached to. + * + * Return: The number of modes available from the panel on success, or 0 on + * failure (no modes). + */ +int drm_panel_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + if (!panel) + return 0; + + if (panel->funcs && panel->funcs->get_modes) { + int num; + + num = panel->funcs->get_modes(panel, connector); + if (num > 0) + return num; + } + + return 0; +} +EXPORT_SYMBOL(drm_panel_get_modes); + +static void __drm_panel_free(struct kref *kref) +{ + struct drm_panel *panel = container_of(kref, struct drm_panel, refcount); + + kfree(panel->container); +} + +/** + * drm_panel_get - Acquire a panel reference + * @panel: DRM panel + * + * This function increments the panel's refcount. + * Returns: + * Pointer to @panel + */ +struct drm_panel *drm_panel_get(struct drm_panel *panel) +{ + if (!panel) + return panel; + + kref_get(&panel->refcount); + + return panel; +} +EXPORT_SYMBOL(drm_panel_get); + +/** + * drm_panel_put - Release a panel reference + * @panel: DRM panel + * + * This function decrements the panel's reference count and frees the + * object if the reference count drops to zero. + */ +void drm_panel_put(struct drm_panel *panel) +{ + if (panel) + kref_put(&panel->refcount, __drm_panel_free); +} +EXPORT_SYMBOL(drm_panel_put); + +/** + * drm_panel_put_void - wrapper to drm_panel_put() taking a void pointer + * + * @data: pointer to @struct drm_panel, cast to a void pointer + * + * Wrapper of drm_panel_put() to be used when a function taking a void + * pointer is needed, for example as a devm action. + */ +static void drm_panel_put_void(void *data) +{ + struct drm_panel *panel = (struct drm_panel *)data; + + drm_panel_put(panel); +} + +void *__devm_drm_panel_alloc(struct device *dev, size_t size, size_t offset, + const struct drm_panel_funcs *funcs, + int connector_type) +{ + void *container; + struct drm_panel *panel; + int err; + + if (!funcs) { + dev_warn(dev, "Missing funcs pointer\n"); + return ERR_PTR(-EINVAL); + } + + container = kzalloc(size, GFP_KERNEL); + if (!container) + return ERR_PTR(-ENOMEM); + + panel = container + offset; + panel->container = container; + panel->funcs = funcs; + kref_init(&panel->refcount); + + err = devm_add_action_or_reset(dev, drm_panel_put_void, panel); + if (err) + return ERR_PTR(err); + + drm_panel_init(panel, dev, funcs, connector_type); + + return container; +} +EXPORT_SYMBOL(__devm_drm_panel_alloc); + +#ifdef CONFIG_OF +/** + * of_drm_find_panel - look up and reference a panel by device tree node + * @np: device tree node of the panel + * + * Searches the set of registered panels for one that matches the given device + * tree node. If a matching panel is found, the panel's reference count is + * incremented before returning a pointer to it. The caller must call + * drm_panel_put() when it no longer needs the panel pointer. + * + * Return: A reference-counted pointer to the panel registered for the specified + * device tree node or an ERR_PTR() if no panel matching the device tree node + * can be found. + * + * Possible error codes returned by this function: + * + * - EPROBE_DEFER: the panel device has not been probed yet, and the caller + * should retry later + * - ENODEV: the device is not available (status != "okay" or "ok") + */ +struct drm_panel *of_drm_find_panel(const struct device_node *np) +{ + struct drm_panel *panel; + + if (!of_device_is_available(np)) + return ERR_PTR(-ENODEV); + + mutex_lock(&panel_lock); + + list_for_each_entry(panel, &panel_list, list) { + if (panel->dev->of_node == np) { + drm_panel_get(panel); + mutex_unlock(&panel_lock); + return panel; + } + } + + mutex_unlock(&panel_lock); + return ERR_PTR(-EPROBE_DEFER); +} +EXPORT_SYMBOL(of_drm_find_panel); +#endif + +/* Find panel by fwnode. This should be identical to of_drm_find_panel(). */ +static struct drm_panel *find_panel_by_fwnode(const struct fwnode_handle *fwnode) +{ + struct drm_panel *panel; + + if (!fwnode_device_is_available(fwnode)) + return ERR_PTR(-ENODEV); + + mutex_lock(&panel_lock); + + list_for_each_entry(panel, &panel_list, list) { + if (dev_fwnode(panel->dev) == fwnode) { + mutex_unlock(&panel_lock); + return panel; + } + } + + mutex_unlock(&panel_lock); + + return ERR_PTR(-EPROBE_DEFER); +} + +/* Find panel by follower device */ +static struct drm_panel *find_panel_by_dev(struct device *follower_dev) +{ + struct fwnode_handle *fwnode; + struct drm_panel *panel; + + fwnode = fwnode_find_reference(dev_fwnode(follower_dev), "panel", 0); + if (IS_ERR(fwnode)) + return ERR_PTR(-ENODEV); + + panel = find_panel_by_fwnode(fwnode); + fwnode_handle_put(fwnode); + + return panel; +} + +/** + * drm_is_panel_follower() - Check if the device is a panel follower + * @dev: The 'struct device' to check + * + * This checks to see if a device needs to be power sequenced together with + * a panel using the panel follower API. + * + * The "panel" property of the follower points to the panel to be followed. + * + * Return: true if we should be power sequenced with a panel; false otherwise. + */ +bool drm_is_panel_follower(struct device *dev) +{ + /* + * The "panel" property is actually a phandle, but for simplicity we + * don't bother trying to parse it here. We just need to know if the + * property is there. + */ + return device_property_present(dev, "panel"); +} +EXPORT_SYMBOL(drm_is_panel_follower); + +/** + * drm_panel_add_follower() - Register something to follow panel state. + * @follower_dev: The 'struct device' for the follower. + * @follower: The panel follower descriptor for the follower. + * + * A panel follower is called right after preparing/enabling the panel and right + * before unpreparing/disabling the panel. It's primary intention is to power on + * an associated touchscreen, though it could be used for any similar devices. + * Multiple devices are allowed the follow the same panel. + * + * If a follower is added to a panel that's already been prepared/enabled, the + * follower's prepared/enabled callback is called right away. + * + * The "panel" property of the follower points to the panel to be followed. + * + * Return: 0 or an error code. Note that -ENODEV means that we detected that + * follower_dev is not actually following a panel. The caller may + * choose to ignore this return value if following a panel is optional. + */ +int drm_panel_add_follower(struct device *follower_dev, + struct drm_panel_follower *follower) +{ + struct drm_panel *panel; + int ret; + + panel = find_panel_by_dev(follower_dev); + if (IS_ERR(panel)) + return PTR_ERR(panel); + + get_device(panel->dev); + follower->panel = panel; + + mutex_lock(&panel->follower_lock); + + list_add_tail(&follower->list, &panel->followers); + if (panel->prepared && follower->funcs->panel_prepared) { + ret = follower->funcs->panel_prepared(follower); + if (ret < 0) + dev_info(panel->dev, "%ps failed: %d\n", + follower->funcs->panel_prepared, ret); + } + if (panel->enabled && follower->funcs->panel_enabled) { + ret = follower->funcs->panel_enabled(follower); + if (ret < 0) + dev_info(panel->dev, "%ps failed: %d\n", + follower->funcs->panel_enabled, ret); + } + + mutex_unlock(&panel->follower_lock); + + return 0; +} +EXPORT_SYMBOL(drm_panel_add_follower); + +/** + * drm_panel_remove_follower() - Reverse drm_panel_add_follower(). + * @follower: The panel follower descriptor for the follower. + * + * Undo drm_panel_add_follower(). This includes calling the follower's + * unpreparing/disabling function if we're removed from a panel that's currently + * prepared/enabled. + * + * Return: 0 or an error code. + */ +void drm_panel_remove_follower(struct drm_panel_follower *follower) +{ + struct drm_panel *panel = follower->panel; + int ret; + + mutex_lock(&panel->follower_lock); + + if (panel->enabled && follower->funcs->panel_disabling) { + ret = follower->funcs->panel_disabling(follower); + if (ret < 0) + dev_info(panel->dev, "%ps failed: %d\n", + follower->funcs->panel_disabling, ret); + } + if (panel->prepared && follower->funcs->panel_unpreparing) { + ret = follower->funcs->panel_unpreparing(follower); + if (ret < 0) + dev_info(panel->dev, "%ps failed: %d\n", + follower->funcs->panel_unpreparing, ret); + } + list_del_init(&follower->list); + + mutex_unlock(&panel->follower_lock); + + put_device(panel->dev); +} +EXPORT_SYMBOL(drm_panel_remove_follower); + +static void drm_panel_remove_follower_void(void *follower) +{ + drm_panel_remove_follower(follower); +} + +/** + * devm_drm_panel_add_follower() - devm version of drm_panel_add_follower() + * @follower_dev: The 'struct device' for the follower. + * @follower: The panel follower descriptor for the follower. + * + * Handles calling drm_panel_remove_follower() using devm on the follower_dev. + * + * Return: 0 or an error code. + */ +int devm_drm_panel_add_follower(struct device *follower_dev, + struct drm_panel_follower *follower) +{ + int ret; + + ret = drm_panel_add_follower(follower_dev, follower); + if (ret) + return ret; + + return devm_add_action_or_reset(follower_dev, + drm_panel_remove_follower_void, follower); +} +EXPORT_SYMBOL(devm_drm_panel_add_follower); + +#if IS_REACHABLE(CONFIG_BACKLIGHT_CLASS_DEVICE) +/** + * drm_panel_of_backlight - use backlight device node for backlight + * @panel: DRM panel + * + * Use this function to enable backlight handling if your panel + * uses device tree and has a backlight phandle. + * + * When the panel is enabled backlight will be enabled after a + * successful call to &drm_panel_funcs.enable() + * + * When the panel is disabled backlight will be disabled before the + * call to &drm_panel_funcs.disable(). + * + * A typical implementation for a panel driver supporting device tree + * will call this function at probe time. Backlight will then be handled + * transparently without requiring any intervention from the driver. + * + * Return: 0 on success or a negative error code on failure. + */ +int drm_panel_of_backlight(struct drm_panel *panel) +{ + struct backlight_device *backlight; + + if (!panel || !panel->dev) + return -EINVAL; + + backlight = devm_of_find_backlight(panel->dev); + + if (IS_ERR(backlight)) + return PTR_ERR(backlight); + + panel->backlight = backlight; + return 0; +} +EXPORT_SYMBOL(drm_panel_of_backlight); +#endif + +MODULE_AUTHOR("Thierry Reding "); +MODULE_DESCRIPTION("DRM panel infrastructure"); +MODULE_LICENSE("GPL and additional rights"); From 38873dd52850c3d713f006c240a2ce0abad52271 Mon Sep 17 00:00:00 2001 From: Pengyu Luo Date: Thu, 9 Jul 2026 22:28:45 +0800 Subject: [PATCH 24/46] dt-bindings: display: panel: Add Novatek NT36536 NT36536 is a driver IC used to drive MIPI-DSI panels. It is found in LENOVO Legion Y700 Gen4 with a dual-link 10-bit CSOT panel. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Pengyu Luo Signed-off-by: Neil Armstrong Link: https://patch.msgid.link/20260709142846.12463-2-mitltlatltl@gmail.com --- .../display/panel/novatek,nt36536.yaml | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/panel/novatek,nt36536.yaml diff --git a/Documentation/devicetree/bindings/display/panel/novatek,nt36536.yaml b/Documentation/devicetree/bindings/display/panel/novatek,nt36536.yaml new file mode 100644 index 000000000000..1e86f5329b22 --- /dev/null +++ b/Documentation/devicetree/bindings/display/panel/novatek,nt36536.yaml @@ -0,0 +1,91 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/display/panel/novatek,nt36536.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Novatek NT36536 based DSI display Panels + +maintainers: + - Pengyu Luo + +description: + The Novatek NT36536 is a generic DSI Panel IC used to drive dsi + panels. Support video mode panels from China Star Optoelectronics + Technology (CSOT), such as PP8807HB1-1 which is a dual-link 10-bit + panel. + +allOf: + - $ref: panel-common-dual.yaml# + +properties: + compatible: + items: + - enum: + - csot,pp8807hb1-1 + - const: novatek,nt36536 + + reg: + maxItems: 1 + + reset-gpios: + maxItems: 1 + + vddio-supply: + description: I/O source voltage rail + + vsp-supply: + description: Positive source voltage rail + + vsn-supply: + description: Negative source voltage rail + + backlight: true + ports: true + +required: + - compatible + - reg + - vddio-supply + - reset-gpios + - ports + +additionalProperties: false + +examples: + - | + #include + + dsi { + #address-cells = <1>; + #size-cells = <0>; + + panel@0 { + compatible = "csot,pp8807hb1-1", "novatek,nt36536"; + reg = <0>; + + vddio-supply = <&vreg_iovdd_1p8>; + reset-gpios = <&tlmm 98 GPIO_ACTIVE_LOW>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + panel_in_0: endpoint { + remote-endpoint = <&dsi0_out>; + }; + }; + + port@1{ + reg = <1>; + panel_in_1: endpoint { + remote-endpoint = <&dsi1_out>; + }; + }; + }; + }; + }; + +... From 75a5dbd1f4f71b1382dae973538af00178ba55a0 Mon Sep 17 00:00:00 2001 From: Pengyu Luo Date: Thu, 9 Jul 2026 22:28:46 +0800 Subject: [PATCH 25/46] drm/panel: Add Novatek NT36536 panel driver Add a driver for panels using the Novatek NT36536 Display Driver IC, including support for the CSOT PP8807HB1-1, a dual-link 10-bit panel found in LENOVO Legion Y700 Gen4. Reviewed-by: Neil Armstrong Signed-off-by: Pengyu Luo Signed-off-by: Neil Armstrong Link: https://patch.msgid.link/20260709142846.12463-3-mitltlatltl@gmail.com --- drivers/gpu/drm/panel/Kconfig | 11 + drivers/gpu/drm/panel/Makefile | 1 + drivers/gpu/drm/panel/panel-novatek-nt36536.c | 488 ++++++++++++++++++ 3 files changed, 500 insertions(+) create mode 100644 drivers/gpu/drm/panel/panel-novatek-nt36536.c diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig index cfbfb371bc67..53c0b95c26ca 100644 --- a/drivers/gpu/drm/panel/Kconfig +++ b/drivers/gpu/drm/panel/Kconfig @@ -662,6 +662,17 @@ config DRM_PANEL_NOVATEK_NT36523 around the Novatek NT36523 display controller, such as some Boe panels used in Xiaomi Mi Pad 5 and 5 Pro tablets. +config DRM_PANEL_NOVATEK_NT36536 + tristate "Novatek NT36536 panel driver" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + select DRM_KMS_HELPER + help + Say Y here if you want to enable support for Novatek NT36536-based + display panels, such as the one found in the LENOVO Legion Y700 + Gen4. + config DRM_PANEL_NOVATEK_NT36672A tristate "Novatek NT36672A DSI panel" depends on GPIOLIB diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile index 0f29f22f589e..3b523cf37833 100644 --- a/drivers/gpu/drm/panel/Makefile +++ b/drivers/gpu/drm/panel/Makefile @@ -64,6 +64,7 @@ obj-$(CONFIG_DRM_PANEL_NOVATEK_NT35532) += panel-novatek-nt35532.o obj-$(CONFIG_DRM_PANEL_NOVATEK_NT35560) += panel-novatek-nt35560.o obj-$(CONFIG_DRM_PANEL_NOVATEK_NT35950) += panel-novatek-nt35950.o obj-$(CONFIG_DRM_PANEL_NOVATEK_NT36523) += panel-novatek-nt36523.o +obj-$(CONFIG_DRM_PANEL_NOVATEK_NT36536) += panel-novatek-nt36536.o obj-$(CONFIG_DRM_PANEL_NOVATEK_NT36672A) += panel-novatek-nt36672a.o obj-$(CONFIG_DRM_PANEL_NOVATEK_NT36672E) += panel-novatek-nt36672e.o obj-$(CONFIG_DRM_PANEL_NOVATEK_NT37700F) += panel-novatek-nt37700f.o diff --git a/drivers/gpu/drm/panel/panel-novatek-nt36536.c b/drivers/gpu/drm/panel/panel-novatek-nt36536.c new file mode 100644 index 000000000000..2a82b54880c3 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-novatek-nt36536.c @@ -0,0 +1,488 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Novatek NT36536 DriverIC panels driver + * Copyright (c) 2026 Pengyu Luo + * + * Based on the sample code which is generated with + * linux-mdss-dsi-panel-driver-generator + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include