From 28fcfb9c20ed9b2ba6065817b46a858baaee3a09 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Tue, 1 Sep 2026 09:45:43 -0700 Subject: [PATCH 01/17] drm/gem: Drop ticket arg to lru_scan() Commit 3392291fc509 ("drm/msm: Fix shrinker deadlock") dropped the only use of the ticket arg, but at the time left drm_gem_lru_scan() unchanged to avoid conflicts with in-flight panthor shrinker support. This commit is the followup to remove the unused arg. Signed-off-by: Rob Clark Reviewed-by: Boris Brezillon Reviewed-by: Liviu Dudau Reviewed-by: Thomas Zimmermann Signed-off-by: Thomas Zimmermann Link: https://patch.msgid.link/20260901164544.642985-1-robin.clark@oss.qualcomm.com --- drivers/gpu/drm/drm_gem.c | 14 +++----------- drivers/gpu/drm/msm/msm_gem_shrinker.c | 22 ++++++++++------------ drivers/gpu/drm/panthor/panthor_gem.c | 14 ++++++-------- drivers/gpu/drm/panthor/panthor_mmu.c | 5 ++--- drivers/gpu/drm/panthor/panthor_mmu.h | 3 +-- include/drm/drm_gem.h | 3 +-- 6 files changed, 23 insertions(+), 38 deletions(-) diff --git a/drivers/gpu/drm/drm_gem.c b/drivers/gpu/drm/drm_gem.c index b0e0e4118573..e263982f78ce 100644 --- a/drivers/gpu/drm/drm_gem.c +++ b/drivers/gpu/drm/drm_gem.c @@ -1648,15 +1648,13 @@ EXPORT_SYMBOL(drm_gem_lru_move_tail); * @nr_to_scan: The number of pages to try to reclaim * @remaining: The number of pages left to reclaim, should be initialized by caller * @shrink: Callback to try to shrink/reclaim the object. - * @ticket: Optional ww_acquire_ctx context to use for locking */ unsigned long drm_gem_lru_scan(struct drm_device *dev, struct drm_gem_lru *lru, unsigned int nr_to_scan, unsigned long *remaining, - bool (*shrink)(struct drm_gem_object *obj, struct ww_acquire_ctx *ticket), - struct ww_acquire_ctx *ticket) + bool (*shrink)(struct drm_gem_object *obj)) { struct drm_gem_lru still_in_lru; struct drm_gem_object *obj; @@ -1689,20 +1687,17 @@ drm_gem_lru_scan(struct drm_device *dev, */ mutex_unlock(&dev->gem_lru_mutex); - if (ticket) - ww_acquire_init(ticket, &reservation_ww_class); - /* * Note that this still needs to be trylock, since we can * hit shrinker in response to trying to get backing pages * for this obj (ie. while it's lock is already held) */ - if (!ww_mutex_trylock(&obj->resv->lock, ticket)) { + if (!ww_mutex_trylock(&obj->resv->lock, NULL)) { *remaining += obj->size >> PAGE_SHIFT; goto tail; } - if (shrink(obj, ticket)) { + if (shrink(obj)) { freed += obj->size >> PAGE_SHIFT; /* @@ -1726,9 +1721,6 @@ drm_gem_lru_scan(struct drm_device *dev, dma_resv_unlock(obj->resv); - if (ticket) - ww_acquire_fini(ticket); - tail: drm_gem_object_put(obj); mutex_lock(&dev->gem_lru_mutex); diff --git a/drivers/gpu/drm/msm/msm_gem_shrinker.c b/drivers/gpu/drm/msm/msm_gem_shrinker.c index 9d2788f79ace..3514d5c84989 100644 --- a/drivers/gpu/drm/msm/msm_gem_shrinker.c +++ b/drivers/gpu/drm/msm/msm_gem_shrinker.c @@ -102,7 +102,7 @@ out_unlock: } static bool -purge(struct drm_gem_object *obj, struct ww_acquire_ctx *unused) +purge(struct drm_gem_object *obj) { if (!is_purgeable(to_msm_bo(obj))) return false; @@ -114,7 +114,7 @@ purge(struct drm_gem_object *obj, struct ww_acquire_ctx *unused) } static bool -evict(struct drm_gem_object *obj, struct ww_acquire_ctx *unused) +evict(struct drm_gem_object *obj) { if (is_unevictable(to_msm_bo(obj))) return false; @@ -133,21 +133,21 @@ wait_for_idle(struct drm_gem_object *obj) } static bool -active_purge(struct drm_gem_object *obj, struct ww_acquire_ctx *ticket) +active_purge(struct drm_gem_object *obj) { if (!wait_for_idle(obj)) return false; - return purge(obj, ticket); + return purge(obj); } static bool -active_evict(struct drm_gem_object *obj, struct ww_acquire_ctx *ticket) +active_evict(struct drm_gem_object *obj) { if (!wait_for_idle(obj)) return false; - return evict(obj, ticket); + return evict(obj); } static unsigned long @@ -156,7 +156,7 @@ msm_gem_shrinker_scan(struct shrinker *shrinker, struct shrink_control *sc) struct msm_drm_private *priv = shrinker->private_data; struct { struct drm_gem_lru *lru; - bool (*shrink)(struct drm_gem_object *obj, struct ww_acquire_ctx *ticket); + bool (*shrink)(struct drm_gem_object *obj); bool cond; unsigned long freed; unsigned long remaining; @@ -180,8 +180,7 @@ msm_gem_shrinker_scan(struct shrinker *shrinker, struct shrink_control *sc) stages[i].freed = drm_gem_lru_scan(priv->dev, stages[i].lru, nr, &stages[i].remaining, - stages[i].shrink, - NULL); + stages[i].shrink); nr -= stages[i].freed; freed += stages[i].freed; remaining += stages[i].remaining; @@ -222,7 +221,7 @@ msm_gem_shrinker_shrink(struct drm_device *dev, unsigned long nr_to_scan) static const int vmap_shrink_limit = 15; static bool -vmap_shrink(struct drm_gem_object *obj, struct ww_acquire_ctx *ticket) +vmap_shrink(struct drm_gem_object *obj) { if (!is_vunmapable(to_msm_bo(obj))) return false; @@ -250,8 +249,7 @@ msm_gem_shrinker_vmap(struct notifier_block *nb, unsigned long event, void *ptr) unmapped += drm_gem_lru_scan(priv->dev, lrus[idx], vmap_shrink_limit - unmapped, &remaining, - vmap_shrink, - NULL); + vmap_shrink); } *(unsigned long *)ptr += unmapped; diff --git a/drivers/gpu/drm/panthor/panthor_gem.c b/drivers/gpu/drm/panthor/panthor_gem.c index 72908be5e144..c07a44057e42 100644 --- a/drivers/gpu/drm/panthor/panthor_gem.c +++ b/drivers/gpu/drm/panthor/panthor_gem.c @@ -1410,8 +1410,7 @@ panthor_gem_shrinker_count(struct shrinker *shrinker, struct shrink_control *sc) return count ? count : SHRINK_EMPTY; } -static bool panthor_gem_try_evict_no_resv_wait(struct drm_gem_object *obj, - struct ww_acquire_ctx *ticket) +static bool panthor_gem_try_evict_no_resv_wait(struct drm_gem_object *obj) { /* * Track last locked entry for unwinding locks in error and @@ -1497,8 +1496,7 @@ out_unlock: return ret == 0; } -static bool panthor_gem_try_evict(struct drm_gem_object *obj, - struct ww_acquire_ctx *ticket) +static bool panthor_gem_try_evict(struct drm_gem_object *obj) { struct panthor_gem_object *bo = to_panthor_bo(obj); @@ -1506,7 +1504,7 @@ static bool panthor_gem_try_evict(struct drm_gem_object *obj, if (dma_resv_wait_timeout(obj->resv, DMA_RESV_USAGE_BOOKKEEP, false, 10) <= 0) return false; - return panthor_gem_try_evict_no_resv_wait(&bo->base, ticket); + return panthor_gem_try_evict_no_resv_wait(&bo->base); } static unsigned long @@ -1521,13 +1519,13 @@ panthor_gem_shrinker_scan(struct shrinker *shrinker, struct shrink_control *sc) freed += drm_gem_lru_scan(&ptdev->base, &ptdev->reclaim.unused, sc->nr_to_scan - freed, &remaining, - panthor_gem_try_evict_no_resv_wait, NULL); + panthor_gem_try_evict_no_resv_wait); if (freed >= sc->nr_to_scan) goto out; freed += drm_gem_lru_scan(&ptdev->base, &ptdev->reclaim.mmapped, sc->nr_to_scan - freed, &remaining, - panthor_gem_try_evict_no_resv_wait, NULL); + panthor_gem_try_evict_no_resv_wait); if (freed >= sc->nr_to_scan) goto out; @@ -1541,7 +1539,7 @@ panthor_gem_shrinker_scan(struct shrinker *shrinker, struct shrink_control *sc) freed += drm_gem_lru_scan(&ptdev->base, &ptdev->reclaim.gpu_mapped_shared, sc->nr_to_scan - freed, &remaining, - panthor_gem_try_evict, NULL); + panthor_gem_try_evict); out: #ifdef CONFIG_DEBUG_FS diff --git a/drivers/gpu/drm/panthor/panthor_mmu.c b/drivers/gpu/drm/panthor/panthor_mmu.c index 6f7b83dca6a5..d75d575473da 100644 --- a/drivers/gpu/drm/panthor/panthor_mmu.c +++ b/drivers/gpu/drm/panthor/panthor_mmu.c @@ -3280,8 +3280,7 @@ int panthor_vm_prepare_mapped_bos_resvs(struct drm_exec *exec, struct panthor_vm unsigned long panthor_mmu_reclaim_priv_bos(struct panthor_device *ptdev, unsigned int nr_to_scan, unsigned long *remaining, - bool (*shrink)(struct drm_gem_object *, - struct ww_acquire_ctx *)) + bool (*shrink)(struct drm_gem_object *)) { unsigned long freed = 0; LIST_HEAD(remaining_vms); @@ -3307,7 +3306,7 @@ panthor_mmu_reclaim_priv_bos(struct panthor_device *ptdev, freed += drm_gem_lru_scan(&ptdev->base, &vm->reclaim.lru, nr_to_scan - freed, - remaining, shrink, NULL); + remaining, shrink); mutex_lock(&ptdev->base.gem_lru_mutex); diff --git a/drivers/gpu/drm/panthor/panthor_mmu.h b/drivers/gpu/drm/panthor/panthor_mmu.h index 3522fbbce369..abc36e7204be 100644 --- a/drivers/gpu/drm/panthor/panthor_mmu.h +++ b/drivers/gpu/drm/panthor/panthor_mmu.h @@ -52,8 +52,7 @@ int panthor_vm_evict_bo_mappings_locked(struct panthor_gem_object *bo); unsigned long panthor_mmu_reclaim_priv_bos(struct panthor_device *ptdev, unsigned int nr_to_scan, unsigned long *remaining, - bool (*shrink)(struct drm_gem_object *, - struct ww_acquire_ctx *)); + bool (*shrink)(struct drm_gem_object *)); int panthor_vm_prepare_mapped_bos_resvs(struct drm_exec *exec, struct panthor_vm *vm, u32 slot_count); diff --git a/include/drm/drm_gem.h b/include/drm/drm_gem.h index 885244e375d3..c135253ab6cb 100644 --- a/include/drm/drm_gem.h +++ b/include/drm/drm_gem.h @@ -616,8 +616,7 @@ drm_gem_lru_scan(struct drm_device *dev, struct drm_gem_lru *lru, unsigned int nr_to_scan, unsigned long *remaining, - bool (*shrink)(struct drm_gem_object *obj, struct ww_acquire_ctx *ticket), - struct ww_acquire_ctx *ticket); + bool (*shrink)(struct drm_gem_object *obj)); int drm_gem_evict_locked(struct drm_gem_object *obj); From a9f09b5ea0c3db1e2d4c0f8d3ebdd612d8aa0366 Mon Sep 17 00:00:00 2001 From: Simon Wright Date: Thu, 21 May 2026 19:36:47 +1200 Subject: [PATCH 02/17] drm/bridge: dw-hdmi-qp: use drm_hdmi_acr_get_n_cts() helper for audio N/CTS dw_hdmi_qp_set_sample_rate() open-coded its own N and CTS lookup tables and search/compute helpers, but it lacked the out-of-table CTS fallback needed by strict HDMI sinks at TMDS rates not in the table. The original LG G3 OLED audio mute (linux-rockchip 070633) was caused by dw_hdmi_qp_find_cts() returning 0 at 185.625 MHz, leaving AUDPKT_ACR_CTS_OVR_EN clear and falling back to the controller's internal CTS auto-measurement, which produces incorrect timing on the wire at out-of-table rates. The shared drm_hdmi_acr_get_n_cts() helper in drivers/gpu/drm/display/drm_hdmi_helper.c already implements the correct behaviour: it has the HDMI 1.4b spec N/CTS tables, and for TMDS rates not in the table it computes CTS = (TMDS * N) / (128 * Fs) inline (the canonical HDMI spec formula). It is already used by drivers/gpu/drm/msm/hdmi/hdmi_audio.c. Convert dw_hdmi_qp_set_sample_rate() to call the helper. This removes ~200 lines of open-coded tables and search functions (dw_hdmi_qp_find_n, _compute_n, _find_cts, _audio_math_diff, _match_tmds_n_table, common_tmds_n_table[], common_tmds_cts_table[]) and fixes the strict-sink audio mute as a side effect of using the helper's complete N+CTS path. Tested on R76S (RK3576) running Linux 7.0.1, against the LG G3 OLED (the sink that originally reported the mute in linux-rockchip 070633) at four TMDS rates spanning HDMI 1.4 and HDMI 2.0: TMDS Mode Audio with v3 148.5 MHz 1080p60 8-bit plays 185.625 MHz 1080p60 10-bit plays 297 MHz 1080p100 8-bit plays 594 MHz 3840p60 8-bit plays Without this change, the LG G3 mutes audio at 185.625, 297, and 594 MHz (every rate outside dw-hdmi-qp's open-coded CTS table, which contained only 148.5 MHz and below). With this change, drm_hdmi_acr_get_n_cts() supplies the correct CTS at every rate -- table-canonical at 148.5 / 297 / 594 MHz, and computed via the HDMI 1.4b formula at 185.625 MHz. The 148.5 MHz row is a regression check confirming the in-table path is unchanged. The Kogan KALED43XU9210STA (a permissive HDMI 2.0 sink that plays audio at all rates with or without this change) was used as a no-regression control: audio plays at 594 MHz with v3 loaded. The open-coded N table in dw-hdmi-qp included optimised N values for some TMDS rates that are not in the helper's table (e.g. various non-CEA rates between 28-162 MHz). For those rates the helper's fallback returns N = 128 * Fs / 1000, which is the same value dw_hdmi_qp_compute_n() returned when no table optimisation was needed; no audio regression has been observed at the rates tested above. Reported-by: Simon Wright Closes: https://lore.kernel.org/linux-rockchip/ME3P282MB21960D9D68BFF520316BDFCEA83E2@ME3P282MB2196.AUSP282.PROD.OUTLOOK.COM/ Suggested-by: Cristian Ciocaltea Suggested-by: Jonas Karlman Assisted-by: Claude:claude-opus-4-7 Signed-off-by: Simon Wright Tested-by: Igor Paunovic # RK3588 Orange Pi 5 Plus, both HDMI controllers Tested-by: Christian Hewitt Reviewed-by: Cristian Ciocaltea Tested-by: Cristian Ciocaltea Signed-off-by: Heiko Stuebner Link: https://patch.msgid.link/86fcf349-0a7a-4618-9001-612371b0f71b@symple.nz --- drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c | 209 +------------------ 1 file changed, 1 insertion(+), 208 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c index cfd1b18c06e6..c9568aea98dc 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c @@ -40,88 +40,6 @@ #define SCRAMB_POLL_DELAY_MS 3000 -/* - * Unless otherwise noted, entries in this table are 100% optimization. - * Values can be obtained from dw_hdmi_qp_compute_n() but that function is - * slow so we pre-compute values we expect to see. - * - * The values for TMDS 25175, 25200, 27000, 54000, 74250 and 148500 kHz are - * the recommended N values specified in the Audio chapter of the HDMI - * specification. - */ -static const struct dw_hdmi_audio_tmds_n { - unsigned long tmds; - unsigned int n_32k; - unsigned int n_44k1; - unsigned int n_48k; -} common_tmds_n_table[] = { - { .tmds = 25175000, .n_32k = 4576, .n_44k1 = 7007, .n_48k = 6864, }, - { .tmds = 25200000, .n_32k = 4096, .n_44k1 = 6272, .n_48k = 6144, }, - { .tmds = 27000000, .n_32k = 4096, .n_44k1 = 6272, .n_48k = 6144, }, - { .tmds = 28320000, .n_32k = 4096, .n_44k1 = 5586, .n_48k = 6144, }, - { .tmds = 30240000, .n_32k = 4096, .n_44k1 = 5642, .n_48k = 6144, }, - { .tmds = 31500000, .n_32k = 4096, .n_44k1 = 5600, .n_48k = 6144, }, - { .tmds = 32000000, .n_32k = 4096, .n_44k1 = 5733, .n_48k = 6144, }, - { .tmds = 33750000, .n_32k = 4096, .n_44k1 = 6272, .n_48k = 6144, }, - { .tmds = 36000000, .n_32k = 4096, .n_44k1 = 5684, .n_48k = 6144, }, - { .tmds = 40000000, .n_32k = 4096, .n_44k1 = 5733, .n_48k = 6144, }, - { .tmds = 49500000, .n_32k = 4096, .n_44k1 = 5488, .n_48k = 6144, }, - { .tmds = 50000000, .n_32k = 4096, .n_44k1 = 5292, .n_48k = 6144, }, - { .tmds = 54000000, .n_32k = 4096, .n_44k1 = 6272, .n_48k = 6144, }, - { .tmds = 65000000, .n_32k = 4096, .n_44k1 = 7056, .n_48k = 6144, }, - { .tmds = 68250000, .n_32k = 4096, .n_44k1 = 5376, .n_48k = 6144, }, - { .tmds = 71000000, .n_32k = 4096, .n_44k1 = 7056, .n_48k = 6144, }, - { .tmds = 72000000, .n_32k = 4096, .n_44k1 = 5635, .n_48k = 6144, }, - { .tmds = 73250000, .n_32k = 11648, .n_44k1 = 14112, .n_48k = 6144, }, - { .tmds = 74250000, .n_32k = 4096, .n_44k1 = 6272, .n_48k = 6144, }, - { .tmds = 75000000, .n_32k = 4096, .n_44k1 = 5880, .n_48k = 6144, }, - { .tmds = 78750000, .n_32k = 4096, .n_44k1 = 5600, .n_48k = 6144, }, - { .tmds = 78800000, .n_32k = 4096, .n_44k1 = 5292, .n_48k = 6144, }, - { .tmds = 79500000, .n_32k = 4096, .n_44k1 = 4704, .n_48k = 6144, }, - { .tmds = 83500000, .n_32k = 4096, .n_44k1 = 7056, .n_48k = 6144, }, - { .tmds = 85500000, .n_32k = 4096, .n_44k1 = 5488, .n_48k = 6144, }, - { .tmds = 88750000, .n_32k = 4096, .n_44k1 = 14112, .n_48k = 6144, }, - { .tmds = 97750000, .n_32k = 4096, .n_44k1 = 14112, .n_48k = 6144, }, - { .tmds = 101000000, .n_32k = 4096, .n_44k1 = 7056, .n_48k = 6144, }, - { .tmds = 106500000, .n_32k = 4096, .n_44k1 = 4704, .n_48k = 6144, }, - { .tmds = 108000000, .n_32k = 4096, .n_44k1 = 5684, .n_48k = 6144, }, - { .tmds = 115500000, .n_32k = 4096, .n_44k1 = 5712, .n_48k = 6144, }, - { .tmds = 119000000, .n_32k = 4096, .n_44k1 = 5544, .n_48k = 6144, }, - { .tmds = 135000000, .n_32k = 4096, .n_44k1 = 5488, .n_48k = 6144, }, - { .tmds = 146250000, .n_32k = 11648, .n_44k1 = 6272, .n_48k = 6144, }, - { .tmds = 148500000, .n_32k = 4096, .n_44k1 = 6272, .n_48k = 6144, }, - { .tmds = 154000000, .n_32k = 4096, .n_44k1 = 5544, .n_48k = 6144, }, - { .tmds = 162000000, .n_32k = 4096, .n_44k1 = 5684, .n_48k = 6144, }, - - /* For 297 MHz+ HDMI spec have some other rule for setting N */ - { .tmds = 297000000, .n_32k = 3073, .n_44k1 = 4704, .n_48k = 5120, }, - { .tmds = 594000000, .n_32k = 3073, .n_44k1 = 9408, .n_48k = 10240,}, - - /* End of table */ - { .tmds = 0, .n_32k = 0, .n_44k1 = 0, .n_48k = 0, }, -}; - -/* - * These are the CTS values as recommended in the Audio chapter of the HDMI - * specification. - */ -static const struct dw_hdmi_audio_tmds_cts { - unsigned long tmds; - unsigned int cts_32k; - unsigned int cts_44k1; - unsigned int cts_48k; -} common_tmds_cts_table[] = { - { .tmds = 25175000, .cts_32k = 28125, .cts_44k1 = 31250, .cts_48k = 28125, }, - { .tmds = 25200000, .cts_32k = 25200, .cts_44k1 = 28000, .cts_48k = 25200, }, - { .tmds = 27000000, .cts_32k = 27000, .cts_44k1 = 30000, .cts_48k = 27000, }, - { .tmds = 54000000, .cts_32k = 54000, .cts_44k1 = 60000, .cts_48k = 54000, }, - { .tmds = 74250000, .cts_32k = 74250, .cts_44k1 = 82500, .cts_48k = 74250, }, - { .tmds = 148500000, .cts_32k = 148500, .cts_44k1 = 165000, .cts_48k = 148500, }, - - /* End of table */ - { .tmds = 0, .cts_32k = 0, .cts_44k1 = 0, .cts_48k = 0, }, -}; - struct dw_hdmi_qp_i2c { struct i2c_adapter adap; @@ -213,130 +131,6 @@ static void dw_hdmi_qp_set_cts_n(struct dw_hdmi_qp *hdmi, unsigned int cts, AUDPKT_ACR_CONTROL1); } -static int dw_hdmi_qp_match_tmds_n_table(struct dw_hdmi_qp *hdmi, - unsigned long pixel_clk, - unsigned long freq) -{ - const struct dw_hdmi_audio_tmds_n *tmds_n = NULL; - int i; - - for (i = 0; common_tmds_n_table[i].tmds != 0; i++) { - if (pixel_clk == common_tmds_n_table[i].tmds) { - tmds_n = &common_tmds_n_table[i]; - break; - } - } - - if (!tmds_n) - return -ENOENT; - - switch (freq) { - case 32000: - return tmds_n->n_32k; - case 44100: - case 88200: - case 176400: - return (freq / 44100) * tmds_n->n_44k1; - case 48000: - case 96000: - case 192000: - return (freq / 48000) * tmds_n->n_48k; - default: - return -ENOENT; - } -} - -static u32 dw_hdmi_qp_audio_math_diff(unsigned int freq, unsigned int n, - unsigned int pixel_clk) -{ - u64 cts = mul_u32_u32(pixel_clk, n); - - return do_div(cts, 128 * freq); -} - -static unsigned int dw_hdmi_qp_compute_n(struct dw_hdmi_qp *hdmi, - unsigned long pixel_clk, - unsigned long freq) -{ - unsigned int min_n = DIV_ROUND_UP((128 * freq), 1500); - unsigned int max_n = (128 * freq) / 300; - unsigned int ideal_n = (128 * freq) / 1000; - unsigned int best_n_distance = ideal_n; - unsigned int best_n = 0; - u64 best_diff = U64_MAX; - int n; - - /* If the ideal N could satisfy the audio math, then just take it */ - if (dw_hdmi_qp_audio_math_diff(freq, ideal_n, pixel_clk) == 0) - return ideal_n; - - for (n = min_n; n <= max_n; n++) { - u64 diff = dw_hdmi_qp_audio_math_diff(freq, n, pixel_clk); - - if (diff < best_diff || - (diff == best_diff && abs(n - ideal_n) < best_n_distance)) { - best_n = n; - best_diff = diff; - best_n_distance = abs(best_n - ideal_n); - } - - /* - * The best N already satisfy the audio math, and also be - * the closest value to ideal N, so just cut the loop. - */ - if (best_diff == 0 && (abs(n - ideal_n) > best_n_distance)) - break; - } - - return best_n; -} - -static unsigned int dw_hdmi_qp_find_n(struct dw_hdmi_qp *hdmi, unsigned long pixel_clk, - unsigned long sample_rate) -{ - int n = dw_hdmi_qp_match_tmds_n_table(hdmi, pixel_clk, sample_rate); - - if (n > 0) - return n; - - dev_warn(hdmi->dev, "Rate %lu missing; compute N dynamically\n", - pixel_clk); - - return dw_hdmi_qp_compute_n(hdmi, pixel_clk, sample_rate); -} - -static unsigned int dw_hdmi_qp_find_cts(struct dw_hdmi_qp *hdmi, unsigned long pixel_clk, - unsigned long sample_rate) -{ - const struct dw_hdmi_audio_tmds_cts *tmds_cts = NULL; - int i; - - for (i = 0; common_tmds_cts_table[i].tmds != 0; i++) { - if (pixel_clk == common_tmds_cts_table[i].tmds) { - tmds_cts = &common_tmds_cts_table[i]; - break; - } - } - - if (!tmds_cts) - return 0; - - switch (sample_rate) { - case 32000: - return tmds_cts->cts_32k; - case 44100: - case 88200: - case 176400: - return tmds_cts->cts_44k1; - case 48000: - case 96000: - case 192000: - return tmds_cts->cts_48k; - default: - return -ENOENT; - } -} - static void dw_hdmi_qp_set_audio_interface(struct dw_hdmi_qp *hdmi, struct hdmi_codec_daifmt *fmt, struct hdmi_codec_params *hparms) @@ -456,8 +250,7 @@ static void dw_hdmi_qp_set_sample_rate(struct dw_hdmi_qp *hdmi, unsigned long lo { unsigned int n, cts; - n = dw_hdmi_qp_find_n(hdmi, tmds_char_rate, sample_rate); - cts = dw_hdmi_qp_find_cts(hdmi, tmds_char_rate, sample_rate); + drm_hdmi_acr_get_n_cts(tmds_char_rate, sample_rate, &n, &cts); dw_hdmi_qp_set_cts_n(hdmi, cts, n); } From 836a0bb73c1719649f03e18670bc625303f0f38f Mon Sep 17 00:00:00 2001 From: Tomi Valkeinen Date: Thu, 3 Sep 2026 12:38:40 +0300 Subject: [PATCH 03/17] ASoC: ti: omap-hdmi: Fix build error from a merge commit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 'ad->card' was removed in commit 9af2c1d4f847 ("ASoC: ti: omap-hdmi: remove unused *card"), but commit 699e5975e2a5 ("drm/omap: Report HDMI hotplug events to the ASoC HDMI codec") moved that code line. These commits were applied to different trees, and the merge was not quite right: assignment to 'ad->card' from the latter commit was left in the file, causing: sound/soc/ti/omap-hdmi.c:408:11: error: ‘struct hdmi_audio_data’ has no member named ‘card’ Fix the issue by removing the line. Reported-by: Mark Brown Closes: https://lore.kernel.org/all/apbbvQSfUbzYRFaw@sirena.org.uk/ Fixes: 1ae7fe832c2d ("Merge drm/drm-next into drm-misc-next") Reviewed-by: Ivaylo Dimitrov Link: https://patch.msgid.link/20260903-omap-hdmi-fix-v1-1-b0447a1d35d5@ideasonboard.com Signed-off-by: Tomi Valkeinen --- sound/soc/ti/omap-hdmi.c | 1 - 1 file changed, 1 deletion(-) diff --git a/sound/soc/ti/omap-hdmi.c b/sound/soc/ti/omap-hdmi.c index c32ec7c0b0cf..2cabd4cc5b0b 100644 --- a/sound/soc/ti/omap-hdmi.c +++ b/sound/soc/ti/omap-hdmi.c @@ -405,7 +405,6 @@ static int omap_hdmi_audio_probe(struct platform_device *pdev) card->dev = dev; atomic_set(&ad->jack_state, -1); - ad->card = card; snd_soc_card_set_drvdata(card, ad); ret = devm_snd_soc_register_card(dev, card); From 730c6d8072d8d9a6dbfd06c7c5ab4479167a792a Mon Sep 17 00:00:00 2001 From: Rodrigo Vivi Date: Thu, 20 Aug 2026 10:28:03 -0400 Subject: [PATCH 04/17] drm/amd/display: Drop KUnit tests for removed parse_hdmi_amd_vsdb() parse_hdmi_amd_vsdb() was removed when HDMI FreeSync detection moved to the common EDID parser, but its declaration and KUnit tests remained, breaking modpost: ERROR: modpost: "parse_hdmi_amd_vsdb" [...connector_test.ko] undefined! Remove the stale declaration and the three dead test cases. Fixes: f65198b3d073 ("drm/amd/display: Use HDMI FreeSync range from common EDID parser") Cc: Alex Huang Cc: Mario Limonciello Assisted-by: Copilot:Claude-Opus-5 Signed-off-by: Rodrigo Vivi Reviewed-by: Mario Limonciello (AMD) Signed-off-by: Alex Deucher Link: https://patch.msgid.link/20260820142802.1342066-2-rodrigo.vivi@intel.com --- .../display/amdgpu_dm/amdgpu_dm_connector.h | 3 - .../tests/amdgpu_dm_connector_test.c | 73 ------------------- 2 files changed, 76 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h index 4e9eb3ff2c90..ce900844602e 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h @@ -153,9 +153,6 @@ void parse_edid_displayid_vrr(struct drm_connector *connector, const struct edid *edid); int get_amd_vsdb(struct amdgpu_dm_connector *aconnector, struct amdgpu_hdmi_vsdb_info *vsdb_info); -int parse_hdmi_amd_vsdb(struct amdgpu_dm_connector *aconnector, - const struct edid *edid, - struct amdgpu_hdmi_vsdb_info *vsdb_info); void amdgpu_dm_connector_funcs_force(struct drm_connector *connector); enum dc_status dm_validate_stream_and_context(struct dc *dc, struct dc_stream_state *stream); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c index 0d2f9dbce0a9..03038cc54cc0 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c @@ -4874,75 +4874,6 @@ static void dm_test_get_amd_vsdb_supported(struct kunit *test) KUNIT_EXPECT_TRUE(test, vsdb_info.replay_mode); } -/** - * dm_test_parse_hdmi_amd_vsdb_null_edid - Test NULL EDID returns -ENODEV - * @test: The KUnit test context - */ -static void dm_test_parse_hdmi_amd_vsdb_null_edid(struct kunit *test) -{ - struct amdgpu_dm_connector *aconnector; - struct amdgpu_hdmi_vsdb_info vsdb_info = {0}; - - aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, aconnector); - - KUNIT_EXPECT_EQ(test, - parse_hdmi_amd_vsdb(aconnector, NULL, &vsdb_info), - -ENODEV); -} - -/** - * dm_test_parse_hdmi_amd_vsdb_no_extensions - Test EDID without extensions - * @test: The KUnit test context - * - * An EDID that declares no extension blocks has no CEA block to parse. - */ -static void dm_test_parse_hdmi_amd_vsdb_no_extensions(struct kunit *test) -{ - struct amdgpu_dm_connector *aconnector; - struct amdgpu_hdmi_vsdb_info vsdb_info = {0}; - struct edid *edid; - - aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, aconnector); - edid = kunit_kzalloc(test, sizeof(*edid), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, edid); - - edid->extensions = 0; - - KUNIT_EXPECT_EQ(test, - parse_hdmi_amd_vsdb(aconnector, edid, &vsdb_info), - -ENODEV); -} - -/** - * dm_test_parse_hdmi_amd_vsdb_no_cea_ext - Test EDID with no CEA extension - * @test: The KUnit test context - * - * An extension block that is not a CEA block leaves no VSDB to parse. - */ -static void dm_test_parse_hdmi_amd_vsdb_no_cea_ext(struct kunit *test) -{ - struct amdgpu_dm_connector *aconnector; - struct amdgpu_hdmi_vsdb_info vsdb_info = {0}; - struct edid *edid; - u8 *raw; - - aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, aconnector); - - /* Base block + one extension block that is NOT a CEA extension. */ - raw = kunit_kzalloc(test, 2 * EDID_LENGTH, GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, raw); - edid = (struct edid *)raw; - edid->extensions = 1; - raw[EDID_LENGTH] = DM_TEST_DISPLAYID_EXT; - - KUNIT_EXPECT_EQ(test, - parse_hdmi_amd_vsdb(aconnector, edid, &vsdb_info), - -ENODEV); -} - /** * dm_test_parse_displayid_vrr_null_edid - Test NULL EDID leaves range untouched * @test: The KUnit test context @@ -5647,10 +5578,6 @@ static struct kunit_case amdgpu_dm_connector_tests[] = { /* get_amd_vsdb */ KUNIT_CASE(dm_test_get_amd_vsdb_unsupported), KUNIT_CASE(dm_test_get_amd_vsdb_supported), - /* parse_hdmi_amd_vsdb */ - KUNIT_CASE(dm_test_parse_hdmi_amd_vsdb_null_edid), - KUNIT_CASE(dm_test_parse_hdmi_amd_vsdb_no_extensions), - KUNIT_CASE(dm_test_parse_hdmi_amd_vsdb_no_cea_ext), /* parse_edid_displayid_vrr */ KUNIT_CASE(dm_test_parse_displayid_vrr_null_edid), KUNIT_CASE(dm_test_parse_displayid_vrr_no_displayid), From dfd2bd7af66564b349a84a2c6a951ea3286a53cf Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Tue, 25 Aug 2026 16:39:22 +0200 Subject: [PATCH 05/17] drm/bridge: lontium: Drop redundant vendor prefix from I2C device name Device names for i2c_device_id are not OF compatibles and have a meaning only for in-tree users (which there are no except the driver itself), thus vendor prefix in this name is completely redundant. It is actually a discouraged pattern because for OF platforms it allows matching driver by I2C bus, instead of OF, thus hiding incomplete way of obtaining driver match data (lack of i2c_get_match_data() usage). Signed-off-by: Krzysztof Kozlowski Reviewed-by: Luca Ceresoli Link: https://patch.msgid.link/20260825143921.512966-2-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/lontium-lt8713sx.c | 2 +- drivers/gpu/drm/bridge/lontium-lt9211.c | 2 +- drivers/gpu/drm/bridge/lontium-lt9611.c | 2 +- drivers/gpu/drm/bridge/lontium-lt9611uxc.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/bridge/lontium-lt8713sx.c b/drivers/gpu/drm/bridge/lontium-lt8713sx.c index bfff28883280..926f9e1e85bf 100644 --- a/drivers/gpu/drm/bridge/lontium-lt8713sx.c +++ b/drivers/gpu/drm/bridge/lontium-lt8713sx.c @@ -572,7 +572,7 @@ static void lt8713sx_remove(struct i2c_client *client) } static struct i2c_device_id lt8713sx_id[] = { - { .name = "lontium,lt8713sx" }, + { .name = "lt8713sx" }, { /* sentinel */ } }; diff --git a/drivers/gpu/drm/bridge/lontium-lt9211.c b/drivers/gpu/drm/bridge/lontium-lt9211.c index 23682384daf4..285cda58f661 100644 --- a/drivers/gpu/drm/bridge/lontium-lt9211.c +++ b/drivers/gpu/drm/bridge/lontium-lt9211.c @@ -773,7 +773,7 @@ static void lt9211_remove(struct i2c_client *client) } static const struct i2c_device_id lt9211_id[] = { - { .name = "lontium,lt9211" }, + { .name = "lt9211" }, { } }; MODULE_DEVICE_TABLE(i2c, lt9211_id); diff --git a/drivers/gpu/drm/bridge/lontium-lt9611.c b/drivers/gpu/drm/bridge/lontium-lt9611.c index 3ebd8992cd94..cdc2b188e2d8 100644 --- a/drivers/gpu/drm/bridge/lontium-lt9611.c +++ b/drivers/gpu/drm/bridge/lontium-lt9611.c @@ -1227,7 +1227,7 @@ static void lt9611_remove(struct i2c_client *client) } static const struct i2c_device_id lt9611_id[] = { - { .name = "lontium,lt9611" }, + { .name = "lt9611" }, { } }; MODULE_DEVICE_TABLE(i2c, lt9611_id); diff --git a/drivers/gpu/drm/bridge/lontium-lt9611uxc.c b/drivers/gpu/drm/bridge/lontium-lt9611uxc.c index 42a6503cd0f9..b3bb7f2cebb3 100644 --- a/drivers/gpu/drm/bridge/lontium-lt9611uxc.c +++ b/drivers/gpu/drm/bridge/lontium-lt9611uxc.c @@ -899,7 +899,7 @@ static void lt9611uxc_remove(struct i2c_client *client) } static const struct i2c_device_id lt9611uxc_id[] = { - { .name = "lontium,lt9611uxc" }, + { .name = "lt9611uxc" }, { /* sentinel */ } }; From 8ce26b09f652c14732eec83f96502db7074fa21b Mon Sep 17 00:00:00 2001 From: Shixiong Ou Date: Thu, 3 Sep 2026 15:11:49 +0800 Subject: [PATCH 06/17] drm/gma500: Create the primary plane in the driver drm_crtc_init() creates the primary plane from a fixed format list that includes ARGB8888. The display engine programs the primary plane with DISPPLANE_32BPP_NO_ALPHA, so it does not support per-pixel alpha and must not advertise alpha formats. Since commit 860e748bddcc ("drm: ensure blend mode supported if pixel format with alpha exposed"), drm_mode_config_validate() warns about this at probe time. Replace drm_crtc_init() with a driver-owned primary plane that advertises only XRGB8888. The plane is allocated by drmm_universal_plane_alloc() and the "pixel blend mode" property is not needed because no format with alpha is exposed. Signed-off-by: Shixiong Ou Signed-off-by: Patrik Jakobsson Link: https://patch.msgid.link/20260903071149.423579-1-oushixiong1025@163.com --- drivers/gpu/drm/gma500/psb_intel_display.c | 34 +++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/gma500/psb_intel_display.c b/drivers/gpu/drm/gma500/psb_intel_display.c index 0df75a4a7739..f79484b9d593 100644 --- a/drivers/gpu/drm/gma500/psb_intel_display.c +++ b/drivers/gpu/drm/gma500/psb_intel_display.c @@ -9,8 +9,11 @@ #include #include +#include #include #include +#include +#include #include #include "framebuffer.h" @@ -471,11 +474,25 @@ out: REG_WRITE(base[gma_crtc->pipe], 0); } +/* + * The display engine programs the primary plane with + * DISPPLANE_32BPP_NO_ALPHA, so no formats with alpha. + */ +static const uint32_t gma_primary_formats[] = { + DRM_FORMAT_XRGB8888, +}; + +static const struct drm_plane_funcs gma_primary_plane_funcs = { + .update_plane = drm_plane_helper_update_primary, + .disable_plane = drm_plane_helper_disable_primary, +}; + void psb_intel_crtc_init(struct drm_device *dev, int pipe, struct psb_intel_mode_device *mode_dev) { struct drm_psb_private *dev_priv = to_drm_psb_private(dev); struct gma_crtc *gma_crtc; + struct drm_plane *primary; int i; /* We allocate a extra array of drm_connector pointers @@ -494,7 +511,17 @@ void psb_intel_crtc_init(struct drm_device *dev, int pipe, return; } - drm_crtc_init(dev, &gma_crtc->base, &gma_crtc_funcs); + primary = drmm_universal_plane_alloc(dev, struct drm_plane, dev, 0, + &gma_primary_plane_funcs, + gma_primary_formats, + ARRAY_SIZE(gma_primary_formats), + NULL, DRM_PLANE_TYPE_PRIMARY, NULL); + if (IS_ERR(primary)) + goto err_free_crtc; + + if (drm_crtc_init_with_planes(dev, &gma_crtc->base, primary, NULL, + &gma_crtc_funcs, NULL)) + goto err_free_crtc; /* Set the CRTC clock functions from chip specific data */ gma_crtc->clock_funcs = dev_priv->ops->clock_funcs; @@ -524,6 +551,11 @@ void psb_intel_crtc_init(struct drm_device *dev, int pipe, /* Set to true so that the pipe is forced off on initial config. */ gma_crtc->active = true; + return; + +err_free_crtc: + kfree(gma_crtc->crtc_state); + kfree(gma_crtc); } struct drm_crtc *psb_intel_get_crtc_from_pipe(struct drm_device *dev, int pipe) From af9b0fc07000db17656580fd38e5ff27c36c2ab9 Mon Sep 17 00:00:00 2001 From: Damon Ding Date: Fri, 28 Aug 2026 14:51:49 +0800 Subject: [PATCH 07/17] dt-bindings: display: bridge: analogix-dp: Add data-lanes support for endpoint Add data-lanes property support to the port@1 endpoint for physical lane mapping configuration. Lane mapping is mainly used for below scenarios: 1. Correct PCB lane swap and differential line routing crossover without hardware changes; 2. Adapt mismatched lane pin definitions between SoC and eDP panel; 3. Support multiple panel hardware variants on the same board by configuring data-lanes in device tree only. Additionally, add data-lanes setting in Rockchip eDP DT node example to show actual lane mapping usage. Acked-by: Conor Dooley Reviewed-by: Sebastian Reichel Reviewed-by: Luca Ceresoli Signed-off-by: Damon Ding Link: https://patch.msgid.link/20260828065153.590802-2-damon.ding@rock-chips.com Signed-off-by: Luca Ceresoli --- .../bindings/display/bridge/analogix,dp.yaml | 19 ++++++++++++++----- .../rockchip/rockchip,analogix-dp.yaml | 1 + 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/Documentation/devicetree/bindings/display/bridge/analogix,dp.yaml b/Documentation/devicetree/bindings/display/bridge/analogix,dp.yaml index 62f0521b0924..ecf206871cdd 100644 --- a/Documentation/devicetree/bindings/display/bridge/analogix,dp.yaml +++ b/Documentation/devicetree/bindings/display/bridge/analogix,dp.yaml @@ -42,13 +42,22 @@ properties: properties: port@0: $ref: /schemas/graph.yaml#/properties/port - description: - Input node to receive pixel data. + description: Input node to receive pixel data. port@1: - $ref: /schemas/graph.yaml#/properties/port - description: - Port node with one endpoint connected to a dp-connector node. + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false + description: Port node with one endpoint connected to sink device node. + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + properties: + data-lanes: + minItems: 1 + maxItems: 4 + items: + enum: [ 0, 1, 2, 3 ] required: - port@0 diff --git a/Documentation/devicetree/bindings/display/rockchip/rockchip,analogix-dp.yaml b/Documentation/devicetree/bindings/display/rockchip/rockchip,analogix-dp.yaml index 6548f157fd96..39dafe75a680 100644 --- a/Documentation/devicetree/bindings/display/rockchip/rockchip,analogix-dp.yaml +++ b/Documentation/devicetree/bindings/display/rockchip/rockchip,analogix-dp.yaml @@ -160,6 +160,7 @@ examples: reg = <1>; edp_out_panel: endpoint { + data-lanes = <0 1>; remote-endpoint = <&panel_in_edp>; }; }; From 6eac4c8bb10302abc0ea0bd1d506c46ec789ce2b Mon Sep 17 00:00:00 2001 From: Damon Ding Date: Fri, 28 Aug 2026 14:51:50 +0800 Subject: [PATCH 08/17] drm/dp: Add helper to validate DP lane counts Add a generic helper function drm_dp_lane_count_is_valid() to check if a DisplayPort lane count is valid. According to the DP specification, only 1, 2, or 4 lanes are supported. This helper avoids duplicating DP lane count validation logic across individual DisplayPort drivers. Suggested-by: Luca Ceresoli Reviewed-by: Luca Ceresoli Signed-off-by: Damon Ding Link: https://patch.msgid.link/20260828065153.590802-3-damon.ding@rock-chips.com Signed-off-by: Luca Ceresoli --- include/drm/display/drm_dp_helper.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/drm/display/drm_dp_helper.h b/include/drm/display/drm_dp_helper.h index ab16c1be3900..5b05a7fbe174 100644 --- a/include/drm/display/drm_dp_helper.h +++ b/include/drm/display/drm_dp_helper.h @@ -139,6 +139,12 @@ bool drm_dp_as_sdp_supported(struct drm_dp_aux *aux, const u8 dpcd[DP_RECEIVER_C int drm_dp_psr_setup_time(const u8 psr_cap[EDP_PSR_RECEIVER_CAP_SIZE]); +static inline bool +drm_dp_lane_count_is_valid(int lane_count) +{ + return lane_count == 1 || lane_count == 2 || lane_count == 4; +} + static inline int drm_dp_max_link_rate(const u8 dpcd[DP_RECEIVER_CAP_SIZE]) { From 02f569dd66c94dd67b2586f776235cf4177409ed Mon Sep 17 00:00:00 2001 From: Damon Ding Date: Fri, 28 Aug 2026 14:51:51 +0800 Subject: [PATCH 09/17] drm/bridge: analogix_dp: Restore mandatory samsung DP DT properties Revert the change that made samsung,link-rate and samsung,lane-count optional for Exynos DP. Add error checking to fail probe early if the required DT properties are missing. If these properties are missing, video_info->max_link_rate and video_info->max_lane_count remain zero, and so do link_train.link_rate and link_train.lane_count used in the subsequent link training flow, resulting in link training failure. There is no way at all a device can work without these properties. Here is the code flow when either max_link_rate or max_lane_count is 0: analogix_dp_commit() -> analogix_dp_full_link_train(dp, max_lanes = 0, max_rate = 0) analogix_dp_full_link_train(max_lanes, max_rate): // Read sink capabilities via DPCD and sanitize them link_rate = read_dpcd(DP_MAX_LINK_RATE); // >= 0x06 after fixup lane_count = read_dpcd(DP_MAX_LANE_COUNT); // >= 1 after fixup // Clamp by the limits from DT if (link_rate > max_rate) // 0x06 > 0, always true link_rate = max_rate; // link_rate = 0 if (lane_count > max_lanes) // 1 > 0, always true lane_count = max_lanes; // lane_count = 0 // Configure TX with the zeroed values set_link_bandwidth(link_rate = 0) // writel() is only executed for bwtype == 0x06/0x0a, // so LINK_BW_SET is never written and stays at // reset value; phy_configure() gets link_rate = 0. set_lane_count(lane_count = 0) // writel(0, ANALOGIX_DP_LANE_COUNT_SET) enables 0 lanes; // phy_configure() is called with lanes = 0. // Program sink for link training drm_dp_dpcd_write(DP_LINK_BW_SET, {link_rate = 0/lane_count = 0}) // DP spec requires link rate in {0x06, 0x0a, 0x14} and // lane count in {1, 2, 4}. Writing zeros is illegal, so // the sink cannot enter the training state. // Training loop for (lane = 0; lane < lane_count /* 0 */; lane++) // loop body never executes; training_lane[] stays // uninitialized and no training register is programmed Since the sanitized sink values are always non-zero (link_rate >= 0x06, lane_count >= 1), the clamping with a zero maximum unconditionally forces the training parameters to zero. Clock recovery can never be achieved, so link training fails deterministically. Consequently, making these properties mandatory again cannot break any existing device: a DT without them could never have worked in the first place. Failing probe early with a clear error message is more helpful than a silent link training failure at runtime. Fixes: 0d0abd894ead ("drm: bridge: analogix/dp: add max link rate and lane count limit for RK3288") Cc: stable@vger.kernel.org Reviewed-by: Luca Ceresoli Signed-off-by: Damon Ding Link: https://patch.msgid.link/20260828065153.590802-4-damon.ding@rock-chips.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/analogix/analogix_dp_core.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c index 566f1e5eb8cd..ddb15d6de05f 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c @@ -1248,6 +1248,7 @@ static int analogix_dp_dt_parse_pdata(struct analogix_dp_device *dp) { struct device_node *dp_node = dp->dev->of_node; struct video_info *video_info = &dp->video_info; + u32 val; switch (dp->plat_data->dev_type) { case RK3288_DP: @@ -1269,10 +1270,14 @@ static int analogix_dp_dt_parse_pdata(struct analogix_dp_device *dp) * NOTE: those property parseing code is used for * providing backward compatibility for samsung platform. */ - of_property_read_u32(dp_node, "samsung,link-rate", - &video_info->max_link_rate); - of_property_read_u32(dp_node, "samsung,lane-count", - &video_info->max_lane_count); + if (of_property_read_u32(dp_node, "samsung,link-rate", &val)) + return dev_err_probe(dp->dev, -EINVAL, + "Failed to get samsung,link-rate\n"); + video_info->max_link_rate = val; + if (of_property_read_u32(dp_node, "samsung,lane-count", &val)) + return dev_err_probe(dp->dev, -EINVAL, + "Failed to get samsung,lane-count\n"); + video_info->max_lane_count = val; break; } From 4ff2f941b7828042feb2971c7039c8f790a105a4 Mon Sep 17 00:00:00 2001 From: Damon Ding Date: Fri, 28 Aug 2026 14:51:52 +0800 Subject: [PATCH 10/17] drm/bridge: analogix_dp: Add validation for samsung,lane-count property Add validity check for samsung,lane-count to ensure DT-provided lane count values are specification-compliant. Suggested-by: Luca Ceresoli Reviewed-by: Luca Ceresoli Signed-off-by: Damon Ding Link: https://patch.msgid.link/20260828065153.590802-5-damon.ding@rock-chips.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/analogix/analogix_dp_core.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c index ddb15d6de05f..a74c855936b2 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c @@ -1274,7 +1274,8 @@ static int analogix_dp_dt_parse_pdata(struct analogix_dp_device *dp) return dev_err_probe(dp->dev, -EINVAL, "Failed to get samsung,link-rate\n"); video_info->max_link_rate = val; - if (of_property_read_u32(dp_node, "samsung,lane-count", &val)) + if (of_property_read_u32(dp_node, "samsung,lane-count", &val) || + !drm_dp_lane_count_is_valid(val)) return dev_err_probe(dp->dev, -EINVAL, "Failed to get samsung,lane-count\n"); video_info->max_lane_count = val; From bf63a72ea005d5e1cc12a9ad5e228c4b44246b0f Mon Sep 17 00:00:00 2001 From: Damon Ding Date: Fri, 28 Aug 2026 14:51:53 +0800 Subject: [PATCH 11/17] drm/bridge: analogix_dp: Add support for optional data-lanes mapping Parse the optional 'data-lanes' device tree property to support custom physical lane mapping configuration. If no valid configuration is found, fall back to the default lane map (0, 1, 2, 3) automatically and keep the driver running. Lane mapping is mainly used for below scenarios: 1. Correct PCB lane swap and differential line routing crossover without hardware changes; 2. Adapt mismatched lane pin definitions between SoC and eDP panel; 3. Support multiple panel hardware variants on the same board by configuring data-lanes in device tree only. Reviewed-by: Sebastian Reichel Signed-off-by: Damon Ding Reviewed-by: Luca Ceresoli Link: https://patch.msgid.link/20260828065153.590802-6-damon.ding@rock-chips.com Signed-off-by: Luca Ceresoli --- .../drm/bridge/analogix/analogix_dp_core.c | 70 +++++++++++++++++++ .../drm/bridge/analogix/analogix_dp_core.h | 4 +- .../gpu/drm/bridge/analogix/analogix_dp_reg.c | 15 ++-- .../gpu/drm/bridge/analogix/analogix_dp_reg.h | 4 ++ 4 files changed, 84 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c index a74c855936b2..18e76d1e78e1 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c @@ -1244,6 +1244,73 @@ static const struct drm_bridge_funcs analogix_dp_bridge_funcs = { .detect = analogix_dp_bridge_detect, }; +static int analogix_dp_dt_parse_lanes_map(struct analogix_dp_device *dp) +{ + struct video_info *video_info = &dp->video_info; + struct device_node *endpoint; + u32 lane_idx[LANE_COUNT4]; + u32 map[LANE_COUNT4] = {0, 1, 2, 3}; + bool used[LANE_COUNT4] = {false}; + int num_lanes; + int ret, i; + + memcpy(video_info->lane_map, map, sizeof(map)); + + num_lanes = drm_of_get_data_lanes_count_ep(dp->dev->of_node, 1, 0, 1, + video_info->max_lane_count); + if (num_lanes < 0) + return -EINVAL; + + endpoint = of_graph_get_endpoint_by_regs(dp->dev->of_node, 1, 0); + if (!endpoint) + return -EINVAL; + + ret = of_property_read_u32_array(endpoint, "data-lanes", lane_idx, num_lanes); + of_node_put(endpoint); + if (ret) + return -EINVAL; + + for (i = 0; i < num_lanes; i++) { + if (lane_idx[i] >= LANE_COUNT4) { + dev_dbg(dp->dev, "data-lanes[%d] = %u is out of range\n", i, lane_idx[i]); + return -EINVAL; + } + + if (used[lane_idx[i]]) { + dev_dbg(dp->dev, "data-lanes[%d] = %u is duplicate\n", i, lane_idx[i]); + return -EINVAL; + } + + used[lane_idx[i]] = true; + map[i] = lane_idx[i]; + } + + /* + * Fill the map[] entries not described by 'data-lanes' with the + * lane indices not used so far, e.g. for 'data-lanes = <3 1>': + * + * used[] = {0, 1, 0, 1} // only lanes 1 and 3 are used + * map[] before = {3, 1, x, x} // x = unassigned + * map[] after = {3, 1, 0, 2} + * + * Unused entries have no effect on the current link with fewer + * lanes in use, but filling them with distinct indices keeps the + * LANE_MAP register holding a valid permutation, just like its + * reset value (0xe4), so the mapping remains sane if a sink with + * a different lane count is connected later, e.g. via DP hot-plug. + */ + for (i = 0; i < LANE_COUNT4 && num_lanes < LANE_COUNT4; i++) { + if (!used[i]) + map[num_lanes++] = i; + } + + dev_dbg(dp->dev, "Using parsed lane map: <%u %u %u %u>\n", map[0], map[1], map[2], map[3]); + + memcpy(video_info->lane_map, map, sizeof(map)); + + return 0; +} + static int analogix_dp_dt_parse_pdata(struct analogix_dp_device *dp) { struct device_node *dp_node = dp->dev->of_node; @@ -1282,6 +1349,9 @@ static int analogix_dp_dt_parse_pdata(struct analogix_dp_device *dp) break; } + if (analogix_dp_dt_parse_lanes_map(dp)) + dev_dbg(dp->dev, "No valid data-lanes found, using default lane map\n"); + return 0; } diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h index c7997677a286..4560597604ad 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h @@ -137,6 +137,8 @@ struct video_info { int max_link_rate; enum link_lane_count_type max_lane_count; + + u32 lane_map[LANE_COUNT4]; }; struct link_train { @@ -177,7 +179,7 @@ struct analogix_dp_device { /* analogix_dp_reg.c */ void analogix_dp_enable_video_mute(struct analogix_dp_device *dp, bool enable); void analogix_dp_stop_video(struct analogix_dp_device *dp); -void analogix_dp_lane_swap(struct analogix_dp_device *dp, bool enable); +void analogix_dp_lane_mapping(struct analogix_dp_device *dp); void analogix_dp_init_analog_param(struct analogix_dp_device *dp); void analogix_dp_init_interrupt(struct analogix_dp_device *dp); void analogix_dp_reset(struct analogix_dp_device *dp); diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c index ea8401293a23..c1344a3f013a 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c @@ -48,16 +48,15 @@ void analogix_dp_stop_video(struct analogix_dp_device *dp) writel(reg, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_1); } -void analogix_dp_lane_swap(struct analogix_dp_device *dp, bool enable) +void analogix_dp_lane_mapping(struct analogix_dp_device *dp) { + u32 *lane_map = dp->video_info.lane_map; u32 reg; - if (enable) - reg = LANE3_MAP_LOGIC_LANE_0 | LANE2_MAP_LOGIC_LANE_1 | - LANE1_MAP_LOGIC_LANE_2 | LANE0_MAP_LOGIC_LANE_3; - else - reg = LANE3_MAP_LOGIC_LANE_3 | LANE2_MAP_LOGIC_LANE_2 | - LANE1_MAP_LOGIC_LANE_1 | LANE0_MAP_LOGIC_LANE_0; + reg = lane_map[0] << LANE0_MAP_SHIFT; + reg |= lane_map[1] << LANE1_MAP_SHIFT; + reg |= lane_map[2] << LANE2_MAP_SHIFT; + reg |= lane_map[3] << LANE3_MAP_SHIFT; writel(reg, dp->reg_base + ANALOGIX_DP_LANE_MAP); } @@ -140,7 +139,7 @@ void analogix_dp_reset(struct analogix_dp_device *dp) usleep_range(20, 30); - analogix_dp_lane_swap(dp, 0); + analogix_dp_lane_mapping(dp); writel(0x0, dp->reg_base + ANALOGIX_DP_SYS_CTL_1); writel(0x40, dp->reg_base + ANALOGIX_DP_SYS_CTL_2); diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.h b/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.h index 12735139046c..ac914e37089b 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.h +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.h @@ -209,6 +209,10 @@ #define LANE0_MAP_LOGIC_LANE_1 (0x1 << 0) #define LANE0_MAP_LOGIC_LANE_2 (0x2 << 0) #define LANE0_MAP_LOGIC_LANE_3 (0x3 << 0) +#define LANE3_MAP_SHIFT (6) +#define LANE2_MAP_SHIFT (4) +#define LANE1_MAP_SHIFT (2) +#define LANE0_MAP_SHIFT (0) /* ANALOGIX_DP_ANALOG_CTL_1 */ #define TX_TERMINAL_CTRL_50_OHM (0x1 << 4) From 0878e6053d01d87c75bb1d00b0187e8a2e105ead Mon Sep 17 00:00:00 2001 From: Esben Haabendal Date: Mon, 31 Aug 2026 14:21:33 +0200 Subject: [PATCH 12/17] drm/bridge: ti-sn65dsi83: Support LVDS Channel B on SN65DSI84 Add support for using SN65DSI84 in single-link mode with output to LVDS Channel B. Reviewed-by: Luca Ceresoli Tested-by: Luca Ceresoli Signed-off-by: Esben Haabendal Link: https://patch.msgid.link/20260831-ti-sn65dsi83-fixes-v5-2-e712765d6c4f@geanix.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/ti-sn65dsi83.c | 34 ++++++++++++++++++--------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi83.c b/drivers/gpu/drm/bridge/ti-sn65dsi83.c index 41c217074cc3..b4a4dd83eb5c 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi83.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi83.c @@ -11,6 +11,8 @@ * = 1x Single-link DSI ~ 2x Single-link or 1x Dual-link LVDS * - Supported * - Dual-link LVDS mode tested + * - Single-link to LVDS Channel A tested + * - Single-link to LVDS Channel B tested * - 2x Single-link LVDS mode unsupported * (should be easy to add by someone who has the HW) * - SN65DSI85 @@ -162,7 +164,7 @@ struct sn65dsi83 { struct gpio_desc *enable_gpio; struct regulator *vcc; bool lvds_dual_link; - bool lvds_dual_link_even_odd_swap; + bool lvds_channel_swap; int lvds_vod_swing_conf[2]; int lvds_term_conf[2]; int irq; @@ -642,7 +644,7 @@ static void sn65dsi83_atomic_pre_enable(struct drm_bridge *bridge, REG_LVDS_VCOM_CHA_LVDS_VOD_SWING(ctx->lvds_vod_swing_conf[CHANNEL_A]) | REG_LVDS_VCOM_CHB_LVDS_VOD_SWING(ctx->lvds_vod_swing_conf[CHANNEL_B])); regmap_write(ctx->regmap, REG_LVDS_LANE, - (ctx->lvds_dual_link_even_odd_swap ? + (ctx->lvds_channel_swap ? REG_LVDS_LANE_EVEN_ODD_SWAP : 0) | (ctx->lvds_term_conf[CHANNEL_A] ? REG_LVDS_LANE_CHA_LVDS_TERM : 0) | @@ -876,6 +878,7 @@ static int sn65dsi83_parse_dt(struct sn65dsi83 *ctx, enum sn65dsi83_model model) { struct drm_bridge *panel_bridge; struct device *dev = ctx->dev; + u32 output_port = 2; int ret; ret = sn65dsi83_parse_lvds_endpoint(ctx, CHANNEL_A); @@ -887,29 +890,38 @@ static int sn65dsi83_parse_dt(struct sn65dsi83 *ctx, enum sn65dsi83_model model) return ret; ctx->lvds_dual_link = false; - ctx->lvds_dual_link_even_odd_swap = false; + ctx->lvds_channel_swap = false; if (model != MODEL_SN65DSI83) { - struct device_node *port2, *port3; + struct device_node *port0, *port1, *port2, *port3; int dual_link; + port0 = of_graph_get_port_by_id(dev->of_node, 0); + port1 = of_graph_get_port_by_id(dev->of_node, 1); port2 = of_graph_get_port_by_id(dev->of_node, 2); port3 = of_graph_get_port_by_id(dev->of_node, 3); dual_link = drm_of_lvds_get_dual_link_pixel_order(port2, port3); - of_node_put(port2); - of_node_put(port3); if (dual_link == DRM_LVDS_DUAL_LINK_ODD_EVEN_PIXELS) { - ctx->lvds_dual_link = true; /* Odd pixels to LVDS Channel A, even pixels to B */ - ctx->lvds_dual_link_even_odd_swap = false; - } else if (dual_link == DRM_LVDS_DUAL_LINK_EVEN_ODD_PIXELS) { ctx->lvds_dual_link = true; + } else if (dual_link == DRM_LVDS_DUAL_LINK_EVEN_ODD_PIXELS) { /* Even pixels to LVDS Channel A, odd pixels to B */ - ctx->lvds_dual_link_even_odd_swap = true; + ctx->lvds_dual_link = true; + ctx->lvds_channel_swap = true; + } else if (port0 && !port1 && port2 && !port3) { + /* DSI Channel A to LVDS Channel A */ + } else if (port0 && !port1 && !port2 && port3) { + /* DSI Channel A to LVDS Channel B */ + ctx->lvds_channel_swap = true; + output_port = 3; } + of_node_put(port0); + of_node_put(port1); + of_node_put(port2); + of_node_put(port3); } - panel_bridge = devm_drm_of_get_bridge(dev, dev->of_node, 2, 0); + panel_bridge = devm_drm_of_get_bridge(dev, dev->of_node, output_port, 0); if (IS_ERR(panel_bridge)) return dev_err_probe(dev, PTR_ERR(panel_bridge), "Failed to get panel bridge\n"); From 6ddd3164f656a9af531b7180c64a158b55d12acc Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Thu, 27 Aug 2026 10:23:44 +0200 Subject: [PATCH 13/17] drm/hibmc: Return IRQ_NONE for unhandled interrupts Let the kernel know if hibmc did not handle an interrupt. Allows the kernel to detect spurious interrupts and maintain diagnostics. Signed-off-by: Thomas Zimmermann Reviewed-by: Yongbang Shi Link: https://patch.msgid.link/20260827082915.61176-2-tzimmermann@suse.de --- drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c index 4d85c89f3f88..602930964e93 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c @@ -49,9 +49,10 @@ static irqreturn_t hibmc_interrupt(int irq, void *arg) writel(HIBMC_RAW_INTERRUPT_VBLANK(1), priv->mmio + HIBMC_RAW_INTERRUPT); drm_handle_vblank(dev, 0); + return IRQ_HANDLED; } - return IRQ_HANDLED; + return IRQ_NONE; } static irqreturn_t hibmc_dp_interrupt(int irq, void *arg) @@ -67,7 +68,7 @@ static irqreturn_t hibmc_dp_interrupt(int irq, void *arg) return IRQ_WAKE_THREAD; } - return IRQ_HANDLED; + return IRQ_NONE; } static int hibmc_dumb_create(struct drm_file *file, struct drm_device *dev, From 225635a3f482aa45841eff9e6128a747e98933d7 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Thu, 27 Aug 2026 10:23:45 +0200 Subject: [PATCH 14/17] drm/hibmc: Set up vblank event to be send on the next vblank Do not send the vblank event when the page flip completes, but when the vblank interrupt fires. Sending the vblank event at the wrong time can break synchronization of the display update with the vblank period. Use the vblank helper drm_crtc_vblank_atomic_flush(), which sets up the vblank event accordingly. In case of an error, it still sends out the vblank event immediately; thus not starving consumers. Signed-off-by: Thomas Zimmermann Reviewed-by: Yongbang Shi Link: https://patch.msgid.link/20260827082915.61176-3-tzimmermann@suse.de --- drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_de.c | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_de.c b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_de.c index 33402cf54072..7849abd0488e 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_de.c +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_de.c @@ -20,6 +20,7 @@ #include #include #include +#include #include "hibmc_drm_drv.h" #include "hibmc_drm_regs.h" @@ -420,19 +421,6 @@ static void hibmc_crtc_atomic_begin(struct drm_crtc *crtc, /* We can add more initialization as needed. */ } -static void hibmc_crtc_atomic_flush(struct drm_crtc *crtc, - struct drm_atomic_commit *state) - -{ - unsigned long flags; - - spin_lock_irqsave(&crtc->dev->event_lock, flags); - if (crtc->state->event) - drm_crtc_send_vblank_event(crtc, crtc->state->event); - crtc->state->event = NULL; - spin_unlock_irqrestore(&crtc->dev->event_lock, flags); -} - static int hibmc_crtc_enable_vblank(struct drm_crtc *crtc) { struct hibmc_drm_private *priv = to_hibmc_drm_private(crtc->dev); @@ -502,7 +490,7 @@ static const struct drm_crtc_funcs hibmc_crtc_funcs = { static const struct drm_crtc_helper_funcs hibmc_crtc_helper_funcs = { .mode_set_nofb = hibmc_crtc_mode_set_nofb, .atomic_begin = hibmc_crtc_atomic_begin, - .atomic_flush = hibmc_crtc_atomic_flush, + .atomic_flush = drm_crtc_vblank_atomic_flush, .atomic_enable = hibmc_crtc_atomic_enable, .atomic_disable = hibmc_crtc_atomic_disable, .mode_valid = hibmc_crtc_mode_valid, From 9f8bf739ed07c59466ea0d9e6d1146250da16091 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Fri, 4 Sep 2026 11:07:00 +0200 Subject: [PATCH 15/17] firmware/sysfb: Remove rotation quirk for Lenovo D330 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Lenovo D330 is a notebook with detachable display. The display is installed in landscape orientation, but the display mode is in portrait orientation. By default, the display output is rotated to the left by 90°. The sysfb quirk breaks the display by swapping display width and height; and thus mixing up the display geometry. Fix this by keeping the display as-is. The sysfb DRM drivers, usually efidrm, will pick the correct panel orientation for the display. The exact behavior might depend on the version of the BIOS release, but the exact version numbers of broken and good releases are unknown. Users are advised to update to the latest firmware. Tested on a Lenovo D330-10IGM IdeaPad with firmware 8NCN43WW. v3: - mention possible dependency on BIOS release in commit description Signed-off-by: Thomas Zimmermann Acked-by: Ard Biesheuvel Reviewed-by: Javier Martinez Canillas Link: https://patch.msgid.link/20260904090850.43060-2-tzimmermann@suse.de --- drivers/firmware/efi/sysfb_efi.c | 9 --------- 1 file changed, 9 deletions(-) diff --git a/drivers/firmware/efi/sysfb_efi.c b/drivers/firmware/efi/sysfb_efi.c index 685283bb7327..2338de73c62c 100644 --- a/drivers/firmware/efi/sysfb_efi.c +++ b/drivers/firmware/efi/sysfb_efi.c @@ -301,15 +301,6 @@ static const struct dmi_system_id efifb_dmi_swap_width_height[] __initconst = { }, .callback = efifb_swap_width_height, }, - { - /* Lenovo D330 with 800x1280 or 1200x1920 portrait screen */ - .matches = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, - "Lenovo ideapad D330-10IGM"), - }, - .callback = efifb_swap_width_height, - }, { /* * Lenovo IdeaPad Duet 3 10IGL5 and 10IGL5-LTE with From 394d0759b169fe9045e474136f825101d98d1ff6 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Fri, 4 Sep 2026 11:07:01 +0200 Subject: [PATCH 16/17] drm/edid: Add drm_edid_detect_panel_size() Add drm_edid_detect_panel_size() to extract the panel's preferred display resolution from a given EDID. Required for setting up DRM's panel orientation quirks in sysfb drivers. v4: - fix test for EDID PTD (Jani) v3: - mention use case in documentation (Jani) - use is_detailed_timing_descriptor() (Jani) - rename helper to drm_edid_detect_panel_size() v2: - handle EDID without pixel timing descriptor (Sashiko) - fix checks for width and height pointers Signed-off-by: Thomas Zimmermann Acked-by: Ard Biesheuvel Reviewed-by: Jani Nikula Reviewed-by: Javier Martinez Canillas Link: https://patch.msgid.link/20260904090850.43060-3-tzimmermann@suse.de --- drivers/gpu/drm/drm_edid.c | 47 ++++++++++++++++++++++++++++++++++++++ include/drm/drm_edid.h | 2 ++ 2 files changed, 49 insertions(+) diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index 9990a836d0b6..1331efb4876c 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -7852,3 +7852,50 @@ bool drm_edid_is_digital(const struct drm_edid *drm_edid) drm_edid->edid->input & DRM_EDID_INPUT_DIGITAL; } EXPORT_SYMBOL(drm_edid_is_digital); + +/** + * drm_edid_detect_panel_size - Get a panel's size from EDID + * @drm_edid: EDID of the panel. + * @width: Returns the panel's width in pixels per scanline, if given + * @height: Returns the panel's height in scanlines, if given + * + * This function detects the preferred size of a panel from the given + * EDID. There is no such information stored in the EDID block directly, + * but the preferred mode often corresponds to the panel's native geometry. + * + * This helper should only be used during initialization before the + * connector is available. For regular use, retrieve the available display + * modes with the connector functions. + * + * Return: Zero on success, or a negative errno code otherwise. + */ +int drm_edid_detect_panel_size(const struct drm_edid *drm_edid, + unsigned int *width, unsigned int *height) +{ + const struct edid *edid = drm_edid->edid; + const struct detailed_timing *dt; + const struct detailed_pixel_timing *pt; + + /* + * Use display mode from the Preferred Timing Descriptor. For old + * and obscure displays, we might need better heuristics. + */ + + if (edid->revision < 4 || !(edid->features & DRM_EDID_FEATURE_PREFERRED_TIMING)) + return -EINVAL; /* no Preferred Timing Descriptor */ + + dt = &edid->detailed_timings[0]; + + if (!is_detailed_timing_descriptor(dt)) + return -EINVAL; + + pt = &dt->data.pixel_data; + + if (width) + *width = (pt->hactive_hblank_hi & 0xf0) << 4 | pt->hactive_lo; + if (height) + *height = (pt->vactive_vblank_hi & 0xf0) << 4 | pt->vactive_lo; + + return 0; +} +EXPORT_SYMBOL(drm_edid_detect_panel_size); diff --git a/include/drm/drm_edid.h b/include/drm/drm_edid.h index 04f7a7f1f108..a2617aa34edf 100644 --- a/include/drm/drm_edid.h +++ b/include/drm/drm_edid.h @@ -490,5 +490,7 @@ u32 drm_edid_get_panel_id(const struct drm_edid *drm_edid); bool drm_edid_match(const struct drm_edid *drm_edid, const struct drm_edid_ident *ident); bool drm_edid_has_quirk(struct drm_connector *connector, enum drm_edid_quirk quirk); +int drm_edid_detect_panel_size(const struct drm_edid *drm_edid, + unsigned int *width, unsigned int *height); #endif /* __DRM_EDID_H__ */ From 75ee88dae80d8c1141ef850bda9a26dbd1fe3c21 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Fri, 4 Sep 2026 11:07:02 +0200 Subject: [PATCH 17/17] drm/sysfb: Use preferred panel size for panel orientation quirks Sysfb drivers currently use the given display mode for looking up the panel orientation. But the look-up table stores the native geometry of the panels, so the lookup fails if the current mode sizes differs. Get the panel's native geometry with drm_edid_detect_panel_size() from the EDID and use it for looking up the panel orientation. v2: - ofdrm: validate EDID header before using it (Sashiko) Signed-off-by: Thomas Zimmermann Acked-by: Ard Biesheuvel Acked-by: Jani Nikula Reviewed-by: Javier Martinez Canillas Link: https://patch.msgid.link/20260904090850.43060-4-tzimmermann@suse.de --- drivers/gpu/drm/sysfb/efidrm.c | 17 ++++++++++++++++- drivers/gpu/drm/sysfb/ofdrm.c | 18 ++++++++++++++++-- drivers/gpu/drm/sysfb/vesadrm.c | 17 ++++++++++++++++- 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/sysfb/efidrm.c b/drivers/gpu/drm/sysfb/efidrm.c index 3f9cd5d03efb..8b19f437308e 100644 --- a/drivers/gpu/drm/sysfb/efidrm.c +++ b/drivers/gpu/drm/sysfb/efidrm.c @@ -152,6 +152,7 @@ static struct efidrm_device *efidrm_device_create(struct drm_driver *drv, const struct screen_info *si; const struct drm_format_info *format; int width, height, stride; + unsigned int panel_width, panel_height; s64 vsize; u64 mem_flags; struct resource resbuf; @@ -217,6 +218,20 @@ static struct efidrm_device *efidrm_device_create(struct drm_driver *drv, if (drm_edid_header_is_valid(dpy->edid.dummy) == 8) sysfb->edid = dpy->edid.dummy; #endif + + panel_width = width; + panel_height = height; + + if (sysfb->edid) { + const struct drm_edid *drm_edid; + + drm_edid = drm_edid_alloc(sysfb->edid, EDID_LENGTH); + if (drm_edid) { + drm_edid_detect_panel_size(drm_edid, &panel_width, &panel_height); + drm_edid_free(drm_edid); + } + } + sysfb->fb_mode = drm_sysfb_mode(width, height, 0, 0); sysfb->fb_format = format; sysfb->fb_pitch = stride; @@ -340,7 +355,7 @@ static struct efidrm_device *efidrm_device_create(struct drm_driver *drv, drm_connector_helper_add(connector, &efidrm_connector_helper_funcs); drm_connector_set_panel_orientation_with_quirk(connector, DRM_MODE_PANEL_ORIENTATION_UNKNOWN, - width, height); + panel_width, panel_height); if (sysfb->edid) drm_connector_attach_edid_property(connector); diff --git a/drivers/gpu/drm/sysfb/ofdrm.c b/drivers/gpu/drm/sysfb/ofdrm.c index 9d60db45139c..c41255569854 100644 --- a/drivers/gpu/drm/sysfb/ofdrm.c +++ b/drivers/gpu/drm/sysfb/ofdrm.c @@ -229,7 +229,7 @@ static const u8 *display_get_edid_of(struct drm_device *dev, struct device_node { int ret = of_property_read_u8_array(of_node, "EDID", buf, EDID_LENGTH); - if (ret) + if (ret || drm_edid_header_is_valid(buf) != 8) return NULL; return buf; } @@ -828,6 +828,7 @@ static struct ofdrm_device *ofdrm_device_create(struct drm_driver *drv, enum ofdrm_model model; bool big_endian; int width, height, depth, linebytes; + unsigned int panel_width, panel_height; const struct drm_format_info *format; u64 address; const u8 *edid; @@ -998,6 +999,19 @@ static struct ofdrm_device *ofdrm_device_create(struct drm_driver *drv, sysfb->fb_gamma_lut_size = OFDRM_GAMMA_LUT_SIZE; sysfb->edid = edid; + panel_width = width; + panel_height = height; + + if (sysfb->edid) { + const struct drm_edid *drm_edid; + + drm_edid = drm_edid_alloc(sysfb->edid, EDID_LENGTH); + if (drm_edid) { + drm_edid_detect_panel_size(drm_edid, &panel_width, &panel_height); + drm_edid_free(drm_edid); + } + } + drm_dbg(dev, "display mode={" DRM_MODE_FMT "}\n", DRM_MODE_ARG(&sysfb->fb_mode)); drm_dbg(dev, "framebuffer format=%p4cc, size=%dx%d, linebytes=%d byte\n", &format->format, width, height, linebytes); @@ -1069,7 +1083,7 @@ static struct ofdrm_device *ofdrm_device_create(struct drm_driver *drv, drm_connector_helper_add(connector, &ofdrm_connector_helper_funcs); drm_connector_set_panel_orientation_with_quirk(connector, DRM_MODE_PANEL_ORIENTATION_UNKNOWN, - width, height); + panel_width, panel_height); if (edid) drm_connector_attach_edid_property(connector); diff --git a/drivers/gpu/drm/sysfb/vesadrm.c b/drivers/gpu/drm/sysfb/vesadrm.c index 6a67b2d2e451..f6358a0a68bd 100644 --- a/drivers/gpu/drm/sysfb/vesadrm.c +++ b/drivers/gpu/drm/sysfb/vesadrm.c @@ -402,6 +402,7 @@ static struct vesadrm_device *vesadrm_device_create(struct drm_driver *drv, const struct screen_info *si; const struct drm_format_info *format; int width, height, stride; + unsigned int panel_width, panel_height; s64 vsize; struct resource resbuf; struct resource *res; @@ -484,6 +485,20 @@ static struct vesadrm_device *vesadrm_device_create(struct drm_driver *drv, if (drm_edid_header_is_valid(dpy->edid.dummy) == 8) sysfb->edid = dpy->edid.dummy; #endif + + panel_width = width; + panel_height = height; + + if (sysfb->edid) { + const struct drm_edid *drm_edid; + + drm_edid = drm_edid_alloc(sysfb->edid, EDID_LENGTH); + if (drm_edid) { + drm_edid_detect_panel_size(drm_edid, &panel_width, &panel_height); + drm_edid_free(drm_edid); + } + } + sysfb->fb_mode = drm_sysfb_mode(width, height, 0, 0); sysfb->fb_format = format; sysfb->fb_pitch = stride; @@ -585,7 +600,7 @@ static struct vesadrm_device *vesadrm_device_create(struct drm_driver *drv, drm_connector_helper_add(connector, &vesadrm_connector_helper_funcs); drm_connector_set_panel_orientation_with_quirk(connector, DRM_MODE_PANEL_ORIENTATION_UNKNOWN, - width, height); + panel_width, panel_height); if (sysfb->edid) drm_connector_attach_edid_property(connector);