From 4685230d8288a99f1c664768b6eb2bd1545524c7 Mon Sep 17 00:00:00 2001 From: Leonardo Costa Date: Tue, 23 Jun 2026 16:57:37 -0300 Subject: [PATCH 01/83] dt-bindings: vendor-prefixes: Add Opto Logic Add vendor prefix for Opto Logic, a Swiss display solutions provider and printing systems manufacturer. Link: https://optologic.ch/ Signed-off-by: Leonardo Costa Acked-by: Krzysztof Kozlowski Reviewed-by: Francesco Dolcini Signed-off-by: Neil Armstrong Link: https://patch.msgid.link/20260623195741.495734-2-leoreis.costa@gmail.com --- Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml index b1af9deac2e9..f96c84320b8b 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -1253,6 +1253,8 @@ patternProperties: description: OpenWrt "^option,.*": description: Option NV + "^optologic,.*": + description: Opto Logic S.A. "^oranth,.*": description: Shenzhen Oranth Technology Co., Ltd. "^ORCL,.*": From e2a9e291275a74e309a21cbb1def6296a72d6aed Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 17 Jul 2026 16:31:18 +0200 Subject: [PATCH 02/83] drm/panel-edp: fix i2c adapter leak on probe failure Make sure to drop the i2c adapter reference on probe failure (e.g. probe deferral) and on driver unbind also if a devicetree redundantly uses the 'ddc-i2c-bus' property to point to the aux ddc bus. Fixes: cc5a3fc041f0 ("drm/panel: panel-simple: Stash DP AUX bus; allow using it for DDC") Cc: stable@vger.kernel.org # 5.15 Reported-by: Douglas Anderson Link: https://lore.kernel.org/r/CAD=FV=VZPhzHU+Pet2m3L+Pqc7mOPfZC-f5p0OuNL79wNZPxRg@mail.gmail.com Signed-off-by: Johan Hovold Reviewed-by: Douglas Anderson Signed-off-by: Douglas Anderson Link: https://patch.msgid.link/20260717143119.1815106-2-johan@kernel.org --- drivers/gpu/drm/panel/panel-edp.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/panel/panel-edp.c b/drivers/gpu/drm/panel/panel-edp.c index df7286679446..ff99fa3d0b59 100644 --- a/drivers/gpu/drm/panel/panel-edp.c +++ b/drivers/gpu/drm/panel/panel-edp.c @@ -832,6 +832,13 @@ static int generic_edp_panel_probe(struct device *dev, struct panel_edp *panel) return 0; } +static void panel_edp_put_adapter(void *_adap) +{ + struct i2c_adapter *adap = _adap; + + put_device(&adap->dev); +} + static int panel_edp_probe(struct device *dev, const struct panel_desc *desc, struct drm_dp_aux *aux) { @@ -879,6 +886,11 @@ static int panel_edp_probe(struct device *dev, const struct panel_desc *desc, if (!panel->ddc) return -EPROBE_DEFER; + + err = devm_add_action_or_reset(dev, panel_edp_put_adapter, + panel->ddc); + if (err) + return err; } else if (aux) { panel->ddc = &aux->ddc; } @@ -890,7 +902,7 @@ static int panel_edp_probe(struct device *dev, const struct panel_desc *desc, err = drm_panel_of_backlight(&panel->base); if (err) - goto err_finished_ddc_init; + return err; /* * We use runtime PM for prepare / unprepare since those power the panel @@ -937,9 +949,6 @@ static int panel_edp_probe(struct device *dev, const struct panel_desc *desc, err_finished_pm_runtime: pm_runtime_dont_use_autosuspend(dev); pm_runtime_disable(dev); -err_finished_ddc_init: - if (panel->ddc && (!panel->aux || panel->ddc != &panel->aux->ddc)) - put_device(&panel->ddc->dev); return err; } @@ -983,8 +992,6 @@ static void panel_edp_remove(struct device *dev) pm_runtime_dont_use_autosuspend(dev); pm_runtime_disable(dev); - if (panel->ddc && (!panel->aux || panel->ddc != &panel->aux->ddc)) - put_device(&panel->ddc->dev); drm_edid_free(panel->drm_edid); panel->drm_edid = NULL; From c7b77a538e01bb3289ae9eec70fc315f99798594 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 17 Jul 2026 16:31:19 +0200 Subject: [PATCH 03/83] drm/panel-edp: take i2c adapter module reference 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 Reviewed-by: Douglas Anderson Signed-off-by: Douglas Anderson Link: https://patch.msgid.link/20260717143119.1815106-3-johan@kernel.org --- drivers/gpu/drm/panel/panel-edp.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/panel/panel-edp.c b/drivers/gpu/drm/panel/panel-edp.c index ff99fa3d0b59..14dd40afa899 100644 --- a/drivers/gpu/drm/panel/panel-edp.c +++ b/drivers/gpu/drm/panel/panel-edp.c @@ -836,7 +836,7 @@ static void panel_edp_put_adapter(void *_adap) { struct i2c_adapter *adap = _adap; - put_device(&adap->dev); + i2c_put_adapter(adap); } static int panel_edp_probe(struct device *dev, const struct panel_desc *desc, @@ -881,7 +881,7 @@ static int panel_edp_probe(struct device *dev, const struct panel_desc *desc, ddc = of_parse_phandle(dev->of_node, "ddc-i2c-bus", 0); if (ddc) { - panel->ddc = of_find_i2c_adapter_by_node(ddc); + panel->ddc = of_get_i2c_adapter_by_node(ddc); of_node_put(ddc); if (!panel->ddc) From abc1e559f8e5996eee506dfdc8e3781c2a1e04f9 Mon Sep 17 00:00:00 2001 From: Osama Abdelkader Date: Thu, 16 Jul 2026 16:03:36 +0200 Subject: [PATCH 04/83] drm/panthor: return PTR_ERR() from devm_drm_dev_alloc() devm_drm_dev_alloc() returns an ERR_PTR() on failure, but panthor_probe() always converts that failure to -ENOMEM. Preserve the actual error code returned by the DRM core instead. Fixes: 4bdca1150792 ("drm/panthor: Add the driver frontend block") Signed-off-by: Osama Abdelkader Reviewed-by: Steven Price Signed-off-by: Steven Price Link: https://patch.msgid.link/20260716140337.10679-1-osama.abdelkader@gmail.com --- drivers/gpu/drm/panthor/panthor_drv.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/panthor/panthor_drv.c b/drivers/gpu/drm/panthor/panthor_drv.c index 106da676fd2e..46a3080b0b20 100644 --- a/drivers/gpu/drm/panthor/panthor_drv.c +++ b/drivers/gpu/drm/panthor/panthor_drv.c @@ -1824,7 +1824,7 @@ static int panthor_probe(struct platform_device *pdev) ptdev = devm_drm_dev_alloc(&pdev->dev, &panthor_drm_driver, struct panthor_device, base); if (IS_ERR(ptdev)) - return -ENOMEM; + return PTR_ERR(ptdev); platform_set_drvdata(pdev, ptdev); From 15dcf7609fc04b9b13faf921f40d98491404dbf0 Mon Sep 17 00:00:00 2001 From: Dave Airlie Date: Sat, 25 Jul 2026 00:59:12 -0300 Subject: [PATCH 05/83] drm/nouveau: allow allocating NVDEC channels via abi16 Add NOUVEAU_FIFO_ENGINE_NVDEC to the abi16 uAPI and accept it in channel allocation, mapping it to the NVDEC engine runlist. Skip the Turing copy-engine workaround object for NVDEC channels: the copy engines are not part of the NVDEC runlist, so the workaround object cannot be instantiated on such channels (and is not needed there). This is required for NVK to implement Vulkan Video H.264 decode on top of the NVDEC engine: https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/31867 Reviewed-by: Daniel Almeida Signed-off-by: Dave Airlie Link: https://patch.msgid.link/20260725035912.1016464-1-daniel.almeida@collabora.com --- drivers/gpu/drm/nouveau/nouveau_abi16.c | 14 ++++++++++---- drivers/gpu/drm/nouveau/nouveau_drv.h | 4 +++- include/uapi/drm/nouveau_drm.h | 1 + 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nouveau_abi16.c b/drivers/gpu/drm/nouveau/nouveau_abi16.c index 291203121f0c..4542d5f4ded8 100644 --- a/drivers/gpu/drm/nouveau/nouveau_abi16.c +++ b/drivers/gpu/drm/nouveau/nouveau_abi16.c @@ -408,6 +408,9 @@ nouveau_abi16_ioctl_channel_alloc(ABI16_IOCTL_ARGS) case NOUVEAU_FIFO_ENGINE_CE: engine = NV_DEVICE_HOST_RUNLIST_ENGINES_CE; break; + case NOUVEAU_FIFO_ENGINE_NVDEC: + engine = NV_DEVICE_HOST_RUNLIST_ENGINES_NVDEC; + break; default: return nouveau_abi16_put(abi16, -ENOSYS); } @@ -485,10 +488,13 @@ nouveau_abi16_ioctl_channel_alloc(ABI16_IOCTL_ARGS) goto done; break; case NV_DEVICE_INFO_V0_TURING: - ret = nvif_object_ctor(&chan->chan->user, "abi16CeWar", 0, TURING_DMA_COPY_A, - NULL, 0, &chan->ce); - if (ret) - goto done; + if (engine != NV_DEVICE_HOST_RUNLIST_ENGINES_NVDEC) { + ret = nvif_object_ctor(&chan->chan->user, "abi16CeWar", + 0, TURING_DMA_COPY_A, NULL, 0, + &chan->ce); + if (ret) + goto done; + } break; default: break; diff --git a/drivers/gpu/drm/nouveau/nouveau_drv.h b/drivers/gpu/drm/nouveau/nouveau_drv.h index 1c2523e2f92e..5fc75dc750ed 100644 --- a/drivers/gpu/drm/nouveau/nouveau_drv.h +++ b/drivers/gpu/drm/nouveau/nouveau_drv.h @@ -10,7 +10,7 @@ #define DRIVER_MAJOR 1 #define DRIVER_MINOR 4 -#define DRIVER_PATCHLEVEL 2 +#define DRIVER_PATCHLEVEL 3 /* * 1.1.1: @@ -39,6 +39,8 @@ * - add variable page sizes and compression for Turing+ * 1.4.2: * - tell userspace LPTE/SPTE races are fixed. + * 1.4.3: + * - VDEC contexts can be created. */ #include diff --git a/include/uapi/drm/nouveau_drm.h b/include/uapi/drm/nouveau_drm.h index 1fa82fa6af38..c4905b3e4e91 100644 --- a/include/uapi/drm/nouveau_drm.h +++ b/include/uapi/drm/nouveau_drm.h @@ -89,6 +89,7 @@ struct drm_nouveau_getparam { #define NOUVEAU_FIFO_ENGINE_PPP 0x04 #define NOUVEAU_FIFO_ENGINE_BSP 0x08 #define NOUVEAU_FIFO_ENGINE_CE 0x30 +#define NOUVEAU_FIFO_ENGINE_NVDEC 0x300 struct drm_nouveau_channel_alloc { __u32 fb_ctxdma_handle; From b1d7f31f7d55dc2e8a334d20d84a559f67ce7825 Mon Sep 17 00:00:00 2001 From: Marc-Olivier Champagne Date: Wed, 22 Jul 2026 13:38:37 -0400 Subject: [PATCH 06/83] drm: panel: jd9365da: Enable LPM mode The JD9365DA-H3 uses DSI LPM for command transmissions, make sure this is configured correctly in the DSI mode flags. Suggested-by: Hugo Villeneuve Reviewed-by: Neil Armstrong Signed-off-by: Marc-Olivier Champagne Signed-off-by: Neil Armstrong Link: https://patch.msgid.link/20260722173840.3576444-2-marc-olivier.champagne@savoirfairelinux.com --- drivers/gpu/drm/panel/panel-jadard-jd9365da-h3.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/panel/panel-jadard-jd9365da-h3.c b/drivers/gpu/drm/panel/panel-jadard-jd9365da-h3.c index 67ef9e0fea03..a9183baa421a 100644 --- a/drivers/gpu/drm/panel/panel-jadard-jd9365da-h3.c +++ b/drivers/gpu/drm/panel/panel-jadard-jd9365da-h3.c @@ -2950,7 +2950,8 @@ static int jadard_dsi_probe(struct mipi_dsi_device *dsi) else dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | - MIPI_DSI_MODE_NO_EOT_PACKET; + MIPI_DSI_MODE_NO_EOT_PACKET | + MIPI_DSI_MODE_LPM; dsi->format = desc->format; dsi->lanes = desc->lanes; From 2599b6a5f7f0b2f034531d01e06eedf5b30d6748 Mon Sep 17 00:00:00 2001 From: Marc-Olivier Champagne Date: Wed, 22 Jul 2026 13:38:38 -0400 Subject: [PATCH 07/83] dt-bindings: vendor-prefixes: Add DCLTEK Add DCL Technologies Inc. (DCLTEK) to the devicetree vendor prefixes registry. Link: https://www.dcltek.com/ Acked-by: Rob Herring (Arm) Signed-off-by: Marc-Olivier Champagne Signed-off-by: Neil Armstrong Link: https://patch.msgid.link/20260722173840.3576444-3-marc-olivier.champagne@savoirfairelinux.com --- Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml index f96c84320b8b..1e54877628c7 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -409,6 +409,8 @@ patternProperties: description: DataImage, Inc. "^davicom,.*": description: DAVICOM Semiconductor, Inc. + "^dcltek,.*": + description: DCL Technologies Inc. "^deepcomputing,.*": description: DeepComputing (HK) Limited "^dell,.*": From ebd2effb165c2e0ba78dd233bc9eb5f2d6e8e425 Mon Sep 17 00:00:00 2001 From: Marc-Olivier Champagne Date: Wed, 22 Jul 2026 13:38:39 -0400 Subject: [PATCH 08/83] dt-bindings: display: panel: add compatible for DCLTek 300250 9.35in panel The DCLTek dt300250 is a panel using the jadard-jd9365da controller. Add a new compatible for it. Suggested-by: Hugo Villeneuve Acked-by: Rob Herring (Arm) Signed-off-by: Marc-Olivier Champagne Signed-off-by: Neil Armstrong Link: https://patch.msgid.link/20260722173840.3576444-4-marc-olivier.champagne@savoirfairelinux.com --- .../devicetree/bindings/display/panel/jadard,jd9365da-h3.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/display/panel/jadard,jd9365da-h3.yaml b/Documentation/devicetree/bindings/display/panel/jadard,jd9365da-h3.yaml index 4eae802de9fd..9e20e2faf82f 100644 --- a/Documentation/devicetree/bindings/display/panel/jadard,jd9365da-h3.yaml +++ b/Documentation/devicetree/bindings/display/panel/jadard,jd9365da-h3.yaml @@ -19,6 +19,7 @@ properties: - anbernic,rg-ds-display-bottom - anbernic,rg-ds-display-top - chongzhou,cz101b4001 + - dcltek,dt300250 - kingdisplay,kd101ne3-40ti - melfas,lmfbx101117480 - radxa,display-10hd-ad001 From ea3d663c10da692172ab73f68046a54d4b5006ff Mon Sep 17 00:00:00 2001 From: Marc-Olivier Champagne Date: Wed, 22 Jul 2026 13:38:40 -0400 Subject: [PATCH 09/83] drm: panel: jd9365da: Add DCLTek 300250 9.35in display The DCLTek dt300250 is a LCD panel using the jd9365da controller. Add the panel to the driver. Suggested-by: Hugo Villeneuve Reviewed-by: Neil Armstrong Signed-off-by: Marc-Olivier Champagne Signed-off-by: Neil Armstrong Link: https://patch.msgid.link/20260722173840.3576444-5-marc-olivier.champagne@savoirfairelinux.com --- .../gpu/drm/panel/panel-jadard-jd9365da-h3.c | 246 ++++++++++++++++++ 1 file changed, 246 insertions(+) diff --git a/drivers/gpu/drm/panel/panel-jadard-jd9365da-h3.c b/drivers/gpu/drm/panel/panel-jadard-jd9365da-h3.c index a9183baa421a..5202c1ffc6e1 100644 --- a/drivers/gpu/drm/panel/panel-jadard-jd9365da-h3.c +++ b/drivers/gpu/drm/panel/panel-jadard-jd9365da-h3.c @@ -2931,6 +2931,248 @@ static const struct jadard_panel_desc waveshare_10_1_inch_b_desc = { MIPI_DSI_MODE_LPM | MIPI_DSI_CLOCK_NON_CONTINUOUS, }; +static int dcltek_dt300250_init_cmds(struct jadard *jadard) +{ + struct mipi_dsi_multi_context dsi_ctx = { .dsi = jadard->dsi }; + + jd9365da_switch_page(&dsi_ctx, 0x00); + jadard_enable_standard_cmds(&dsi_ctx); + + jd9365da_switch_page(&dsi_ctx, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x01, 0x2B); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x03, 0x10); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x04, 0x2B); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0C, 0x74); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x17, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x18, 0xCF); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x19, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1A, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1B, 0xCF); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1C, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x24, 0xFE); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x37, 0x09); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x38, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x39, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3A, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3C, 0x5C); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3D, 0xFF); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3E, 0xFF); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3F, 0x7F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x40, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x41, 0xC8); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x42, 0x66); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x43, 0x10); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x44, 0x0F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x45, 0x28); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x55, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x57, 0x8D); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x59, 0x0A); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5A, 0x29); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5B, 0x1A); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5D, 0x7F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5E, 0x65); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5F, 0x53); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x60, 0x45); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x61, 0x3E); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x62, 0x30); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x63, 0x33); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x64, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x65, 0x3B); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x66, 0x3C); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x67, 0x3F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x68, 0x5D); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x69, 0x49); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6A, 0x4D); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6B, 0x3D); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6C, 0x37); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6D, 0x28); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6E, 0x15); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6F, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x70, 0x7F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x71, 0x65); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x72, 0x53); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x73, 0x45); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x74, 0x3E); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x75, 0x30); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x76, 0x33); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x77, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x78, 0x3B); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x79, 0x3C); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7A, 0x3F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7B, 0x5D); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7C, 0x49); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7D, 0x4D); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7E, 0x3D); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x7F, 0x44); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x80, 0x28); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x81, 0x15); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x82, 0x00); + + jd9365da_switch_page(&dsi_ctx, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x00, 0x41); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x01, 0x5F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x02, 0x5F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x03, 0x4B); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x04, 0x5F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x05, 0x5C); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x06, 0x5F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x07, 0x49); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x08, 0x5F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x09, 0x5A); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0A, 0x5F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0B, 0x47); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0C, 0x5F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0D, 0x4F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0E, 0x5F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0F, 0x45); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x10, 0x5F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x11, 0x4D); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x12, 0x5F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x13, 0x5E); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x14, 0x51); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x15, 0x5F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x16, 0x40); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x17, 0x5F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x18, 0x5F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x19, 0x4A); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1A, 0x5F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1B, 0x5B); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1C, 0x5F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1D, 0x48); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1E, 0x5F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x1F, 0x59); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x20, 0x5F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x21, 0x46); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x22, 0x5F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x23, 0x4E); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x24, 0x5F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x25, 0x44); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x26, 0x5F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x27, 0x4C); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x28, 0x5F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x29, 0x5E); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2A, 0x50); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2B, 0x5F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2C, 0x10); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2D, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2E, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2F, 0x0C); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x30, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x31, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x32, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x33, 0x0E); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x34, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x35, 0x06); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x36, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x37, 0x19); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x38, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x39, 0x08); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3A, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3B, 0x1B); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3C, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3D, 0x0A); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3E, 0x1E); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x3F, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x40, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x41, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x42, 0x11); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x43, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x44, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x45, 0x0D); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x46, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x47, 0x05); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x48, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x49, 0x0F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x4A, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x4B, 0x07); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x4C, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x4D, 0x1A); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x4E, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x4F, 0x09); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x50, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x51, 0x1C); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x52, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x53, 0x0B); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x54, 0x1E); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x55, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x56, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x57, 0x1F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x58, 0x40); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5B, 0x10); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5C, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5D, 0x70); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5E, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5F, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x60, 0x70); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x61, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x62, 0x02); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x63, 0x06); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x64, 0x4A); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x65, 0x56); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x66, 0x4F); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x67, 0xF7); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x68, 0x01); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x69, 0x06); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6A, 0x4A); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6B, 0x10); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6C, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6D, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6E, 0x00); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6F, 0x88); + + jd9365da_switch_page(&dsi_ctx, 0x04); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x00, 0x0E); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x02, 0xB3); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x09, 0x60); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x0e, 0x4A); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x37, 0x58); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2b, 0x0F); + + jd9365da_switch_page(&dsi_ctx, 0x05); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x15, 0x34); + mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x16, 0x76); + + jd9365da_switch_page(&dsi_ctx, 0x00); + + mipi_dsi_msleep(&dsi_ctx, 120); + + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + + mipi_dsi_msleep(&dsi_ctx, 120); + + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + + mipi_dsi_msleep(&dsi_ctx, 20); + + mipi_dsi_dcs_set_tear_on_multi(&dsi_ctx, MIPI_DSI_DCS_TEAR_MODE_VBLANK); + + return dsi_ctx.accum_err; +} + +static const struct jadard_panel_desc dcltek_dt300250_desc = { + .mode_4ln = &(const struct drm_display_mode) { + .clock = (600 + 48 + 32 + 80) * (1600 + 18 + 6 + 23) * 60 / 1000, + + .hdisplay = 600, + .hsync_start = 600 + 48, + .hsync_end = 600 + 48 + 32, + .htotal = 600 + 48 + 32 + 80, + + .vdisplay = 1600, + .vsync_start = 1600 + 18, + .vsync_end = 1600 + 18 + 6, + .vtotal = 1600 + 18 + 6 + 23, + + .width_mm = 83, + .height_mm = 221, + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, + }, + .lanes = 4, + .format = MIPI_DSI_FMT_RGB888, + .init = dcltek_dt300250_init_cmds, +}; + static int jadard_dsi_probe(struct mipi_dsi_device *dsi) { struct device *dev = &dsi->dev; @@ -3037,6 +3279,10 @@ static const struct of_device_id jadard_of_match[] = { .compatible = "chongzhou,cz101b4001", .data = &cz101b4001_desc }, + { + .compatible = "dcltek,dt300250", + .data = &dcltek_dt300250_desc + }, { .compatible = "kingdisplay,kd101ne3-40ti", .data = &kingdisplay_kd101ne3_40ti_desc From 9836f0236b4325b142c1f29904d09395ac4b87a5 Mon Sep 17 00:00:00 2001 From: Alexandre Hamamdjian Date: Thu, 23 Jul 2026 18:26:28 +0700 Subject: [PATCH 10/83] dt-bindings: display: Add AYANEO Pocket DS lower panel The lower (secondary) 5.0" 1024x768 IPS LCD panel of the AYANEO Pocket DS dual-screen handheld is driven by a Sitronix ST7703 DDIC, as identified by its ST7703 manufacturer command set (SETEXTC 0xB9, SETMIPI 0xBA, SETGIP1/2 0xE9/0xEA). Document its compatible alongside the other ST7703 panels. Signed-off-by: Alexandre Hamamdjian Acked-by: Krzysztof Kozlowski Signed-off-by: Neil Armstrong Link: https://patch.msgid.link/20260723-b4-st7703-pocketds-lower-v1-1-e3db246589f4@gmail.com --- .../devicetree/bindings/display/panel/rocktech,jh057n00900.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/display/panel/rocktech,jh057n00900.yaml b/Documentation/devicetree/bindings/display/panel/rocktech,jh057n00900.yaml index ebfc825b8346..91e06d6a6819 100644 --- a/Documentation/devicetree/bindings/display/panel/rocktech,jh057n00900.yaml +++ b/Documentation/devicetree/bindings/display/panel/rocktech,jh057n00900.yaml @@ -22,6 +22,8 @@ properties: enum: # Anberic RG353V-V2 5.0" 640x480 TFT LCD panel - anbernic,rg353v-panel-v2 + # AYANEO Pocket DS lower 5.0" 1024x768 IPS LCD panel + - ayaneo,pocket-ds-lower-panel # GameForce Chi 3.5" 640x480 TFT LCD panel - gameforce,chi-panel # Powkiddy RGB10MAX3 5.0" 720x1280 TFT LCD panel From 4c0fe6422f3a06d565c6d891587db5d3a1b20bca Mon Sep 17 00:00:00 2001 From: Alexandre Hamamdjian Date: Thu, 23 Jul 2026 18:26:29 +0700 Subject: [PATCH 11/83] drm/panel: st7703: Add AYANEO Pocket DS Lower Panel Support Add support for the lower (secondary) panel of the AYANEO Pocket DS, a dual-screen QCS8550 (Snapdragon 8 Gen 2) handheld. It is a 5.0" 1024x768 4:3 IPS LCD driven by a Sitronix ST7703 DDIC: every command in the vendor init sequence maps to an ST7703 manufacturer command (SETEXTC, SETMIPI, SETRGBIF, SETSCR, SETPOWER, SETGAMMA, SETGIP1/2), and the SETMIPI and SETRGBIF payloads are near-identical to the already-supported Powkiddy RGB10MAX3. The panel is mounted in portrait (768x1024) and rotated 270 degrees in the device, expressed through the panel rotation property. The SETEXTC unlock password ends in 0x87 rather than the canonical 0x83; this is the value shipped by the vendor firmware and is kept verbatim. Init sequence extracted from the AYANEO Pocket DS BSP kernel, via the ROCKNIX SM8550 port. Signed-off-by: Alexandre Hamamdjian Reviewed-by: Neil Armstrong Signed-off-by: Neil Armstrong Link: https://patch.msgid.link/20260723-b4-st7703-pocketds-lower-v1-2-e3db246589f4@gmail.com --- drivers/gpu/drm/panel/panel-sitronix-st7703.c | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/drivers/gpu/drm/panel/panel-sitronix-st7703.c b/drivers/gpu/drm/panel/panel-sitronix-st7703.c index fcbb0b7d50e4..70f7282471e6 100644 --- a/drivers/gpu/drm/panel/panel-sitronix-st7703.c +++ b/drivers/gpu/drm/panel/panel-sitronix-st7703.c @@ -47,6 +47,7 @@ #define ST7703_CMD_SETCABC 0xC8 #define ST7703_CMD_SETPANEL 0xCC #define ST7703_CMD_SETGAMMA 0xE0 +#define ST7703_CMD_UNKNOWN_E1 0xE1 #define ST7703_CMD_SETEQ 0xE3 #define ST7703_CMD_SETGIP1 0xE9 #define ST7703_CMD_SETGIP2 0xEA @@ -673,6 +674,100 @@ static const struct st7703_panel_desc gameforcechi_desc = { .init_sequence = gameforcechi_init_sequence, }; +static void ayaneo_pocket_ds_lower_init_sequence(struct mipi_dsi_multi_context *dsi_ctx) +{ + /* Init sequence extracted from the AYANEO Pocket DS BSP kernel. */ + + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETEXTC, 0xf1, 0x12, + 0x87); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETDISP, 0x00, 0x01, + 0x78); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETRGBIF, 0x10, 0x10, + 0x28, 0x28, 0x03, 0xff, 0x00, 0x00, 0x00, + 0x00); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETCYC, 0x80); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETBGP, 0x0a, 0x0a); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETVCOM, 0x90, 0x90); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETPOWER_EXT, 0x76, + 0x22, 0xf0, 0x13); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETMIPI, 0x33, 0x81, + 0x05, 0xf9, 0x0e, 0x0e, 0x20, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x25, + 0x00, 0x91, 0x0a, 0x00, 0x00, 0x01, 0x4f, + 0x01, 0x00, 0x00, 0x37); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETVDC, 0x47); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_UNKNOWN_BF, 0x02, 0x10, + 0x00, 0x80, 0x04); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETSCR, 0x73, 0x73, + 0x50, 0x50, 0x00, 0x00, 0x12, 0x73, 0x00); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETPOWER, 0x55, 0x00, + 0x32, 0x32, 0x77, 0xf1, 0xf7, 0xf7, 0xcc, + 0xcc, 0x7f, 0x7f, 0x11, 0x11, 0x00, 0x00, + 0x32); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETIO, 0xb8, 0x00, + 0x0a, 0x10, 0x01, 0x09, 0x00, 0x00, 0xed, + 0xc5, 0x00, 0xa5); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETCABC, 0x10, 0x40, + 0x1e, 0x03); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETPANEL, 0x0b); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETGAMMA, 0x00, 0x00, + 0x00, 0x25, 0x31, 0x3f, 0x26, 0x29, 0x04, + 0x08, 0x0d, 0x0e, 0x12, 0x0f, 0x11, 0x12, + 0x18, 0x00, 0x00, 0x00, 0x25, 0x31, 0x3f, + 0x26, 0x29, 0x04, 0x08, 0x0d, 0x0e, 0x12, + 0x0f, 0x11, 0x12, 0x18); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_UNKNOWN_E1, 0x11, 0x11, + 0x91, 0x00, 0x00, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETEQ, 0x07, 0x07, + 0x0b, 0x0b, 0x0b, 0x0b, 0x00, 0x00, 0x00, + 0x00, 0xff, 0x84, 0xc0, 0x10); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETGIP1, 0xc8, 0x10, + 0x04, 0x04, 0x02, 0x80, 0x81, 0x12, 0x31, + 0x23, 0x2f, 0x80, 0x80, 0x81, 0x27, 0x20, + 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0f, 0x00, 0x00, 0x00, 0x44, 0x00, + 0x88, 0xff, 0x00, 0x22, 0x44, 0x66, 0x88, + 0x88, 0x88, 0x55, 0x11, 0x88, 0xff, 0x11, + 0x33, 0x55, 0x77, 0x88, 0x88, 0x88, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_SETGIP2, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x11, 0x55, 0xff, 0x88, + 0x77, 0x55, 0x33, 0x11, 0x88, 0x88, 0x88, + 0x00, 0x44, 0xff, 0x88, 0x66, 0x44, 0x22, + 0x00, 0x88, 0x88, 0x88, 0x23, 0x00, 0x00, + 0x00, 0xec, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x20, 0x80, 0x81, 0x00, + 0x00, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(dsi_ctx, ST7703_CMD_UNKNOWN_EF, 0xff, 0xff, + 0x01); +} + +static const struct drm_display_mode ayaneo_pocket_ds_lower_mode = { + .hdisplay = 768, + .hsync_start = 768 + 110, + .hsync_end = 768 + 110 + 45, + .htotal = 768 + 110 + 45 + 110, + .vdisplay = 1024, + .vsync_start = 1024 + 18, + .vsync_end = 1024 + 18 + 6, + .vtotal = 1024 + 18 + 6 + 16, + .clock = 65946, + .width_mm = 76, + .height_mm = 102, +}; + +static const struct st7703_panel_desc ayaneo_pocket_ds_lower_desc = { + .mode = &ayaneo_pocket_ds_lower_mode, + .lanes = 4, + .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_CLOCK_NON_CONTINUOUS | MIPI_DSI_MODE_LPM, + .format = MIPI_DSI_FMT_RGB888, + .init_sequence = ayaneo_pocket_ds_lower_init_sequence, +}; + static int st7703_enable(struct drm_panel *panel) { struct st7703 *ctx = panel_to_st7703(panel); @@ -916,6 +1011,7 @@ static void st7703_remove(struct mipi_dsi_device *dsi) static const struct of_device_id st7703_of_match[] = { { .compatible = "anbernic,rg353v-panel-v2", .data = &rg353v2_desc }, + { .compatible = "ayaneo,pocket-ds-lower-panel", .data = &ayaneo_pocket_ds_lower_desc }, { .compatible = "gameforce,chi-panel", .data = &gameforcechi_desc }, { .compatible = "powkiddy,rgb10max3-panel", .data = &rgb10max3_panel_desc }, { .compatible = "powkiddy,rgb30-panel", .data = &rgb30panel_desc }, From 108a73346f52e5c6dc01f545dae5355482ec3ab1 Mon Sep 17 00:00:00 2001 From: Paul Louvel Date: Thu, 23 Jul 2026 18:31:49 +0200 Subject: [PATCH 12/83] dt-bindings: display: simple: Add Ampire AM-800480N3TZQW-00H Add compatible for the AM-800480N3TZQW-00H from Ampire Co. Ltd. Signed-off-by: Paul Louvel Acked-by: Krzysztof Kozlowski Signed-off-by: Neil Armstrong Link: https://patch.msgid.link/20260723-support-am-800480n3tzqw-00h-v1-1-6a4ae0575a19@bootlin.com --- .../devicetree/bindings/display/panel/panel-simple.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/display/panel/panel-simple.yaml b/Documentation/devicetree/bindings/display/panel/panel-simple.yaml index 21453f0d7ce2..3d1cc11ba3aa 100644 --- a/Documentation/devicetree/bindings/display/panel/panel-simple.yaml +++ b/Documentation/devicetree/bindings/display/panel/panel-simple.yaml @@ -37,6 +37,8 @@ properties: - ampire,am-480272h3tmqw-t01h # Ampire AM-800480L1TMQW-T00H 5" WVGA TFT LCD panel - ampire,am-800480l1tmqw-t00h + # Ampire AM-800480N3TZQW-00H 5" WVGA TFT LCD panel + - ampire,am-800480n3tzqw-00h # Ampire AM-800480R3TMQW-A1H 7.0" WVGA TFT LCD panel - ampire,am800480r3tmqwa1h # Ampire AM-800600P5TMQW-TB8H 8.0" SVGA TFT LCD panel From e1bde8ef0abe4e299fd14694cb2d557d5807c864 Mon Sep 17 00:00:00 2001 From: Paul Louvel Date: Thu, 23 Jul 2026 18:31:50 +0200 Subject: [PATCH 13/83] drm/panel: simple: Add Ampire AM-800480N3TZQW-00H Add support for the Ampire AM-800480N3TZQW-00H 5" WVGA TFT LCD panel. Signed-off-by: Paul Louvel Reviewed-by: Neil Armstrong Signed-off-by: Neil Armstrong Link: https://patch.msgid.link/20260723-support-am-800480n3tzqw-00h-v1-2-6a4ae0575a19@bootlin.com --- drivers/gpu/drm/panel/panel-simple.c | 33 ++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c index 292212119fda..c95964ba25a9 100644 --- a/drivers/gpu/drm/panel/panel-simple.c +++ b/drivers/gpu/drm/panel/panel-simple.c @@ -912,6 +912,36 @@ static const struct panel_desc ampire_am_800480l1tmqw_t00h = { .connector_type = DRM_MODE_CONNECTOR_DPI, }; +static const struct display_timing ampire_am_800480n3tzqw_00h_timing = { + .pixelclock = {23000000, 25000000, 27000000}, + .hactive = { 800, 800, 800 }, + .hfront_porch = { 4, 8, 48 }, + .hback_porch = { 4, 8, 48 }, + .hsync_len = { 2, 8, 8 }, + .vactive = { 480, 480, 480 }, + .vfront_porch = { 4, 8, 12 }, + .vback_porch = { 4, 8, 12 }, + .vsync_len = { 2, 4, 8 }, + .flags = DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_VSYNC_LOW | + DISPLAY_FLAGS_DE_HIGH | DISPLAY_FLAGS_PIXDATA_POSEDGE | + DISPLAY_FLAGS_SYNC_POSEDGE, +}; + +static const struct panel_desc ampire_am_800480n3tzqw_00h = { + .timings = &ire_am_800480n3tzqw_00h_timing, + .num_timings = 1, + .bpc = 8, + .size = { + .width = 108, + .height = 65, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X24, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | + DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE | + DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE, + .connector_type = DRM_MODE_CONNECTOR_DPI, +}; + static const struct panel_desc ampire_am800480r3tmqwa1h = { .modes = &ire_am800480r3tmqwa1h_mode, .num_modes = 1, @@ -5648,6 +5678,9 @@ static const struct of_device_id platform_of_match[] = { }, { .compatible = "ampire,am-800480l1tmqw-t00h", .data = &ire_am_800480l1tmqw_t00h, + }, { + .compatible = "ampire,am-800480n3tzqw-00h", + .data = &ire_am_800480n3tzqw_00h, }, { .compatible = "ampire,am800480r3tmqwa1h", .data = &ire_am800480r3tmqwa1h, From 4085da1e6ad90ca7a227d8528de2c77042fabf8a Mon Sep 17 00:00:00 2001 From: David Carlier Date: Fri, 24 Jul 2026 05:17:46 +0100 Subject: [PATCH 14/83] drm/panel: novatek-nt36536: Fix panel double-remove on attach failure The DSI attach error path calls drm_panel_remove() by hand even though the panel was registered with devm_drm_panel_add(), which already arranges for drm_panel_remove() to run on driver detach. When mipi_dsi_attach() fails the panel is therefore removed twice: once directly and once again while devres unwinds. drm_panel_add() takes a reference and drm_panel_remove() drops one, so the extra removal releases the last reference early and frees the panel container. The put registered by devm_drm_panel_alloc() then operates on freed memory, resulting in a use-after-free and a reference-count underflow when a DSI host rejects the requested configuration during probe. Drop the manual drm_panel_remove() and let the managed cleanup handle it, matching the other dual-DSI panel drivers. Fixes: 75a5dbd1f4f7 ("drm/panel: Add Novatek NT36536 panel driver") Signed-off-by: David Carlier Reviewed-by: Pengyu Luo Signed-off-by: Neil Armstrong Link: https://patch.msgid.link/20260724041746.12887-1-devnexen@gmail.com --- drivers/gpu/drm/panel/panel-novatek-nt36536.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/gpu/drm/panel/panel-novatek-nt36536.c b/drivers/gpu/drm/panel/panel-novatek-nt36536.c index 2a82b54880c3..8bd125650168 100644 --- a/drivers/gpu/drm/panel/panel-novatek-nt36536.c +++ b/drivers/gpu/drm/panel/panel-novatek-nt36536.c @@ -429,11 +429,9 @@ static int novatek_probe(struct mipi_dsi_device *dsi) ctx->dsi[i]->mode_flags = desc->mode_flags; ctx->dsi[i]->dsc = &ctx->dsc; ret = devm_mipi_dsi_attach(dev, ctx->dsi[i]); - if (ret < 0) { - drm_panel_remove(&ctx->panel); + if (ret < 0) return dev_err_probe(dev, ret, "Failed to attach to DSI host\n"); - } } if (desc->has_dcs_backlight) { From 19ff4e174fe5d5bdf0985a670549c02b7eb84c68 Mon Sep 17 00:00:00 2001 From: Osama Abdelkader Date: Thu, 21 May 2026 23:52:26 +0200 Subject: [PATCH 15/83] drm/bridge: tc358762: use devm for bridge registration and DSI attach Replace manual drm_bridge_remove()/mipi_dsi_detach() in remove with devm_drm_bridge_add() and devm_mipi_dsi_attach() in probe. Also check the return value from bridge registration. Signed-off-by: Osama Abdelkader Reviewed-by: Luca Ceresoli Link: https://patch.msgid.link/20260521215228.188615-1-osama.abdelkader@gmail.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/tc358762.c | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/drivers/gpu/drm/bridge/tc358762.c b/drivers/gpu/drm/bridge/tc358762.c index 6124d3123f57..76564d5e4f69 100644 --- a/drivers/gpu/drm/bridge/tc358762.c +++ b/drivers/gpu/drm/bridge/tc358762.c @@ -292,25 +292,17 @@ static int tc358762_probe(struct mipi_dsi_device *dsi) ctx->bridge.of_node = dev->of_node; ctx->bridge.pre_enable_prev_first = true; - drm_bridge_add(&ctx->bridge); + ret = devm_drm_bridge_add(dev, &ctx->bridge); + if (ret < 0) + return ret; - ret = mipi_dsi_attach(dsi); - if (ret < 0) { - drm_bridge_remove(&ctx->bridge); + ret = devm_mipi_dsi_attach(dev, dsi); + if (ret < 0) dev_err(dev, "failed to attach dsi\n"); - } return ret; } -static void tc358762_remove(struct mipi_dsi_device *dsi) -{ - struct tc358762 *ctx = mipi_dsi_get_drvdata(dsi); - - mipi_dsi_detach(dsi); - drm_bridge_remove(&ctx->bridge); -} - static const struct of_device_id tc358762_of_match[] = { { .compatible = "toshiba,tc358762" }, { } @@ -319,7 +311,6 @@ MODULE_DEVICE_TABLE(of, tc358762_of_match); static struct mipi_dsi_driver tc358762_driver = { .probe = tc358762_probe, - .remove = tc358762_remove, .driver = { .name = "tc358762", .of_match_table = tc358762_of_match, From 05271b55f1cd9c1e2472c3be58474490a29c39c8 Mon Sep 17 00:00:00 2001 From: Osama Abdelkader Date: Thu, 21 May 2026 23:52:27 +0200 Subject: [PATCH 16/83] drm/bridge: tc358764: use devm for bridge registration and DSI attach Replace manual drm_bridge_remove()/mipi_dsi_detach() in remove with devm_drm_bridge_add() and devm_mipi_dsi_attach() in probe. Also check the return value from bridge registration. Signed-off-by: Osama Abdelkader Reviewed-by: Luca Ceresoli Link: https://patch.msgid.link/20260521215228.188615-2-osama.abdelkader@gmail.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/tc358764.c | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/drivers/gpu/drm/bridge/tc358764.c b/drivers/gpu/drm/bridge/tc358764.c index f55a56cbd88f..049215e65988 100644 --- a/drivers/gpu/drm/bridge/tc358764.c +++ b/drivers/gpu/drm/bridge/tc358764.c @@ -376,25 +376,17 @@ static int tc358764_probe(struct mipi_dsi_device *dsi) ctx->bridge.of_node = dev->of_node; ctx->bridge.pre_enable_prev_first = true; - drm_bridge_add(&ctx->bridge); + ret = devm_drm_bridge_add(dev, &ctx->bridge); + if (ret < 0) + return ret; - ret = mipi_dsi_attach(dsi); - if (ret < 0) { - drm_bridge_remove(&ctx->bridge); + ret = devm_mipi_dsi_attach(dev, dsi); + if (ret < 0) dev_err(dev, "failed to attach dsi\n"); - } return ret; } -static void tc358764_remove(struct mipi_dsi_device *dsi) -{ - struct tc358764 *ctx = mipi_dsi_get_drvdata(dsi); - - mipi_dsi_detach(dsi); - drm_bridge_remove(&ctx->bridge); -} - static const struct of_device_id tc358764_of_match[] = { { .compatible = "toshiba,tc358764" }, { } @@ -403,7 +395,6 @@ MODULE_DEVICE_TABLE(of, tc358764_of_match); static struct mipi_dsi_driver tc358764_driver = { .probe = tc358764_probe, - .remove = tc358764_remove, .driver = { .name = "tc358764", .of_match_table = tc358764_of_match, From c093fd7539cb8bd6e76f84ad2c5f13af21e68d28 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Sat, 4 Jul 2026 01:41:43 +0300 Subject: [PATCH 17/83] drm/bridge: Replace outdated forward declaration The drm_bridge.h header forward-declares struct edid, whose last usage in the file was removed in commit 27b8f91c08d9 ("drm/bridge: remove ->get_edid callback"). Commit 11f6c4b1b259 ("drm/bridge: Add connector-related bridge operations and data") then introduced usage of struct drm_edid, without a corresponding forward declaration. Fix those two issues by replacing the struct edid forward declaration with struct drm_edid. Signed-off-by: Laurent Pinchart Reviewed-by: Luca Ceresoli Link: https://patch.msgid.link/20260703224143.3886069-1-laurent.pinchart+renesas@ideasonboard.com Signed-off-by: Luca Ceresoli --- include/drm/drm_bridge.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h index 3ac84ed57ab2..58fff047f43b 100644 --- a/include/drm/drm_bridge.h +++ b/include/drm/drm_bridge.h @@ -40,9 +40,9 @@ struct drm_bridge; struct drm_bridge_timings; struct drm_connector; struct drm_display_info; +struct drm_edid; struct drm_minor; struct drm_panel; -struct edid; struct hdmi_codec_daifmt; struct hdmi_codec_params; struct i2c_adapter; From d767cc461fb8d1763d86a670dd9c9f1b3c4579d5 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Tue, 14 Jul 2026 20:03:59 +0100 Subject: [PATCH 18/83] drm/bridge: lontium-lt8912b: make read-only const array supply_names static Don't populate the read-only const array supply_names on the stack at run time, instead make it static Signed-off-by: Colin Ian King Reviewed-by: Luca Ceresoli Acked-by: Francesco Dolcini Link: https://patch.msgid.link/20260714190400.194605-1-colin.i.king@gmail.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/lontium-lt8912b.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/bridge/lontium-lt8912b.c b/drivers/gpu/drm/bridge/lontium-lt8912b.c index df5d07e7644c..515e86f8c301 100644 --- a/drivers/gpu/drm/bridge/lontium-lt8912b.c +++ b/drivers/gpu/drm/bridge/lontium-lt8912b.c @@ -677,7 +677,7 @@ static DEFINE_SIMPLE_DEV_PM_OPS(lt8912_bridge_pm_ops, lt8912_bridge_suspend, lt8 static int lt8912_get_regulators(struct lt8912 *lt) { unsigned int i; - const char * const supply_names[] = { + static const char * const supply_names[] = { "vdd", "vccmipirx", "vccsysclk", "vcclvdstx", "vcchdmitx", "vcclvdspll", "vcchdmipll" }; From 09b195a7bb23df56269cd2a95d01ba3a5533af13 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 17 Jul 2026 11:08:19 +0200 Subject: [PATCH 19/83] drm/bridge: dw-hdmi: fix i2c adapter leak on probe failure Make sure to drop the i2c adapter device and module references before returning when detecting a malformed devicetree during probe. Fixes: 80e2f97968b5 ("drm: bridge: dw-hdmi: Switch to regmap for register access") Cc: stable@vger.kernel.org # 4.12 Cc: Neil Armstrong Signed-off-by: Johan Hovold Reviewed-by: Luca Ceresoli Reviewed-by: Laurent Pinchart Link: https://patch.msgid.link/20260717090819.1630965-1-johan@kernel.org Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index a408dbd414bf..34c5f583e910 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -3389,7 +3389,8 @@ struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev, break; default: dev_err(dev, "reg-io-width must be 1 or 4\n"); - return ERR_PTR(-EINVAL); + ret = -EINVAL; + goto err_res; } iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); From a2a334802883908ee86e0571faf5e9ea6a008c3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Mon, 20 Jul 2026 13:25:47 +0200 Subject: [PATCH 20/83] drm/bridge: Use named initializers for arrays of i2c_device_data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While being less compact, using named initializers allows to more easily see which members of the structs are assigned which value without having to lookup the declaration of the struct. And it's also more robust against changes to the struct definition. The mentioned robustness is relevant for a planned change to struct i2c_device_id that replaces .driver_data by an anonymous union. While touching all these arrays, unify usage of whitespace in the list terminator and drop trailing commas there. This patch doesn't modify the compiled arrays, only their representation in source form benefits. The former was confirmed with x86 and arm64 builds. Reviewed-by: Luca Ceresoli Signed-off-by: Uwe Kleine-König (The Capable Hub) Acked-by: Douglas Anderson # ti-sn65dsi86.c Link: https://patch.msgid.link/9fa3a8e372b7211c06ec885617051f5006227e3a.1784545092.git.u.kleine-koenig@baylibre.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/adv7511/adv7511_drv.c | 10 +++++----- drivers/gpu/drm/bridge/analogix/analogix-anx6345.c | 2 +- drivers/gpu/drm/bridge/analogix/anx7625.c | 4 ++-- drivers/gpu/drm/bridge/chipone-icn6211.c | 4 ++-- drivers/gpu/drm/bridge/chrontel-ch7033.c | 2 +- drivers/gpu/drm/bridge/ite-it6263.c | 2 +- drivers/gpu/drm/bridge/ite-it6505.c | 2 +- drivers/gpu/drm/bridge/lontium-lt8713sx.c | 2 +- drivers/gpu/drm/bridge/lontium-lt8912b.c | 4 ++-- drivers/gpu/drm/bridge/lontium-lt9211.c | 4 ++-- drivers/gpu/drm/bridge/lontium-lt9611.c | 4 ++-- drivers/gpu/drm/bridge/lontium-lt9611uxc.c | 2 +- .../gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c | 8 ++++---- drivers/gpu/drm/bridge/nxp-ptn3460.c | 4 ++-- drivers/gpu/drm/bridge/parade-ps8622.c | 8 ++++---- drivers/gpu/drm/bridge/sii902x.c | 2 +- drivers/gpu/drm/bridge/sii9234.c | 2 +- drivers/gpu/drm/bridge/sil-sii8620.c | 2 +- drivers/gpu/drm/bridge/tc358767.c | 2 +- drivers/gpu/drm/bridge/tc358768.c | 4 ++-- drivers/gpu/drm/bridge/tc358775.c | 4 ++-- drivers/gpu/drm/bridge/tda998x_drv.c | 2 +- drivers/gpu/drm/bridge/ti-dlpc3433.c | 2 +- drivers/gpu/drm/bridge/ti-sn65dsi83.c | 6 +++--- drivers/gpu/drm/bridge/ti-sn65dsi86.c | 4 ++-- drivers/gpu/drm/bridge/ti-tfp410.c | 2 +- 26 files changed, 47 insertions(+), 47 deletions(-) diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c index db99e252aeeb..038b852405b5 100644 --- a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c +++ b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c @@ -1471,11 +1471,11 @@ static const struct adv7511_chip_info adv7535_chip_info = { }; static const struct i2c_device_id adv7511_i2c_ids[] = { - { "adv7511", (kernel_ulong_t)&adv7511_chip_info }, - { "adv7511w", (kernel_ulong_t)&adv7511_chip_info }, - { "adv7513", (kernel_ulong_t)&adv7511_chip_info }, - { "adv7533", (kernel_ulong_t)&adv7533_chip_info }, - { "adv7535", (kernel_ulong_t)&adv7535_chip_info }, + { .name = "adv7511", .driver_data = (kernel_ulong_t)&adv7511_chip_info }, + { .name = "adv7511w", .driver_data = (kernel_ulong_t)&adv7511_chip_info }, + { .name = "adv7513", .driver_data = (kernel_ulong_t)&adv7511_chip_info }, + { .name = "adv7533", .driver_data = (kernel_ulong_t)&adv7533_chip_info }, + { .name = "adv7535", .driver_data = (kernel_ulong_t)&adv7535_chip_info }, { } }; MODULE_DEVICE_TABLE(i2c, adv7511_i2c_ids); diff --git a/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c b/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c index d152c02419f5..407834a03b63 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c +++ b/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c @@ -783,7 +783,7 @@ static void anx6345_i2c_remove(struct i2c_client *client) } static const struct i2c_device_id anx6345_id[] = { - { "anx6345" }, + { .name = "anx6345" }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(i2c, anx6345_id); diff --git a/drivers/gpu/drm/bridge/analogix/anx7625.c b/drivers/gpu/drm/bridge/analogix/anx7625.c index ebd34657908e..c3af88e14f8b 100644 --- a/drivers/gpu/drm/bridge/analogix/anx7625.c +++ b/drivers/gpu/drm/bridge/analogix/anx7625.c @@ -2993,8 +2993,8 @@ static void anx7625_i2c_remove(struct i2c_client *client) } static const struct i2c_device_id anx7625_id[] = { - { "anx7625" }, - {} + { .name = "anx7625" }, + { } }; MODULE_DEVICE_TABLE(i2c, anx7625_id); diff --git a/drivers/gpu/drm/bridge/chipone-icn6211.c b/drivers/gpu/drm/bridge/chipone-icn6211.c index eb628a0ce6ec..0f6173c61502 100644 --- a/drivers/gpu/drm/bridge/chipone-icn6211.c +++ b/drivers/gpu/drm/bridge/chipone-icn6211.c @@ -784,8 +784,8 @@ static struct mipi_dsi_driver chipone_dsi_driver = { }; static const struct i2c_device_id chipone_i2c_id[] = { - { "chipone,icn6211" }, - {}, + { .name = "chipone,icn6211" }, + { } }; MODULE_DEVICE_TABLE(i2c, chipone_i2c_id); diff --git a/drivers/gpu/drm/bridge/chrontel-ch7033.c b/drivers/gpu/drm/bridge/chrontel-ch7033.c index 92d464727e41..4bc64b2885f7 100644 --- a/drivers/gpu/drm/bridge/chrontel-ch7033.c +++ b/drivers/gpu/drm/bridge/chrontel-ch7033.c @@ -601,7 +601,7 @@ static const struct of_device_id ch7033_dt_ids[] = { MODULE_DEVICE_TABLE(of, ch7033_dt_ids); static const struct i2c_device_id ch7033_ids[] = { - { "ch7033" }, + { .name = "ch7033" }, { } }; MODULE_DEVICE_TABLE(i2c, ch7033_ids); diff --git a/drivers/gpu/drm/bridge/ite-it6263.c b/drivers/gpu/drm/bridge/ite-it6263.c index 4c0b670806cc..21ac8e58b9d2 100644 --- a/drivers/gpu/drm/bridge/ite-it6263.c +++ b/drivers/gpu/drm/bridge/ite-it6263.c @@ -910,7 +910,7 @@ static const struct of_device_id it6263_of_match[] = { MODULE_DEVICE_TABLE(of, it6263_of_match); static const struct i2c_device_id it6263_i2c_ids[] = { - { "it6263" }, + { .name = "it6263" }, { } }; MODULE_DEVICE_TABLE(i2c, it6263_i2c_ids); diff --git a/drivers/gpu/drm/bridge/ite-it6505.c b/drivers/gpu/drm/bridge/ite-it6505.c index 8ecb43611dba..025a19c1e530 100644 --- a/drivers/gpu/drm/bridge/ite-it6505.c +++ b/drivers/gpu/drm/bridge/ite-it6505.c @@ -3652,7 +3652,7 @@ static void it6505_i2c_remove(struct i2c_client *client) } static const struct i2c_device_id it6505_id[] = { - { "it6505" }, + { .name = "it6505" }, { } }; diff --git a/drivers/gpu/drm/bridge/lontium-lt8713sx.c b/drivers/gpu/drm/bridge/lontium-lt8713sx.c index 4a6420b876da..bfff28883280 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[] = { - { "lontium,lt8713sx", 0 }, + { .name = "lontium,lt8713sx" }, { /* sentinel */ } }; diff --git a/drivers/gpu/drm/bridge/lontium-lt8912b.c b/drivers/gpu/drm/bridge/lontium-lt8912b.c index 515e86f8c301..e347d04b7b2d 100644 --- a/drivers/gpu/drm/bridge/lontium-lt8912b.c +++ b/drivers/gpu/drm/bridge/lontium-lt8912b.c @@ -820,8 +820,8 @@ static const struct of_device_id lt8912_dt_match[] = { MODULE_DEVICE_TABLE(of, lt8912_dt_match); static const struct i2c_device_id lt8912_id[] = { - { "lt8912" }, - {} + { .name = "lt8912" }, + { } }; MODULE_DEVICE_TABLE(i2c, lt8912_id); diff --git a/drivers/gpu/drm/bridge/lontium-lt9211.c b/drivers/gpu/drm/bridge/lontium-lt9211.c index 6d97ae5640c4..23682384daf4 100644 --- a/drivers/gpu/drm/bridge/lontium-lt9211.c +++ b/drivers/gpu/drm/bridge/lontium-lt9211.c @@ -773,8 +773,8 @@ static void lt9211_remove(struct i2c_client *client) } static const struct i2c_device_id lt9211_id[] = { - { "lontium,lt9211" }, - {}, + { .name = "lontium,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 b6a368f71285..fb34f661ee0a 100644 --- a/drivers/gpu/drm/bridge/lontium-lt9611.c +++ b/drivers/gpu/drm/bridge/lontium-lt9611.c @@ -1229,8 +1229,8 @@ static void lt9611_remove(struct i2c_client *client) } static const struct i2c_device_id lt9611_id[] = { - { "lontium,lt9611" }, - {} + { .name = "lontium,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 6625ba23761c..42a6503cd0f9 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[] = { - { "lontium,lt9611uxc" }, + { .name = "lontium,lt9611uxc" }, { /* sentinel */ } }; diff --git a/drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c b/drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c index 25ff0a0acc93..4d244e6d4ed9 100644 --- a/drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c +++ b/drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c @@ -317,8 +317,8 @@ static void stdp4028_ge_b850v3_fw_remove(struct i2c_client *stdp4028_i2c) } static const struct i2c_device_id stdp4028_ge_b850v3_fw_i2c_table[] = { - { "stdp4028_ge_fw" }, - {} + { .name = "stdp4028_ge_fw" }, + { } }; MODULE_DEVICE_TABLE(i2c, stdp4028_ge_b850v3_fw_i2c_table); @@ -364,8 +364,8 @@ static void stdp2690_ge_b850v3_fw_remove(struct i2c_client *stdp2690_i2c) } static const struct i2c_device_id stdp2690_ge_b850v3_fw_i2c_table[] = { - { "stdp2690_ge_fw" }, - {} + { .name = "stdp2690_ge_fw" }, + { } }; MODULE_DEVICE_TABLE(i2c, stdp2690_ge_b850v3_fw_i2c_table); diff --git a/drivers/gpu/drm/bridge/nxp-ptn3460.c b/drivers/gpu/drm/bridge/nxp-ptn3460.c index a9b4ac2defc9..f9eff8bfceb7 100644 --- a/drivers/gpu/drm/bridge/nxp-ptn3460.c +++ b/drivers/gpu/drm/bridge/nxp-ptn3460.c @@ -323,8 +323,8 @@ static void ptn3460_remove(struct i2c_client *client) } static const struct i2c_device_id ptn3460_i2c_table[] = { - { "ptn3460" }, - {} + { .name = "ptn3460" }, + { } }; MODULE_DEVICE_TABLE(i2c, ptn3460_i2c_table); diff --git a/drivers/gpu/drm/bridge/parade-ps8622.c b/drivers/gpu/drm/bridge/parade-ps8622.c index 33844db753aa..3a2ec482037d 100644 --- a/drivers/gpu/drm/bridge/parade-ps8622.c +++ b/drivers/gpu/drm/bridge/parade-ps8622.c @@ -534,10 +534,10 @@ static void ps8622_remove(struct i2c_client *client) } static const struct i2c_device_id ps8622_i2c_table[] = { - /* Device type, max_lane_count */ - {"ps8622", 1}, - {"ps8625", 2}, - {}, + /* Device type, driver_data holds the maximal lane_count */ + { .name = "ps8622", .driver_data = 1 }, + { .name = "ps8625", .driver_data = 2 }, + { } }; MODULE_DEVICE_TABLE(i2c, ps8622_i2c_table); diff --git a/drivers/gpu/drm/bridge/sii902x.c b/drivers/gpu/drm/bridge/sii902x.c index f575b2dbe32b..9aac44ed5b67 100644 --- a/drivers/gpu/drm/bridge/sii902x.c +++ b/drivers/gpu/drm/bridge/sii902x.c @@ -1235,7 +1235,7 @@ static const struct of_device_id sii902x_dt_ids[] = { MODULE_DEVICE_TABLE(of, sii902x_dt_ids); static const struct i2c_device_id sii902x_i2c_ids[] = { - { "sii9022" }, + { .name = "sii9022" }, { } }; MODULE_DEVICE_TABLE(i2c, sii902x_i2c_ids); diff --git a/drivers/gpu/drm/bridge/sii9234.c b/drivers/gpu/drm/bridge/sii9234.c index be67642ab7d3..9fef332db248 100644 --- a/drivers/gpu/drm/bridge/sii9234.c +++ b/drivers/gpu/drm/bridge/sii9234.c @@ -944,7 +944,7 @@ static const struct of_device_id sii9234_dt_match[] = { MODULE_DEVICE_TABLE(of, sii9234_dt_match); static const struct i2c_device_id sii9234_id[] = { - { "SII9234" }, + { .name = "SII9234" }, { } }; MODULE_DEVICE_TABLE(i2c, sii9234_id); diff --git a/drivers/gpu/drm/bridge/sil-sii8620.c b/drivers/gpu/drm/bridge/sil-sii8620.c index 22c81b38b41e..b452748ad9db 100644 --- a/drivers/gpu/drm/bridge/sil-sii8620.c +++ b/drivers/gpu/drm/bridge/sil-sii8620.c @@ -2374,7 +2374,7 @@ static const struct of_device_id sii8620_dt_match[] = { MODULE_DEVICE_TABLE(of, sii8620_dt_match); static const struct i2c_device_id sii8620_id[] = { - { "sii8620" }, + { .name = "sii8620" }, { } }; diff --git a/drivers/gpu/drm/bridge/tc358767.c b/drivers/gpu/drm/bridge/tc358767.c index 8f1648f799a7..e3a57f8228da 100644 --- a/drivers/gpu/drm/bridge/tc358767.c +++ b/drivers/gpu/drm/bridge/tc358767.c @@ -2613,7 +2613,7 @@ static void tc_remove(struct i2c_client *client) } static const struct i2c_device_id tc358767_i2c_ids[] = { - { "tc358767" }, + { .name = "tc358767" }, { } }; MODULE_DEVICE_TABLE(i2c, tc358767_i2c_ids); diff --git a/drivers/gpu/drm/bridge/tc358768.c b/drivers/gpu/drm/bridge/tc358768.c index 112a421f99b5..d1fc6af37cc5 100644 --- a/drivers/gpu/drm/bridge/tc358768.c +++ b/drivers/gpu/drm/bridge/tc358768.c @@ -1362,8 +1362,8 @@ static const struct regmap_config tc358768_regmap_config = { }; static const struct i2c_device_id tc358768_i2c_ids[] = { - { "tc358768" }, - { "tc358778" }, + { .name = "tc358768" }, + { .name = "tc358778" }, { } }; MODULE_DEVICE_TABLE(i2c, tc358768_i2c_ids); diff --git a/drivers/gpu/drm/bridge/tc358775.c b/drivers/gpu/drm/bridge/tc358775.c index 728938f9dcb4..203dc738ecef 100644 --- a/drivers/gpu/drm/bridge/tc358775.c +++ b/drivers/gpu/drm/bridge/tc358775.c @@ -727,8 +727,8 @@ static void tc_remove(struct i2c_client *client) } static const struct i2c_device_id tc358775_i2c_ids[] = { - { "tc358765", TC358765, }, - { "tc358775", TC358775, }, + { .name = "tc358765", .driver_data = TC358765 }, + { .name = "tc358775", .driver_data = TC358775 }, { } }; MODULE_DEVICE_TABLE(i2c, tc358775_i2c_ids); diff --git a/drivers/gpu/drm/bridge/tda998x_drv.c b/drivers/gpu/drm/bridge/tda998x_drv.c index 16c745591175..0b966c9844e7 100644 --- a/drivers/gpu/drm/bridge/tda998x_drv.c +++ b/drivers/gpu/drm/bridge/tda998x_drv.c @@ -2064,7 +2064,7 @@ MODULE_DEVICE_TABLE(of, tda998x_dt_ids); #endif static const struct i2c_device_id tda998x_ids[] = { - { "tda998x" }, + { .name = "tda998x" }, { } }; MODULE_DEVICE_TABLE(i2c, tda998x_ids); diff --git a/drivers/gpu/drm/bridge/ti-dlpc3433.c b/drivers/gpu/drm/bridge/ti-dlpc3433.c index c16eb3a05cfa..c9f789e30687 100644 --- a/drivers/gpu/drm/bridge/ti-dlpc3433.c +++ b/drivers/gpu/drm/bridge/ti-dlpc3433.c @@ -389,7 +389,7 @@ static void dlpc3433_remove(struct i2c_client *client) } static const struct i2c_device_id dlpc3433_id[] = { - { "ti,dlpc3433" }, + { .name = "ti,dlpc3433" }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(i2c, dlpc3433_id); diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi83.c b/drivers/gpu/drm/bridge/ti-sn65dsi83.c index e6dbe51d0dba..f9fdbf48c6b3 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi83.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi83.c @@ -1072,9 +1072,9 @@ static void sn65dsi83_remove(struct i2c_client *client) } static const struct i2c_device_id sn65dsi83_id[] = { - { "ti,sn65dsi83", MODEL_SN65DSI83 }, - { "ti,sn65dsi84", MODEL_SN65DSI84 }, - {}, + { .name = "ti,sn65dsi83", .driver_data = MODEL_SN65DSI83 }, + { .name = "ti,sn65dsi84", .driver_data = MODEL_SN65DSI84 }, + { } }; MODULE_DEVICE_TABLE(i2c, sn65dsi83_id); diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c index 1da242f17413..5590ad230624 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c @@ -2116,8 +2116,8 @@ static int ti_sn65dsi86_probe(struct i2c_client *client) } static const struct i2c_device_id ti_sn65dsi86_id[] = { - { "ti,sn65dsi86" }, - {} + { .name = "ti,sn65dsi86" }, + { } }; MODULE_DEVICE_TABLE(i2c, ti_sn65dsi86_id); diff --git a/drivers/gpu/drm/bridge/ti-tfp410.c b/drivers/gpu/drm/bridge/ti-tfp410.c index f0a14f52aaac..629ff7095e87 100644 --- a/drivers/gpu/drm/bridge/ti-tfp410.c +++ b/drivers/gpu/drm/bridge/ti-tfp410.c @@ -437,7 +437,7 @@ static void tfp410_i2c_remove(struct i2c_client *client) } static const struct i2c_device_id tfp410_i2c_ids[] = { - { "tfp410" }, + { .name = "tfp410" }, { } }; MODULE_DEVICE_TABLE(i2c, tfp410_i2c_ids); From a7e8cf887ada1173641042b67ee4bc571417957e Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Mon, 20 Jul 2026 17:36:16 -0400 Subject: [PATCH 21/83] drm/nouveau/kms/nv50-: Add .blend_modes to nv50_wndw_func At the moment, we currently (incorrectly) only expose a blend property on the plane if we have a nv50_wndw_func->blend_set function for the given wndw type. But we need to expose a blend property for all types of planes that can display color formats with an alpha channel. Prepare for doing this by introducing a way of looking up the supported blend modes for a type of wndw by introducing .blend_modes, which describes all possible blending modes nouveau currently supports for a type of wndw. Signed-off-by: Lyude Paul Reviewed-by: Dave Airlie Fixes: 860e748bddcc ("drm: ensure blend mode supported if pixel format with alpha exposed") Link: https://patch.msgid.link/20260720215058.398210-2-lyude@redhat.com --- drivers/gpu/drm/nouveau/dispnv50/base.h | 6 ++++++ drivers/gpu/drm/nouveau/dispnv50/base507c.c | 1 + drivers/gpu/drm/nouveau/dispnv50/base827c.c | 1 + drivers/gpu/drm/nouveau/dispnv50/base907c.c | 1 + drivers/gpu/drm/nouveau/dispnv50/curs507a.c | 3 +++ drivers/gpu/drm/nouveau/dispnv50/wndw.h | 10 ++++++++++ drivers/gpu/drm/nouveau/dispnv50/wndwc37e.c | 1 + drivers/gpu/drm/nouveau/dispnv50/wndwc57e.c | 1 + drivers/gpu/drm/nouveau/dispnv50/wndwc67e.c | 1 + drivers/gpu/drm/nouveau/dispnv50/wndwca7e.c | 1 + 10 files changed, 26 insertions(+) diff --git a/drivers/gpu/drm/nouveau/dispnv50/base.h b/drivers/gpu/drm/nouveau/dispnv50/base.h index 085bd3aeb40b..fbfd62d8fc79 100644 --- a/drivers/gpu/drm/nouveau/dispnv50/base.h +++ b/drivers/gpu/drm/nouveau/dispnv50/base.h @@ -1,5 +1,7 @@ #ifndef __NV50_KMS_BASE_H__ #define __NV50_KMS_BASE_H__ +#include + #include "wndw.h" int base507c_new(struct nouveau_drm *, int, s32, struct nv50_wndw **); @@ -24,4 +26,8 @@ extern const struct nv50_wndw_func base907c; int base917c_new(struct nouveau_drm *, int, s32, struct nv50_wndw **); int nv50_base_new(struct nouveau_drm *, int head, struct nv50_wndw **); + +/* The only blend mode supported by base channels */ +#define BASE_BLEND_MODES BIT(DRM_MODE_BLEND_PIXEL_NONE) + #endif diff --git a/drivers/gpu/drm/nouveau/dispnv50/base507c.c b/drivers/gpu/drm/nouveau/dispnv50/base507c.c index a431f6c5f6fa..41b648ab3c5e 100644 --- a/drivers/gpu/drm/nouveau/dispnv50/base507c.c +++ b/drivers/gpu/drm/nouveau/dispnv50/base507c.c @@ -297,6 +297,7 @@ base507c = { .image_set = base507c_image_set, .image_clr = base507c_image_clr, .update = base507c_update, + .blend_modes = BASE_BLEND_MODES, }; int diff --git a/drivers/gpu/drm/nouveau/dispnv50/base827c.c b/drivers/gpu/drm/nouveau/dispnv50/base827c.c index 4545cc5f3a14..6c290324b4f3 100644 --- a/drivers/gpu/drm/nouveau/dispnv50/base827c.c +++ b/drivers/gpu/drm/nouveau/dispnv50/base827c.c @@ -93,6 +93,7 @@ base827c = { .image_set = base827c_image_set, .image_clr = base507c_image_clr, .update = base507c_update, + .blend_modes = BASE_BLEND_MODES, }; int diff --git a/drivers/gpu/drm/nouveau/dispnv50/base907c.c b/drivers/gpu/drm/nouveau/dispnv50/base907c.c index 4a2d5a259e15..a94d8350224b 100644 --- a/drivers/gpu/drm/nouveau/dispnv50/base907c.c +++ b/drivers/gpu/drm/nouveau/dispnv50/base907c.c @@ -205,6 +205,7 @@ base907c = { .image_set = base907c_image_set, .image_clr = base507c_image_clr, .update = base507c_update, + .blend_modes = BASE_BLEND_MODES, }; int diff --git a/drivers/gpu/drm/nouveau/dispnv50/curs507a.c b/drivers/gpu/drm/nouveau/dispnv50/curs507a.c index 1a889139cb05..5f2ff40388e7 100644 --- a/drivers/gpu/drm/nouveau/dispnv50/curs507a.c +++ b/drivers/gpu/drm/nouveau/dispnv50/curs507a.c @@ -29,6 +29,7 @@ #include #include +#include #include bool @@ -164,6 +165,8 @@ curs507a_wndw = { .acquire = curs507a_acquire, .release = curs507a_release, .prepare = curs507a_prepare, + // TODO: Cursors also support premulti, but we haven't hooked it up anywhere yet. + .blend_modes = BIT(DRM_MODE_BLEND_COVERAGE), }; int diff --git a/drivers/gpu/drm/nouveau/dispnv50/wndw.h b/drivers/gpu/drm/nouveau/dispnv50/wndw.h index 90d100514bef..d12043b756c8 100644 --- a/drivers/gpu/drm/nouveau/dispnv50/wndw.h +++ b/drivers/gpu/drm/nouveau/dispnv50/wndw.h @@ -1,6 +1,8 @@ #ifndef __NV50_KMS_WNDW_H__ #define __NV50_KMS_WNDW_H__ #define nv50_wndw(p) container_of((p), struct nv50_wndw, plane) +#include + #include "disp.h" #include "atom.h" #include "lut.h" @@ -74,6 +76,7 @@ struct nv50_wndw_func { int (*image_clr)(struct nv50_wndw *); int (*scale_set)(struct nv50_wndw *, struct nv50_wndw_atom *); int (*blend_set)(struct nv50_wndw *, struct nv50_wndw_atom *); + unsigned int blend_modes; int (*update)(struct nv50_wndw *, u32 *interlock); }; @@ -139,4 +142,11 @@ int wndwca7e_new(struct nouveau_drm *, enum drm_plane_type, int, s32, int nv50_wndw_new(struct nouveau_drm *, enum drm_plane_type, int index, struct nv50_wndw **); + +/* A set of blend modes supported by all wndws */ +#define WNDW_BLEND_MODES \ + ( BIT(DRM_MODE_BLEND_COVERAGE) \ + | BIT(DRM_MODE_BLEND_PREMULTI) \ + | BIT(DRM_MODE_BLEND_PIXEL_NONE)) + #endif diff --git a/drivers/gpu/drm/nouveau/dispnv50/wndwc37e.c b/drivers/gpu/drm/nouveau/dispnv50/wndwc37e.c index 554c4f91f8be..7ab4d532118e 100644 --- a/drivers/gpu/drm/nouveau/dispnv50/wndwc37e.c +++ b/drivers/gpu/drm/nouveau/dispnv50/wndwc37e.c @@ -343,6 +343,7 @@ wndwc37e = { .image_set = wndwc37e_image_set, .image_clr = wndwc37e_image_clr, .blend_set = wndwc37e_blend_set, + .blend_modes = WNDW_BLEND_MODES, .update = wndwc37e_update, }; diff --git a/drivers/gpu/drm/nouveau/dispnv50/wndwc57e.c b/drivers/gpu/drm/nouveau/dispnv50/wndwc57e.c index d1ca51aae58c..e220f43b5bca 100644 --- a/drivers/gpu/drm/nouveau/dispnv50/wndwc57e.c +++ b/drivers/gpu/drm/nouveau/dispnv50/wndwc57e.c @@ -231,6 +231,7 @@ wndwc57e = { .image_set = wndwc57e_image_set, .image_clr = wndwc37e_image_clr, .blend_set = wndwc37e_blend_set, + .blend_modes = WNDW_BLEND_MODES, .update = wndwc37e_update, }; diff --git a/drivers/gpu/drm/nouveau/dispnv50/wndwc67e.c b/drivers/gpu/drm/nouveau/dispnv50/wndwc67e.c index 52af293c98f4..580cf7abd819 100644 --- a/drivers/gpu/drm/nouveau/dispnv50/wndwc67e.c +++ b/drivers/gpu/drm/nouveau/dispnv50/wndwc67e.c @@ -95,6 +95,7 @@ wndwc67e = { .image_set = wndwc67e_image_set, .image_clr = wndwc37e_image_clr, .blend_set = wndwc37e_blend_set, + .blend_modes = WNDW_BLEND_MODES, .update = wndwc37e_update, }; diff --git a/drivers/gpu/drm/nouveau/dispnv50/wndwca7e.c b/drivers/gpu/drm/nouveau/dispnv50/wndwca7e.c index 2cec8cfbd546..d65ec438b434 100644 --- a/drivers/gpu/drm/nouveau/dispnv50/wndwca7e.c +++ b/drivers/gpu/drm/nouveau/dispnv50/wndwca7e.c @@ -231,6 +231,7 @@ wndwca7e = { .image_set = wndwca7e_image_set, .image_clr = wndwca7e_image_clr, .blend_set = wndwc37e_blend_set, + .blend_modes = WNDW_BLEND_MODES, .update = wndwc37e_update, }; From 8d092b0523cac5999db9e25e95c08f90560e2806 Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Mon, 20 Jul 2026 17:36:17 -0400 Subject: [PATCH 22/83] drm/nouveau/kms/nv50-: Introduce nv50_wndw_default_state() While we don't currently read-in the hardware state of planes, now that we're about to start exposing blend properties for all planes that can support alpha channels: We need to make sure that the initial atomic state for a wndw always starts off with a supported value in pixel_blend_mode. The easiest way to do this is to introduce a nv50_wndw_default_state() function, and use it in nv50_display_read_hw_state() - and use that function to enforce a valid value for pixel_blend_mode during driver startup. Signed-off-by: Lyude Paul Reviewed-by: Dave Airlie Fixes: 860e748bddcc ("drm: ensure blend mode supported if pixel format with alpha exposed") Link: https://patch.msgid.link/20260720215058.398210-3-lyude@redhat.com --- drivers/gpu/drm/nouveau/dispnv50/disp.c | 4 ++++ drivers/gpu/drm/nouveau/dispnv50/wndw.c | 22 ++++++++++++++++++++++ drivers/gpu/drm/nouveau/dispnv50/wndw.h | 1 + 3 files changed, 27 insertions(+) diff --git a/drivers/gpu/drm/nouveau/dispnv50/disp.c b/drivers/gpu/drm/nouveau/dispnv50/disp.c index 364227f5456f..2c66e480b511 100644 --- a/drivers/gpu/drm/nouveau/dispnv50/disp.c +++ b/drivers/gpu/drm/nouveau/dispnv50/disp.c @@ -2768,6 +2768,7 @@ nv50_display_read_hw_state(struct nouveau_drm *drm) { struct drm_device *dev = drm->dev; struct drm_encoder *encoder; + struct drm_plane *plane; struct drm_modeset_acquire_ctx ctx; struct nv50_disp *disp = nv50_disp(dev); int ret; @@ -2781,6 +2782,9 @@ nv50_display_read_hw_state(struct nouveau_drm *drm) nv50_display_read_hw_or_state(dev, disp, nouveau_encoder(encoder)); } + drm_for_each_plane(plane, dev) + nv50_wndw_default_state(nv50_wndw(plane)); + DRM_MODESET_LOCK_ALL_END(dev, ctx, ret); } diff --git a/drivers/gpu/drm/nouveau/dispnv50/wndw.c b/drivers/gpu/drm/nouveau/dispnv50/wndw.c index 2635458d52ac..dfa1ba45acd9 100644 --- a/drivers/gpu/drm/nouveau/dispnv50/wndw.c +++ b/drivers/gpu/drm/nouveau/dispnv50/wndw.c @@ -848,6 +848,28 @@ static const u64 nv50_cursor_format_modifiers[] = { DRM_FORMAT_MOD_INVALID, }; +/* + * Setup defaults for the atomic wndw state + */ +void +nv50_wndw_default_state(struct nv50_wndw *wndw) +{ + struct nv50_wndw_atom *armw = nv50_wndw_atom(wndw->plane.state); + const unsigned int blend_modes = wndw->func->blend_modes; + + drm_modeset_lock_assert_held(&wndw->plane.mutex); + + /* Ensure the plane's atomic state didn't default to a pixel_blend_mode we don't support */ + if (blend_modes && (!(BIT(armw->state.pixel_blend_mode) & blend_modes))) { + if (blend_modes & BIT(DRM_MODE_BLEND_COVERAGE)) + armw->state.pixel_blend_mode = DRM_MODE_BLEND_COVERAGE; + else if (blend_modes & BIT(DRM_MODE_BLEND_PREMULTI)) + armw->state.pixel_blend_mode = DRM_MODE_BLEND_PREMULTI; + else if (blend_modes & BIT(DRM_MODE_BLEND_PIXEL_NONE)) + armw->state.pixel_blend_mode = DRM_MODE_BLEND_PIXEL_NONE; + } +} + int nv50_wndw_new_(const struct nv50_wndw_func *func, struct drm_device *dev, enum drm_plane_type type, const char *name, int index, diff --git a/drivers/gpu/drm/nouveau/dispnv50/wndw.h b/drivers/gpu/drm/nouveau/dispnv50/wndw.h index d12043b756c8..7bd8bcc199db 100644 --- a/drivers/gpu/drm/nouveau/dispnv50/wndw.h +++ b/drivers/gpu/drm/nouveau/dispnv50/wndw.h @@ -46,6 +46,7 @@ void nv50_wndw_flush_clr(struct nv50_wndw *, u32 *interlock, bool flush, struct nv50_wndw_atom *); void nv50_wndw_ntfy_enable(struct nv50_wndw *, struct nv50_wndw_atom *); int nv50_wndw_wait_armed(struct nv50_wndw *, struct nv50_wndw_atom *); +void nv50_wndw_default_state(struct nv50_wndw *wndw); struct nv50_wndw_func { int (*acquire)(struct nv50_wndw *, struct nv50_wndw_atom *asyw, From df0311845915442ab75d03ee89fb443b444c1370 Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Mon, 20 Jul 2026 17:36:18 -0400 Subject: [PATCH 23/83] drm/nouveau/kms/nv50-: Unconditionally create blend_mode prop for wndws Since the commit mentioned down below, it looks like we've been triggering this WARN_ON(): [ 31.477009] ------------[ cut here ]------------ [ 31.477012] [PLANE:66:curs-0] pixel format with alpha exposed but blend mode not setup [ 31.477014] WARNING: drivers/gpu/drm/drm_mode_config.c:872 at drm_mode_config_validate+0x38c/0x4c0, CPU#1: modprobe/1264 [ 31.477125] CPU: 1 UID: 0 PID: 1264 Comm: modprobe Not tainted 7.2.0-rc2Lyude-Test+ #1 PREEMPT(lazy) [ 31.477129] Hardware name: Micro-Star International Co., Ltd. MS-7A39/A320M GAMING PRO (MS-7A39), BIOS 1.M4 05/10/2023 [ 31.477132] RIP: 0010:drm_mode_config_validate+0x394/0x4c0 [ 31.477136] Code: 41 3b 9e 80 00 00 00 73 bd 49 8b 46 78 8b 3c 98 e8 c1 3d ff ff 80 78 14 00 74 e1 48 8d 3d 64 e6 e7 01 49 8b 56 18 41 8b 76 50 <67> 48 0f b9 3a 49 8b 46 08 4c 8d 70 f8 49 39 c5 75 97 8b 95 b8 02 [ 31.477139] RSP: 0018:ffffd48e053bb7f8 EFLAGS: 00010202 [ 31.477143] RAX: ffffffffaf1da9c0 RBX: 0000000000000000 RCX: 0000000000000034 [ 31.477145] RDX: ffff8cd883c1feb0 RSI: 0000000000000042 RDI: ffffffffb069f840 [ 31.477148] RBP: ffff8cd88ea3d800 R08: 0000000034325241 R09: ffff8cd88ea3dac0 [ 31.477150] R10: fffffaf5c4043600 R11: ffff8cd88004f600 R12: 0000000000000001 [ 31.477152] R13: ffff8cd88ea3da90 R14: ffff8cd88b9ec038 R15: ffff8cd88ea3dab0 [ 31.477154] FS: 00007fb934898780(0000) GS:ffff8cd9f3a6a000(0000) knlGS:0000000000000000 [ 31.477157] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 31.477160] CR2: 0000555efff79520 CR3: 0000000122cd5000 CR4: 00000000003506f0 [ 31.477162] Call Trace: [ 31.477165] [ 31.477168] ? nouveau_hwmon_init+0x2d6/0x330 [nouveau] [ 31.477346] drm_dev_register+0x19d/0x270 [ 31.477352] nouveau_drm_device_init+0x17a/0x230 [nouveau] [ 31.477523] nouveau_drm_probe+0x13d/0x1e0 [nouveau] [ 31.477690] local_pci_probe+0x3e/0x90 [ 31.477697] pci_call_probe+0x59/0x190 [ 31.477702] ? srso_return_thunk+0x5/0x5f [ 31.477706] ? pci_match_device+0x15a/0x180 [ 31.477710] ? pci_assign_irq+0x2d/0x160 [ 31.477714] pci_device_probe+0x95/0x160 [ 31.477719] call_driver_probe+0x26/0x100 [ 31.477724] ? driver_sysfs_add+0x59/0xd0 [ 31.477738] really_probe+0xcc/0x2c0 [ 31.477743] ? srso_return_thunk+0x5/0x5f [ 31.477749] __driver_probe_device+0x85/0x170 [ 31.477754] driver_probe_device+0x1f/0x90 [ 31.477760] ? __pfx___driver_attach+0x10/0x10 [ 31.477766] __driver_attach+0xcb/0x210 [ 31.477772] bus_for_each_dev+0x85/0xd0 [ 31.477779] bus_add_driver+0x13e/0x220 [ 31.477786] ? __pfx_init_module+0x10/0x10 [nouveau] [ 31.477979] driver_register+0x75/0xe0 [ 31.477984] ? srso_return_thunk+0x5/0x5f [ 31.477990] nouveau_drm_init+0x8b/0xff0 [nouveau] [ 31.478192] do_one_initcall+0x5d/0x300 [ 31.478204] do_init_module+0x86/0x290 [ 31.478212] init_module_from_file+0xd8/0x140 [ 31.478223] idempotent_init_module+0x114/0x310 [ 31.478233] __x64_sys_finit_module+0x71/0xe0 [ 31.478238] do_syscall_64+0xe2/0x630 [ 31.478252] ? srso_return_thunk+0x5/0x5f [ 31.478257] ? vfs_read+0x165/0x390 [ 31.478267] ? srso_return_thunk+0x5/0x5f [ 31.478271] ? srso_return_thunk+0x5/0x5f [ 31.478276] ? switch_fpu_return+0x52/0xe0 [ 31.478282] ? srso_return_thunk+0x5/0x5f [ 31.478286] ? arch_exit_to_user_mode_prepare.isra.0+0x7e/0x90 [ 31.478292] ? srso_return_thunk+0x5/0x5f [ 31.478296] ? do_syscall_64+0x11f/0x630 [ 31.478301] ? srso_return_thunk+0x5/0x5f [ 31.478305] ? arch_exit_to_user_mode_prepare.isra.0+0x7e/0x90 [ 31.478310] ? srso_return_thunk+0x5/0x5f [ 31.478314] ? do_syscall_64+0x11f/0x630 [ 31.478320] ? srso_return_thunk+0x5/0x5f [ 31.478324] ? arch_exit_to_user_mode_prepare.isra.0+0x7e/0x90 [ 31.478329] ? srso_return_thunk+0x5/0x5f [ 31.478334] ? srso_return_thunk+0x5/0x5f [ 31.478338] ? do_syscall_64+0x99/0x630 [ 31.478343] ? exc_page_fault+0x82/0x1c0 [ 31.478350] entry_SYSCALL_64_after_hwframe+0x76/0x7e [ 31.478355] RIP: 0033:0x7fb93410030d [ 31.478372] Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 90 f3 0f 1e fa 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 8b 0d c3 5a 0f 00 f7 d8 64 89 01 48 [ 31.478375] RSP: 002b:00007ffc587ce738 EFLAGS: 00000246 ORIG_RAX: 0000000000000139 [ 31.478381] RAX: ffffffffffffffda RBX: 0000562f5fb3da60 RCX: 00007fb93410030d [ 31.478385] RDX: 0000000000000000 RSI: 0000562f5fb40590 RDI: 000000000000000d [ 31.478388] RBP: 00007ffc587ce7d0 R08: 0000000000000000 R09: 0000000000000000 [ 31.478391] R10: 0000000000000000 R11: 0000000000000246 R12: 0000562f5fb40590 [ 31.478394] R13: 0000000000040000 R14: 0000562f5fb3db80 R15: 0000000000000009 [ 31.478404] [ 31.478407] ---[ end trace 0000000000000000 ]--- Fix it by making sure we unconditionally call drm_plane_create_blend_mode_property for all types of wndws, including those (such as cursors) without a blend_set function. Fixes: 860e748bddcc ("drm: ensure blend mode supported if pixel format with alpha exposed") Reviewed-by: Dave Airlie Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260720215058.398210-4-lyude@redhat.com --- drivers/gpu/drm/nouveau/dispnv50/wndw.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/nouveau/dispnv50/wndw.c b/drivers/gpu/drm/nouveau/dispnv50/wndw.c index dfa1ba45acd9..15a322422f4e 100644 --- a/drivers/gpu/drm/nouveau/dispnv50/wndw.c +++ b/drivers/gpu/drm/nouveau/dispnv50/wndw.c @@ -930,13 +930,6 @@ nv50_wndw_new_(const struct nv50_wndw_func *func, struct drm_device *dev, ret = drm_plane_create_alpha_property(&wndw->plane); if (ret) return ret; - - ret = drm_plane_create_blend_mode_property(&wndw->plane, - BIT(DRM_MODE_BLEND_PIXEL_NONE) | - BIT(DRM_MODE_BLEND_PREMULTI) | - BIT(DRM_MODE_BLEND_COVERAGE)); - if (ret) - return ret; } else { ret = drm_plane_create_zpos_immutable_property(&wndw->plane, nv50_wndw_zpos_default(&wndw->plane)); @@ -944,6 +937,17 @@ nv50_wndw_new_(const struct nv50_wndw_func *func, struct drm_device *dev, return ret; } + /* + * DRM requires that we have a blend mode property for any type of plane that exposes color + * formats with an alpha channel. So do this, even if we don't actually have control for the + * blend property hooked up with blend_set. + */ + if (func->blend_modes) { + ret = drm_plane_create_blend_mode_property(&wndw->plane, func->blend_modes); + if (ret) + return ret; + } + return 0; } From d12ff7e0116f085b57550c2b431f37fd1b2c065f Mon Sep 17 00:00:00 2001 From: Liu Ying Date: Thu, 23 Jul 2026 14:37:32 +0800 Subject: [PATCH 24/83] drm/bridge: imx: Add i.MX93 parallel display format configuration support NXP i.MX93 mediamix blk-ctrl contains one DISPLAY_MUX register which configures parallel display format by using the "PARALLEL_DISP_FORMAT" field. Add a DRM bridge driver to support the display format configuration. [m.felsch@pengutronix.de: port to v7.0-rc1] [m.felsch@pengutronix.de: add review feedback (Alexander)] [m.felsch@pengutronix.de: fix to short Kconfig description (checkpath)] [m.felsch@pengutronix.de: use "GPL" instead of "GPL v2" (checkpatch)] [m.felsch@pengutronix.de: add bus-width support] [ Liu Ying: Port to drm-misc/drm-misc-next(v7.2-rc2 based) ] [ Liu Ying: Add nxp,imx91-pdfc compatible string ] [ Liu Ying: Call drm_bridge_get() for next_bridge ] [ Liu Ying: Reject unsupported output bus fmt in .atomic_get_input_bus_fmts() ] Reviewed-by: Luca Ceresoli Signed-off-by: Marco Felsch Reviewed-by: Alexander Stein Reviewed-by: Frank Li Reviewed-by: Laurentiu Palcu Link: https://patch.msgid.link/20260723-v6-18-topic-imx93-parallel-display-v13-1-ccf3f9bbc0fc@nxp.com Signed-off-by: Liu Ying --- drivers/gpu/drm/bridge/imx/Kconfig | 11 ++ drivers/gpu/drm/bridge/imx/Makefile | 1 + drivers/gpu/drm/bridge/imx/imx93-pdfc.c | 222 ++++++++++++++++++++++++ 3 files changed, 234 insertions(+) create mode 100644 drivers/gpu/drm/bridge/imx/imx93-pdfc.c diff --git a/drivers/gpu/drm/bridge/imx/Kconfig b/drivers/gpu/drm/bridge/imx/Kconfig index 005a745e5c47..49fcecfee38e 100644 --- a/drivers/gpu/drm/bridge/imx/Kconfig +++ b/drivers/gpu/drm/bridge/imx/Kconfig @@ -107,4 +107,15 @@ config DRM_IMX93_MIPI_DSI Choose this to enable MIPI DSI controller found in Freescale i.MX93 processor. +config DRM_IMX93_PARALLEL_DISP_FMT_CONVERTER + tristate "NXP i.MX91/i.MX93 parallel display format converter" + depends on OF + select DRM_KMS_HELPER + help + On i.MX93 and i.MX91 SoCs the parallel display format output is + controlled via the MEDIAMIX BLK-CTRL DISPLAY_MUX. + + Say 'Y' or 'M' if you use the parallel display output path on a + i.MX93 or i.MX91 SoC. + endif # ARCH_MXC || COMPILE_TEST diff --git a/drivers/gpu/drm/bridge/imx/Makefile b/drivers/gpu/drm/bridge/imx/Makefile index 94ac8c40ebe9..99f28cd013ae 100644 --- a/drivers/gpu/drm/bridge/imx/Makefile +++ b/drivers/gpu/drm/bridge/imx/Makefile @@ -10,3 +10,4 @@ obj-$(CONFIG_DRM_IMX8QXP_PIXEL_COMBINER) += imx8qxp-pixel-combiner.o obj-$(CONFIG_DRM_IMX8QXP_PIXEL_LINK) += imx8qxp-pixel-link.o obj-$(CONFIG_DRM_IMX8QXP_PIXEL_LINK_TO_DPI) += imx8qxp-pxl2dpi.o obj-$(CONFIG_DRM_IMX93_MIPI_DSI) += imx93-mipi-dsi.o +obj-$(CONFIG_DRM_IMX93_PARALLEL_DISP_FMT_CONVERTER) += imx93-pdfc.o diff --git a/drivers/gpu/drm/bridge/imx/imx93-pdfc.c b/drivers/gpu/drm/bridge/imx/imx93-pdfc.c new file mode 100644 index 000000000000..2d6ca95ed554 --- /dev/null +++ b/drivers/gpu/drm/bridge/imx/imx93-pdfc.c @@ -0,0 +1,222 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/* + * Copyright 2022-2025 NXP + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define IMX93_DISPLAY_MUX_REG 0x60 +#define PARALLEL_DISP_FORMAT GENMASK(10, 8) +#define FORMAT_RGB888_TO_RGB888 FIELD_PREP(PARALLEL_DISP_FORMAT, 0) +#define FORMAT_RGB888_TO_RGB666 FIELD_PREP(PARALLEL_DISP_FORMAT, 1) +#define FORMAT_RGB565_TO_RGB565 FIELD_PREP(PARALLEL_DISP_FORMAT, 2) + +struct imx93_pdfc { + struct drm_bridge bridge; + struct device *dev; + struct regmap *regmap; + u32 phy_bus_width; +}; + +static struct imx93_pdfc *bridge_to_imx93_pdfc(struct drm_bridge *bridge) +{ + return container_of(bridge, struct imx93_pdfc, bridge); +} + +static int +imx93_pdfc_bridge_attach(struct drm_bridge *bridge, struct drm_encoder *encoder, + enum drm_bridge_attach_flags flags) +{ + return drm_bridge_attach(bridge->encoder, bridge->next_bridge, bridge, flags); +} + +static void imx93_pdfc_bridge_atomic_enable(struct drm_bridge *bridge, + struct drm_atomic_commit *state) +{ + struct imx93_pdfc *pdfc = bridge_to_imx93_pdfc(bridge); + const struct drm_bridge_state *bridge_state; + unsigned int mask = PARALLEL_DISP_FORMAT; + unsigned int val; + + bridge_state = drm_atomic_get_new_bridge_state(state, bridge); + + switch (bridge_state->output_bus_cfg.format) { + case MEDIA_BUS_FMT_RGB888_1X24: + case MEDIA_BUS_FMT_FIXED: + val = FORMAT_RGB888_TO_RGB888; + if (pdfc->phy_bus_width == 18) { + /* + * Can be valid if physical bus limitation exists, + * therefore use dev_dbg(). + */ + dev_dbg(pdfc->dev, "Truncate two LSBs from each color\n"); + val = FORMAT_RGB888_TO_RGB666; + } + break; + case MEDIA_BUS_FMT_RGB666_1X18: + val = FORMAT_RGB888_TO_RGB666; + break; + case MEDIA_BUS_FMT_RGB565_1X16: + val = FORMAT_RGB565_TO_RGB565; + break; + } + + regmap_update_bits(pdfc->regmap, IMX93_DISPLAY_MUX_REG, mask, val); +} + +/* TODO: Add YUV formats */ +static const u32 imx93_pdfc_bus_output_fmts[] = { + MEDIA_BUS_FMT_FIXED, + MEDIA_BUS_FMT_RGB888_1X24, + MEDIA_BUS_FMT_RGB666_1X18, + MEDIA_BUS_FMT_RGB565_1X16, +}; + +static bool imx93_pdfc_bus_output_fmt_supported(u32 fmt) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(imx93_pdfc_bus_output_fmts); i++) { + if (imx93_pdfc_bus_output_fmts[i] == fmt) + return true; + } + + return false; +} + +static u32 * +imx93_pdfc_bridge_atomic_get_input_bus_fmts(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state, + u32 output_fmt, + unsigned int *num_input_fmts) +{ + u32 *input_fmts; + + *num_input_fmts = 0; + + if (!imx93_pdfc_bus_output_fmt_supported(output_fmt)) + return NULL; + + input_fmts = kmalloc_obj(*input_fmts); + if (!input_fmts) + return NULL; + + *num_input_fmts = 1; + + switch (output_fmt) { + case MEDIA_BUS_FMT_RGB888_1X24: + case MEDIA_BUS_FMT_RGB565_1X16: + input_fmts[0] = output_fmt; + break; + case MEDIA_BUS_FMT_RGB666_1X18: + case MEDIA_BUS_FMT_FIXED: + input_fmts[0] = MEDIA_BUS_FMT_RGB888_1X24; + break; + } + + return input_fmts; +} + +static int imx93_pdfc_bridge_atomic_check(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct imx93_pdfc *pdfc = bridge_to_imx93_pdfc(bridge); + u32 format = bridge_state->output_bus_cfg.format; + + if (imx93_pdfc_bus_output_fmt_supported(format)) + return 0; + + dev_warn(pdfc->dev, "Unsupported output bus format: 0x%x\n", format); + + return -EINVAL; +} + +static const struct drm_bridge_funcs funcs = { + .attach = imx93_pdfc_bridge_attach, + .atomic_enable = imx93_pdfc_bridge_atomic_enable, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_get_input_bus_fmts = imx93_pdfc_bridge_atomic_get_input_bus_fmts, + .atomic_check = imx93_pdfc_bridge_atomic_check, + .atomic_create_state = drm_atomic_helper_bridge_create_state, +}; + +static int imx93_pdfc_bridge_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct drm_bridge *next_bridge; + struct imx93_pdfc *pdfc; + struct device_node *ep; + int err; + + pdfc = devm_drm_bridge_alloc(dev, struct imx93_pdfc, bridge, &funcs); + if (IS_ERR(pdfc)) + return PTR_ERR(pdfc); + + pdfc->regmap = syscon_node_to_regmap(dev->of_node->parent); + if (IS_ERR(pdfc->regmap)) + return dev_err_probe(dev, PTR_ERR(pdfc->regmap), + "failed to get regmap\n"); + + /* No limits per default */ + pdfc->phy_bus_width = 24; + + /* Get output ep (port1/endpoint) */ + ep = of_graph_get_endpoint_by_regs(dev->of_node, 1, -1); + if (ep) { + err = of_property_read_u32(ep, "bus-width", &pdfc->phy_bus_width); + of_node_put(ep); + + /* bus-width is optional but it must have valid data if present */ + if (err && err != -EINVAL) + return dev_err_probe(dev, err, + "failed to query bus-width\n"); + } + + next_bridge = devm_drm_of_get_bridge(dev, dev->of_node, 1, 0); + if (IS_ERR(next_bridge)) + return dev_err_probe(dev, PTR_ERR(next_bridge), + "failed to get next bridge\n"); + pdfc->dev = dev; + pdfc->bridge.of_node = dev->of_node; + pdfc->bridge.type = DRM_MODE_CONNECTOR_DPI; + pdfc->bridge.next_bridge = drm_bridge_get(next_bridge); + + return devm_drm_bridge_add(dev, &pdfc->bridge); +} + +static const struct of_device_id imx93_pdfc_dt_ids[] = { + { .compatible = "nxp,imx91-pdfc", }, + { .compatible = "nxp,imx93-pdfc", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx93_pdfc_dt_ids); + +static struct platform_driver imx93_pdfc_bridge_driver = { + .probe = imx93_pdfc_bridge_probe, + .driver = { + .of_match_table = imx93_pdfc_dt_ids, + .name = "imx93_pdfc", + }, +}; +module_platform_driver(imx93_pdfc_bridge_driver); + +MODULE_DESCRIPTION("NXP i.MX93 parallel display format configuration driver"); +MODULE_AUTHOR("Liu Ying "); +MODULE_LICENSE("GPL"); From 8fe3fc37564920ae315d2b8780025f280859a530 Mon Sep 17 00:00:00 2001 From: Krzysztof Niemiec Date: Wed, 29 Jul 2026 11:18:16 +0200 Subject: [PATCH 25/83] gpu: Fix dependencies in CONFIG_GPU_BUDDY_KUNIT_TEST CONFIG_GPU_BUDDY is currently not selectable by the user, mirroring how CONFIG_DRM_BUDDY does it - the idea is for a driver that makes use of it to include a "select GPU_BUDDY" line in its kconfig. CONFIG_DRM_BUDDY is also selected when building with CONFIG_DRM_KUNIT_TEST, as it can be thought of as building a mock "driver" running the tests, that makes use of the buddy functionality. CONFIG_GPU_BUDDY_KUNIT_TEST doesn't follow that logic, requiring GPU_BUDDY to be selected already instead of selecting it itself. This requires building the test kernel with another driver that uses DRM_BUDDY, as this is currently the only kconfig option that selects GPU_BUDDY. That shouldn't be required to run unit tests for a module now completely independent from DRM, let alone any specific driver. Fix the dependency declaration in CONFIG_GPU_BUDDY_KUNIT_TEST to make it work as intended, mirroring the setup in CONFIG_DRM_KUNIT_TEST. Signed-off-by: Krzysztof Niemiec Reviewed-by: Sebastian Brzezinka Reviewed-by: Andi Shyti Reviewed-by: Krzysztof Karas Link: https://patch.msgid.link/20260729091816.27860-3-krzysztof.niemiec@intel.com Signed-off-by: Maarten Lankhorst --- drivers/gpu/Kconfig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/Kconfig b/drivers/gpu/Kconfig index ebb2ad4b7ea0..3bcf08260d73 100644 --- a/drivers/gpu/Kconfig +++ b/drivers/gpu/Kconfig @@ -7,7 +7,8 @@ config GPU_BUDDY config GPU_BUDDY_KUNIT_TEST tristate "KUnit tests for GPU buddy allocator" if !KUNIT_ALL_TESTS - depends on GPU_BUDDY && KUNIT + depends on KUNIT + select GPU_BUDDY default KUNIT_ALL_TESTS help KUnit tests for the GPU buddy allocator. From bbcf7f0ee9530c5e5b9b6efa82d19ef66c0dfbc3 Mon Sep 17 00:00:00 2001 From: Krzysztof Niemiec Date: Wed, 29 Jul 2026 11:18:17 +0200 Subject: [PATCH 26/83] gpu/tests: Add a dedicated .kunitconfig Add a dedicated .kunitconfig for running the GPU buddy allocator tests. Signed-off-by: Krzysztof Niemiec Reviewed-by: Sebastian Brzezinka Reviewed-by: Andi Shyti Reviewed-by: Krzysztof Karas Link: https://patch.msgid.link/20260729091816.27860-4-krzysztof.niemiec@intel.com Signed-off-by: Maarten Lankhorst --- drivers/gpu/tests/.kunitconfig | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 drivers/gpu/tests/.kunitconfig diff --git a/drivers/gpu/tests/.kunitconfig b/drivers/gpu/tests/.kunitconfig new file mode 100644 index 000000000000..fd611b3dfe09 --- /dev/null +++ b/drivers/gpu/tests/.kunitconfig @@ -0,0 +1,2 @@ +CONFIG_KUNIT=y +CONFIG_GPU_BUDDY_KUNIT_TEST=y From 8321b093fa6c297b80586460ce6914d9655df170 Mon Sep 17 00:00:00 2001 From: Osama Abdelkader Date: Mon, 20 Jul 2026 13:32:11 +0200 Subject: [PATCH 27/83] drm/panthor: harden firmware build-info bounds checks panthor_fw_read_build_info() checks whether the metadata range fits in the firmware image with hdr.meta_start + hdr.meta_size. Both fields are u32, so the addition can wrap and let an out-of-bounds range pass validation. The function also reads the "git_sha: " prefix without first checking that the metadata is long enough, and meta_size == 0 can underflow the NULL terminator index. Use subtraction-based bounds checking and reject metadata that is too short to contain the expected prefix and trailing NULL byte. Fixes: 2718d91816ee ("drm/panthor: Add the FW logical block") Cc: stable@vger.kernel.org Signed-off-by: Osama Abdelkader Reviewed-by: Steven Price Signed-off-by: Steven Price Link: https://patch.msgid.link/20260720113212.11981-1-osama.abdelkader@gmail.com --- drivers/gpu/drm/panthor/panthor_fw.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/panthor/panthor_fw.c b/drivers/gpu/drm/panthor/panthor_fw.c index de8e6689a869..6a6f014d63e8 100644 --- a/drivers/gpu/drm/panthor/panthor_fw.c +++ b/drivers/gpu/drm/panthor/panthor_fw.c @@ -697,7 +697,8 @@ static int panthor_fw_read_build_info(struct panthor_device *ptdev, return ret; if (hdr.meta_start > fw->size || - hdr.meta_start + hdr.meta_size > fw->size) { + hdr.meta_size > fw->size - hdr.meta_start || + hdr.meta_size <= header_len) { drm_err(&ptdev->base, "Firmware build info corrupt\n"); /* We don't need the build info, so continue */ return 0; From 985f5e12f3cdf43030e13c2bbd154913133b5c3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Larumbe?= Date: Mon, 20 Jul 2026 16:48:41 +0100 Subject: [PATCH 28/83] drm/panthor: Add vm_bind region with kbo range overlap check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a VM is created, caller has to specify the range of the address space carve-out set aside for mapping kernel BO's. That means vm_bind mappings of UM-exposed BO's should not intersect with that region, but at the moment we're not checking this. At first, I thought of giving these values to drm_gpuvm_init() through its reserve_{offset, range} arguments, but it turns out that is meant for VM address spans that are not managed through the usual drm_gpuvm split/merge circuit, so storing the end of the user VA range at VM creation time and doing a quick check in the vm_bind ioctl path was the simplest workaround. The new check also makes sure vm_bind range doesn't overflow the size of a 64-bit unsigned integer. That was already being done further down the call stack inside drm_gpuvm_sm_map -> drm_gpuvm_range_valid, but it's best to fail early in the driver before GPUVM functions are invoked so that we won't waste time allocating vm_bind context resources. Fixes: 12cf826bf1dd ("drm/panthor: Support sparse mappings") Fixes: 647810ec2476 ("drm/panthor: Add the MMU/VM logical block") Reviewed-by: Boris Brezillon Signed-off-by: Adrián Larumbe Reviewed-by: Steven Price Link: https://patch.msgid.link/20260720-vm_bind_checks-v6-1-c2c7dbe93a73@collabora.com Signed-off-by: Steven Price --- drivers/gpu/drm/panthor/panthor_mmu.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/gpu/drm/panthor/panthor_mmu.c b/drivers/gpu/drm/panthor/panthor_mmu.c index f45ef5824ff2..80a1dac40a6a 100644 --- a/drivers/gpu/drm/panthor/panthor_mmu.c +++ b/drivers/gpu/drm/panthor/panthor_mmu.c @@ -310,6 +310,9 @@ struct panthor_vm { u64 end; } kernel_auto_va; + /** @user_va_range: Upper boundary of VAs VM users can map objects against. */ + u64 user_va_range; + /** @as: Address space related fields. */ struct { /** @@ -2892,6 +2895,8 @@ panthor_vm_create(struct panthor_device *ptdev, bool for_mcu, va_range = full_va_range; } + vm->user_va_range = kernel_va_start; + mutex_init(&vm->mm_lock); drm_mm_init(&vm->mm, kernel_va_start, kernel_va_size); vm->kernel_auto_va.start = auto_kernel_va_start; @@ -2980,6 +2985,10 @@ panthor_vm_bind_prepare_op_ctx(struct drm_file *file, if (!IS_ALIGNED(op->va | op->size | op->bo_offset, vm_pgsz)) return -EINVAL; + /* We don't allow mappings that overlap with kbo's reserved range */ + if (range_overflows(op->va, op->size, vm->user_va_range)) + return -EINVAL; + switch (op->flags & DRM_PANTHOR_VM_BIND_OP_TYPE_MASK) { case DRM_PANTHOR_VM_BIND_OP_TYPE_MAP: if (!(op->flags & DRM_PANTHOR_VM_BIND_OP_MAP_SPARSE)) { From 4d49f1adfbf226c26ebba2958cc184f787abab41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Larumbe?= Date: Mon, 20 Jul 2026 16:48:42 +0100 Subject: [PATCH 29/83] drm/panthor: Fix comment to reflect actual struct field name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The mismatch would pop up when building the kernel with W=1. Reviewed-by: Boris Brezillon Reviewed-by: Steven Price Reviewed-by: Liviu Dudau Signed-off-by: Adrián Larumbe Link: https://patch.msgid.link/20260720-vm_bind_checks-v6-2-c2c7dbe93a73@collabora.com Signed-off-by: Steven Price --- drivers/gpu/drm/panthor/panthor_mmu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/panthor/panthor_mmu.c b/drivers/gpu/drm/panthor/panthor_mmu.c index 80a1dac40a6a..0182b72f1932 100644 --- a/drivers/gpu/drm/panthor/panthor_mmu.c +++ b/drivers/gpu/drm/panthor/panthor_mmu.c @@ -306,7 +306,7 @@ struct panthor_vm { /** @kernel_auto_va.start: Start of the automatic VA-range for kernel BOs. */ u64 start; - /** @kernel_auto_va.size: Size of the automatic VA-range for kernel BOs. */ + /** @kernel_auto_va.end: End of the automatic VA-range for kernel BOs. */ u64 end; } kernel_auto_va; From fdde70f52306f74c7f2466706626f3f1ef973e61 Mon Sep 17 00:00:00 2001 From: Michail Tatas Date: Thu, 23 Jul 2026 23:35:59 +0300 Subject: [PATCH 30/83] drm/panel: ilitek-ili9805: Use _multi variants Convert functions mipi_dsi_* to mipi_dsi_*_multi as per the gpu/todo file. The multi variant of these functions include improved error handling. Signed-off-by: Michail Tatas Reviewed-by: Neil Armstrong Reviewed-by: Douglas Anderson Signed-off-by: Douglas Anderson Link: https://patch.msgid.link/20260723203601.806697-2-michail.tatas@gmail.com --- drivers/gpu/drm/panel/panel-ilitek-ili9805.c | 51 +++++--------------- 1 file changed, 13 insertions(+), 38 deletions(-) diff --git a/drivers/gpu/drm/panel/panel-ilitek-ili9805.c b/drivers/gpu/drm/panel/panel-ilitek-ili9805.c index e6c483851f1f..7e9587afebbe 100644 --- a/drivers/gpu/drm/panel/panel-ilitek-ili9805.c +++ b/drivers/gpu/drm/panel/panel-ilitek-ili9805.c @@ -159,36 +159,23 @@ static int ili9805_power_off(struct ili9805 *ctx) static int ili9805_activate(struct ili9805 *ctx) { - struct mipi_dsi_device *dsi = ctx->dsi; - struct device *dev = &dsi->dev; - int i, ret; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = ctx->dsi }; + int i; for (i = 0; i < ctx->desc->init_length; i++) { const struct ili9805_instr *instr = &ctx->desc->init[i]; - ret = mipi_dsi_dcs_write_buffer(ctx->dsi, instr->data, instr->len); - if (ret < 0) - return ret; + mipi_dsi_dcs_write_buffer_multi(&dsi_ctx, instr->data, instr->len); if (instr->delay > 0) - msleep(instr->delay); + mipi_dsi_msleep(&dsi_ctx, instr->delay); } - ret = mipi_dsi_dcs_exit_sleep_mode(ctx->dsi); - if (ret) { - dev_err(dev, "Failed to exit sleep mode (%d)\n", ret); - return ret; - } + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + mipi_dsi_usleep_range(&dsi_ctx, 5000, 6000); + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); - usleep_range(5000, 6000); - - ret = mipi_dsi_dcs_set_display_on(ctx->dsi); - if (ret) { - dev_err(dev, "Failed to set display ON (%d)\n", ret); - return ret; - } - - return 0; + return dsi_ctx.accum_err; } static int ili9805_prepare(struct drm_panel *panel) @@ -211,25 +198,13 @@ static int ili9805_prepare(struct drm_panel *panel) static int ili9805_deactivate(struct ili9805 *ctx) { - struct mipi_dsi_device *dsi = ctx->dsi; - struct device *dev = &dsi->dev; - int ret; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = ctx->dsi }; - ret = mipi_dsi_dcs_set_display_off(ctx->dsi); - if (ret < 0) { - dev_err(dev, "Failed to set display OFF (%d)\n", ret); - return ret; - } + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + mipi_dsi_usleep_range(&dsi_ctx, 5000, 10000); + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); - usleep_range(5000, 10000); - - ret = mipi_dsi_dcs_enter_sleep_mode(ctx->dsi); - if (ret < 0) { - dev_err(dev, "Failed to enter sleep mode (%d)\n", ret); - return ret; - } - - return 0; + return dsi_ctx.accum_err; } static int ili9805_unprepare(struct drm_panel *panel) From 438caed529a6b6dd985afc85ee52a29bc20ab3f6 Mon Sep 17 00:00:00 2001 From: Michail Tatas Date: Thu, 23 Jul 2026 23:36:00 +0300 Subject: [PATCH 31/83] drm/panel: ilitek-ili9805: Use dsi_multi in init Drop the struct ili9805_instr and the init table and use init functions. Signed-off-by: Michail Tatas Reviewed-by: Neil Armstrong Reviewed-by: Douglas Anderson Signed-off-by: Douglas Anderson Link: https://patch.msgid.link/20260723203601.806697-3-michail.tatas@gmail.com --- drivers/gpu/drm/panel/panel-ilitek-ili9805.c | 161 +++++++++---------- 1 file changed, 76 insertions(+), 85 deletions(-) diff --git a/drivers/gpu/drm/panel/panel-ilitek-ili9805.c b/drivers/gpu/drm/panel/panel-ilitek-ili9805.c index 7e9587afebbe..1339604a0c94 100644 --- a/drivers/gpu/drm/panel/panel-ilitek-ili9805.c +++ b/drivers/gpu/drm/panel/panel-ilitek-ili9805.c @@ -25,22 +25,8 @@ #define ILI9805_SETEXTC_PARAMETER2 (0x98) #define ILI9805_SETEXTC_PARAMETER3 (0x05) -#define ILI9805_INSTR(_delay, ...) { \ - .delay = (_delay), \ - .len = sizeof((u8[]) {__VA_ARGS__}), \ - .data = (u8[]){__VA_ARGS__} \ - } - -struct ili9805_instr { - size_t len; - const u8 *data; - u32 delay; -}; - struct ili9805_desc { - const char *name; - const struct ili9805_instr *init; - const size_t init_length; + void (*init)(struct mipi_dsi_multi_context *ctx); const struct drm_display_mode *mode; u32 width_mm; u32 height_mm; @@ -56,65 +42,80 @@ struct ili9805 { struct gpio_desc *reset_gpio; }; -static const struct ili9805_instr gpm1780a0_init[] = { - ILI9805_INSTR(100, ILI9805_EXTCMD_CMD_SET_ENABLE_REG, ILI9805_SETEXTC_PARAMETER1, - ILI9805_SETEXTC_PARAMETER2, ILI9805_SETEXTC_PARAMETER3), - ILI9805_INSTR(100, 0xFD, 0x0F, 0x10, 0x44, 0x00), - ILI9805_INSTR(0, 0xf8, 0x18, 0x02, 0x02, 0x18, 0x02, 0x02, 0x30, 0x00, - 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00), - ILI9805_INSTR(0, 0xB8, 0x62), - ILI9805_INSTR(0, 0xF1, 0x00), - ILI9805_INSTR(0, 0xF2, 0x00, 0x58, 0x40), - ILI9805_INSTR(0, 0xF3, 0x60, 0x83, 0x04), - ILI9805_INSTR(0, 0xFC, 0x04, 0x0F, 0x01), - ILI9805_INSTR(0, 0xEB, 0x08, 0x0F), - ILI9805_INSTR(0, 0xe0, 0x00, 0x08, 0x0d, 0x0e, 0x0e, 0x0d, 0x0a, 0x08, 0x04, - 0x08, 0x0d, 0x0f, 0x0b, 0x1c, 0x14, 0x0a), - ILI9805_INSTR(0, 0xe1, 0x00, 0x08, 0x0d, 0x0e, 0x0e, 0x0d, 0x0a, 0x08, 0x04, - 0x08, 0x0d, 0x0f, 0x0b, 0x1c, 0x14, 0x0a), - ILI9805_INSTR(10, 0xc1, 0x13, 0x39, 0x19, 0x06), - ILI9805_INSTR(10, 0xc7, 0xe5), - ILI9805_INSTR(10, 0xB1, 0x00, 0x12, 0x14), - ILI9805_INSTR(10, 0xB4, 0x02), - ILI9805_INSTR(0, 0xBB, 0x14, 0x55), - ILI9805_INSTR(0, MIPI_DCS_SET_ADDRESS_MODE, 0x08), - ILI9805_INSTR(0, MIPI_DCS_SET_PIXEL_FORMAT, 0x77), - ILI9805_INSTR(0, 0x20), - ILI9805_INSTR(0, 0xB0, 0x01), - ILI9805_INSTR(0, 0xB6, 0x31, 0x00, 0xef), - ILI9805_INSTR(0, 0xDF, 0x23), - ILI9805_INSTR(0, 0xB9, 0x02, 0x00), -}; +static void gpm1780a0_init(struct mipi_dsi_multi_context *ctx) +{ + mipi_dsi_dcs_write_seq_multi(ctx, ILI9805_EXTCMD_CMD_SET_ENABLE_REG, + ILI9805_SETEXTC_PARAMETER1, ILI9805_SETEXTC_PARAMETER2, + ILI9805_SETEXTC_PARAMETER3); + mipi_dsi_msleep(ctx, 100); + mipi_dsi_dcs_write_seq_multi(ctx, 0xfd, 0x0f, 0x10, 0x44, 0x00); + mipi_dsi_msleep(ctx, 100); + mipi_dsi_dcs_write_seq_multi(ctx, 0xf8, 0x18, 0x02, 0x02, 0x18, 0x02, 0x02, 0x30, 0x00, + 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0xb8, 0x62); + mipi_dsi_dcs_write_seq_multi(ctx, 0xf1, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0xf2, 0x00, 0x58, 0x40); + mipi_dsi_dcs_write_seq_multi(ctx, 0xf3, 0x60, 0x83, 0x04); + mipi_dsi_dcs_write_seq_multi(ctx, 0xfc, 0x04, 0x0f, 0x01); + mipi_dsi_dcs_write_seq_multi(ctx, 0xeb, 0x08, 0x0f); + mipi_dsi_dcs_write_seq_multi(ctx, 0xe0, 0x00, 0x08, 0x0d, 0x0e, 0x0e, 0x0d, 0x0a, 0x08, + 0x04, 0x08, 0x0d, 0x0f, 0x0b, 0x1c, 0x14, 0x0a); + mipi_dsi_dcs_write_seq_multi(ctx, 0xe1, 0x00, 0x08, 0x0d, 0x0e, 0x0e, 0x0d, 0x0a, 0x08, + 0x04, 0x08, 0x0d, 0x0f, 0x0b, 0x1c, 0x14, 0x0a); + mipi_dsi_dcs_write_seq_multi(ctx, 0xc1, 0x13, 0x39, 0x19, 0x06); + mipi_dsi_msleep(ctx, 10); + mipi_dsi_dcs_write_seq_multi(ctx, 0xc7, 0xe5); + mipi_dsi_msleep(ctx, 10); + mipi_dsi_dcs_write_seq_multi(ctx, 0xb1, 0x00, 0x12, 0x14); + mipi_dsi_msleep(ctx, 10); + mipi_dsi_dcs_write_seq_multi(ctx, 0xb4, 0x02); + mipi_dsi_msleep(ctx, 10); + mipi_dsi_dcs_write_seq_multi(ctx, 0xbb, 0x14, 0x55); + mipi_dsi_dcs_write_seq_multi(ctx, MIPI_DCS_SET_ADDRESS_MODE, 0x08); + mipi_dsi_dcs_set_pixel_format_multi(ctx, 0x77); + mipi_dsi_dcs_write_seq_multi(ctx, 0x20); + mipi_dsi_dcs_write_seq_multi(ctx, 0xb0, 0x01); + mipi_dsi_dcs_write_seq_multi(ctx, 0xb6, 0x31, 0x00, 0xef); + mipi_dsi_dcs_write_seq_multi(ctx, 0xdf, 0x23); + mipi_dsi_dcs_write_seq_multi(ctx, 0xb9, 0x02, 0x00); +} -static const struct ili9805_instr tm041xdhg01_init[] = { - ILI9805_INSTR(100, ILI9805_EXTCMD_CMD_SET_ENABLE_REG, ILI9805_SETEXTC_PARAMETER1, - ILI9805_SETEXTC_PARAMETER2, ILI9805_SETEXTC_PARAMETER3), - ILI9805_INSTR(100, 0xFD, 0x0F, 0x13, 0x44, 0x00), - ILI9805_INSTR(0, 0xf8, 0x18, 0x02, 0x02, 0x18, 0x02, 0x02, 0x30, 0x01, - 0x01, 0x30, 0x01, 0x01, 0x30, 0x01, 0x01), - ILI9805_INSTR(0, 0xB8, 0x74), - ILI9805_INSTR(0, 0xF1, 0x00), - ILI9805_INSTR(0, 0xF2, 0x00, 0x58, 0x40), - ILI9805_INSTR(0, 0xFC, 0x04, 0x0F, 0x01), - ILI9805_INSTR(0, 0xEB, 0x08, 0x0F), - ILI9805_INSTR(0, 0xe0, 0x01, 0x0d, 0x15, 0x0e, 0x0f, 0x0f, 0x0b, 0x08, 0x04, - 0x07, 0x0a, 0x0d, 0x0c, 0x15, 0x0f, 0x08), - ILI9805_INSTR(0, 0xe1, 0x01, 0x0d, 0x15, 0x0e, 0x0f, 0x0f, 0x0b, 0x08, 0x04, - 0x07, 0x0a, 0x0d, 0x0c, 0x15, 0x0f, 0x08), - ILI9805_INSTR(10, 0xc1, 0x15, 0x03, 0x03, 0x31), - ILI9805_INSTR(10, 0xB1, 0x00, 0x12, 0x14), - ILI9805_INSTR(10, 0xB4, 0x02), - ILI9805_INSTR(0, 0xBB, 0x14, 0x55), - ILI9805_INSTR(0, MIPI_DCS_SET_ADDRESS_MODE, 0x0a), - ILI9805_INSTR(0, MIPI_DCS_SET_PIXEL_FORMAT, 0x77), - ILI9805_INSTR(0, 0x20), - ILI9805_INSTR(0, 0xB0, 0x00), - ILI9805_INSTR(0, 0xB6, 0x01), - ILI9805_INSTR(0, 0xc2, 0x11), - ILI9805_INSTR(0, 0x51, 0xFF), - ILI9805_INSTR(0, 0x53, 0x24), - ILI9805_INSTR(0, 0x55, 0x00), -}; +static void tm041xdhg01_init(struct mipi_dsi_multi_context *ctx) +{ + mipi_dsi_dcs_write_seq_multi(ctx, ILI9805_EXTCMD_CMD_SET_ENABLE_REG, + ILI9805_SETEXTC_PARAMETER1, ILI9805_SETEXTC_PARAMETER2, + ILI9805_SETEXTC_PARAMETER3); + mipi_dsi_msleep(ctx, 100); + mipi_dsi_dcs_write_seq_multi(ctx, 0xfd, 0x0f, 0x13, 0x44, 0x00); + mipi_dsi_msleep(ctx, 100); + mipi_dsi_dcs_write_seq_multi(ctx, 0xf8, 0x18, 0x02, 0x02, 0x18, 0x02, 0x02, 0x30, 0x01, + 0x01, 0x30, 0x01, 0x01, 0x30, 0x01, 0x01); + mipi_dsi_dcs_write_seq_multi(ctx, 0xb8, 0x74); + mipi_dsi_dcs_write_seq_multi(ctx, 0xf1, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0xf2, 0x00, 0x58, 0x40); + mipi_dsi_dcs_write_seq_multi(ctx, 0xfc, 0x04, 0x0f, 0x01); + mipi_dsi_dcs_write_seq_multi(ctx, 0xeb, 0x08, 0x0f); + mipi_dsi_dcs_write_seq_multi(ctx, 0xe0, 0x01, 0x0d, 0x15, 0x0e, 0x0f, 0x0f, 0x0b, 0x08, + 0x04, 0x07, 0x0a, 0x0d, 0x0c, 0x15, 0x0f, 0x08); + mipi_dsi_dcs_write_seq_multi(ctx, 0xe1, 0x01, 0x0d, 0x15, 0x0e, 0x0f, 0x0f, 0x0b, 0x08, + 0x04, 0x07, 0x0a, 0x0d, 0x0c, 0x15, 0x0f, 0x08); + mipi_dsi_dcs_write_seq_multi(ctx, 0xc1, 0x15, 0x03, 0x03, 0x31); + mipi_dsi_msleep(ctx, 10); + mipi_dsi_dcs_write_seq_multi(ctx, 0xb1, 0x00, 0x12, 0x14); + mipi_dsi_msleep(ctx, 10); + mipi_dsi_dcs_write_seq_multi(ctx, 0xb4, 0x02); + mipi_dsi_msleep(ctx, 10); + mipi_dsi_dcs_write_seq_multi(ctx, 0xbb, 0x14, 0x55); + mipi_dsi_dcs_write_seq_multi(ctx, MIPI_DCS_SET_ADDRESS_MODE, 0x0a); + mipi_dsi_dcs_set_pixel_format_multi(ctx, 0x77); + mipi_dsi_dcs_write_seq_multi(ctx, 0x20); + mipi_dsi_dcs_write_seq_multi(ctx, 0xb0, 0x00); + mipi_dsi_dcs_write_seq_multi(ctx, 0xb6, 0x01); + mipi_dsi_dcs_write_seq_multi(ctx, 0xc2, 0x11); + mipi_dsi_dcs_write_seq_multi(ctx, 0x51, 0xff); + mipi_dsi_dcs_write_seq_multi(ctx, 0x53, 0x24); + mipi_dsi_dcs_write_seq_multi(ctx, 0x55, 0x00); +} static inline struct ili9805 *panel_to_ili9805(struct drm_panel *panel) { @@ -160,16 +161,8 @@ static int ili9805_power_off(struct ili9805 *ctx) static int ili9805_activate(struct ili9805 *ctx) { struct mipi_dsi_multi_context dsi_ctx = { .dsi = ctx->dsi }; - int i; - for (i = 0; i < ctx->desc->init_length; i++) { - const struct ili9805_instr *instr = &ctx->desc->init[i]; - - mipi_dsi_dcs_write_buffer_multi(&dsi_ctx, instr->data, instr->len); - - if (instr->delay > 0) - mipi_dsi_msleep(&dsi_ctx, instr->delay); - } + ctx->desc->init(&dsi_ctx); mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); mipi_dsi_usleep_range(&dsi_ctx, 5000, 6000); @@ -246,7 +239,7 @@ static const struct drm_display_mode tm041xdhg01_timing = { }; static int ili9805_get_modes(struct drm_panel *panel, - struct drm_connector *connector) + struct drm_connector *connector) { struct ili9805 *ctx = panel_to_ili9805(panel); struct drm_display_mode *mode; @@ -343,7 +336,6 @@ static void ili9805_dsi_remove(struct mipi_dsi_device *dsi) static const struct ili9805_desc gpm1780a0_desc = { .init = gpm1780a0_init, - .init_length = ARRAY_SIZE(gpm1780a0_init), .mode = &gpm1780a0_timing, .width_mm = 65, .height_mm = 65, @@ -351,7 +343,6 @@ static const struct ili9805_desc gpm1780a0_desc = { static const struct ili9805_desc tm041xdhg01_desc = { .init = tm041xdhg01_init, - .init_length = ARRAY_SIZE(tm041xdhg01_init), .mode = &tm041xdhg01_timing, .width_mm = 42, .height_mm = 96, From eb7979f6014a18f7086ecbb70da3e442056b5673 Mon Sep 17 00:00:00 2001 From: Michail Tatas Date: Thu, 23 Jul 2026 23:36:01 +0300 Subject: [PATCH 32/83] drm/panel: ilitek-ili9805: make cleanup funcs void Make cleanup functions return void since no one is checking their return values Signed-off-by: Michail Tatas Reviewed-by: Neil Armstrong Reviewed-by: Douglas Anderson Signed-off-by: Douglas Anderson Link: https://patch.msgid.link/20260723203601.806697-4-michail.tatas@gmail.com --- drivers/gpu/drm/panel/panel-ilitek-ili9805.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/panel/panel-ilitek-ili9805.c b/drivers/gpu/drm/panel/panel-ilitek-ili9805.c index 1339604a0c94..5610a225f77b 100644 --- a/drivers/gpu/drm/panel/panel-ilitek-ili9805.c +++ b/drivers/gpu/drm/panel/panel-ilitek-ili9805.c @@ -149,13 +149,11 @@ static int ili9805_power_on(struct ili9805 *ctx) return 0; } -static int ili9805_power_off(struct ili9805 *ctx) +static void ili9805_power_off(struct ili9805 *ctx) { gpiod_set_value(ctx->reset_gpio, 0); regulator_disable(ctx->dvdd); regulator_disable(ctx->avdd); - - return 0; } static int ili9805_activate(struct ili9805 *ctx) @@ -189,15 +187,13 @@ static int ili9805_prepare(struct drm_panel *panel) return 0; } -static int ili9805_deactivate(struct ili9805 *ctx) +static void ili9805_deactivate(struct ili9805 *ctx) { struct mipi_dsi_multi_context dsi_ctx = { .dsi = ctx->dsi }; mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); mipi_dsi_usleep_range(&dsi_ctx, 5000, 10000); mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); - - return dsi_ctx.accum_err; } static int ili9805_unprepare(struct drm_panel *panel) From 354b9c43d36ce9b41e48b2d67c5d9b953a0e9d64 Mon Sep 17 00:00:00 2001 From: Weiji Wang Date: Tue, 28 Jul 2026 03:58:20 +0800 Subject: [PATCH 33/83] drm/panel-edp: Add BOE NE140QDM-NX2 Add timing from datasheet for BOE NE140QDM-NX2. edid-decode (hex): 00 ff ff ff ff ff ff 00 09 e5 f2 0a 00 00 00 00 20 1f 01 04 a5 1e 13 78 03 ee 96 a3 54 4c 99 26 0f 4e 51 00 00 00 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 60 d2 00 a0 a0 40 32 60 30 20 35 00 2e bd 10 00 00 18 c8 9d 00 a0 a0 40 32 60 30 20 35 00 2e bd 10 00 00 18 00 00 00 fd 00 30 78 c6 c6 36 01 0a 20 20 20 20 20 20 00 00 00 fe 00 4e 45 31 34 30 51 44 4d 2d 4e 58 32 20 01 f8 70 13 79 00 00 03 01 14 30 69 00 05 ff 09 9f 00 2f 00 1f 00 3f 06 31 00 02 00 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 4d 90 Signed-off-by: Weiji Wang Reviewed-by: Douglas Anderson Signed-off-by: Douglas Anderson Link: https://patch.msgid.link/82fc6395-3425-4f71-ba58-813856cee61d@gmail.com --- drivers/gpu/drm/panel/panel-edp.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/panel/panel-edp.c b/drivers/gpu/drm/panel/panel-edp.c index 14dd40afa899..c6cdcd12002e 100644 --- a/drivers/gpu/drm/panel/panel-edp.c +++ b/drivers/gpu/drm/panel/panel-edp.c @@ -2036,6 +2036,7 @@ static const struct edp_panel_entry edp_panels[] = { EDP_PANEL_ENTRY('B', 'O', 'E', 0x0a84, &delay_200_500_e50, "NV133WUM-T01"), EDP_PANEL_ENTRY('B', 'O', 'E', 0x0ac5, &delay_200_500_e50, "NV116WHM-N4C"), EDP_PANEL_ENTRY('B', 'O', 'E', 0x0ae8, &delay_200_500_e50_p2e80, "NV140WUM-N41"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x0af2, &delay_200_500_e50_p2e80, "NE140QDM-NX2"), EDP_PANEL_ENTRY('B', 'O', 'E', 0x0b09, &delay_200_500_e50_po2e200, "NV140FHM-NZ"), EDP_PANEL_ENTRY('B', 'O', 'E', 0x0b1e, &delay_200_500_e80, "NE140QDM-N6A"), EDP_PANEL_ENTRY('B', 'O', 'E', 0x0b34, &delay_200_500_e80_d50, "NV122WUM-N41"), From 35b6cf07851a2b430b0eefbcb7b5a856f9030264 Mon Sep 17 00:00:00 2001 From: Terry Hsiao Date: Tue, 28 Jul 2026 11:59:24 +0800 Subject: [PATCH 34/83] drm/panel-edp: Add CMN N116BCP-EA2 (HW: C3) The raw EDID: 00 ff ff ff ff ff ff 00 0d ae 6c 11 00 00 00 00 02 24 01 04 95 1a 0e 78 03 67 75 98 59 53 90 27 1c 50 54 00 00 00 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 da 1d 56 e2 50 00 20 30 30 20 a6 00 00 90 10 00 00 1a e7 13 56 e2 50 00 20 30 30 20 a6 00 00 90 10 00 00 1a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 00 0c 47 ff 08 3c 7d 0d 0a 15 7d 00 00 00 00 05 Signed-off-by: Terry Hsiao Reviewed-by: Douglas Anderson Signed-off-by: Douglas Anderson Link: https://patch.msgid.link/20260728035931.10342-1-terry_hsiao@compal.corp-partner.google.com --- drivers/gpu/drm/panel/panel-edp.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/panel/panel-edp.c b/drivers/gpu/drm/panel/panel-edp.c index c6cdcd12002e..98bdcf352701 100644 --- a/drivers/gpu/drm/panel/panel-edp.c +++ b/drivers/gpu/drm/panel/panel-edp.c @@ -2084,6 +2084,7 @@ static const struct edp_panel_entry edp_panels[] = { EDP_PANEL_ENTRY('C', 'M', 'N', 0x1161, &delay_200_500_e80, "N116BCP-EA2"), EDP_PANEL_ENTRY('C', 'M', 'N', 0x1163, &delay_200_500_e80_d50, "N116BCJ-EAK"), EDP_PANEL_ENTRY('C', 'M', 'N', 0x1169, &delay_200_500_e80_d50, "N116BCN-EA1"), + EDP_PANEL_ENTRY('C', 'M', 'N', 0x116c, &delay_200_500_e80_d50, "N116BCP-EA2"), EDP_PANEL_ENTRY('C', 'M', 'N', 0x116d, &delay_200_500_e80_d50, "N116BCP-EA2"), EDP_PANEL_ENTRY('C', 'M', 'N', 0x117a, &delay_200_500_e80_d50, "N116BCL-EAK"), EDP_PANEL_ENTRY('C', 'M', 'N', 0x1247, &delay_200_500_e80_d50, "N120ACA-EA1"), From 9c950822f0fa923ccd344d7a143872d25efe89a3 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 23 Jul 2026 21:16:42 +0800 Subject: [PATCH 35/83] drm/bridge: ti-sn65dsi86: Remove redundant dev_err_probe() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_threaded_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err_probe() call. Signed-off-by: Pan Chuang Reviewed-by: Douglas Anderson Signed-off-by: Douglas Anderson Link: https://patch.msgid.link/20260723131649.134127-11-panchuang@vivo.com --- drivers/gpu/drm/bridge/ti-sn65dsi86.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c index 5590ad230624..48b83df9aed6 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c @@ -2080,7 +2080,7 @@ static int ti_sn65dsi86_probe(struct i2c_client *client) dev_name(pdata->dev), pdata); if (ret) - return dev_err_probe(dev, ret, "failed to request interrupt\n"); + return ret; } /* From 6a47f9fd2d970674ed9dedc52fc7ab76fd015785 Mon Sep 17 00:00:00 2001 From: Osama Abdelkader Date: Mon, 20 Jul 2026 15:44:35 +0200 Subject: [PATCH 36/83] drm/panthor: fix firmware control interface bounds checks panthor_init_cs_iface() and panthor_init_csg_iface() validate firmware control interface offsets with 32-bit arithmetic and the size of the host wrapper structures. The offsets are derived from firmware-provided strides, so the arithmetic can wrap before the bounds check, and the host wrapper size is not the size of the firmware control interface being mapped. Use 64-bit arithmetic for the computed offsets and validate against the actual firmware control interface structure sizes with subtraction-based bounds checks. Also validate that the shared section is large enough for the global control interface before using it. Fixes: 2718d91816ee ("drm/panthor: Add the FW logical block") Cc: stable@vger.kernel.org Signed-off-by: Osama Abdelkader Reviewed-by: Steven Price Reviewed-by: Liviu Dudau Link: https://patch.msgid.link/20260720134435.13377-1-osama.abdelkader@gmail.com Signed-off-by: Steven Price --- drivers/gpu/drm/panthor/panthor_fw.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/panthor/panthor_fw.c b/drivers/gpu/drm/panthor/panthor_fw.c index 6a6f014d63e8..fc1a423e48a8 100644 --- a/drivers/gpu/drm/panthor/panthor_fw.c +++ b/drivers/gpu/drm/panthor/panthor_fw.c @@ -878,14 +878,15 @@ static int panthor_init_cs_iface(struct panthor_device *ptdev, struct panthor_fw_csg_iface *csg_iface = panthor_fw_get_csg_iface(ptdev, csg_idx); struct panthor_fw_cs_iface *cs_iface = &ptdev->fw->iface.streams[csg_idx][cs_idx]; u64 shared_section_sz = panthor_kernel_bo_size(ptdev->fw->shared_section->mem); - u32 iface_offset = CSF_GROUP_CONTROL_OFFSET + - (csg_idx * glb_iface->control->group_stride) + + u64 iface_offset = CSF_GROUP_CONTROL_OFFSET + + ((u64)csg_idx * glb_iface->control->group_stride) + CSF_STREAM_CONTROL_OFFSET + - (cs_idx * csg_iface->control->stream_stride); + ((u64)cs_idx * csg_iface->control->stream_stride); struct panthor_fw_cs_iface *first_cs_iface = panthor_fw_get_cs_iface(ptdev, 0, 0); - if (iface_offset + sizeof(*cs_iface) >= shared_section_sz) + if (iface_offset > shared_section_sz || + sizeof(*cs_iface->control) > shared_section_sz - iface_offset) return -EINVAL; spin_lock_init(&cs_iface->lock); @@ -933,10 +934,12 @@ static int panthor_init_csg_iface(struct panthor_device *ptdev, struct panthor_fw_global_iface *glb_iface = panthor_fw_get_glb_iface(ptdev); struct panthor_fw_csg_iface *csg_iface = &ptdev->fw->iface.groups[csg_idx]; u64 shared_section_sz = panthor_kernel_bo_size(ptdev->fw->shared_section->mem); - u32 iface_offset = CSF_GROUP_CONTROL_OFFSET + (csg_idx * glb_iface->control->group_stride); + u64 iface_offset = CSF_GROUP_CONTROL_OFFSET + + ((u64)csg_idx * glb_iface->control->group_stride); unsigned int i; - if (iface_offset + sizeof(*csg_iface) >= shared_section_sz) + if (iface_offset > shared_section_sz || + sizeof(*csg_iface->control) > shared_section_sz - iface_offset) return -EINVAL; spin_lock_init(&csg_iface->lock); @@ -986,11 +989,15 @@ static u32 panthor_get_instr_features(struct panthor_device *ptdev) static int panthor_fw_init_ifaces(struct panthor_device *ptdev) { struct panthor_fw_global_iface *glb_iface = &ptdev->fw->iface.global; + u64 shared_section_sz = panthor_kernel_bo_size(ptdev->fw->shared_section->mem); unsigned int i; if (!ptdev->fw->shared_section->mem->kmap) return -EINVAL; + if (sizeof(*glb_iface->control) > shared_section_sz) + return -EINVAL; + spin_lock_init(&glb_iface->lock); glb_iface->control = ptdev->fw->shared_section->mem->kmap; From 2224d6642136767ec01d146d48ffc881b55b32f7 Mon Sep 17 00:00:00 2001 From: Brajesh Gupta Date: Wed, 29 Jul 2026 09:49:08 +0530 Subject: [PATCH 37/83] drm/imagination: Set scheduler timeout period higher than Firmware timeout Firmware schedules workloads on the GPU and tracks progress. It is also responsible for detecting any lockup and triggering recovery. Update the GPU scheduler timeout to a reasonably high value to avoid premature timeout at the GPU scheduler end. Signed-off-by: Brajesh Gupta Reviewed-by: Alessio Belle Link: https://patch.msgid.link/20260729-sched_timeout-v1-1-4adc801b1997@imgtec.com Signed-off-by: Alessio Belle --- drivers/gpu/drm/imagination/pvr_queue.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/imagination/pvr_queue.c b/drivers/gpu/drm/imagination/pvr_queue.c index 54e88b4208d7..09993e858df8 100644 --- a/drivers/gpu/drm/imagination/pvr_queue.c +++ b/drivers/gpu/drm/imagination/pvr_queue.c @@ -17,6 +17,7 @@ #include "pvr_rogue_fwif_client.h" #define MAX_DEADLINE_MS 30000 +#define SCHED_TIMEOUT_PERIOD (2 * MAX_DEADLINE_MS) #define CTX_COMPUTE_CCCB_SIZE_LOG2 15 #define CTX_FRAG_CCCB_SIZE_LOG2 15 @@ -1283,7 +1284,7 @@ struct pvr_queue *pvr_queue_create(struct pvr_context *ctx, .submit_wq = pvr_dev->sched_wq, .credit_limit = 64 * 1024, .hang_limit = 1, - .timeout = msecs_to_jiffies(500), + .timeout = msecs_to_jiffies(SCHED_TIMEOUT_PERIOD), .timeout_wq = pvr_dev->sched_wq, .name = "pvr-queue", .dev = pvr_dev->base.dev, From bc47d5937f21c5fc94504f03e18f1adb56d97634 Mon Sep 17 00:00:00 2001 From: Alexandru Dadu Date: Wed, 29 Jul 2026 15:40:25 +0300 Subject: [PATCH 38/83] drm/imagination: Reorder some fields in struct pvr_device_features Reorder the fields of struct pvr_device_features so they match the order of the device info enum found in pvr_rogue_fwif_dev_info.h. Signed-off-by: Alexandru Dadu Reviewed-by: Alessio Belle Link: https://patch.msgid.link/20260729-b4-pvr-device-features-members-reorder-v1-1-d2e18ed8cd5f@imgtec.com Signed-off-by: Alessio Belle --- drivers/gpu/drm/imagination/pvr_device_info.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/imagination/pvr_device_info.h b/drivers/gpu/drm/imagination/pvr_device_info.h index f61fb988b553..f6c2845530da 100644 --- a/drivers/gpu/drm/imagination/pvr_device_info.h +++ b/drivers/gpu/drm/imagination/pvr_device_info.h @@ -22,12 +22,12 @@ struct pvr_device_features { bool has_coreid_per_os; bool has_dynamic_dust_power; bool has_ecc_rams; - bool has_fb_cdc_v4; - bool has_fbc_max_default_descriptors; - bool has_fbc_max_large_descriptors; bool has_fbcdc; bool has_fbcdc_algorithm; bool has_fbcdc_architecture; + bool has_fbc_max_default_descriptors; + bool has_fbc_max_large_descriptors; + bool has_fb_cdc_v4; bool has_gpu_multicore_support; bool has_gpu_virtualisation; bool has_gs_rta_support; From e0c92f0f9e200799eec6ba76d921643fd5b9f2b2 Mon Sep 17 00:00:00 2001 From: Alexandru Dadu Date: Wed, 29 Jul 2026 15:57:06 +0100 Subject: [PATCH 39/83] drm/imagination: Move all FW interface check macros to pvr_checks.h The same macros are redefined in three places, so move them to a common file to reduce duplication and make it easier to reuse them. Signed-off-by: Alexandru Dadu Co-developed-by: Alessio Belle Reviewed-by: Brajesh Gupta Link: https://patch.msgid.link/20260729-fwif-checks-updates-v1-1-af65e9606a7e@imgtec.com Signed-off-by: Alessio Belle --- drivers/gpu/drm/imagination/pvr_check.h | 36 +++++++++++++++++++ .../drm/imagination/pvr_rogue_fwif_check.h | 26 +------------- .../imagination/pvr_rogue_fwif_client_check.h | 9 +---- .../imagination/pvr_rogue_fwif_shared_check.h | 9 +---- 4 files changed, 39 insertions(+), 41 deletions(-) create mode 100644 drivers/gpu/drm/imagination/pvr_check.h diff --git a/drivers/gpu/drm/imagination/pvr_check.h b/drivers/gpu/drm/imagination/pvr_check.h new file mode 100644 index 000000000000..94764ce9bbd1 --- /dev/null +++ b/drivers/gpu/drm/imagination/pvr_check.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ +/* Copyright (c) 2026 Imagination Technologies Ltd. */ + +#ifndef PVR_CHECK_H +#define PVR_CHECK_H + +#include +#include +#include + +#define OFFSET_CHECK(type, member, offset) \ + static_assert(offsetof(type, member) == (offset), \ + "offsetof(" #type ", " #member ") incorrect") + +#define SIZE_CHECK(type, size) \ + static_assert(sizeof(type) == (size), #type " is incorrect size") + +#define ALIGN_CHECK(type, align) \ + static_assert(__alignof__(type) <= (align), #type " has incorrect alignment") + +/* + * Where the last member of a struct is a flexible array member, using + * SIZE_CHECK() is pointless. If the structure is not already padded to + * alignment without the flexible array member, sizeof() will not match the + * offset of the flexible array member and the "correct" sizeof() value is + * completely meaningless. + * + * In those instances, use FLEX_ARRAY_CHECK() instead to assert that the final + * field is a flexible array member and that it behaves as expected. + */ +#define FLEX_ARRAY_CHECK(type, member) \ + static_assert(flex_array_size((type *)NULL, member, 1) == \ + sizeof_field(type, member[0]), \ + #type "->" #member " is incorrect size") + +#endif /* PVR_CHECK_H */ diff --git a/drivers/gpu/drm/imagination/pvr_rogue_fwif_check.h b/drivers/gpu/drm/imagination/pvr_rogue_fwif_check.h index e72f4064af18..5048866359b9 100644 --- a/drivers/gpu/drm/imagination/pvr_rogue_fwif_check.h +++ b/drivers/gpu/drm/imagination/pvr_rogue_fwif_check.h @@ -4,31 +4,7 @@ #ifndef PVR_ROGUE_FWIF_CHECK_H #define PVR_ROGUE_FWIF_CHECK_H -#include -#include -#include - -#define OFFSET_CHECK(type, member, offset) \ - static_assert(offsetof(type, member) == (offset), \ - "offsetof(" #type ", " #member ") incorrect") - -#define SIZE_CHECK(type, size) \ - static_assert(sizeof(type) == (size), #type " is incorrect size") - -/* - * Where the last member of a struct is a flexible array member, using - * SIZE_CHECK() is pointless. If the structure is not already padded to - * alignment without the flexible array member, sizeof() will not match the - * offset of the flexible array member and the "correct" sizeof() value is - * completely meaningless. - * - * In those instances, use FLEX_ARRAY_CHECK() instead to assert that the final - * field is a flexible array member and that it behaves as expected. - */ -#define FLEX_ARRAY_CHECK(type, member) \ - static_assert(flex_array_size((type *)NULL, member, 1) == \ - sizeof_field(type, member[0]), \ - #type "->" #member " is incorrect size") +#include "pvr_check.h" OFFSET_CHECK(struct rogue_fwif_file_info_buf, path, 0); OFFSET_CHECK(struct rogue_fwif_file_info_buf, info, 200); diff --git a/drivers/gpu/drm/imagination/pvr_rogue_fwif_client_check.h b/drivers/gpu/drm/imagination/pvr_rogue_fwif_client_check.h index 54aa4474163e..467d1d2073a1 100644 --- a/drivers/gpu/drm/imagination/pvr_rogue_fwif_client_check.h +++ b/drivers/gpu/drm/imagination/pvr_rogue_fwif_client_check.h @@ -4,14 +4,7 @@ #ifndef PVR_ROGUE_FWIF_CLIENT_CHECK_H #define PVR_ROGUE_FWIF_CLIENT_CHECK_H -#include - -#define OFFSET_CHECK(type, member, offset) \ - static_assert(offsetof(type, member) == (offset), \ - "offsetof(" #type ", " #member ") incorrect") - -#define SIZE_CHECK(type, size) \ - static_assert(sizeof(type) == (size), #type " is incorrect size") +#include "pvr_check.h" OFFSET_CHECK(struct rogue_fwif_geom_regs, vdm_ctrl_stream_base, 0); OFFSET_CHECK(struct rogue_fwif_geom_regs, tpu_border_colour_table, 8); diff --git a/drivers/gpu/drm/imagination/pvr_rogue_fwif_shared_check.h b/drivers/gpu/drm/imagination/pvr_rogue_fwif_shared_check.h index 597ed54bbd3a..59ef0a9aa395 100644 --- a/drivers/gpu/drm/imagination/pvr_rogue_fwif_shared_check.h +++ b/drivers/gpu/drm/imagination/pvr_rogue_fwif_shared_check.h @@ -4,14 +4,7 @@ #ifndef PVR_ROGUE_FWIF_SHARED_CHECK_H #define PVR_ROGUE_FWIF_SHARED_CHECK_H -#include - -#define OFFSET_CHECK(type, member, offset) \ - static_assert(offsetof(type, member) == (offset), \ - "offsetof(" #type ", " #member ") incorrect") - -#define SIZE_CHECK(type, size) \ - static_assert(sizeof(type) == (size), #type " is incorrect size") +#include "pvr_check.h" OFFSET_CHECK(struct rogue_fwif_dma_addr, dev_addr, 0); OFFSET_CHECK(struct rogue_fwif_dma_addr, fw_addr, 8); From 173dceaed68eef074b77278bd92e6fdb4e09606d Mon Sep 17 00:00:00 2001 From: Alessio Belle Date: Wed, 29 Jul 2026 15:57:07 +0100 Subject: [PATCH 40/83] drm/imagination: Reuse layout check macros for MIPS FW structures Replace static asserts on MIPS firmware structures offsets and sizes with more compact macros that are already used for similar checks on common firmware interface structures. Reviewed-by: Brajesh Gupta Link: https://patch.msgid.link/20260729-fwif-checks-updates-v1-2-af65e9606a7e@imgtec.com Signed-off-by: Alessio Belle --- .../drm/imagination/pvr_rogue_mips_check.h | 71 +++++++------------ 1 file changed, 24 insertions(+), 47 deletions(-) diff --git a/drivers/gpu/drm/imagination/pvr_rogue_mips_check.h b/drivers/gpu/drm/imagination/pvr_rogue_mips_check.h index 824b4bf33ac1..aebf231ce90f 100644 --- a/drivers/gpu/drm/imagination/pvr_rogue_mips_check.h +++ b/drivers/gpu/drm/imagination/pvr_rogue_mips_check.h @@ -4,55 +4,32 @@ #ifndef PVR_ROGUE_MIPS_CHECK_H #define PVR_ROGUE_MIPS_CHECK_H -#include +#include "pvr_check.h" -static_assert(offsetof(struct rogue_mips_tlb_entry, tlb_page_mask) == 0, - "offsetof(struct rogue_mips_tlb_entry, tlb_page_mask) incorrect"); -static_assert(offsetof(struct rogue_mips_tlb_entry, tlb_hi) == 4, - "offsetof(struct rogue_mips_tlb_entry, tlb_hi) incorrect"); -static_assert(offsetof(struct rogue_mips_tlb_entry, tlb_lo0) == 8, - "offsetof(struct rogue_mips_tlb_entry, tlb_lo0) incorrect"); -static_assert(offsetof(struct rogue_mips_tlb_entry, tlb_lo1) == 12, - "offsetof(struct rogue_mips_tlb_entry, tlb_lo1) incorrect"); -static_assert(sizeof(struct rogue_mips_tlb_entry) == 16, - "struct rogue_mips_tlb_entry is incorrect size"); +OFFSET_CHECK(struct rogue_mips_tlb_entry, tlb_page_mask, 0); +OFFSET_CHECK(struct rogue_mips_tlb_entry, tlb_hi, 4); +OFFSET_CHECK(struct rogue_mips_tlb_entry, tlb_lo0, 8); +OFFSET_CHECK(struct rogue_mips_tlb_entry, tlb_lo1, 12); +SIZE_CHECK(struct rogue_mips_tlb_entry, 16); -static_assert(offsetof(struct rogue_mips_remap_entry, remap_addr_in) == 0, - "offsetof(struct rogue_mips_remap_entry, remap_addr_in) incorrect"); -static_assert(offsetof(struct rogue_mips_remap_entry, remap_addr_out) == 4, - "offsetof(struct rogue_mips_remap_entry, remap_addr_out) incorrect"); -static_assert(offsetof(struct rogue_mips_remap_entry, remap_region_size) == 8, - "offsetof(struct rogue_mips_remap_entry, remap_region_size) incorrect"); -static_assert(sizeof(struct rogue_mips_remap_entry) == 12, - "struct rogue_mips_remap_entry is incorrect size"); +OFFSET_CHECK(struct rogue_mips_remap_entry, remap_addr_in, 0); +OFFSET_CHECK(struct rogue_mips_remap_entry, remap_addr_out, 4); +OFFSET_CHECK(struct rogue_mips_remap_entry, remap_region_size, 8); +SIZE_CHECK(struct rogue_mips_remap_entry, 12); -static_assert(offsetof(struct rogue_mips_state, error_state) == 0, - "offsetof(struct rogue_mips_state, error_state) incorrect"); -static_assert(offsetof(struct rogue_mips_state, error_epc) == 4, - "offsetof(struct rogue_mips_state, error_epc) incorrect"); -static_assert(offsetof(struct rogue_mips_state, status_register) == 8, - "offsetof(struct rogue_mips_state, status_register) incorrect"); -static_assert(offsetof(struct rogue_mips_state, cause_register) == 12, - "offsetof(struct rogue_mips_state, cause_register) incorrect"); -static_assert(offsetof(struct rogue_mips_state, bad_register) == 16, - "offsetof(struct rogue_mips_state, bad_register) incorrect"); -static_assert(offsetof(struct rogue_mips_state, epc) == 20, - "offsetof(struct rogue_mips_state, epc) incorrect"); -static_assert(offsetof(struct rogue_mips_state, sp) == 24, - "offsetof(struct rogue_mips_state, sp) incorrect"); -static_assert(offsetof(struct rogue_mips_state, debug) == 28, - "offsetof(struct rogue_mips_state, debug) incorrect"); -static_assert(offsetof(struct rogue_mips_state, depc) == 32, - "offsetof(struct rogue_mips_state, depc) incorrect"); -static_assert(offsetof(struct rogue_mips_state, bad_instr) == 36, - "offsetof(struct rogue_mips_state, bad_instr) incorrect"); -static_assert(offsetof(struct rogue_mips_state, unmapped_address) == 40, - "offsetof(struct rogue_mips_state, unmapped_address) incorrect"); -static_assert(offsetof(struct rogue_mips_state, tlb) == 44, - "offsetof(struct rogue_mips_state, tlb) incorrect"); -static_assert(offsetof(struct rogue_mips_state, remap) == 300, - "offsetof(struct rogue_mips_state, remap) incorrect"); -static_assert(sizeof(struct rogue_mips_state) == 684, - "struct rogue_mips_state is incorrect size"); +OFFSET_CHECK(struct rogue_mips_state, error_state, 0); +OFFSET_CHECK(struct rogue_mips_state, error_epc, 4); +OFFSET_CHECK(struct rogue_mips_state, status_register, 8); +OFFSET_CHECK(struct rogue_mips_state, cause_register, 12); +OFFSET_CHECK(struct rogue_mips_state, bad_register, 16); +OFFSET_CHECK(struct rogue_mips_state, epc, 20); +OFFSET_CHECK(struct rogue_mips_state, sp, 24); +OFFSET_CHECK(struct rogue_mips_state, debug, 28); +OFFSET_CHECK(struct rogue_mips_state, depc, 32); +OFFSET_CHECK(struct rogue_mips_state, bad_instr, 36); +OFFSET_CHECK(struct rogue_mips_state, unmapped_address, 40); +OFFSET_CHECK(struct rogue_mips_state, tlb, 44); +OFFSET_CHECK(struct rogue_mips_state, remap, 300); +SIZE_CHECK(struct rogue_mips_state, 684); #endif /* PVR_ROGUE_MIPS_CHECK_H */ From 6ed8d820cea9cae226f500d3af86e156eede27f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Roux?= Date: Sun, 2 Aug 2026 15:50:50 +0200 Subject: [PATCH 41/83] drm/panel-edp: Add Sharp LQ120P1JX51 (Surface Pro 12in 1st Ed.) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The internal eDP panel of the Microsoft Surface Pro 12in 1st Edition (Snapdragon X Plus, X1P42100) is not in edp_panels[], so every boot produces the deliberate WARN_ON splat in panel_edp_probe() and the panel falls back to conservative timings: WARNING: drivers/gpu/drm/panel/panel-edp.c:814 panel_edp_probe+0x53c/0x56c panel-simple-dp-aux aux-aea0000.displayport-controller: Unknown panel SHP 0x15a7, using conservative timings EDID, read over the panel's DP AUX DDC bus: 00 ff ff ff ff ff ff 00 4d 10 a7 15 a0 00 00 1f 31 22 01 04 a5 19 11 78 07 ee 91 a3 54 4c 99 26 0f 50 54 00 00 00 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 62 53 94 a0 80 b8 2e 50 18 10 3a 00 fe a9 00 00 00 18 13 7d 94 a0 80 b8 2e 50 18 10 3a 00 fe a9 00 00 00 18 00 00 00 fd 00 18 5a 88 88 21 01 00 00 00 00 00 00 00 00 00 00 fc 00 4c 51 31 32 30 50 31 4a 58 35 31 0a 20 00 69 Manufacturer: SHP Model: 0x15a7 Made in: week 49 of 2024 Display Product Name: 'LQ120P1JX51' DTD 1: 2196x1464 60.001799 Hz 3:2 (254 mm x 169 mm) DTD 2: 2196x1464 90.002698 Hz 3:2 (254 mm x 169 mm) Timings are deliberately cautious rather than datasheet-derived. The conservative fallback sets unprepare=2000 and enable=200; unprepare=2000 is a safety margin for unknown panels only and is the single occurrence of that value in the file -- no real panel entry uses it. delay_200_500_e200 keeps the same generous 200 ms enable delay while using the 500 ms unprepare shared by every actual panel in the table. A shorter enable delay may well be fine, but has not been validated here. Signed-off-by: François Roux Reviewed-by: Douglas Anderson Signed-off-by: Douglas Anderson Link: https://patch.msgid.link/20260802135107.4420-1-info@humanlearning.ch --- drivers/gpu/drm/panel/panel-edp.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/panel/panel-edp.c b/drivers/gpu/drm/panel/panel-edp.c index 98bdcf352701..3aa51ad21305 100644 --- a/drivers/gpu/drm/panel/panel-edp.c +++ b/drivers/gpu/drm/panel/panel-edp.c @@ -2174,6 +2174,7 @@ static const struct edp_panel_entry edp_panels[] = { EDP_PANEL_ENTRY('S', 'H', 'P', 0x154c, &delay_200_500_p2e100, "LQ116M1JW10"), EDP_PANEL_ENTRY('S', 'H', 'P', 0x158f, &delay_200_500_p2e100, "LQ134Z1"), EDP_PANEL_ENTRY('S', 'H', 'P', 0x1593, &delay_200_500_p2e100, "LQ134N1"), + EDP_PANEL_ENTRY('S', 'H', 'P', 0x15a7, &delay_200_500_e200, "LQ120P1JX51"), EDP_PANEL_ENTRY('S', 'T', 'A', 0x0004, &delay_200_500_e200, "116KHD024006"), EDP_PANEL_ENTRY('S', 'T', 'A', 0x0009, &delay_200_500_e250, "116QHD024002"), From 44e9eb5a762142a4aa46c0b5da7c39bfeb78910e Mon Sep 17 00:00:00 2001 From: Robert Mader Date: Wed, 22 Jul 2026 13:01:45 +0200 Subject: [PATCH 42/83] dma-buf/udmabuf: Disable the size limit by default As udmabuf increasingly enjoys popularity - being used in projects like libcamera, Gstreamer, Mesa, KWin and Weston - users more frequently encounter cases where the current default size limit of 64MB is too low. Examples include allocating video buffers at a 8K resolution - and even 4K is affected when using non-subsampled video formats and high bit depths. In its current form the size limit for individual buffers does not seem to provide any additional level of protection - such as limiting the amount of memory a process can pin - as the later can just allocate multiple buffers. If additional guardrails are desired, they would likely require some kind accounting not limited to individual buffers. Therefor let's disable the size limit by default by setting it to the maximal possible value, INT_MAX. Signed-off-by: Robert Mader Acked-by: Vivek Kasireddy Link: https://lore.kernel.org/dri-devel/20260711144814.8205-1-robert.mader@collabora.com/ Link: https://lore.kernel.org/dri-devel/6764ca6f-b4d8-4baa-9d27-2ca867ac2d41@amd.com/ Signed-off-by: Vivek Kasireddy Link: https://patch.msgid.link/20260722110145.36641-1-robert.mader@collabora.com --- drivers/dma-buf/udmabuf.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/dma-buf/udmabuf.c b/drivers/dma-buf/udmabuf.c index bced421c0d65..639e93704924 100644 --- a/drivers/dma-buf/udmabuf.c +++ b/drivers/dma-buf/udmabuf.c @@ -20,9 +20,9 @@ static int list_limit = 1024; module_param(list_limit, int, 0644); MODULE_PARM_DESC(list_limit, "udmabuf_create_list->count limit. Default is 1024."); -static int size_limit_mb = 64; +static int size_limit_mb = INT_MAX; module_param(size_limit_mb, int, 0644); -MODULE_PARM_DESC(size_limit_mb, "Max size of a dmabuf, in megabytes. Default is 64."); +MODULE_PARM_DESC(size_limit_mb, "Max size of a dmabuf, in megabytes. Default is INT_MAX."); struct udmabuf { pgoff_t pagecount; From 97c03b32b28a9f7f13f768f2b06e1eaafe850e66 Mon Sep 17 00:00:00 2001 From: Andreas Kemnade Date: Thu, 2 Jul 2026 17:27:11 +0200 Subject: [PATCH 43/83] drm/omap: dsi: Do not copy isr table To be able to unregister stuff from isrs, the corresponding table was copied. Nobody seems to unregister stuff that way, so it does not help. But there are stack-allocated objects passed to these isrs giving chances of UAF of these objects if irqs are unregistered while they are handled, so better do not copy that table. Fixes: 4ae2ddddf44cd ("OMAP: DSS2: DSI: Add ISR support") Signed-off-by: Andreas Kemnade Link: https://patch.msgid.link/20260702-dsi-uaf-v2-1-dbb4aa0f0b8e@kemnade.info Signed-off-by: Tomi Valkeinen --- drivers/gpu/drm/omapdrm/dss/dsi.c | 7 +------ drivers/gpu/drm/omapdrm/dss/dsi.h | 2 -- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/drivers/gpu/drm/omapdrm/dss/dsi.c b/drivers/gpu/drm/omapdrm/dss/dsi.c index 7aaee94f86e4..6da720e8732d 100644 --- a/drivers/gpu/drm/omapdrm/dss/dsi.c +++ b/drivers/gpu/drm/omapdrm/dss/dsi.c @@ -456,15 +456,10 @@ static irqreturn_t omap_dsi_irq_handler(int irq, void *arg) timer_delete(&dsi->te_timer); #endif - /* make a copy and unlock, so that isrs can unregister - * themselves */ - memcpy(&dsi->isr_tables_copy, &dsi->isr_tables, - sizeof(dsi->isr_tables)); + dsi_handle_isrs(&dsi->isr_tables, irqstatus, vcstatus, ciostatus); spin_unlock(&dsi->irq_lock); - dsi_handle_isrs(&dsi->isr_tables_copy, irqstatus, vcstatus, ciostatus); - dsi_handle_irq_errors(dsi, irqstatus, vcstatus, ciostatus); dsi_collect_irq_stats(dsi, irqstatus, vcstatus, ciostatus); diff --git a/drivers/gpu/drm/omapdrm/dss/dsi.h b/drivers/gpu/drm/omapdrm/dss/dsi.h index 601707c0ecc4..2b25247ea893 100644 --- a/drivers/gpu/drm/omapdrm/dss/dsi.h +++ b/drivers/gpu/drm/omapdrm/dss/dsi.h @@ -379,8 +379,6 @@ struct dsi_data { spinlock_t irq_lock; struct dsi_isr_tables isr_tables; - /* space for a copy used by the interrupt handler */ - struct dsi_isr_tables isr_tables_copy; int update_vc; #ifdef DSI_PERF_MEASURE From ae2854a7622fa088a2cebd0ac51ef72226301e99 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Sun, 5 Jul 2026 18:35:36 +0200 Subject: [PATCH 44/83] drm/bridge: remove unneeded 'fast_io' parameter in regmap_config When using MMIO with regmap, fast_io is implied. No need to set it again. Signed-off-by: Wolfram Sang Reviewed-by: Luca Ceresoli Link: https://patch.msgid.link/20260705163536.1850-6-wsa+renesas@sang-engineering.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/synopsys/dw-dp.c | 1 - drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi2.c | 1 - 2 files changed, 2 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c index 3445c82e6f50..8b8529e1723c 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c @@ -1949,7 +1949,6 @@ static const struct regmap_config dw_dp_regmap_config = { .reg_bits = 32, .reg_stride = 4, .val_bits = 32, - .fast_io = true, .max_register = DW_DP_MAX_REGISTER, .rd_table = &dw_dp_readable_table, }; diff --git a/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi2.c b/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi2.c index 5f06cb03b4c0..7fd2f1e60654 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi2.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi2.c @@ -907,7 +907,6 @@ static const struct regmap_config dw_mipi_dsi2_regmap_config = { .reg_bits = 32, .val_bits = 32, .reg_stride = 4, - .fast_io = true, }; static struct dw_mipi_dsi2 * From dd517e49a3123f868151ff1a193add8e10ae69ba Mon Sep 17 00:00:00 2001 From: Natalie Vock Date: Tue, 4 Aug 2026 10:25:16 +0200 Subject: [PATCH 45/83] cgroup/dmem: Add queries for protection values MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Callers can use this feedback to be more aggressive in making space for allocations of a cgroup if they know it is protected. These are counterparts to memcg's mem_cgroup_below_{min,low}. Reviewed-by: Maarten Lankhorst Reviewed-by: Timur Kristóf Signed-off-by: Natalie Vock Link: https://patch.msgid.link/20260804-dmemcg-aggressive-protect-v8-1-07af96681bf8@gmx.de --- include/linux/cgroup_dmem.h | 16 ++++++++++ kernel/cgroup/dmem.c | 62 +++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/include/linux/cgroup_dmem.h b/include/linux/cgroup_dmem.h index dd4869f1d736..1a88cd0c9eb0 100644 --- a/include/linux/cgroup_dmem.h +++ b/include/linux/cgroup_dmem.h @@ -24,6 +24,10 @@ void dmem_cgroup_uncharge(struct dmem_cgroup_pool_state *pool, u64 size); bool dmem_cgroup_state_evict_valuable(struct dmem_cgroup_pool_state *limit_pool, struct dmem_cgroup_pool_state *test_pool, bool ignore_low, bool *ret_hit_low); +bool dmem_cgroup_below_min(struct dmem_cgroup_pool_state *root, + struct dmem_cgroup_pool_state *test); +bool dmem_cgroup_below_low(struct dmem_cgroup_pool_state *root, + struct dmem_cgroup_pool_state *test); void dmem_cgroup_pool_state_put(struct dmem_cgroup_pool_state *pool); #else @@ -59,6 +63,18 @@ bool dmem_cgroup_state_evict_valuable(struct dmem_cgroup_pool_state *limit_pool, return true; } +static inline bool dmem_cgroup_below_min(struct dmem_cgroup_pool_state *root, + struct dmem_cgroup_pool_state *test) +{ + return false; +} + +static inline bool dmem_cgroup_below_low(struct dmem_cgroup_pool_state *root, + struct dmem_cgroup_pool_state *test) +{ + return false; +} + static inline void dmem_cgroup_pool_state_put(struct dmem_cgroup_pool_state *pool) { } diff --git a/kernel/cgroup/dmem.c b/kernel/cgroup/dmem.c index 39930c59cb76..9df3b33c6504 100644 --- a/kernel/cgroup/dmem.c +++ b/kernel/cgroup/dmem.c @@ -700,6 +700,68 @@ int dmem_cgroup_try_charge(struct dmem_cgroup_region *region, u64 size, } EXPORT_SYMBOL_GPL(dmem_cgroup_try_charge); +/** + * dmem_cgroup_below_min() - Tests whether current usage is within min limit. + * + * @root: Root of the subtree to calculate protection for, or NULL to calculate global protection. + * @test: The pool to test the usage/min limit of. + * + * Return: true if usage is below min and the cgroup is protected, false otherwise. + */ +bool dmem_cgroup_below_min(struct dmem_cgroup_pool_state *root, + struct dmem_cgroup_pool_state *test) +{ + if (root == test || !pool_parent(test)) + return false; + + if (!root) { + for (root = test; pool_parent(root); root = pool_parent(root)) + {} + } + + /* + * In mem_cgroup_below_min(), the memcg pendant, this call is missing. + * mem_cgroup_below_min() gets called during traversal of the cgroup tree, where + * protection is already calculated as part of the traversal. dmem cgroup eviction + * does not traverse the cgroup tree, so we need to recalculate effective protection + * here. + */ + dmem_cgroup_calculate_protection(root, test); + return page_counter_read(&test->cnt) <= READ_ONCE(test->cnt.emin); +} +EXPORT_SYMBOL_GPL(dmem_cgroup_below_min); + +/** + * dmem_cgroup_below_low() - Tests whether current usage is within low limit. + * + * @root: Root of the subtree to calculate protection for, or NULL to calculate global protection. + * @test: The pool to test the usage/low limit of. + * + * Return: true if usage is below low and the cgroup is protected, false otherwise. + */ +bool dmem_cgroup_below_low(struct dmem_cgroup_pool_state *root, + struct dmem_cgroup_pool_state *test) +{ + if (root == test || !pool_parent(test)) + return false; + + if (!root) { + for (root = test; pool_parent(root); root = pool_parent(root)) + {} + } + + /* + * In mem_cgroup_below_low(), the memcg pendant, this call is missing. + * mem_cgroup_below_low() gets called during traversal of the cgroup tree, where + * protection is already calculated as part of the traversal. dmem cgroup eviction + * does not traverse the cgroup tree, so we need to recalculate effective protection + * here. + */ + dmem_cgroup_calculate_protection(root, test); + return page_counter_read(&test->cnt) <= READ_ONCE(test->cnt.elow); +} +EXPORT_SYMBOL_GPL(dmem_cgroup_below_low); + static int dmem_cgroup_region_capacity_show(struct seq_file *sf, void *v) { struct dmem_cgroup_region *region; From 34f3e259925382cd87187dd426d3918379c6144e Mon Sep 17 00:00:00 2001 From: Natalie Vock Date: Tue, 4 Aug 2026 10:25:17 +0200 Subject: [PATCH 46/83] cgroup,cgroup/dmem: Add (dmem_)cgroup_common_ancestor helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This helps to find a common subtree of two resources, which is important when determining whether it's helpful to evict one resource in favor of another. To facilitate this, add a common helper to find the ancestor of two cgroups using each cgroup's ancestor array. Tested-by: Thadeu Lima de Souza Cascardo Reviewed-by: Maarten Lankhorst Reviewed-by: Timur Kristóf Signed-off-by: Natalie Vock Link: https://patch.msgid.link/20260804-dmemcg-aggressive-protect-v8-2-07af96681bf8@gmx.de --- include/linux/cgroup.h | 21 +++++++++++++++++++ include/linux/cgroup_dmem.h | 9 ++++++++ kernel/cgroup/dmem.c | 42 +++++++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+) diff --git a/include/linux/cgroup.h b/include/linux/cgroup.h index f2aa46a4f871..83a17ded1c51 100644 --- a/include/linux/cgroup.h +++ b/include/linux/cgroup.h @@ -623,6 +623,27 @@ static inline struct cgroup *cgroup_ancestor(struct cgroup *cgrp, return cgrp->ancestors[ancestor_level]; } +/** + * cgroup_common_ancestor - find common ancestor of two cgroups + * @a: first cgroup to find common ancestor of + * @b: second cgroup to find common ancestor of + * + * Find the first cgroup that is an ancestor of both @a and @b, if it exists + * and return a pointer to it. If such a cgroup doesn't exist, return NULL. + * + * This function is safe to call as long as both @a and @b are accessible. + */ +static inline struct cgroup *cgroup_common_ancestor(struct cgroup *a, + struct cgroup *b) +{ + int level; + + for (level = min(a->level, b->level); level >= 0; level--) + if (a->ancestors[level] == b->ancestors[level]) + return a->ancestors[level]; + return NULL; +} + /** * task_under_cgroup_hierarchy - test task's membership of cgroup ancestry * @task: the task to be tested diff --git a/include/linux/cgroup_dmem.h b/include/linux/cgroup_dmem.h index 1a88cd0c9eb0..9d72457c4cb9 100644 --- a/include/linux/cgroup_dmem.h +++ b/include/linux/cgroup_dmem.h @@ -28,6 +28,8 @@ bool dmem_cgroup_below_min(struct dmem_cgroup_pool_state *root, struct dmem_cgroup_pool_state *test); bool dmem_cgroup_below_low(struct dmem_cgroup_pool_state *root, struct dmem_cgroup_pool_state *test); +struct dmem_cgroup_pool_state *dmem_cgroup_get_common_ancestor(struct dmem_cgroup_pool_state *a, + struct dmem_cgroup_pool_state *b); void dmem_cgroup_pool_state_put(struct dmem_cgroup_pool_state *pool); #else @@ -75,6 +77,13 @@ static inline bool dmem_cgroup_below_low(struct dmem_cgroup_pool_state *root, return false; } +static inline +struct dmem_cgroup_pool_state *dmem_cgroup_get_common_ancestor(struct dmem_cgroup_pool_state *a, + struct dmem_cgroup_pool_state *b) +{ + return NULL; +} + static inline void dmem_cgroup_pool_state_put(struct dmem_cgroup_pool_state *pool) { } diff --git a/kernel/cgroup/dmem.c b/kernel/cgroup/dmem.c index 9df3b33c6504..1af08b0464b8 100644 --- a/kernel/cgroup/dmem.c +++ b/kernel/cgroup/dmem.c @@ -762,6 +762,48 @@ bool dmem_cgroup_below_low(struct dmem_cgroup_pool_state *root, } EXPORT_SYMBOL_GPL(dmem_cgroup_below_low); +/** + * dmem_cgroup_get_common_ancestor(): Find the first common ancestor of two pools. + * @a: First pool to find the common ancestor of. + * @b: First pool to find the common ancestor of. + * + * Return: The first pool that is a parent of both @a and @b, or NULL if either @a or @b are NULL, + * or if such a pool does not exist. A reference to the returned pool is grabbed and must be + * released by the caller when it is done using the pool. + */ +struct dmem_cgroup_pool_state *dmem_cgroup_get_common_ancestor(struct dmem_cgroup_pool_state *a, + struct dmem_cgroup_pool_state *b) +{ + struct cgroup *ancestor_cgroup; + struct cgroup_subsys_state *ancestor_css; + struct dmemcg_state *ancestor_dmemcs = NULL; + struct dmem_cgroup_pool_state *pool = NULL; + + if (!a || !b) + return NULL; + + ancestor_cgroup = cgroup_common_ancestor(a->cs->css.cgroup, b->cs->css.cgroup); + if (!ancestor_cgroup) + return NULL; + + rcu_read_lock(); + ancestor_css = cgroup_e_css(ancestor_cgroup, &dmem_cgrp_subsys); + if (css_tryget(ancestor_css)) + ancestor_dmemcs = css_to_dmemcs(ancestor_css); + rcu_read_unlock(); + + if (ancestor_dmemcs) { + pool = get_cg_pool_unlocked(css_to_dmemcs(ancestor_css), + a->region); + if (WARN_ON(IS_ERR(pool))) { + pool = NULL; + css_put(ancestor_css); + } + } + return pool; +} +EXPORT_SYMBOL_GPL(dmem_cgroup_get_common_ancestor); + static int dmem_cgroup_region_capacity_show(struct seq_file *sf, void *v) { struct dmem_cgroup_region *region; From 3f356c0e36a057e45301b8279da4ed4b68fe80a6 Mon Sep 17 00:00:00 2001 From: Natalie Vock Date: Tue, 4 Aug 2026 10:25:18 +0200 Subject: [PATCH 47/83] drm/ttm: Extract code for attempting allocation in a place MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move all code for attempting allocation for a specific place to ttm_bo_alloc_place. With subsequent patches, this logic is going to get more complicated, so it helps readability to have this separate. ttm_bo_alloc_at_place takes a pointer to a struct ttm_bo_alloc_state. This struct holds various state produced by the allocation (e.g. cgroup resource associated with the allocation) that the caller needs to keep track of (and potentially dispose of). This is just the limiting cgroup pool for now, but future patches will add more state needing to be tracked. ttm_bo_alloc_at_place also communicates via return codes if eviction using ttm_bo_evict_alloc should be attempted. This is preparation for attempting eviction in more cases than just force_space being set. No functional change intended. Reviewed-by: Tvrtko Ursulin Reviewed-by: Maarten Lankhorst Reviewed-by: Timur Kristóf Signed-off-by: Natalie Vock Link: https://patch.msgid.link/20260804-dmemcg-aggressive-protect-v8-3-07af96681bf8@gmx.de --- drivers/gpu/drm/ttm/ttm_bo.c | 90 +++++++++++++++++++++++++++--------- 1 file changed, 68 insertions(+), 22 deletions(-) diff --git a/drivers/gpu/drm/ttm/ttm_bo.c b/drivers/gpu/drm/ttm/ttm_bo.c index 3980f376e3ba..1cdb2172d592 100644 --- a/drivers/gpu/drm/ttm/ttm_bo.c +++ b/drivers/gpu/drm/ttm/ttm_bo.c @@ -488,6 +488,51 @@ int ttm_bo_evict_first(struct ttm_device *bdev, struct ttm_resource_manager *man return ret; } +struct ttm_bo_alloc_state { + /** @limit_pool: Which pool limit we should test against */ + struct dmem_cgroup_pool_state *limit_pool; +}; + +/** + * ttm_bo_alloc_at_place - Attempt allocating a BO's backing store in a place + * + * @bo: The buffer to allocate the backing store of + * @place: The place to attempt allocation in + * @ctx: ttm_operation_ctx associated with this allocation + * @force_space: If we should evict buffers to force space + * @res: On allocation success, the resulting struct ttm_resource. + * @alloc_state: Object holding allocation state such as charged cgroups. + * + * Returns: + * -EBUSY: No space available, but allocation should be retried with ttm_bo_evict_alloc. + * -ENOSPC: No space available, allocation should not be retried. + * -ERESTARTSYS: An interruptible sleep was interrupted by a signal. + * + */ +static int ttm_bo_alloc_at_place(struct ttm_buffer_object *bo, + const struct ttm_place *place, + bool force_space, + struct ttm_resource **res, + struct ttm_bo_alloc_state *alloc_state) +{ + bool may_evict; + int ret; + + may_evict = force_space && place->mem_type != TTM_PL_SYSTEM; + + ret = ttm_resource_alloc(bo, place, res, + force_space ? &alloc_state->limit_pool : NULL); + + if (ret) { + if (ret == -ENOSPC && may_evict) + return -EBUSY; + + return ret; + } + + return 0; +} + /** * struct ttm_bo_evict_walk - Parameters for the evict walk. */ @@ -503,12 +548,13 @@ struct ttm_bo_evict_walk { /** @evicted: Number of successful evictions. */ unsigned long evicted; - /** @limit_pool: Which pool limit we should test against */ - struct dmem_cgroup_pool_state *limit_pool; /** @try_low: Whether we should attempt to evict BO's with low watermark threshold */ bool try_low; /** @hit_low: If we cannot evict a bo when @try_low is false (first pass) */ bool hit_low; + + /** @alloc_state: State associated with the allocation attempt. */ + struct ttm_bo_alloc_state *alloc_state; }; static s64 ttm_bo_evict_cb(struct ttm_lru_walk *walk, struct ttm_buffer_object *bo) @@ -517,8 +563,9 @@ static s64 ttm_bo_evict_cb(struct ttm_lru_walk *walk, struct ttm_buffer_object * container_of(walk, typeof(*evict_walk), walk); s64 lret; - if (!dmem_cgroup_state_evict_valuable(evict_walk->limit_pool, bo->resource->css, - evict_walk->try_low, &evict_walk->hit_low)) + if (!dmem_cgroup_state_evict_valuable(evict_walk->alloc_state->limit_pool, + bo->resource->css, evict_walk->try_low, + &evict_walk->hit_low)) return 0; if (bo->pin_count || !bo->bdev->funcs->eviction_valuable(bo, evict_walk->place)) @@ -560,7 +607,7 @@ static int ttm_bo_evict_alloc(struct ttm_device *bdev, struct ttm_operation_ctx *ctx, struct ww_acquire_ctx *ticket, struct ttm_resource **res, - struct dmem_cgroup_pool_state *limit_pool) + struct ttm_bo_alloc_state *state) { struct ttm_bo_evict_walk evict_walk = { .walk = { @@ -573,7 +620,7 @@ static int ttm_bo_evict_alloc(struct ttm_device *bdev, .place = place, .evictor = evictor, .res = res, - .limit_pool = limit_pool, + .alloc_state = state, }; s64 lret; @@ -724,9 +771,8 @@ static int ttm_bo_alloc_resource(struct ttm_buffer_object *bo, for (i = 0; i < placement->num_placement; ++i) { const struct ttm_place *place = &placement->placement[i]; - struct dmem_cgroup_pool_state *limit_pool = NULL; + struct ttm_bo_alloc_state alloc_state = {}; struct ttm_resource_manager *man; - bool may_evict; man = ttm_manager_type(bdev, place->mem_type); if (!man || !ttm_resource_manager_used(man)) @@ -736,25 +782,25 @@ static int ttm_bo_alloc_resource(struct ttm_buffer_object *bo, TTM_PL_FLAG_FALLBACK)) continue; - may_evict = (force_space && place->mem_type != TTM_PL_SYSTEM); - ret = ttm_resource_alloc(bo, place, res, force_space ? &limit_pool : NULL); - if (ret) { - if (ret != -ENOSPC) { - dmem_cgroup_pool_state_put(limit_pool); - return ret; - } - if (!may_evict) { - dmem_cgroup_pool_state_put(limit_pool); - continue; - } + ret = ttm_bo_alloc_at_place(bo, place, force_space, res, + &alloc_state); + if (ret == -ENOSPC) { + dmem_cgroup_pool_state_put(alloc_state.limit_pool); + continue; + } else if (ret == -EBUSY) { ret = ttm_bo_evict_alloc(bdev, man, place, bo, ctx, - ticket, res, limit_pool); - dmem_cgroup_pool_state_put(limit_pool); + ticket, res, &alloc_state); + + dmem_cgroup_pool_state_put(alloc_state.limit_pool); + if (ret == -EBUSY) continue; - if (ret) + else if (ret) return ret; + } else if (ret) { + dmem_cgroup_pool_state_put(alloc_state.limit_pool); + return ret; } ret = ttm_bo_add_pipelined_eviction_fences(bo, man, ctx->no_wait_gpu); From 4e6d9bc1586494358e368f96e61d5689a17b31e0 Mon Sep 17 00:00:00 2001 From: Natalie Vock Date: Tue, 4 Aug 2026 10:25:19 +0200 Subject: [PATCH 48/83] drm/ttm: Split cgroup charge and resource allocation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Coupling resource allocation and cgroup charging is racy when charging succeeds, but subsequent resource allocation fails. Certain eviction decisions are made on the basis of whether the allocating cgroup is protected, i.e. within its min/low limits, but with the charge being tied to resource allocation (and uncharged when the resource allocation fails), this check is done at a point where the allocation is not actually charged to the cgroup. This is subtly wrong if the allocation were to cause the cgroup to exceed the min/low protection, but it's even more wrong if the same cgroup tries allocating multiple buffers concurrently: In this case, the min/low protection may pass for all allocation attempts when the real min/low protection covers only some, or potentially none of the allocated buffers. Instead, charge the allocation to the cgroup once and keep the charge for as long as we try to allocate a ttm_resource, and only undo the charge if allocating the resource is ultimately unsuccessful and we move on to a different ttm_place. Reviewed-by: Maarten Lankhorst Reviewed-by: Timur Kristóf Signed-off-by: Natalie Vock Link: https://patch.msgid.link/20260804-dmemcg-aggressive-protect-v8-4-07af96681bf8@gmx.de --- drivers/gpu/drm/ttm/ttm_bo.c | 57 ++++++++++++++++++++++++------ drivers/gpu/drm/ttm/ttm_resource.c | 57 ++++++++++++++++++++---------- include/drm/ttm/ttm_resource.h | 6 +++- 3 files changed, 89 insertions(+), 31 deletions(-) diff --git a/drivers/gpu/drm/ttm/ttm_bo.c b/drivers/gpu/drm/ttm/ttm_bo.c index 1cdb2172d592..3becddd2c43b 100644 --- a/drivers/gpu/drm/ttm/ttm_bo.c +++ b/drivers/gpu/drm/ttm/ttm_bo.c @@ -489,8 +489,12 @@ int ttm_bo_evict_first(struct ttm_device *bdev, struct ttm_resource_manager *man } struct ttm_bo_alloc_state { + /** @charge_pool: The memory pool the resource is charged to */ + struct dmem_cgroup_pool_state *charge_pool; /** @limit_pool: Which pool limit we should test against */ struct dmem_cgroup_pool_state *limit_pool; + /** @in_evict: Whether we are currently evicting buffers */ + bool in_evict; }; /** @@ -518,18 +522,39 @@ static int ttm_bo_alloc_at_place(struct ttm_buffer_object *bo, bool may_evict; int ret; - may_evict = force_space && place->mem_type != TTM_PL_SYSTEM; - - ret = ttm_resource_alloc(bo, place, res, - force_space ? &alloc_state->limit_pool : NULL); + may_evict = !alloc_state->in_evict && force_space && + place->mem_type != TTM_PL_SYSTEM; + if (!alloc_state->charge_pool) { + ret = ttm_resource_try_charge(bo, place, &alloc_state->charge_pool, + force_space ? &alloc_state->limit_pool + : NULL); + if (ret) { + /* + * -EAGAIN means the charge failed, which we treat + * like an allocation failure. Therefore, return an + * error code indicating the allocation failed - + * either -EBUSY if the allocation should be + * retried with eviction, or -ENOSPC if there should + * be no second attempt. + */ + if (ret == -EAGAIN) + ret = may_evict ? -EBUSY : -ENOSPC; + return ret; + } + } + ret = ttm_resource_alloc(bo, place, res, alloc_state->charge_pool); if (ret) { if (ret == -ENOSPC && may_evict) - return -EBUSY; - + ret = -EBUSY; return ret; } + /* + * Ownership of charge_pool has been transferred to the TTM resource, + * don't make the caller think we still hold a reference to it. + */ + alloc_state->charge_pool = NULL; return 0; } @@ -584,8 +609,10 @@ static s64 ttm_bo_evict_cb(struct ttm_lru_walk *walk, struct ttm_buffer_object * evict_walk->evicted++; if (evict_walk->res) - lret = ttm_resource_alloc(evict_walk->evictor, evict_walk->place, - evict_walk->res, NULL); + lret = ttm_bo_alloc_at_place(evict_walk->evictor, + evict_walk->place, false, + evict_walk->res, + evict_walk->alloc_state); if (lret == 0) return 1; out: @@ -624,6 +651,8 @@ static int ttm_bo_evict_alloc(struct ttm_device *bdev, }; s64 lret; + state->in_evict = true; + evict_walk.walk.arg.trylock_only = true; lret = ttm_lru_walk_for_evict(&evict_walk.walk, bdev, man, 1); @@ -654,6 +683,7 @@ static int ttm_bo_evict_alloc(struct ttm_device *bdev, goto retry; } out: + state->in_evict = false; if (lret < 0) return lret; if (lret == 0) @@ -786,6 +816,7 @@ static int ttm_bo_alloc_resource(struct ttm_buffer_object *bo, &alloc_state); if (ret == -ENOSPC) { + dmem_cgroup_uncharge(alloc_state.charge_pool, bo->base.size); dmem_cgroup_pool_state_put(alloc_state.limit_pool); continue; } else if (ret == -EBUSY) { @@ -794,11 +825,15 @@ static int ttm_bo_alloc_resource(struct ttm_buffer_object *bo, dmem_cgroup_pool_state_put(alloc_state.limit_pool); - if (ret == -EBUSY) - continue; - else if (ret) + if (ret) { + dmem_cgroup_uncharge(alloc_state.charge_pool, + bo->base.size); + if (ret == -EBUSY) + continue; return ret; + } } else if (ret) { + dmem_cgroup_uncharge(alloc_state.charge_pool, bo->base.size); dmem_cgroup_pool_state_put(alloc_state.limit_pool); return ret; } diff --git a/drivers/gpu/drm/ttm/ttm_resource.c b/drivers/gpu/drm/ttm/ttm_resource.c index 154d6739256f..02eca679cb68 100644 --- a/drivers/gpu/drm/ttm/ttm_resource.c +++ b/drivers/gpu/drm/ttm/ttm_resource.c @@ -386,33 +386,52 @@ void ttm_resource_fini(struct ttm_resource_manager *man, } EXPORT_SYMBOL(ttm_resource_fini); -int ttm_resource_alloc(struct ttm_buffer_object *bo, - const struct ttm_place *place, - struct ttm_resource **res_ptr, - struct dmem_cgroup_pool_state **ret_limit_pool) +/** + * ttm_resource_try_charge - charge a resource manager's cgroup pool + * @bo: buffer for which an allocation should be charged + * @place: where the allocation is attempted to be placed + * @ret_pool: on charge success, the pool that was charged + * @ret_limit_pool: on charge failure, the pool responsible for the failure + * + * Should be used to charge cgroups before attempting resource allocation. + * When charging succeeds, the value of ret_pool should be passed to + * ttm_resource_alloc. + * + * Returns: 0 on charge success, negative errno on failure. + */ +int ttm_resource_try_charge(struct ttm_buffer_object *bo, + const struct ttm_place *place, + struct dmem_cgroup_pool_state **ret_pool, + struct dmem_cgroup_pool_state **ret_limit_pool) +{ + struct ttm_resource_manager *man = + ttm_manager_type(bo->bdev, place->mem_type); + + if (!man->cg) { + *ret_pool = NULL; + if (ret_limit_pool) + *ret_limit_pool = NULL; + return 0; + } + + return dmem_cgroup_try_charge(man->cg, bo->base.size, ret_pool, + ret_limit_pool); +} + +int ttm_resource_alloc(struct ttm_buffer_object *bo, + const struct ttm_place *place, + struct ttm_resource **res_ptr, + struct dmem_cgroup_pool_state *charge_pool) { struct ttm_resource_manager *man = ttm_manager_type(bo->bdev, place->mem_type); - struct dmem_cgroup_pool_state *pool = NULL; int ret; - if (man->cg) { - ret = dmem_cgroup_try_charge(man->cg, bo->base.size, &pool, ret_limit_pool); - if (ret) { - if (ret == -EAGAIN) - ret = -ENOSPC; - return ret; - } - } - ret = man->func->alloc(man, bo, place, res_ptr); - if (ret) { - if (pool) - dmem_cgroup_uncharge(pool, bo->base.size); + if (ret) return ret; - } - (*res_ptr)->css = pool; + (*res_ptr)->css = charge_pool; spin_lock(&bo->bdev->lru_lock); ttm_resource_add_bulk_move(*res_ptr, bo); diff --git a/include/drm/ttm/ttm_resource.h b/include/drm/ttm/ttm_resource.h index a5d386583fb6..e567b7ec8218 100644 --- a/include/drm/ttm/ttm_resource.h +++ b/include/drm/ttm/ttm_resource.h @@ -458,10 +458,14 @@ void ttm_resource_init(struct ttm_buffer_object *bo, void ttm_resource_fini(struct ttm_resource_manager *man, struct ttm_resource *res); +int ttm_resource_try_charge(struct ttm_buffer_object *bo, + const struct ttm_place *place, + struct dmem_cgroup_pool_state **ret_pool, + struct dmem_cgroup_pool_state **ret_limit_pool); int ttm_resource_alloc(struct ttm_buffer_object *bo, const struct ttm_place *place, struct ttm_resource **res, - struct dmem_cgroup_pool_state **ret_limit_pool); + struct dmem_cgroup_pool_state *charge_pool); void ttm_resource_free(struct ttm_buffer_object *bo, struct ttm_resource **res); bool ttm_resource_intersects(struct ttm_device *bdev, struct ttm_resource *res, From bbc744c062f84834fc3c69aa011477c806460d20 Mon Sep 17 00:00:00 2001 From: Natalie Vock Date: Tue, 4 Aug 2026 10:25:20 +0200 Subject: [PATCH 49/83] drm/ttm: Be more aggressive when allocating below protection limit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the cgroup's memory usage is below the low/min limit and allocation fails, try evicting some unprotected buffers to make space. Otherwise, application buffers may be forced to go into GTT even though usage is below the corresponding low/min limit, if other applications filled VRAM with their allocations first. Reviewed-by: Maarten Lankhorst Reviewed-by: Timur Kristóf Signed-off-by: Natalie Vock Link: https://patch.msgid.link/20260804-dmemcg-aggressive-protect-v8-5-07af96681bf8@gmx.de --- drivers/gpu/drm/ttm/ttm_bo.c | 53 ++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/ttm/ttm_bo.c b/drivers/gpu/drm/ttm/ttm_bo.c index 3becddd2c43b..6bbea148838e 100644 --- a/drivers/gpu/drm/ttm/ttm_bo.c +++ b/drivers/gpu/drm/ttm/ttm_bo.c @@ -495,6 +495,10 @@ struct ttm_bo_alloc_state { struct dmem_cgroup_pool_state *limit_pool; /** @in_evict: Whether we are currently evicting buffers */ bool in_evict; + /** @may_try_low: If only unprotected BOs, i.e. BOs whose cgroup + * is exceeding its dmem low/min protection, should be considered for eviction + */ + bool may_try_low; }; /** @@ -537,12 +541,50 @@ static int ttm_bo_alloc_at_place(struct ttm_buffer_object *bo, * retried with eviction, or -ENOSPC if there should * be no second attempt. */ + if (!alloc_state->in_evict) + alloc_state->may_try_low = may_evict; if (ret == -EAGAIN) ret = may_evict ? -EBUSY : -ENOSPC; return ret; } } + /* + * cgroup protection plays a special role in eviction. + * Conceptually, protection of memory via the dmem cgroup controller + * entitles the protected cgroup to use a certain amount of memory. + * There are two types of protection - the 'low' limit is a + * "best-effort" protection, whereas the 'min' limit provides a hard + * guarantee that memory within the cgroup's allowance will not be + * evicted under any circumstance. + * + * To faithfully model this concept in TTM, we also need to take cgroup + * protection into account when allocating. When allocation in one + * place fails, TTM will default to trying other places first before + * evicting. + * If the allocation is covered by dmem cgroup protection, however, + * this prevents the allocation from using the memory it is "entitled" + * to. To make sure unprotected allocations cannot push new protected + * allocations out of places they are "entitled" to use, we should + * evict buffers not covered by any cgroup protection, if this + * allocation is covered by cgroup protection. + * + * Buffers covered by 'min' protection are a special case - the 'min' + * limit is a stronger guarantee than 'low', and thus buffers protected + * by 'low' but not 'min' should also be considered for eviction. + * Buffers protected by 'min' will never be considered for eviction + * anyway, so the regular eviction path should be triggered here. + * Buffers protected by 'low' but not 'min' will take a special + * eviction path that only evicts buffers covered by neither 'low' or + * 'min' protections. + */ + if (!alloc_state->in_evict) { + may_evict |= dmem_cgroup_below_min(NULL, alloc_state->charge_pool); + alloc_state->may_try_low = may_evict; + + may_evict |= dmem_cgroup_below_low(NULL, alloc_state->charge_pool); + } + ret = ttm_resource_alloc(bo, place, res, alloc_state->charge_pool); if (ret) { if (ret == -ENOSPC && may_evict) @@ -656,8 +698,12 @@ static int ttm_bo_evict_alloc(struct ttm_device *bdev, evict_walk.walk.arg.trylock_only = true; lret = ttm_lru_walk_for_evict(&evict_walk.walk, bdev, man, 1); - /* One more attempt if we hit low limit? */ - if (!lret && evict_walk.hit_low) { + /* If we failed to find enough BOs to evict, but we skipped over + * some BOs because they were covered by dmem low protection, retry + * evicting these protected BOs too, except if we're told not to + * consider protected BOs at all. + */ + if (!lret && evict_walk.hit_low && state->may_try_low) { evict_walk.try_low = true; lret = ttm_lru_walk_for_evict(&evict_walk.walk, bdev, man, 1); } @@ -678,7 +724,8 @@ static int ttm_bo_evict_alloc(struct ttm_device *bdev, } while (!lret && evict_walk.evicted); /* We hit the low limit? Try once more */ - if (!lret && evict_walk.hit_low && !evict_walk.try_low) { + if (!lret && evict_walk.hit_low && !evict_walk.try_low && + state->may_try_low) { evict_walk.try_low = true; goto retry; } From 3d33e9c726d677be4087dcb33c1710176e85d9c1 Mon Sep 17 00:00:00 2001 From: Natalie Vock Date: Tue, 4 Aug 2026 10:25:21 +0200 Subject: [PATCH 50/83] drm/ttm: Use common ancestor of evictor and evictee as limit pool MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When checking whether to skip certain buffers because they're protected by dmem.low, we're checking the effective protection of the evictee's cgroup, but depending on how the evictor's cgroup relates to the evictee's, the semantics of effective protection values change. When testing against cgroups from different subtrees, page_counter's recursive protection propagates memory protection afforded to a parent down to the child cgroups, even if the children were not explicitly protected. This prevents cgroups whose parents were afforded no protection from stealing memory from cgroups whose parents were afforded more protection, without users having to explicitly propagate this protection. However, if we always calculate protection from the root cgroup, this breaks prioritization of sibling cgroups: If one cgroup was explicitly protected and its siblings were not, the protected cgroup should get higher priority, i.e. the protected cgroup should be able to steal from unprotected siblings. This only works if we restrict the protection calculation to the subtree shared by evictor and evictee. Reviewed-by: Maarten Lankhorst Reviewed-by: Timur Kristóf Signed-off-by: Natalie Vock Link: https://patch.msgid.link/20260804-dmemcg-aggressive-protect-v8-6-07af96681bf8@gmx.de --- drivers/gpu/drm/ttm/ttm_bo.c | 43 +++++++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/ttm/ttm_bo.c b/drivers/gpu/drm/ttm/ttm_bo.c index 6bbea148838e..79c96aba4bf4 100644 --- a/drivers/gpu/drm/ttm/ttm_bo.c +++ b/drivers/gpu/drm/ttm/ttm_bo.c @@ -628,11 +628,48 @@ static s64 ttm_bo_evict_cb(struct ttm_lru_walk *walk, struct ttm_buffer_object * { struct ttm_bo_evict_walk *evict_walk = container_of(walk, typeof(*evict_walk), walk); + struct dmem_cgroup_pool_state *limit_pool, *ancestor = NULL; + bool evict_valuable; s64 lret; - if (!dmem_cgroup_state_evict_valuable(evict_walk->alloc_state->limit_pool, - bo->resource->css, evict_walk->try_low, - &evict_walk->hit_low)) + /* + * If may_try_low is not set, then we're trying to evict unprotected + * buffers in favor of a protected allocation for charge_pool. Explicitly skip + * buffers belonging to the same cgroup here - that cgroup is definitely protected, + * even though dmem_cgroup_state_evict_valuable would allow the eviction because a + * cgroup is always allowed to evict from itself even if it is protected. + */ + if (!evict_walk->alloc_state->may_try_low && + bo->resource->css == evict_walk->alloc_state->charge_pool) + return 0; + + limit_pool = evict_walk->alloc_state->limit_pool; + /* + * If there is no explicit limit pool, find the root of the shared subtree between + * evictor and evictee. This is important so that recursive protection rules can + * apply properly: Recursive protection distributes cgroup protection afforded + * to a parent cgroup but not used explicitly by a child cgroup between all child + * cgroups (see docs of effective_protection in mm/page_counter.c). However, when + * direct siblings compete for memory, siblings that were explicitly protected + * should get prioritized over siblings that weren't. This only happens correctly + * when the root of the shared subtree is passed to + * dmem_cgroup_state_evict_valuable. Otherwise, the effective-protection + * calculation cannot distinguish direct siblings from unrelated subtrees and the + * calculated protection ends up wrong. + */ + if (!limit_pool) { + ancestor = dmem_cgroup_get_common_ancestor(bo->resource->css, + evict_walk->alloc_state->charge_pool); + limit_pool = ancestor; + } + + evict_valuable = dmem_cgroup_state_evict_valuable(limit_pool, bo->resource->css, + evict_walk->try_low, + &evict_walk->hit_low); + if (ancestor) + dmem_cgroup_pool_state_put(ancestor); + + if (!evict_valuable) return 0; if (bo->pin_count || !bo->bdev->funcs->eviction_valuable(bo, evict_walk->place)) From a4377c7722b824d3d2eb940eb681cf37b0969a24 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Wed, 29 Jul 2026 13:54:53 +0100 Subject: [PATCH 51/83] dt-bindings: display: bridge: renesas,dsi: Document RZ/G3L The RZ/G3L DSI IP is similar to the RZ/G2L but has different global PHY timings and also the PLLCLK is ungateble clock. Add the compatible string "renesas,r9a08g046-mipi-dsi" to handle these difference for the Renesas RZ/G3L SoC. The power to DSI region is controlled by SYSC block. Acked-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260729125516.230757-2-biju.das.jz@bp.renesas.com Signed-off-by: Biju Das --- .../devicetree/bindings/display/bridge/renesas,dsi.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/display/bridge/renesas,dsi.yaml b/Documentation/devicetree/bindings/display/bridge/renesas,dsi.yaml index c20625b8425e..3832a553871b 100644 --- a/Documentation/devicetree/bindings/display/bridge/renesas,dsi.yaml +++ b/Documentation/devicetree/bindings/display/bridge/renesas,dsi.yaml @@ -28,6 +28,7 @@ properties: - const: renesas,r9a09g057-mipi-dsi - enum: + - renesas,r9a08g046-mipi-dsi # RZ/G3L - renesas,r9a09g057-mipi-dsi # RZ/V2H(P) reg: From 7b7009e7009a90158a25f1850e166735a70b1299 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Wed, 29 Jul 2026 13:54:59 +0100 Subject: [PATCH 52/83] dt-bindings: display: renesas,rzg2l-du: Document RZ/G3L SoC The DU block on the RZ/G3L SoC is identical to the one found on the RZ/G2L SoC. However, it supports the DSI, DPI, and LVDS interfaces, while the RZ/G2L supports only the DSI and DPI interfaces. Due to this difference, a SoC-specific compatible string, 'renesas,r9a08g046-du', is added for the RZ/G3L SoC. Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260729125516.230757-8-biju.das.jz@bp.renesas.com Signed-off-by: Biju Das --- .../bindings/display/renesas,rzg2l-du.yaml | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/display/renesas,rzg2l-du.yaml b/Documentation/devicetree/bindings/display/renesas,rzg2l-du.yaml index 7c84a9ecc7a7..5c9b15a09dee 100644 --- a/Documentation/devicetree/bindings/display/renesas,rzg2l-du.yaml +++ b/Documentation/devicetree/bindings/display/renesas,rzg2l-du.yaml @@ -20,6 +20,7 @@ properties: - enum: - renesas,r9a07g043u-du # RZ/G2UL - renesas,r9a07g044-du # RZ/G2{L,LC} + - renesas,r9a08g046-du # RZ/G3L - renesas,r9a09g057-du # RZ/V2H(P) - renesas,r9a09g077-du # RZ/T2H - items: @@ -65,7 +66,7 @@ properties: model-dependent. Each port shall have a single endpoint. patternProperties: - "^port@[0-1]$": + "^port@[0-2]$": $ref: /schemas/graph.yaml#/properties/port unevaluatedProperties: false @@ -108,6 +109,7 @@ allOf: port@0: description: DPI port@1: false + port@2: false required: - port@0 @@ -124,10 +126,31 @@ allOf: description: DSI port@1: description: DPI + port@2: false required: - port@0 - port@1 + - if: + properties: + compatible: + contains: + const: renesas,r9a08g046-du + then: + properties: + ports: + properties: + port@0: + description: DSI + port@1: + description: DPI + port@2: + description: LVDS + + required: + - port@0 + - port@1 + - port@2 - if: properties: compatible: @@ -140,6 +163,7 @@ allOf: port@0: description: DSI port@1: false + port@2: false required: - port@0 From 9a882c7737bf572fd0098240948168ac637aed9f Mon Sep 17 00:00:00 2001 From: Biju Das Date: Wed, 29 Jul 2026 13:55:01 +0100 Subject: [PATCH 53/83] dt-bindings: display: bridge: Document Renesas RZ/G3L LVDS encoder Document the LVDS encoder IP found on the RZ/G3L SoC. It supports single-link mode. LVDS and the DSI interface share a peripheral clock and the MIPI_DSI_PRESET_N reset signal. However, the LVDS module cannot be used at the same time as MIPI-DSI. Signed-off-by: Tommaso Merciai Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260729125516.230757-10-biju.das.jz@bp.renesas.com Signed-off-by: Biju Das --- .../bridge/renesas,r9a08g046-lvds.yaml | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/bridge/renesas,r9a08g046-lvds.yaml diff --git a/Documentation/devicetree/bindings/display/bridge/renesas,r9a08g046-lvds.yaml b/Documentation/devicetree/bindings/display/bridge/renesas,r9a08g046-lvds.yaml new file mode 100644 index 000000000000..4cd7b688fbf7 --- /dev/null +++ b/Documentation/devicetree/bindings/display/bridge/renesas,r9a08g046-lvds.yaml @@ -0,0 +1,120 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/display/bridge/renesas,r9a08g046-lvds.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Renesas RZ/G3L LVDS Encoder + +maintainers: + - Biju Das + - Tommaso Merciai + +description: | + This binding describes the LVDS encoder embedded in the Renesas RZ/G3L + SoC. The encoder can operate in LVDS Single-link mode with 4 lanes + (Data) + 1 lane (Clock). + +properties: + compatible: + const: renesas,r9a08g046-lvds + + reg: + maxItems: 1 + + clocks: + items: + - description: Peripheral clock + - description: PHY clock + - description: Dot clock + + clock-names: + items: + - const: pclk + - const: phyclk + - const: dotclk + + resets: + items: + - description: LVDS_RESET_N + - description: MIPI_DSI_PRESET_N + - description: MIPI_DSI_CMN_RSTB + - description: MIPI_DSI_ARESET_N + + reset-names: + items: + - const: lvdrst + - const: prst + - const: rst + - const: arst + + power-domains: + maxItems: 1 + + ports: + $ref: /schemas/graph.yaml#/properties/ports + + properties: + port@0: + $ref: /schemas/graph.yaml#/properties/port + description: Input channel, directly connected to the Display Unit. + + port@1: + $ref: /schemas/graph.yaml#/properties/port + description: | + Output channel, directly connected to the LVDS panel or bridge. + + required: + - port@0 + - port@1 + +required: + - compatible + - reg + - clocks + - clock-names + - resets + - reset-names + - power-domains + - ports + +additionalProperties: false + +examples: + - | + #include + + lvds@108a0000 { + compatible = "renesas,r9a08g046-lvds"; + reg = <0x108a0000 0x10000>; + clocks = <&cpg CPG_MOD R9A08G046_MIPI_DSI_PCLK>, + <&cpg CPG_MOD R9A08G046_LVDS_PLLCLK>, + <&cpg CPG_MOD R9A08G046_LVDS_CLK_DOT0>; + clock-names = "pclk", "phyclk", "dotclk"; + resets = <&cpg R9A08G046_LVDS_RESET_N>, + <&cpg R9A08G046_MIPI_DSI_PRESET_N>, + <&cpg R9A08G046_MIPI_DSI_CMN_RSTB>, + <&cpg R9A08G046_MIPI_DSI_ARESET_N>; + reset-names = "lvdrst", "prst", "rst", "arst"; + power-domains = <&cpg>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + lvds0_in: endpoint { + remote-endpoint = <&du_out_lvds0>; + }; + }; + + port@1 { + reg = <1>; + lvds0_out: endpoint { + remote-endpoint = <&panel_in>; + }; + }; + }; + }; +... From ba9fdfabe28c29e8d929b781578776aaaeaa8f38 Mon Sep 17 00:00:00 2001 From: Natalie Vock Date: Fri, 10 Apr 2026 10:13:22 +0200 Subject: [PATCH 54/83] drm/nouveau: Wire up dmem cgroups Userspace can now make use of memory protection via dmem cgroups. Let nouveau benefit from this as well by registering the vram region with the dmem cgroup controller. This patch adapts the approach amdgpu and Xe have taken for enabling dmem cgroups. Signed-off-by: Natalie Vock Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260410081322.5577-1-natalie.vock@gmx.de --- drivers/gpu/drm/nouveau/nouveau_ttm.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/gpu/drm/nouveau/nouveau_ttm.c b/drivers/gpu/drm/nouveau/nouveau_ttm.c index ad01f922aa86..860bca7e3ce2 100644 --- a/drivers/gpu/drm/nouveau/nouveau_ttm.c +++ b/drivers/gpu/drm/nouveau/nouveau_ttm.c @@ -188,6 +188,11 @@ nouveau_ttm_init_vram(struct nouveau_drm *drm) man->func = &nouveau_vram_manager; + man->cg = drmm_cgroup_register_region(drm->dev, "vram", + drm->gem.vram_available); + if (IS_ERR(man->cg)) + return PTR_ERR(man->cg); + ttm_resource_manager_init(man, &drm->ttm.bdev, drm->gem.vram_available >> PAGE_SHIFT); ttm_set_driver_manager(&drm->ttm.bdev, TTM_PL_VRAM, man); From c8d3d795dd40c7fb35b39ebcb99ae5f2f1884b48 Mon Sep 17 00:00:00 2001 From: Hsin-Te Yuan Date: Mon, 3 Aug 2026 17:47:19 +0800 Subject: [PATCH 55/83] drm/panel-edp: Add pre_unprepare delay In eDP power-off timing specifications, T10 defines the minimum delay required between the end of valid video data from the source and panel power-down (main VDD off). Currently, panel-edp only supports the 'disable' delay, which corresponds to T9 (backlight off to end of video data) and runs in panel_edp_disable() while video data is still active. For panels that require a delay after video data has stopped before cutting power, this delay must occur in panel_edp_unprepare() before turning off power rails. Add a 'pre_unprepare' field to struct panel_delay to model T10-min, and delay for this duration in panel_edp_unprepare() before calling pm_runtime_put_sync_suspend(). Additionally, adjust the timing entry for TM156VDXP25 to use delay_200_500_e80_pu100 (enable = 80ms, pre_unprepare = 100ms) to match its panel specification, replacing the previous delay_200_500_e50_d100. Fixes: b9e2d5cdaab0 ("drm/panel-edp: Support NV140FHM-N5B and TM156VDXP25") Signed-off-by: Hsin-Te Yuan Reviewed-by: Douglas Anderson Signed-off-by: Douglas Anderson Link: https://patch.msgid.link/20260803-edp-v1-1-3e92dec1d56c@chromium.org --- drivers/gpu/drm/panel/panel-edp.c | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/panel/panel-edp.c b/drivers/gpu/drm/panel/panel-edp.c index 3aa51ad21305..2b455369ed70 100644 --- a/drivers/gpu/drm/panel/panel-edp.c +++ b/drivers/gpu/drm/panel/panel-edp.c @@ -144,6 +144,16 @@ struct panel_delay { */ unsigned int disable; + /** + * @pre_unprepare: Time for the end of video data to power off. + * + * The time (in milliseconds) that it needs to have passed between + * the end of valid video data from source and start powering off. + * + * This is T10-min on eDP timing diagrams. It is not common to set. + */ + unsigned int pre_unprepare; + /** * @unprepare: Time to power down completely. * @@ -413,8 +423,12 @@ static int panel_edp_suspend(struct device *dev) static int panel_edp_unprepare(struct drm_panel *panel) { + struct panel_edp *p = to_panel_edp(panel); int ret; + if (p->desc->delay.pre_unprepare) + msleep(p->desc->delay.pre_unprepare); + ret = pm_runtime_put_sync_suspend(panel->dev); if (ret < 0) return ret; @@ -1810,6 +1824,13 @@ static const struct panel_delay delay_80_500_e80_p2e200 = { .prepare_to_enable = 200, }; +static const struct panel_delay delay_200_500_e80_pu100 = { + .hpd_absent = 200, + .unprepare = 500, + .enable = 80, + .pre_unprepare = 100, +}; + static const struct panel_delay delay_100_500_e200 = { .hpd_absent = 100, .unprepare = 500, @@ -2182,7 +2203,7 @@ static const struct edp_panel_entry edp_panels[] = { EDP_PANEL_ENTRY('T', 'M', 'A', 0x0811, &delay_200_500_e80_d50, "TM140VDXP01-04"), EDP_PANEL_ENTRY('T', 'M', 'A', 0x2094, &delay_200_500_e50_d100, "TL140VDMS03-01"), - EDP_PANEL_ENTRY('T', 'M', 'A', 0x2139, &delay_200_500_e50_d100, "TM156VDXP25"), + EDP_PANEL_ENTRY('T', 'M', 'A', 0x2139, &delay_200_500_e80_pu100, "TM156VDXP25"), { /* sentinal */ } }; From 2d5e8c2167ab7af17535cd7a6f5bda995e85c7b2 Mon Sep 17 00:00:00 2001 From: Chen-Yu Tsai Date: Mon, 3 Aug 2026 19:13:18 +0800 Subject: [PATCH 56/83] drm/sun4i: framebuffer: Set mode_config.normalize_zpos Back when support for zpos was added in commit 47a05f4a68f9 ("drm/sun4i: backend: Add support for zpos"), a custom atomic_check callback was also added in commit b8f1230dd3bf ("drm/sun4i: framebuffer: Add a custom atomic_check") to have a place to call drm_atomic_normalize_zpos(). Commit 49efffc7fbd4 ("drm: Add drm_mode_config->normalize_zpos boolean") added drm_atomic_normalize_zpos() to the standard atomic_check function. Set mode_config.normalize_zpos and drop the custom atomic_check implementation, which at this point is just a simplified copy of the standard one. The standard one also checks whether async updates are valid and updates the self refresh state, but neither feature is supported by this driver. Acked-by: Jernej Skrabec Link: https://patch.msgid.link/20260803111319.2836240-1-wenst@chromium.org Signed-off-by: Chen-Yu Tsai --- drivers/gpu/drm/sun4i/sun4i_framebuffer.c | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/drivers/gpu/drm/sun4i/sun4i_framebuffer.c b/drivers/gpu/drm/sun4i/sun4i_framebuffer.c index 9c8ef927d929..fc822bc2c62b 100644 --- a/drivers/gpu/drm/sun4i/sun4i_framebuffer.c +++ b/drivers/gpu/drm/sun4i/sun4i_framebuffer.c @@ -14,24 +14,8 @@ #include "sun4i_drv.h" #include "sun4i_framebuffer.h" -static int sun4i_de_atomic_check(struct drm_device *dev, - struct drm_atomic_commit *state) -{ - int ret; - - ret = drm_atomic_helper_check_modeset(dev, state); - if (ret) - return ret; - - ret = drm_atomic_normalize_zpos(dev, state); - if (ret) - return ret; - - return drm_atomic_helper_check_planes(dev, state); -} - static const struct drm_mode_config_funcs sun4i_de_mode_config_funcs = { - .atomic_check = sun4i_de_atomic_check, + .atomic_check = drm_atomic_helper_check, .atomic_commit = drm_atomic_helper_commit, .fb_create = drm_gem_fb_create, }; @@ -46,6 +30,7 @@ void sun4i_framebuffer_init(struct drm_device *drm) drm->mode_config.max_width = 8192; drm->mode_config.max_height = 8192; + drm->mode_config.normalize_zpos = true; drm->mode_config.funcs = &sun4i_de_mode_config_funcs; drm->mode_config.helper_private = &sun4i_de_mode_config_helpers; From abffce8b1eb5e119efccc51fc6a18ba38e2f6db6 Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Mon, 3 Aug 2026 18:10:39 +0200 Subject: [PATCH 57/83] drm/sun4i: Fix V3s YUV scanline size The VI scaler line buffer on V3s can hold 1024 pixels for subsampled formats, not 2048 as currently claimed. Since coarse horizontal scaling is engaged only once the source width exceeds that limit, YUV layers wider than 1024 pixels are passed to the scaler unchanged and the output is corrupted. Use the value from the vendor driver. Fixes: 2586de70c15c ("drm/sun4i: Add VI scaler line size quirk for DE2/DE3") Signed-off-by: Jernej Skrabec Acked-by: Chen-Yu Tsai Link: https://patch.msgid.link/75ad4947981f2dc33fd42c05ae0b0cedda7647bd.1785772659.git.jernej.skrabec@gmail.com Signed-off-by: Chen-Yu Tsai --- drivers/gpu/drm/sun4i/sun8i_mixer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/sun4i/sun8i_mixer.c b/drivers/gpu/drm/sun4i/sun8i_mixer.c index 4e4226295010..b6cd7352e76c 100644 --- a/drivers/gpu/drm/sun4i/sun8i_mixer.c +++ b/drivers/gpu/drm/sun4i/sun8i_mixer.c @@ -785,7 +785,7 @@ static const struct sun8i_mixer_cfg sun8i_v3s_mixer_cfg = { .de_type = SUN8I_MIXER_DE2, .vi_scaler_num = 2, .scaler_mask = 0x3, - .scanline_yuv = 2048, + .scanline_yuv = 1024, }, .de_type = SUN8I_MIXER_DE2, .mod_rate = 150000000, From 5c31990b21f0b535732deb2b658b78b07464f56c Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Mon, 3 Aug 2026 18:10:40 +0200 Subject: [PATCH 58/83] drm/sun4i: vi scaler: Fix coefficient selection Currently, vertical coefficients are selected based on horizontal scaling, which is wrong. Additionally, chroma coefficients should be selected based on format subsampling. Fix all that. Fixes: b862a648de3b ("drm/sun4i: Add support for HW scaling to DE2") Signed-off-by: Jernej Skrabec Reviewed-by: Chen-Yu Tsai Link: https://patch.msgid.link/263a4a41442a3c8b072b170256b72658f1b90802.1785772659.git.jernej.skrabec@gmail.com Signed-off-by: Chen-Yu Tsai --- drivers/gpu/drm/sun4i/sun8i_vi_scaler.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/sun4i/sun8i_vi_scaler.c b/drivers/gpu/drm/sun4i/sun8i_vi_scaler.c index 3dec4eeb1ba2..d4412a62c2f0 100644 --- a/drivers/gpu/drm/sun4i/sun8i_vi_scaler.c +++ b/drivers/gpu/drm/sun4i/sun8i_vi_scaler.c @@ -894,20 +894,26 @@ static void sun8i_vi_scaler_set_coeff(struct regmap *map, u32 base, lan3coefftab32_left[offset + i]); regmap_write(map, SUN8I_SCALER_VSU_YHCOEFF1(base, i), lan3coefftab32_right[offset + i]); + } + offset = sun8i_vi_scaler_coef_index(vstep) * + SUN8I_VI_SCALER_COEFF_COUNT; + for (i = 0; i < SUN8I_VI_SCALER_COEFF_COUNT; i++) + regmap_write(map, SUN8I_SCALER_VSU_YVCOEFF(base, i), + lan2coefftab32[offset + i]); + + offset = sun8i_vi_scaler_coef_index(hstep / format->hsub) * + SUN8I_VI_SCALER_COEFF_COUNT; + for (i = 0; i < SUN8I_VI_SCALER_COEFF_COUNT; i++) { regmap_write(map, SUN8I_SCALER_VSU_CHCOEFF0(base, i), ch_left[offset + i]); regmap_write(map, SUN8I_SCALER_VSU_CHCOEFF1(base, i), ch_right[offset + i]); } - - offset = sun8i_vi_scaler_coef_index(hstep) * + offset = sun8i_vi_scaler_coef_index(vstep / format->vsub) * SUN8I_VI_SCALER_COEFF_COUNT; - for (i = 0; i < SUN8I_VI_SCALER_COEFF_COUNT; i++) { - regmap_write(map, SUN8I_SCALER_VSU_YVCOEFF(base, i), - lan2coefftab32[offset + i]); + for (i = 0; i < SUN8I_VI_SCALER_COEFF_COUNT; i++) regmap_write(map, SUN8I_SCALER_VSU_CVCOEFF(base, i), cy[offset + i]); - } } void sun8i_vi_scaler_enable(struct sun8i_layer *layer, bool enable) From 583d99165a85c3aaee924078c0f03c72e25644ac Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Mon, 3 Aug 2026 18:10:41 +0200 Subject: [PATCH 59/83] drm/sun4i: vi scaler: Restore opaque alpha in video modes The DE3 mixer initialization clears the entire mixer register space. This also clears VSU_GLOBAL_ALPHA, despite its hardware reset value being 0xff. The VI scaler uses Video Normal mode for subsampled YUV formats. In this mode, VSU_GLOBAL_ALPHA provides the scaler output alpha. Leaving the register at zero causes the scaler to produce fully transparent output. Set VSU_GLOBAL_ALPHA to 0xff whenever configuring a DE3 or newer VI scaler. The register is ignored in UI scaling mode. Fixes: c50519e6db4d ("drm/sun4i: Add basic support for DE3") Signed-off-by: Jernej Skrabec Acked-by: Chen-Yu Tsai Link: https://patch.msgid.link/c48ba85b9e4478d51afde4f36839fd1c1d363b23.1785772659.git.jernej.skrabec@gmail.com Signed-off-by: Chen-Yu Tsai --- drivers/gpu/drm/sun4i/sun8i_vi_scaler.c | 2 ++ drivers/gpu/drm/sun4i/sun8i_vi_scaler.h | 1 + 2 files changed, 3 insertions(+) diff --git a/drivers/gpu/drm/sun4i/sun8i_vi_scaler.c b/drivers/gpu/drm/sun4i/sun8i_vi_scaler.c index d4412a62c2f0..a5cc99981006 100644 --- a/drivers/gpu/drm/sun4i/sun8i_vi_scaler.c +++ b/drivers/gpu/drm/sun4i/sun8i_vi_scaler.c @@ -975,6 +975,8 @@ void sun8i_vi_scaler_setup(struct sun8i_layer *layer, regmap_write(layer->regs, SUN50I_SCALER_VSU_SCALE_MODE(base), val); + regmap_write(layer->regs, + SUN50I_SCALER_VSU_GLB_ALPHA(base), 0xff); } regmap_write(layer->regs, diff --git a/drivers/gpu/drm/sun4i/sun8i_vi_scaler.h b/drivers/gpu/drm/sun4i/sun8i_vi_scaler.h index 245fe2f431c3..d89c0cbe188d 100644 --- a/drivers/gpu/drm/sun4i/sun8i_vi_scaler.h +++ b/drivers/gpu/drm/sun4i/sun8i_vi_scaler.h @@ -36,6 +36,7 @@ #define SUN50I_SCALER_VSU_EDSCL_CTRL(base) ((base) + 0x28) #define SUN50I_SCALER_VSU_ANGLE_THR(base) ((base) + 0x2c) #define SUN8I_SCALER_VSU_OUTSIZE(base) ((base) + 0x40) +#define SUN50I_SCALER_VSU_GLB_ALPHA(base) ((base) + 0x44) #define SUN8I_SCALER_VSU_YINSIZE(base) ((base) + 0x80) #define SUN8I_SCALER_VSU_YHSTEP(base) ((base) + 0x88) #define SUN8I_SCALER_VSU_YVSTEP(base) ((base) + 0x8c) From 62bac3202042a8cfd4d4969a6d057cebd8b7cc1a Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Mon, 3 Aug 2026 18:10:42 +0200 Subject: [PATCH 60/83] drm/sun4i: tcon-top: Keep mixer routes distinct Both mixer selectors reset to TCON 0. Selecting the same TCON for both mixers causes black or corrupted output. When a route would collide, park the other mixer on another described TCON, or an unused selector if none exists. Since the TCON index is now used as a shift, also reject negative values. Tested on Orange Pi 3 with TCON_LCD0 and TCON_TV0. Link: https://lore.kernel.org/linux-sunxi/Zn8GVkpwXwhaUFno@titan/ Link: https://lore.kernel.org/linux-sunxi/20241108-tcon_fix-v1-1-616218cc0d5f@jookia.org/ Fixes: 05db311a792d ("drm/sun4i: tcon-top: Add helpers for mux switching") Signed-off-by: Jernej Skrabec Reviewed-by: Chen-Yu Tsai Link: https://patch.msgid.link/52eb247169b268054302afa71e598add0b04748d.1785772659.git.jernej.skrabec@gmail.com Signed-off-by: Chen-Yu Tsai --- drivers/gpu/drm/sun4i/sun8i_tcon_top.c | 71 +++++++++++++++++++++----- drivers/gpu/drm/sun4i/sun8i_tcon_top.h | 6 +++ 2 files changed, 65 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/sun4i/sun8i_tcon_top.c b/drivers/gpu/drm/sun4i/sun8i_tcon_top.c index 8adda578c51b..9cbd655518b2 100644 --- a/drivers/gpu/drm/sun4i/sun8i_tcon_top.c +++ b/drivers/gpu/drm/sun4i/sun8i_tcon_top.c @@ -25,6 +25,49 @@ static bool sun8i_tcon_top_node_is_tcon_top(struct device_node *node) return !!of_match_node(sun8i_tcon_top_of_table, node); } +static unsigned int sun8i_tcon_top_get_tcon_map(struct device_node *node) +{ + static const u32 out_ports[] = { + TCON_TOP_MIXER0_OUT_PORT, + TCON_TOP_MIXER1_OUT_PORT, + }; + unsigned int i, map = 0; + + for (i = 0; i < ARRAY_SIZE(out_ports); i++) { + struct device_node *port; + + port = of_graph_get_port_by_id(node, out_ports[i]); + if (!port) + continue; + + for_each_of_graph_port_endpoint(port, ep) { + struct of_endpoint endpoint; + + if (of_graph_parse_endpoint(ep, &endpoint)) + continue; + + if (endpoint.id < TCON_TOP_PORT_TCON_NUM) + map |= BIT(endpoint.id); + } + + of_node_put(port); + } + + return map; +} + +static unsigned int sun8i_tcon_top_park_index(struct sun8i_tcon_top *tcon_top, + int tcon) +{ + unsigned int candidates; + + candidates = tcon_top->tcon_map & ~BIT(tcon); + if (!candidates) + candidates = GENMASK(TCON_TOP_PORT_TCON_NUM - 1, 0) & ~BIT(tcon); + + return ffs(candidates) - 1; +} + int sun8i_tcon_top_set_hdmi_src(struct device *dev, int tcon) { struct sun8i_tcon_top *tcon_top = dev_get_drvdata(dev); @@ -57,6 +100,7 @@ EXPORT_SYMBOL(sun8i_tcon_top_set_hdmi_src); int sun8i_tcon_top_de_config(struct device *dev, int mixer, int tcon) { struct sun8i_tcon_top *tcon_top = dev_get_drvdata(dev); + u32 mixer_msk, other_msk; unsigned long flags; u32 reg; @@ -70,21 +114,27 @@ int sun8i_tcon_top_de_config(struct device *dev, int mixer, int tcon) return -EINVAL; } - if (tcon > 3) { - dev_err(dev, "TCON index is too high!\n"); + if (tcon < 0 || tcon >= TCON_TOP_PORT_TCON_NUM) { + dev_err(dev, "TCON index is invalid!\n"); return -EINVAL; } + mixer_msk = mixer ? TCON_TOP_PORT_DE1_MSK : TCON_TOP_PORT_DE0_MSK; + other_msk = mixer ? TCON_TOP_PORT_DE0_MSK : TCON_TOP_PORT_DE1_MSK; + spin_lock_irqsave(&tcon_top->reg_lock, flags); reg = readl(tcon_top->regs + TCON_TOP_PORT_SEL_REG); - if (mixer == 0) { - reg &= ~TCON_TOP_PORT_DE0_MSK; - reg |= FIELD_PREP(TCON_TOP_PORT_DE0_MSK, tcon); - } else { - reg &= ~TCON_TOP_PORT_DE1_MSK; - reg |= FIELD_PREP(TCON_TOP_PORT_DE1_MSK, tcon); + + reg &= ~mixer_msk; + reg |= field_prep(mixer_msk, tcon); + + if (field_get(other_msk, reg) == tcon) { + reg &= ~other_msk; + reg |= field_prep(other_msk, + sun8i_tcon_top_park_index(tcon_top, tcon)); } + writel(reg, tcon_top->regs + TCON_TOP_PORT_SEL_REG); spin_unlock_irqrestore(&tcon_top->reg_lock, flags); @@ -143,6 +193,7 @@ static int sun8i_tcon_top_bind(struct device *dev, struct device *master, return -ENOMEM; clk_data->num = CLK_NUM; tcon_top->clk_data = clk_data; + tcon_top->tcon_map = sun8i_tcon_top_get_tcon_map(dev->of_node); spin_lock_init(&tcon_top->reg_lock); @@ -175,10 +226,6 @@ static int sun8i_tcon_top_bind(struct device *dev, struct device *master, goto err_assert_reset; } - /* - * At least on H6, some registers have some bits set by default - * which may cause issues. Clear them here. - */ writel(0, regs + TCON_TOP_PORT_SEL_REG); writel(0, regs + TCON_TOP_GATE_SRC_REG); diff --git a/drivers/gpu/drm/sun4i/sun8i_tcon_top.h b/drivers/gpu/drm/sun4i/sun8i_tcon_top.h index 0390584a330e..2b887470a49f 100644 --- a/drivers/gpu/drm/sun4i/sun8i_tcon_top.h +++ b/drivers/gpu/drm/sun4i/sun8i_tcon_top.h @@ -14,6 +14,10 @@ #define TCON_TOP_PORT_SEL_REG 0x1C #define TCON_TOP_PORT_DE0_MSK GENMASK(1, 0) #define TCON_TOP_PORT_DE1_MSK GENMASK(5, 4) +#define TCON_TOP_PORT_TCON_NUM 4 + +#define TCON_TOP_MIXER0_OUT_PORT 1 +#define TCON_TOP_MIXER1_OUT_PORT 3 #define TCON_TOP_GATE_SRC_REG 0x20 #define TCON_TOP_HDMI_SRC_MSK GENMASK(29, 28) @@ -29,6 +33,8 @@ struct sun8i_tcon_top { void __iomem *regs; struct reset_control *rst; + unsigned int tcon_map; + /* * spinlock is used to synchronize access to same * register where multiple clock gates can be set. From 9c90199b39637ad94253c4fd8e7b1333ca1d3e0d Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Mon, 3 Aug 2026 18:10:43 +0200 Subject: [PATCH 61/83] drm/sun4i: tcon: Set output mux for DSI and LVDS DSI and LVDS skip output mux setup, so TCON TOP cannot route the selected mixer. Configure them like other channel 0 outputs. In practice this matters for D1, where channel 0 TCONs are fed through TCON TOP. The remaining set_mux implementations only handle TMDS and return an error for other encoder types, as before. Fixes: b9b52d2f4aaf ("drm/sun4i: Add support for D1 TCONs") Signed-off-by: Jernej Skrabec Acked-by: Chen-Yu Tsai Link: https://patch.msgid.link/7e9dad9eed2e91a79c4e1202caa8fed7c2427531.1785772659.git.jernej.skrabec@gmail.com Signed-off-by: Chen-Yu Tsai --- drivers/gpu/drm/sun4i/sun4i_tcon.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c index d4c1723c5e3d..43d48a5a820f 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c @@ -717,9 +717,11 @@ void sun4i_tcon_mode_set(struct sun4i_tcon *tcon, case DRM_MODE_ENCODER_DSI: /* DSI is tied to special case of CPU interface */ sun4i_tcon0_mode_set_cpu(tcon, encoder, mode); + sun4i_tcon_set_mux(tcon, 0, encoder); break; case DRM_MODE_ENCODER_LVDS: sun4i_tcon0_mode_set_lvds(tcon, encoder, mode); + sun4i_tcon_set_mux(tcon, 0, encoder); break; case DRM_MODE_ENCODER_NONE: sun4i_tcon0_mode_set_rgb(tcon, encoder, mode); From 8208832a38ff3d2560eb8a77a9d7a2f17d8ebcdc Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Mon, 3 Aug 2026 18:10:44 +0200 Subject: [PATCH 62/83] drm/sun4i: tcon: Drop TCON TOP device reference of_find_device_by_node() takes a device reference. Drop it after mux configuration succeeds. Fixes: 0305189afb32 ("drm/sun4i: tcon: Add support for R40 TCON") Signed-off-by: Jernej Skrabec Acked-by: Chen-Yu Tsai Link: https://patch.msgid.link/871a3108086c15a483eef23301984c8d2254dfa7.1785772659.git.jernej.skrabec@gmail.com Signed-off-by: Chen-Yu Tsai --- drivers/gpu/drm/sun4i/sun4i_tcon.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c index 43d48a5a820f..d4f51eccaa38 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c @@ -1411,7 +1411,7 @@ static int sun8i_r40_tcon_tv_set_mux(struct sun4i_tcon *tcon, { struct device_node *port, *remote; struct platform_device *pdev; - int id, ret; + int id, ret = 0; /* find TCON TOP platform device and TCON id */ @@ -1434,21 +1434,20 @@ static int sun8i_r40_tcon_tv_set_mux(struct sun4i_tcon *tcon, if (IS_ENABLED(CONFIG_DRM_SUN8I_TCON_TOP) && encoder->encoder_type == DRM_MODE_ENCODER_TMDS) { ret = sun8i_tcon_top_set_hdmi_src(&pdev->dev, id); - if (ret) { - put_device(&pdev->dev); - return ret; - } + if (ret) + goto out_put_device; } if (IS_ENABLED(CONFIG_DRM_SUN8I_TCON_TOP)) { ret = sun8i_tcon_top_de_config(&pdev->dev, tcon->id, id); - if (ret) { - put_device(&pdev->dev); - return ret; - } + if (ret) + goto out_put_device; } - return 0; +out_put_device: + put_device(&pdev->dev); + + return ret; } static const struct sun4i_tcon_quirks sun4i_a10_quirks = { From f5c3b1b0d228624786d22973a20c908c84e1a576 Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Mon, 3 Aug 2026 18:10:45 +0200 Subject: [PATCH 63/83] drm/sun4i: hdmi: Don't leak sync polarity bits into packet control sun4i_hdmi_enable() keeps using the same variable after it programmed the video timing polarity register with it. The leftover TX_CLK, HSYNC and VSYNC bits are then ORed into the packet control register, where each nibble selects the packet type sent in one slot. As a result, slot 0 selects packet type 3 instead of the AVI infoframe whenever the mode has positive HSYNC polarity, and the TX_CLK bits set nibbles which the driver never programs. Assign the packet types instead of ORing them into the stale value. Fixes: 9ca6bc246035 ("drm/sun4i: hdmi: Move mode_set into enable") Signed-off-by: Jernej Skrabec Acked-by: Chen-Yu Tsai Link: https://patch.msgid.link/51ba0918ce016a4b45313d5df1b6ce31b8c8731e.1785772659.git.jernej.skrabec@gmail.com Signed-off-by: Chen-Yu Tsai --- drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c index e99f52ebb26f..97253d631b2b 100644 --- a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c @@ -160,7 +160,7 @@ static void sun4i_hdmi_enable(struct drm_encoder *encoder, drm_atomic_helper_connector_hdmi_update_infoframes(connector, state); - val |= SUN4I_HDMI_PKT_CTRL_TYPE(0, SUN4I_HDMI_PKT_AVI); + val = SUN4I_HDMI_PKT_CTRL_TYPE(0, SUN4I_HDMI_PKT_AVI); val |= SUN4I_HDMI_PKT_CTRL_TYPE(1, SUN4I_HDMI_PKT_END); writel(val, hdmi->base + SUN4I_HDMI_PKT_CTRL_REG(0)); From 7061ff05ed4a3cf16e83f7e3ad09cbd212508a32 Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Mon, 3 Aug 2026 18:10:46 +0200 Subject: [PATCH 64/83] drm/sun4i: crtc: Propagate layer initialization error sun4i_crtc_init() returns plain NULL when layer initialization fails, while all its other error paths return an error pointer. The only caller, sun4i_tcon_bind(), checks the result with IS_ERR() and happily continues with tcon->crtc set to NULL. sun4i_rgb_init() and sun4i_lvds_init() then dereference it in drm_crtc_mask(), which oopses. Return the error pointer instead. Fixes: dcd215801b02 ("drm/sun4i: Drop primary layer pointer from sun4i_drv") Signed-off-by: Jernej Skrabec Acked-by: Chen-Yu Tsai Link: https://patch.msgid.link/b26a0d427d9dfae9c82e3ca90a67d24d8ece5a28.1785772659.git.jernej.skrabec@gmail.com Signed-off-by: Chen-Yu Tsai --- drivers/gpu/drm/sun4i/sun4i_crtc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/sun4i/sun4i_crtc.c b/drivers/gpu/drm/sun4i/sun4i_crtc.c index c2f7f69baf75..3fa1f4fda0f0 100644 --- a/drivers/gpu/drm/sun4i/sun4i_crtc.c +++ b/drivers/gpu/drm/sun4i/sun4i_crtc.c @@ -208,7 +208,7 @@ struct sun4i_crtc *sun4i_crtc_init(struct drm_device *drm, planes = sunxi_engine_layers_init(drm, engine); if (IS_ERR(planes)) { dev_err(drm->dev, "Couldn't create the planes\n"); - return NULL; + return ERR_CAST(planes); } /* find primary and cursor planes for drm_crtc_init_with_planes */ From 3f77e4072630e2301efdbe521e7fca10311043ec Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Mon, 3 Aug 2026 18:10:47 +0200 Subject: [PATCH 65/83] drm/sun4i: tcon: Drop remote endpoint reference sun4i_tcon_of_get_id_from_port() never drops the reference taken by of_graph_get_remote_endpoint(). The function is not only called during bind, but also on every mode set through sun8i_r40_tcon_tv_set_mux(), so the leak accumulates. Fixes: e8d5bbf7f4c4 ("drm/sun4i: tcon: get TCON ID and matching engine with remote endpoint ID") Signed-off-by: Jernej Skrabec Acked-by: Chen-Yu Tsai Reviewed-by: Jernej Skrabec Link: https://patch.msgid.link/3f5ec952ad80cb51efebf2fe230df50259041a23.1785772659.git.jernej.skrabec@gmail.com Signed-off-by: Chen-Yu Tsai --- drivers/gpu/drm/sun4i/sun4i_tcon.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c index d4f51eccaa38..bf405a2aa324 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c @@ -972,6 +972,7 @@ static int sun4i_tcon_of_get_id_from_port(struct device_node *port) continue; ret = of_property_read_u32(remote, "reg", ®); + of_node_put(remote); if (ret) continue; From d2a242e5688a17b79c89cd966cd31820c5096d80 Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Mon, 3 Aug 2026 18:10:48 +0200 Subject: [PATCH 66/83] drm/sun4i: dw-hdmi: Drop TCON TOP port reference When the HDMI controller is fed by TCON TOP, the port node used to enumerate the possible CRTCs is never released. Fixes: 57e23de02f48 ("drm/sun4i: DW HDMI: Expand algorithm for possible crtcs") Signed-off-by: Jernej Skrabec Acked-by: Chen-Yu Tsai Link: https://patch.msgid.link/43ffcc17f7c3f94c1d7bd1ee89134c766e84df35.1785772659.git.jernej.skrabec@gmail.com Signed-off-by: Chen-Yu Tsai --- drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c index 9694ad142a7d..bd3923ef5a41 100644 --- a/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c +++ b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c @@ -86,6 +86,8 @@ static u32 sun8i_dw_hdmi_find_possible_crtcs(struct drm_device *drm, of_node_put(remote_port); } } + + of_node_put(port); } else { crtcs = drm_of_find_possible_crtcs(drm, node); } From 08a91f2a0664b73c2af9295ecbd441e852abe33c Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Mon, 3 Aug 2026 18:10:49 +0200 Subject: [PATCH 67/83] drm/sun4i: Drop node references while building component list Two references are leaked every time the display pipeline is walked: the output port node in sun4i_drv_traverse_endpoints(), which was never released since the driver was introduced, and each node taken out of the endpoint fifo in sun4i_drv_probe(), which stopped being released when the fifo was introduced. The latter is still safe to drop right after processing, since drm_of_component_match_add() takes its own reference. Fixes: 9026e0d122ac ("drm: Add Allwinner A10 Display Engine support") Fixes: 8b11aaface2b ("drm/sun4i: Implement endpoint parsing using kfifo") Signed-off-by: Jernej Skrabec Acked-by: Chen-Yu Tsai Link: https://patch.msgid.link/759c74e3a22b97ca066ef7910ca4b91b852011e6.1785772659.git.jernej.skrabec@gmail.com Signed-off-by: Chen-Yu Tsai --- drivers/gpu/drm/sun4i/sun4i_drv.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/sun4i/sun4i_drv.c b/drivers/gpu/drm/sun4i/sun4i_drv.c index 8a409eee1dca..e9baca360160 100644 --- a/drivers/gpu/drm/sun4i/sun4i_drv.c +++ b/drivers/gpu/drm/sun4i/sun4i_drv.c @@ -289,6 +289,8 @@ static void sun4i_drv_traverse_endpoints(struct endpoint_list *list, kfifo_put(&list->fifo, remote); } + + of_node_put(port); } static int sun4i_drv_add_endpoints(struct device *dev, @@ -394,6 +396,7 @@ static int sun4i_drv_probe(struct platform_device *pdev) /* process this endpoint */ ret = sun4i_drv_add_endpoints(&pdev->dev, &list, &match, endpoint); + of_node_put(endpoint); /* sun4i_drv_add_endpoints can fail to allocate memory */ if (ret < 0) From 0ba6deddaae74f0539c0303bfb5f860adbe1a68b Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Mon, 3 Aug 2026 18:10:50 +0200 Subject: [PATCH 68/83] drm/sun4i: hdmi-phy: Fix H6 8-bit MPLL config at 594 MHz The 8-bit entry of the last MPLL row (594 MHz) doesn't lock reliably on H6. 4K@60 RGB/YUV444, which is the mode that reaches this entry, doesn't come up. Align the value with the vendor driver. Other entries are left alone, they are used by lower pixel clocks which work fine. Tested with 4K@60 on a LG TV. Fixes: 0fb4b858b102 ("drm/sun4i: Add support for H6 HDMI PHY") Signed-off-by: Jernej Skrabec Acked-by: Chen-Yu Tsai Reviewed-by: Chen-Yu Tsai Link: https://patch.msgid.link/aec9060209473b8176eb43bc7c63c20b21306adf.1785772659.git.jernej.skrabec@gmail.com Signed-off-by: Chen-Yu Tsai --- drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c index 4fa69c463dc4..489ea94693ff 100644 --- a/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c +++ b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c @@ -91,7 +91,7 @@ static const struct dw_hdmi_mpll_config sun50i_h6_mpll_cfg[] = { }, }, { 594000000, { - { 0x1a40, 0x0003 }, + { 0x1a7c, 0x0003 }, { 0x3b4c, 0x0003 }, { 0x5a64, 0x0003 }, }, From 151ebbc20aa2365fa854a77432043b16606b5cfe Mon Sep 17 00:00:00 2001 From: Tejas Upadhyay Date: Thu, 6 Aug 2026 11:06:25 +0530 Subject: [PATCH 69/83] drm/gpu: Add gpu_buddy_allocated_addr_to_block helper Add helper with primary purpose is to efficiently trace a specific physical memory address back to its corresponding TTM buffer object. v3: - use mm->chunk_size minimum allocation granularity (Arun) v2: - %s/gpu_buddy_addr_to_block/gpu_buddy_allocated_addr_to_block(MattA) - remove clear->avail and split nodes check(MattA) - Adapt lockdep(MattB) Signed-off-by: Tejas Upadhyay Cc: Arunpravin Paneer Selvam Cc: dri-devel@lists.freedesktop.org Reviewed-by: Arunpravin Paneer Selvam Signed-off-by: Arunpravin Paneer Selvam Link: https://patch.msgid.link/20260806053624.3215216-5-tejas.upadhyay@intel.com --- drivers/gpu/buddy.c | 53 +++++++++++++++++++++++++++++++++++++++ include/linux/gpu_buddy.h | 2 ++ 2 files changed, 55 insertions(+) diff --git a/drivers/gpu/buddy.c b/drivers/gpu/buddy.c index dc81fe0301ce..4d5ac375a538 100644 --- a/drivers/gpu/buddy.c +++ b/drivers/gpu/buddy.c @@ -630,6 +630,59 @@ void gpu_buddy_free_block(struct gpu_buddy *mm, } EXPORT_SYMBOL(gpu_buddy_free_block); +/** + * gpu_buddy_allocated_addr_to_block - given relative address find the allocated block + * + * @mm: GPU buddy manager + * @addr: Relative address + * + * Returns: + * gpu_buddy_block on success, NULL or error code on failure + */ +struct gpu_buddy_block *gpu_buddy_allocated_addr_to_block(struct gpu_buddy *mm, u64 addr) +{ + struct gpu_buddy_block *block; + LIST_HEAD(dfs); + u64 end; + int i; + + gpu_buddy_driver_lock_held(mm); + + end = addr + mm->chunk_size - 1; + for (i = 0; i < mm->n_roots; ++i) + list_add_tail(&mm->roots[i]->tmp_link, &dfs); + + do { + u64 block_start; + u64 block_end; + + block = list_first_entry_or_null(&dfs, + struct gpu_buddy_block, + tmp_link); + if (!block) + break; + + list_del(&block->tmp_link); + + block_start = gpu_buddy_block_offset(block); + block_end = block_start + gpu_buddy_block_size(mm, block) - 1; + + if (!overlaps(addr, end, block_start, block_end)) + continue; + + if (gpu_buddy_block_is_allocated(block)) + return block; + else if (gpu_buddy_block_is_free(block)) + return NULL; + + list_add(&block->right->tmp_link, &dfs); + list_add(&block->left->tmp_link, &dfs); + } while (1); + + return ERR_PTR(-ENXIO); +} +EXPORT_SYMBOL(gpu_buddy_allocated_addr_to_block); + static void __gpu_buddy_free_list(struct gpu_buddy *mm, struct list_head *objects, bool mark_clear, diff --git a/include/linux/gpu_buddy.h b/include/linux/gpu_buddy.h index e037714563d8..2c36124bb696 100644 --- a/include/linux/gpu_buddy.h +++ b/include/linux/gpu_buddy.h @@ -287,6 +287,8 @@ void gpu_buddy_reset_clear(struct gpu_buddy *mm, bool is_clear); void gpu_buddy_free_block(struct gpu_buddy *mm, struct gpu_buddy_block *block); +struct gpu_buddy_block *gpu_buddy_allocated_addr_to_block(struct gpu_buddy *mm, u64 addr); + void gpu_buddy_free_list(struct gpu_buddy *mm, struct list_head *objects, unsigned int flags); From 3de014f7debc7162ae26db2f6151af1ba5456dc0 Mon Sep 17 00:00:00 2001 From: Tejas Upadhyay Date: Thu, 6 Aug 2026 11:06:26 +0530 Subject: [PATCH 70/83] gpu/tests/gpu_buddy: Add KUnit test for gpu_buddy_allocated_addr_to_block Add a new KUnit test gpu_test_buddy_addr_to_block() that validates the gpu_buddy_allocated_addr_to_block() helper which traces a address back to its allocated buddy block. The test covers: - Exact address matching returns the correct allocated block - An unallocated address inside the manager should return NULL - An address outside the manager should return -ENXIO v4(MattA): - Add test for unaligned address v3(Sashiko): - remove unused target_addr variable v2(Sashiko): - Drop the mutex and lockdep annotation; standalone KUnit tests do not register a driver lock. Signed-off-by: Tejas Upadhyay Reviewed-by: Matthew Auld Signed-off-by: Arunpravin Paneer Selvam Link: https://patch.msgid.link/20260806053624.3215216-6-tejas.upadhyay@intel.com --- drivers/gpu/tests/gpu_buddy_test.c | 45 ++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/drivers/gpu/tests/gpu_buddy_test.c b/drivers/gpu/tests/gpu_buddy_test.c index 89698563c61b..5860bfc84a25 100644 --- a/drivers/gpu/tests/gpu_buddy_test.c +++ b/drivers/gpu/tests/gpu_buddy_test.c @@ -1422,6 +1422,50 @@ static void gpu_test_buddy_alloc_exceeds_max_order(struct kunit *test) gpu_buddy_fini(&mm); } +static void gpu_test_buddy_addr_to_block(struct kunit *test) +{ + struct gpu_buddy_block *allocated_block, *found_block; + LIST_HEAD(allocated_list); + const u64 test_size = SZ_4M + SZ_2M; + const u64 alloc_start = SZ_4M; + const u64 alloc_size = SZ_4K; + const u64 chunk_size = SZ_4K; + struct gpu_buddy mm; + + KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_init(&mm, test_size, chunk_size), + "buddy_init failed\n"); + + KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, alloc_start, + alloc_start + alloc_size, + alloc_size, chunk_size, + &allocated_list, 0), + "buddy_alloc failed\n"); + + allocated_block = list_first_entry(&allocated_list, struct gpu_buddy_block, link); + KUNIT_EXPECT_EQ(test, gpu_buddy_block_offset(allocated_block), alloc_start); + KUNIT_EXPECT_EQ(test, gpu_buddy_block_size(&mm, allocated_block), alloc_size); + + found_block = gpu_buddy_allocated_addr_to_block(&mm, alloc_start); + KUNIT_EXPECT_PTR_EQ(test, found_block, allocated_block); + + /* Unaligned address inside the allocated block (should resolve to the same block) */ + found_block = gpu_buddy_allocated_addr_to_block(&mm, alloc_start + 16); + KUNIT_EXPECT_PTR_EQ(test, found_block, allocated_block); + + /* An unallocated address inside the manager should return NULL. */ + found_block = gpu_buddy_allocated_addr_to_block(&mm, + alloc_start - chunk_size); + KUNIT_EXPECT_NULL(test, found_block); + + /* An address outside the manager should return -ENXIO. */ + found_block = gpu_buddy_allocated_addr_to_block(&mm, test_size); + KUNIT_EXPECT_EQ(test, PTR_ERR(found_block), -ENXIO); + + /* 3. Standard inline cleanup flow */ + gpu_buddy_free_list(&mm, &allocated_list, 0); + gpu_buddy_fini(&mm); +} + static int gpu_buddy_suite_init(struct kunit_suite *suite) { while (!random_seed) @@ -1446,6 +1490,7 @@ static struct kunit_case gpu_buddy_tests[] = { KUNIT_CASE(gpu_test_buddy_alloc_exceeds_max_order), KUNIT_CASE(gpu_test_buddy_offset_aligned_allocation), KUNIT_CASE(gpu_test_buddy_subtree_offset_alignment_stress), + KUNIT_CASE(gpu_test_buddy_addr_to_block), {} }; From 2b209e52a5b40b93ce3803fe1cc9ddbe859ccb5d Mon Sep 17 00:00:00 2001 From: Arunpravin Paneer Selvam Date: Mon, 3 Aug 2026 12:26:56 +0530 Subject: [PATCH 71/83] drm/tests/gpu_buddy: fix interleaving in buffer clearance test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The resume clearance test skipped every other allocation, expecting an interleaved clear/dirty layout. But the buddy allocator hands out blocks contiguously, so this just allocated half the pages in one chunk and never exercised gpu_buddy_reset_clear()'s force-merge of opposite-state buddies. Allocate all pages into two lists instead and free one cleared, one dirty, to build a truly interleaved pattern. v2: Use for loops instead of do-while for the allocation loops (Jani Nikula) Fixes: e3335ccbf4da ("drm/tests/gpu_buddy: add a new test case for buffer clearance during resume") Reported-by: Sashiko-bot Closes: https://sashiko.dev/#/patchset/20260721114236.507578-1-Arunpravin.PaneerSelvam@amd.com?part=1 Cc: Matthew Auld Cc: Christian König Assisted-by: GitHub_Copilot:claude-opus-4.8 Signed-off-by: Arunpravin Paneer Selvam Reviewed-by: Matthew Auld Link: https://patch.msgid.link/20260803065656.2960810-1-Arunpravin.PaneerSelvam@amd.com --- drivers/gpu/tests/gpu_buddy_test.c | 57 +++++++++++++++++------------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/drivers/gpu/tests/gpu_buddy_test.c b/drivers/gpu/tests/gpu_buddy_test.c index 5860bfc84a25..ed4c1c3acb3c 100644 --- a/drivers/gpu/tests/gpu_buddy_test.c +++ b/drivers/gpu/tests/gpu_buddy_test.c @@ -1004,42 +1004,51 @@ static void gpu_test_buddy_alloc_clear(struct kunit *test) gpu_buddy_fini(&mm); /* - * Using a non-power-of-two mm size, allocate alternating blocks of 4KiB in an - * even sequence and free them as cleared. All blocks should be marked as - * dirty and the split blocks should be merged back to their original - * size when the blocks clear reset function is called. + * Using a non-power-of-two mm size, allocate all 4KiB blocks and split + * them across two alternating lists, then free one list as cleared and + * the other as dirty. This interleaves cleared and dirty blocks so that + * neighbouring buddies cannot be merged, fragmenting the address space. + * After gpu_buddy_reset_clear(false) every block should be marked dirty + * and the split blocks should be merged back to their original size, so + * clear_avail must drop to 0. */ KUNIT_EXPECT_FALSE(test, gpu_buddy_init(&mm, mm_size, ps)); KUNIT_EXPECT_EQ(test, mm.max_order, max_order); - i = 0; n_pages = mm_size / ps; - do { - if (i % 2 == 0) - KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size, - ps, ps, &allocated, 0), - "buddy_alloc hit an error size=%lu\n", ps); - } while (++i < n_pages); + for (i = 0; i < n_pages; i++) { + struct list_head *list = (i % 2) ? &clean : &dirty; - gpu_buddy_free_list(&mm, &allocated, GPU_BUDDY_CLEARED); + KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size, + ps, ps, list, 0), + "buddy_alloc hit an error size=%lu\n", ps); + } + + gpu_buddy_free_list(&mm, &clean, GPU_BUDDY_CLEARED); + gpu_buddy_free_list(&mm, &dirty, 0); gpu_buddy_reset_clear(&mm, false); KUNIT_EXPECT_EQ(test, mm.clear_avail, 0); + gpu_buddy_fini(&mm); /* - * Using a non-power-of-two mm size, allocate alternating blocks of 4KiB in an - * odd sequence and free them as cleared. All blocks should be marked as - * cleared and the split blocks should be merged back to their original - * size when the blocks clear reset function is called. + * Repeat the same fragmented setup, but this time call + * gpu_buddy_reset_clear(true). Every block should be marked cleared and + * the split blocks should be merged back to their original size, so the + * whole address space (clear_avail) must equal mm_size. */ - i = 0; - do { - if (i % 2 != 0) - KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size, - ps, ps, &allocated, 0), - "buddy_alloc hit an error size=%lu\n", ps); - } while (++i < n_pages); + KUNIT_EXPECT_FALSE(test, gpu_buddy_init(&mm, mm_size, ps)); + KUNIT_EXPECT_EQ(test, mm.max_order, max_order); - gpu_buddy_free_list(&mm, &allocated, GPU_BUDDY_CLEARED); + for (i = 0; i < n_pages; i++) { + struct list_head *list = (i % 2) ? &clean : &dirty; + + KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size, + ps, ps, list, 0), + "buddy_alloc hit an error size=%lu\n", ps); + } + + gpu_buddy_free_list(&mm, &clean, GPU_BUDDY_CLEARED); + gpu_buddy_free_list(&mm, &dirty, 0); gpu_buddy_reset_clear(&mm, true); KUNIT_EXPECT_EQ(test, mm.clear_avail, mm_size); gpu_buddy_fini(&mm); From a16d5be8ea2ad36aa95373095fd635b3c5f84be4 Mon Sep 17 00:00:00 2001 From: Amit Barzilai Date: Wed, 29 Jul 2026 08:30:53 +0300 Subject: [PATCH 72/83] drm/ssd130x: Scale ssd133x per-channel contrast by brightness on init ssd133x_init() wrote the SSD133X_CONTRAST_A/B/C commands with magic hex values (0x91/0x50/0x7d). These are a per-channel white-balance calibration: the A/B/C channels drive sub-pixels whose OLED materials differ in luminous efficiency, so the values set the white point at full brightness. Extract them into ssd133x_set_contrast(), which scales each channel by a requested brightness via ssd130x_scale_contrast(), instead of writing the calibration unconditionally. This makes the sequence readable, avoids repetition, and is a prerequisite for wiring up an ssd133x backlight controller that dims while preserving the white point. Note this changes the ssd133x power-on brightness. Previously the init wrote the calibration unscaled and ignored ssd130x->contrast, so the panel always booted at full brightness. It now scales by the shared default contrast of 127, i.e. half of MAX_CONTRAST (255). This is intentional and matches ssd130x, whose contrast register also defaults to 127 (mid-scale), so all families now power on at ~50% and report props.brightness = 127 / max_brightness = 255 to userspace. Assisted-by: Claude:claude-fable-5 Signed-off-by: Amit Barzilai Reviewed-by: Javier Martinez Canillas Link: https://patch.msgid.link/20260729053054.29374-2-amit.barzilai22@gmail.com Signed-off-by: Javier Martinez Canillas --- drivers/gpu/drm/solomon/ssd130x.c | 43 ++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/solomon/ssd130x.c b/drivers/gpu/drm/solomon/ssd130x.c index 0940eae7a2e4..f49e5245b216 100644 --- a/drivers/gpu/drm/solomon/ssd130x.c +++ b/drivers/gpu/drm/solomon/ssd130x.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -140,6 +141,11 @@ #define SSD133X_SET_PRECHARGE_VOLTAGE 0xbb #define SSD133X_SET_VCOMH_VOLTAGE 0xbe +/* ssd133x A/B/C channel contrast at full brightness (white balance) */ +#define SSD133X_DEFAULT_CONTRAST_A 0x91 +#define SSD133X_DEFAULT_CONTRAST_B 0x50 +#define SSD133X_DEFAULT_CONTRAST_C 0x7d + #define MAX_CONTRAST 255 const struct ssd130x_deviceinfo ssd130x_variants[] = { @@ -582,12 +588,37 @@ static int ssd132x_init(struct ssd130x_device *ssd130x) return ssd130x_run_cmd_seq(ssd130x, cmds); } -static int ssd133x_init(struct ssd130x_device *ssd130x) +/* Scale a channel's white-balance calibration contrast by the requested brightness */ +static u8 ssd130x_scale_contrast(u8 calibration, u32 brightness) +{ + return DIV_ROUND_CLOSEST(calibration * brightness, MAX_CONTRAST); +} + +/* + * The A/B/C contrast channels drive sub-pixels whose OLED materials differ + * in luminous efficiency, so the per-channel values are a white-balance + * calibration. Scale them by the requested brightness instead of + * overwriting them, to keep the white point while dimming. + */ +static int ssd133x_set_contrast(struct ssd130x_device *ssd130x, u32 brightness) { const u8 cmds[] = { - 2, SSD133X_CONTRAST_A, 0x91, - 2, SSD133X_CONTRAST_B, 0x50, - 2, SSD133X_CONTRAST_C, 0x7d, + 2, SSD133X_CONTRAST_A, + ssd130x_scale_contrast(SSD133X_DEFAULT_CONTRAST_A, brightness), + 2, SSD133X_CONTRAST_B, + ssd130x_scale_contrast(SSD133X_DEFAULT_CONTRAST_B, brightness), + 2, SSD133X_CONTRAST_C, + ssd130x_scale_contrast(SSD133X_DEFAULT_CONTRAST_C, brightness), + 0, + }; + + return ssd130x_run_cmd_seq(ssd130x, cmds); +} + +static int ssd133x_init(struct ssd130x_device *ssd130x) +{ + int ret; + const u8 cmds[] = { 2, SSD133X_SET_MASTER_CURRENT, 0x06, 3, SSD133X_SET_COL_RANGE, 0x00, ssd130x->width - 1, 3, SSD133X_SET_ROW_RANGE, 0x00, ssd130x->height - 1, @@ -614,6 +645,10 @@ static int ssd133x_init(struct ssd130x_device *ssd130x) 0, }; + ret = ssd133x_set_contrast(ssd130x, ssd130x->contrast); + if (ret < 0) + return ret; + return ssd130x_run_cmd_seq(ssd130x, cmds); } From 2996ceebe33f989b32fb967a98b64d3a3b652f46 Mon Sep 17 00:00:00 2001 From: Amit Barzilai Date: Wed, 29 Jul 2026 08:30:54 +0300 Subject: [PATCH 73/83] drm/ssd130x: Add per-family update backlight logic ssd130x_update_bl() runs for every SSD13xx panel, but it only works for SSD130x and SSD132x: it writes the single global SSD13XX_CONTRAST (0x81) command, which those two families expose. SSD133x has no such command -- it has three per-channel contrast registers (CONTRAST_A/B/C) that must be scaled together -- so ssd130x_update_bl() has no effect on it. Make backlight_ops.update_status a per-family choice. SSD130x and SSD132x keep ssd130x_update_bl() because they share the SSD13XX_CONTRAST interface, while SSD133x gets ssd133x_update_bl(), which drives the three channels through ssd133x_set_contrast(). Signed-off-by: Amit Barzilai Reviewed-by: Javier Martinez Canillas Link: https://patch.msgid.link/20260729053054.29374-3-amit.barzilai22@gmail.com Signed-off-by: Javier Martinez Canillas --- drivers/gpu/drm/solomon/ssd130x.c | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/solomon/ssd130x.c b/drivers/gpu/drm/solomon/ssd130x.c index f49e5245b216..0b0fc6fe3df2 100644 --- a/drivers/gpu/drm/solomon/ssd130x.c +++ b/drivers/gpu/drm/solomon/ssd130x.c @@ -1711,8 +1711,25 @@ static int ssd130x_update_bl(struct backlight_device *bdev) return 0; } -static const struct backlight_ops ssd130xfb_bl_ops = { - .update_status = ssd130x_update_bl, +static int ssd133x_update_bl(struct backlight_device *bdev) +{ + struct ssd130x_device *ssd130x = bl_get_data(bdev); + + ssd130x->contrast = backlight_get_brightness(bdev); + + return ssd133x_set_contrast(ssd130x, ssd130x->contrast); +} + +static const struct backlight_ops ssd130xfb_bl_ops[] = { + [SSD130X_FAMILY] = { + .update_status = ssd130x_update_bl, + }, + [SSD132X_FAMILY] = { + .update_status = ssd130x_update_bl, + }, + [SSD133X_FAMILY] = { + .update_status = ssd133x_update_bl, + }, }; static void ssd130x_parse_properties(struct ssd130x_device *ssd130x) @@ -1919,7 +1936,8 @@ struct ssd130x_device *ssd130x_probe(struct device *dev, struct regmap *regmap) return ERR_PTR(ret); bl = devm_backlight_device_register(dev, dev_name(dev), dev, ssd130x, - &ssd130xfb_bl_ops, NULL); + &ssd130xfb_bl_ops[ssd130x->device_info->family_id], + NULL); if (IS_ERR(bl)) return ERR_PTR(dev_err_probe(dev, PTR_ERR(bl), "Unable to register backlight device\n")); From 11bc94f80a85dd77c40dbf099b9450b9279714e2 Mon Sep 17 00:00:00 2001 From: Matt Coster Date: Tue, 4 Aug 2026 16:18:27 +0100 Subject: [PATCH 74/83] drm/imagination: fixup some docs in pvr_gem.h Update and remove some old comment in the PVR GEM documentation. Signed-off-by: Matt Coster Signed-off-by: Luigi Santivetti Reviewed-by: Alessio Belle Link: https://patch.msgid.link/20260804-staging-pvr-docs-fixes-v2-1-a5a9569a1c1d@imgtec.com Signed-off-by: Alessio Belle --- drivers/gpu/drm/imagination/pvr_gem.h | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/imagination/pvr_gem.h b/drivers/gpu/drm/imagination/pvr_gem.h index c99f30cc6208..4286e047ae24 100644 --- a/drivers/gpu/drm/imagination/pvr_gem.h +++ b/drivers/gpu/drm/imagination/pvr_gem.h @@ -84,8 +84,10 @@ struct pvr_gem_object { /** * @base: The underlying &struct drm_gem_shmem_object. * - * Do not access this member directly, instead call - * shem_gem_from_pvr_gem(). + * .. note:: + * + * This member should not be accessed directly, but instead by + * calling shmem_gem_from_pvr_gem(). */ struct drm_gem_shmem_object base; @@ -97,12 +99,6 @@ struct pvr_gem_object { * changed after creation. * * Must be a combination of DRM_PVR_BO_* and/or PVR_BO_* flags. - * - * .. note:: - * - * This member is declared const to indicate that none of these - * options may change or be changed throughout the object's - * lifetime. */ u64 flags; From 62481fecc36f0b1ccef674e03e2ef148eb8cbe5d Mon Sep 17 00:00:00 2001 From: Alexandru Dadu Date: Tue, 4 Aug 2026 16:18:28 +0100 Subject: [PATCH 75/83] drm/imagination: Update Rogue heap comments Update Rogue heap memory comments to fix typos. Signed-off-by: Alexandru Dadu Signed-off-by: Luigi Santivetti Reviewed-by: Alessio Belle Link: https://patch.msgid.link/20260804-staging-pvr-docs-fixes-v2-2-a5a9569a1c1d@imgtec.com Signed-off-by: Alessio Belle --- drivers/gpu/drm/imagination/pvr_rogue_heap_config.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/imagination/pvr_rogue_heap_config.h b/drivers/gpu/drm/imagination/pvr_rogue_heap_config.h index 684766006703..5c33b342dda5 100644 --- a/drivers/gpu/drm/imagination/pvr_rogue_heap_config.h +++ b/drivers/gpu/drm/imagination/pvr_rogue_heap_config.h @@ -33,12 +33,12 @@ /* 0 MiB to 4 MiB, size of 4 MiB : RESERVED */ /* 0x00_0040_0000 - 0x7F_FFC0_0000 **/ -/* 4 MiB to 512 GiB, size of 512 GiB less 4 MiB : RESERVED **/ +/* 4 MiB to 512 GiB, size of 512 GiB less 4 MiB : RESERVED */ /* 0x80_0000_0000 ************************************************************/ /* 0x80_0000_0000 - 0x9F_FFFF_FFFF **/ -/* 512 GiB to 640 GiB, size of 128 GiB : GENERAL_HEAP **/ +/* 512 GiB to 640 GiB, size of 128 GiB : GENERAL_HEAP */ #define ROGUE_GENERAL_HEAP_BASE 0x8000000000ull #define ROGUE_GENERAL_HEAP_SIZE SZ_128G @@ -62,7 +62,7 @@ #define ROGUE_PDSCODEDATA_HEAP_SIZE SZ_4G /* 0xDB_0000_0000 - 0xDB_FFFF_FFFF */ -/* 876 GiB to 880 GiB, size of 256 MiB (reserved 4GiB) : BRN **/ +/* 876 GiB to 880 GiB, size of 256 MiB (reserved 4GiB) : BRN */ /* * The BRN63142 quirk workaround requires Region Header memory to be at the top * of a 16GiB aligned range. This is so when masked with 0x03FFFFFFFF the @@ -82,7 +82,7 @@ /* 0xE1_0000_0000 - 0xE1_BFFF_FFFF */ /* 900 GiB to 903 GiB, size of 3 GiB : RESERVED */ -/* 0xE1_C000_000 - 0xE1_FFFF_FFFF */ +/* 0xE1_C000_0000 - 0xE1_FFFF_FFFF */ /* 903 GiB to 904 GiB, reserved 1 GiB, : FIRMWARE_HEAP */ #define ROGUE_FW_HEAP_BASE 0xE1C0000000ull @@ -90,7 +90,7 @@ /* 904 GiB to 912 GiB, size of 8 GiB : FREE */ /* 0xE4_0000_0000 - 0xE7_FFFF_FFFF */ -/* 912 GiB to 968 GiB, size of 16 GiB : TRANSFER_FRAG */ +/* 912 GiB to 928 GiB, size of 16 GiB : TRANSFER_FRAG */ #define ROGUE_TRANSFER_FRAG_HEAP_BASE 0xE400000000ull #define ROGUE_TRANSFER_FRAG_HEAP_SIZE SZ_16G From 559b757a7122a2ead2e200cc735b008dfc074266 Mon Sep 17 00:00:00 2001 From: Alessio Belle Date: Tue, 4 Aug 2026 16:18:29 +0100 Subject: [PATCH 76/83] drm/imagination: Fix repeated typo in KCCB documentation Fix sent -> send in the documentation for all variants of pvr_kccb_send_cmd*(). Signed-off-by: Luigi Santivetti Reviewed-by: Alexandru Dadu Link: https://patch.msgid.link/20260804-staging-pvr-docs-fixes-v2-3-a5a9569a1c1d@imgtec.com Signed-off-by: Alessio Belle --- drivers/gpu/drm/imagination/pvr_ccb.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/imagination/pvr_ccb.c b/drivers/gpu/drm/imagination/pvr_ccb.c index 4accf18e2341..b702d122d791 100644 --- a/drivers/gpu/drm/imagination/pvr_ccb.c +++ b/drivers/gpu/drm/imagination/pvr_ccb.c @@ -255,7 +255,7 @@ pvr_kccb_used_slot_count_locked(struct pvr_device *pvr_dev) * pvr_kccb_send_cmd_reserved_powered() - Send command to the KCCB, with the PM ref * held and a slot pre-reserved * @pvr_dev: Device pointer. - * @cmd: Command to sent. + * @cmd: Command to send. * @kccb_slot: Address to store the KCCB slot for this command. May be %NULL. */ void @@ -361,7 +361,7 @@ static int pvr_kccb_reserve_slot_sync(struct pvr_device *pvr_dev) /** * pvr_kccb_send_cmd_powered() - Send command to the KCCB, with a PM ref held * @pvr_dev: Device pointer. - * @cmd: Command to sent. + * @cmd: Command to send. * @kccb_slot: Address to store the KCCB slot for this command. May be %NULL. * * Returns: @@ -385,7 +385,7 @@ pvr_kccb_send_cmd_powered(struct pvr_device *pvr_dev, struct rogue_fwif_kccb_cmd /** * pvr_kccb_send_cmd() - Send command to the KCCB * @pvr_dev: Device pointer. - * @cmd: Command to sent. + * @cmd: Command to send. * @kccb_slot: Address to store the KCCB slot for this command. May be %NULL. * * Returns: From 263728b4c4d03dbed2e6106cccd24626d682b6cb Mon Sep 17 00:00:00 2001 From: Akash Sukhavasi Date: Thu, 30 Jul 2026 19:11:31 -0500 Subject: [PATCH 77/83] drm/panel: tdo-tl070wsh30: Use mipi_dsi_*_multi(); fix minor bugs The mipi_dsi_dcs_*() functions used by this driver are deprecated in favor of their _multi() counterparts, as noted in Documentation/gpu/todo.rst. The _multi() variants record the first error in a context structure and skip every later call once an error is set, so the return value no longer has to be checked after each command. They also log their own failures, which makes the per-call dev_err() calls redundant. Convert prepare() and unprepare(). prepare() uses mipi_dsi_msleep() for the delays between DSI commands. unprepare() uses plain usleep_range() so the delays run unconditionally after the accumulated error is cleared. The delays in the GPIO reset sequence stay as plain msleep() and usleep_range(), since they run before any DSI transaction. unprepare() now disables the regulator unconditionally and returns 0. Previously a failure of set_display_off() was logged and the sequence continued, while a failure of enter_sleep_mode() returned early, leaving the regulator enabled and the panel unable to be brought back up, since drm_panel_unprepare() skips panel->prepared = false on error. The accumulated error from set_display_off() is cleared so that the delay and enter_sleep_mode() are still attempted, preserving the original fall-through behavior. Both drm_panel_prepare() and drm_panel_unprepare() return void, so the error was never propagated to a caller in any case. Signed-off-by: Akash Sukhavasi Reviewed-by: Douglas Anderson Signed-off-by: Douglas Anderson Link: https://patch.msgid.link/20260730-mipi-dsi-tl070wsh30-multi-v3-1-60592caef4f4@gmail.com --- drivers/gpu/drm/panel/panel-tdo-tl070wsh30.c | 42 ++++++-------------- 1 file changed, 13 insertions(+), 29 deletions(-) diff --git a/drivers/gpu/drm/panel/panel-tdo-tl070wsh30.c b/drivers/gpu/drm/panel/panel-tdo-tl070wsh30.c index 13cfe252a838..fa961abdd2d7 100644 --- a/drivers/gpu/drm/panel/panel-tdo-tl070wsh30.c +++ b/drivers/gpu/drm/panel/panel-tdo-tl070wsh30.c @@ -35,6 +35,7 @@ struct tdo_tl070wsh30_panel *to_tdo_tl070wsh30_panel(struct drm_panel *panel) static int tdo_tl070wsh30_panel_prepare(struct drm_panel *panel) { struct tdo_tl070wsh30_panel *tdo_tl070wsh30 = to_tdo_tl070wsh30_panel(panel); + struct mipi_dsi_multi_context dsi_ctx = { .dsi = tdo_tl070wsh30->link }; int err; err = regulator_enable(tdo_tl070wsh30->supply); @@ -51,44 +52,27 @@ static int tdo_tl070wsh30_panel_prepare(struct drm_panel *panel) msleep(200); - err = mipi_dsi_dcs_exit_sleep_mode(tdo_tl070wsh30->link); - if (err < 0) { - dev_err(panel->dev, "failed to exit sleep mode: %d\n", err); + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 200); + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); + mipi_dsi_msleep(&dsi_ctx, 20); + + if (dsi_ctx.accum_err) regulator_disable(tdo_tl070wsh30->supply); - return err; - } - msleep(200); - - err = mipi_dsi_dcs_set_display_on(tdo_tl070wsh30->link); - if (err < 0) { - dev_err(panel->dev, "failed to set display on: %d\n", err); - regulator_disable(tdo_tl070wsh30->supply); - return err; - } - - msleep(20); - - return 0; + return dsi_ctx.accum_err; } static int tdo_tl070wsh30_panel_unprepare(struct drm_panel *panel) { struct tdo_tl070wsh30_panel *tdo_tl070wsh30 = to_tdo_tl070wsh30_panel(panel); - int err; - - err = mipi_dsi_dcs_set_display_off(tdo_tl070wsh30->link); - if (err < 0) - dev_err(panel->dev, "failed to set display off: %d\n", err); + struct mipi_dsi_multi_context dsi_ctx = { .dsi = tdo_tl070wsh30->link }; + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); + /* Reset error to continue power-down sequence even if display off failed */ + dsi_ctx.accum_err = 0; usleep_range(10000, 11000); - - err = mipi_dsi_dcs_enter_sleep_mode(tdo_tl070wsh30->link); - if (err < 0) { - dev_err(panel->dev, "failed to enter sleep mode: %d\n", err); - return err; - } - + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); usleep_range(10000, 11000); regulator_disable(tdo_tl070wsh30->supply); From e773798e14ac0aea54ca9676083b91f445e5bc59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Hellstr=C3=B6m?= Date: Sat, 25 Jul 2026 12:00:31 +0200 Subject: [PATCH 78/83] drm/amdgpu: Fix init ordering in amdgpu_vram_mgr_init() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit drmm_cgroup_register_region() is called before INIT_LIST_HEAD() and gpu_buddy_init() in amdgpu_vram_mgr_init(). If it fails, the function returns early and bypasses those initializations. Since adev->mman.initialized is set to true before amdgpu_vram_mgr_init() is called, a failure triggers amdgpu_ttm_fini(), which calls amdgpu_vram_mgr_fini(), which then: - Calls list_for_each_entry_safe() on reservations_pending and reserved_pages, whose list_head::next pointers are zero-initialized (NULL). The loop does not recognize them as empty and dereferences NULL. - Calls gpu_buddy_fini(), which iterates free_trees[] unconditionally via for_each_free_tree(). Since mm->free_trees is NULL (never allocated), this dereferences NULL. Both result in a kernel panic on the module load error path. Fix by moving drmm_cgroup_register_region() to after the list and buddy allocator are fully initialized, so the teardown path is safe to run. Reported-by: Sashiko-bot Closes: https://sashiko.dev/#/patchset/20260428073116.15687-1-thomas.hellstrom@linux.intel.com?part=4 Fixes: 2b624a2c1865 ("drm/ttm: Handle cgroup based eviction in TTM") Cc: Friedrich Vock Cc: Maarten Lankhorst Cc: Tejun Heo Cc: Maxime Ripard Cc: Christian König Cc: Alex Deucher Cc: amd-gfx@lists.freedesktop.org Cc: dri-devel@lists.freedesktop.org Cc: stable@vger.kernel.org # v6.14+ Assisted-by: GitHub_Copilot:claude-sonnet-4.6 Signed-off-by: Thomas Hellström Reviewed-by: Arunpravin Paneer Selvam Reviewed-By: Maarten Lankhorst Tested-by: Thadeu Lima de Souza Cascardo Link: https://patch.msgid.link/20260725100036.2372-2-thomas.hellstrom@linux.intel.com Acked-by: Christian König Signed-off-by: Maarten Lankhorst --- drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.c index 2a241a5b12c4..ac3f71d77140 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.c @@ -918,9 +918,6 @@ int amdgpu_vram_mgr_init(struct amdgpu_device *adev) struct ttm_resource_manager *man = &mgr->manager; int err; - man->cg = drmm_cgroup_register_region(adev_to_drm(adev), "vram", adev->gmc.real_vram_size); - if (IS_ERR(man->cg)) - return PTR_ERR(man->cg); ttm_resource_manager_init(man, &adev->mman.bdev, adev->gmc.real_vram_size); @@ -935,6 +932,10 @@ int amdgpu_vram_mgr_init(struct amdgpu_device *adev) if (err) return err; + man->cg = drmm_cgroup_register_region(adev_to_drm(adev), "vram", adev->gmc.real_vram_size); + if (IS_ERR(man->cg)) + return PTR_ERR(man->cg); + ttm_set_driver_manager(&adev->mman.bdev, TTM_PL_VRAM, &mgr->manager); ttm_resource_manager_set_used(man, true); return 0; From e11640b9cea4e0c448df1d31b9e950fa52cf19af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Hellstr=C3=B6m?= Date: Sat, 25 Jul 2026 12:00:32 +0200 Subject: [PATCH 79/83] cgroup/dmem: Introduce struct dmem_cgroup_init for region initialization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the bare u64 size argument to dmem_cgroup_register_region() and drmm_cgroup_register_region() with a const struct dmem_cgroup_init * pointer. The struct currently carries only the size field, but using a struct makes the API extensible: future callers can supply additional initialization parameters without adding more positional arguments. Update all in-tree callers (amdgpu, xe) to use a compound-literal initializer. v5: - Commit introduced. Assisted-by: GitHub_Copilot:claude-sonnet-4.6 Signed-off-by: Thomas Hellström Reviewed-by: Maarten Lankhorst Tested-by: Thadeu Lima de Souza Cascardo Link: https://patch.msgid.link/20260725100036.2372-3-thomas.hellstrom@linux.intel.com Acked-by: Dave Airlie Acked-by: Christian König Signed-off-by: Maarten Lankhorst --- drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.c | 6 +++++- drivers/gpu/drm/drm_drv.c | 8 +++++--- drivers/gpu/drm/nouveau/nouveau_ttm.c | 5 ++++- drivers/gpu/drm/xe/xe_ttm_vram_mgr.c | 7 ++++++- include/drm/drm_drv.h | 4 +++- include/linux/cgroup_dmem.h | 16 +++++++++++++--- kernel/cgroup/dmem.c | 10 ++++++---- 7 files changed, 42 insertions(+), 14 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.c index ac3f71d77140..08f05c3aed1d 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.c @@ -23,6 +23,7 @@ */ #include +#include #include #include #include @@ -932,7 +933,10 @@ int amdgpu_vram_mgr_init(struct amdgpu_device *adev) if (err) return err; - man->cg = drmm_cgroup_register_region(adev_to_drm(adev), "vram", adev->gmc.real_vram_size); + man->cg = drmm_cgroup_register_region(adev_to_drm(adev), "vram", + &(struct dmem_cgroup_init){ + .size = adev->gmc.real_vram_size, + }); if (IS_ERR(man->cg)) return PTR_ERR(man->cg); diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c index 1ff0bf7cba6a..3c570f9393b9 100644 --- a/drivers/gpu/drm/drm_drv.c +++ b/drivers/gpu/drm/drm_drv.c @@ -960,17 +960,19 @@ static void drmm_cg_unregister_region(struct drm_device *dev, void *arg) * drmm_cgroup_register_region - Register a region of a DRM device to cgroups * @dev: device for region * @region_name: Region name for registering - * @size: Size of region in bytes + * @init: Initialization parameters for the region. * * This decreases the ref-count of @dev by one. The device is destroyed if the * ref-count drops to zero. */ -struct dmem_cgroup_region *drmm_cgroup_register_region(struct drm_device *dev, const char *region_name, u64 size) +struct dmem_cgroup_region * +drmm_cgroup_register_region(struct drm_device *dev, const char *region_name, + const struct dmem_cgroup_init *init) { struct dmem_cgroup_region *region; int ret; - region = dmem_cgroup_register_region(size, "drm/%s/%s", dev->unique, region_name); + region = dmem_cgroup_register_region(init, "drm/%s/%s", dev->unique, region_name); if (IS_ERR_OR_NULL(region)) return region; diff --git a/drivers/gpu/drm/nouveau/nouveau_ttm.c b/drivers/gpu/drm/nouveau/nouveau_ttm.c index 860bca7e3ce2..cfc0868e2dd3 100644 --- a/drivers/gpu/drm/nouveau/nouveau_ttm.c +++ b/drivers/gpu/drm/nouveau/nouveau_ttm.c @@ -23,6 +23,7 @@ * USE OR OTHER DEALINGS IN THE SOFTWARE. */ +#include #include #include @@ -189,7 +190,9 @@ nouveau_ttm_init_vram(struct nouveau_drm *drm) man->func = &nouveau_vram_manager; man->cg = drmm_cgroup_register_region(drm->dev, "vram", - drm->gem.vram_available); + &(struct dmem_cgroup_init){ + .size = drm->gem.vram_available + }); if (IS_ERR(man->cg)) return PTR_ERR(man->cg); diff --git a/drivers/gpu/drm/xe/xe_ttm_vram_mgr.c b/drivers/gpu/drm/xe/xe_ttm_vram_mgr.c index b518f7dec680..308fda4248eb 100644 --- a/drivers/gpu/drm/xe/xe_ttm_vram_mgr.c +++ b/drivers/gpu/drm/xe/xe_ttm_vram_mgr.c @@ -4,6 +4,8 @@ * Copyright (C) 2021-2022 Red Hat */ +#include + #include #include #include @@ -303,7 +305,10 @@ int __xe_ttm_vram_mgr_init(struct xe_device *xe, struct xe_ttm_vram_mgr *mgr, int err; name = mem_type == XE_PL_VRAM0 ? "vram0" : "vram1"; - man->cg = drmm_cgroup_register_region(&xe->drm, name, size); + man->cg = drmm_cgroup_register_region(&xe->drm, name, + &(struct dmem_cgroup_init){ + .size = size, + }); if (IS_ERR(man->cg)) return PTR_ERR(man->cg); diff --git a/include/drm/drm_drv.h b/include/drm/drm_drv.h index e09559495c5b..b23830494ed4 100644 --- a/include/drm/drm_drv.h +++ b/include/drm/drm_drv.h @@ -34,6 +34,7 @@ #include +struct dmem_cgroup_init; struct dmem_cgroup_region; struct drm_fb_helper; struct drm_fb_helper_surface_size; @@ -433,7 +434,8 @@ void *__devm_drm_dev_alloc(struct device *parent, struct dmem_cgroup_region * drmm_cgroup_register_region(struct drm_device *dev, - const char *region_name, u64 size); + const char *region_name, + const struct dmem_cgroup_init *init); /** * devm_drm_dev_alloc - Resource managed allocation of a &drm_device instance diff --git a/include/linux/cgroup_dmem.h b/include/linux/cgroup_dmem.h index 9d72457c4cb9..0575bab38d24 100644 --- a/include/linux/cgroup_dmem.h +++ b/include/linux/cgroup_dmem.h @@ -14,8 +14,18 @@ struct dmem_cgroup_pool_state; /* Opaque definition of a cgroup region, used internally */ struct dmem_cgroup_region; +/** + * struct dmem_cgroup_init - Initialization parameters for a dmem cgroup region. + * @size: Size of the region in bytes. + */ +struct dmem_cgroup_init { + u64 size; +}; + #if IS_ENABLED(CONFIG_CGROUP_DMEM) -struct dmem_cgroup_region *dmem_cgroup_register_region(u64 size, const char *name_fmt, ...) __printf(2,3); +struct dmem_cgroup_region * +dmem_cgroup_register_region(const struct dmem_cgroup_init *init, + const char *name_fmt, ...) __printf(2, 3); void dmem_cgroup_unregister_region(struct dmem_cgroup_region *region); int dmem_cgroup_try_charge(struct dmem_cgroup_region *region, u64 size, struct dmem_cgroup_pool_state **ret_pool, @@ -33,8 +43,8 @@ struct dmem_cgroup_pool_state *dmem_cgroup_get_common_ancestor(struct dmem_cgrou void dmem_cgroup_pool_state_put(struct dmem_cgroup_pool_state *pool); #else -static inline __printf(2,3) struct dmem_cgroup_region * -dmem_cgroup_register_region(u64 size, const char *name_fmt, ...) +static inline __printf(2, 3) struct dmem_cgroup_region * +dmem_cgroup_register_region(const struct dmem_cgroup_init *init, const char *name_fmt, ...) { return NULL; } diff --git a/kernel/cgroup/dmem.c b/kernel/cgroup/dmem.c index 1af08b0464b8..aea21e39180a 100644 --- a/kernel/cgroup/dmem.c +++ b/kernel/cgroup/dmem.c @@ -502,7 +502,7 @@ EXPORT_SYMBOL_GPL(dmem_cgroup_unregister_region); /** * dmem_cgroup_register_region() - Register a regions for dev cgroup. - * @size: Size of region to register, in bytes. + * @init: Initialization parameters for the region. * @fmt: Region parameters to register * * This function registers a node in the dmem cgroup with the @@ -511,13 +511,15 @@ EXPORT_SYMBOL_GPL(dmem_cgroup_unregister_region); * * Return: NULL or a struct on success, PTR_ERR on failure. */ -struct dmem_cgroup_region *dmem_cgroup_register_region(u64 size, const char *fmt, ...) +struct dmem_cgroup_region * +dmem_cgroup_register_region(const struct dmem_cgroup_init *init, + const char *fmt, ...) { struct dmem_cgroup_region *ret; char *region_name; va_list ap; - if (!size) + if (!init || !init->size) return NULL; va_start(ap, fmt); @@ -534,7 +536,7 @@ struct dmem_cgroup_region *dmem_cgroup_register_region(u64 size, const char *fmt INIT_LIST_HEAD(&ret->pools); ret->name = region_name; - ret->size = size; + ret->size = init->size; kref_init(&ret->ref); spin_lock(&dmemcg_lock); From 747c4bb450adcd8b99d3543448419e1952755832 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Hellstr=C3=B6m?= Date: Sat, 25 Jul 2026 12:00:33 +0200 Subject: [PATCH 80/83] cgroup/dmem: Add reclaim callback for lowering max below current usage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add an optional reclaim callback to struct dmem_cgroup_region. When dmem.max is set below the current usage of a cgroup pool, the new limit is applied immediately (so that concurrent allocations are throttled while reclaim is in progress) and then the driver is asked to evict memory to bring usage back below the limit. Reclaim is attempted up to a bounded number of times. No error is returned to userspace if usage remains above the limit after reclaim, and a pending signal will abort the reclaim loop early. This matches the behavior of memory.max in the memory cgroup controller. Also honor O_NONBLOCK so that if that flag is set during the max value write, no reclaim is initiated. The idea is to avoid charging the reclaim cost to the writer of the max value. v2: - Write max before reclaim is attempted (Maarten) - Let signals abort the reclaim without error (Maarten) - If a new max value is written with the O_NONBLOCK flag, reclaim is not attempted (Maarten) - Extract region from the pool parameter rather than passing it explicitly to set_resource_xxx(). v3: - Use an rw_semaphore (unregister_sem) to protect reclaim callbacks against concurrent region unregistration: readers (reclaim) hold the read side; dmem_cgroup_unregister_region() takes the write side to drain in-flight callbacks before returning. (Sashiko-bot) v5: - Rebased on the introduction of struct dmem_cgroup_init. - Use nonblock=true in reset_all_resource_limits() to avoid sleeping inside rcu_read_lock() in dmemcs_offline(). (Sashiko-bot) - Compare usage against the truncated limit value stored in cnt.max, not the original u64. (Sashiko-bot) - Use a DMEM_MAX_RECLAIM_RETRIES (16) retry budget instead of 5, matching the memcg controller's MAX_RECLAIM_RETRIES. Only -ENOSPC (no progress) counts against the retry budget; other errors terminate the loop immediately. v6: - Fix dmem_cgroup_ops->reclaim docstring: -ENOSPC does not stop reclaim immediately but is retried up to DMEM_MAX_RECLAIM_RETRIES times; only other negative errors terminate the loop. (Sashiko-bot) v7: - Replace the per-region rw_semaphore with a static SRCU domain (dmemcg_srcu). SRCU is a better fit than rwsem for this use: it avoids the per-region lock overhead on every reclaim call, and synchronize_srcu() at unregister time is a rare operation. (Maarten) - Trim in-function comments to focus on what rather than how. Assisted-by: GitHub_Copilot:claude-sonnet-4.6 Signed-off-by: Thomas Hellström Reviewed-by: Maarten Lankhorst Tested-by: Thadeu Lima de Souza Cascardo Link: https://patch.msgid.link/20260725100036.2372-4-thomas.hellstrom@linux.intel.com Signed-off-by: Maarten Lankhorst --- include/linux/cgroup_dmem.h | 22 ++++++++++ kernel/cgroup/dmem.c | 80 +++++++++++++++++++++++++++++++------ 2 files changed, 90 insertions(+), 12 deletions(-) diff --git a/include/linux/cgroup_dmem.h b/include/linux/cgroup_dmem.h index 0575bab38d24..c6cad9501770 100644 --- a/include/linux/cgroup_dmem.h +++ b/include/linux/cgroup_dmem.h @@ -14,12 +14,34 @@ struct dmem_cgroup_pool_state; /* Opaque definition of a cgroup region, used internally */ struct dmem_cgroup_region; +/** + * struct dmem_cgroup_ops - Operations for a dmem cgroup region. + * @reclaim: Optional callback invoked when dmem.max is set below the current + * usage of a pool. The driver should attempt to free at least + * @target_bytes from @pool. May be called multiple times if usage + * remains above the limit after returning. + * + * Return: 0 if some progress was made (even if less than + * @target_bytes was freed), -ENOSPC if no progress could be made + * (the caller will retry up to a bounded number of times), or + * another negative error code if a fatal error occurred (stops + * further reclaim attempts immediately). + */ +struct dmem_cgroup_ops { + int (*reclaim)(struct dmem_cgroup_pool_state *pool, + u64 target_bytes, void *priv); +}; + /** * struct dmem_cgroup_init - Initialization parameters for a dmem cgroup region. * @size: Size of the region in bytes. + * @ops: Optional operations for this region. May be NULL. + * @reclaim_priv: Opaque pointer passed to @ops->reclaim. May be NULL. */ struct dmem_cgroup_init { u64 size; + const struct dmem_cgroup_ops *ops; + void *reclaim_priv; }; #if IS_ENABLED(CONFIG_CGROUP_DMEM) diff --git a/kernel/cgroup/dmem.c b/kernel/cgroup/dmem.c index aea21e39180a..4683f3d68022 100644 --- a/kernel/cgroup/dmem.c +++ b/kernel/cgroup/dmem.c @@ -17,6 +17,13 @@ #include #include #include +#include + +/* Maximum reclaim attempts before giving up when lowering dmem.max. */ +#define DMEM_MAX_RECLAIM_RETRIES 16 + +/* SRCU domain serialising reclaim callbacks against region unregistration. */ +DEFINE_STATIC_SRCU(dmemcg_srcu); struct dmem_cgroup_region { /** @@ -48,9 +55,18 @@ struct dmem_cgroup_region { /** * @unregistered: Whether the region is unregistered by its caller. - * No new pools should be added to the region afterwards. + * No new pools should be added to the region afterwards, and no new + * reclaim callbacks should be invoked. */ bool unregistered; + + /** + * @ops: Optional driver operations for this region. + */ + const struct dmem_cgroup_ops *ops; + + /** @reclaim_priv: Private data passed to @ops->reclaim. */ + void *reclaim_priv; }; struct dmemcg_state { @@ -145,21 +161,52 @@ static void free_cg_pool(struct dmem_cgroup_pool_state *pool) } static void -set_resource_min(struct dmem_cgroup_pool_state *pool, u64 val) +set_resource_min(struct dmem_cgroup_pool_state *pool, u64 val, bool nonblock) { page_counter_set_min(&pool->cnt, val); } static void -set_resource_low(struct dmem_cgroup_pool_state *pool, u64 val) +set_resource_low(struct dmem_cgroup_pool_state *pool, u64 val, bool nonblock) { page_counter_set_low(&pool->cnt, val); } static void -set_resource_max(struct dmem_cgroup_pool_state *pool, u64 val) +set_resource_max(struct dmem_cgroup_pool_state *pool, u64 val, bool nonblock) { - page_counter_set_max(&pool->cnt, val); + struct dmem_cgroup_region *region = pool->region; + unsigned long limit = (unsigned long)val; + + /* Apply the new limit immediately so concurrent allocations are throttled. */ + xchg(&pool->cnt.max, limit); + + if (nonblock) + return; + + int srcu_idx = srcu_read_lock(&dmemcg_srcu); + + if (!READ_ONCE(region->unregistered) && region->ops && region->ops->reclaim) { + for (int retries = DMEM_MAX_RECLAIM_RETRIES; ; ) { + u64 usage = page_counter_read(&pool->cnt); + int ret; + + if (usage <= limit) + break; + + if (signal_pending(current)) + break; + + ret = region->ops->reclaim(pool, usage - limit, region->reclaim_priv); + + /* -ENOSPC means no progress; other errors are fatal. */ + if (ret && (ret != -ENOSPC || !retries--)) + break; + + cond_resched(); + } + } + srcu_read_unlock(&dmemcg_srcu, srcu_idx); } static u64 get_resource_low(struct dmem_cgroup_pool_state *pool) @@ -189,9 +236,10 @@ static u64 get_resource_peak(struct dmem_cgroup_pool_state *pool) static void reset_all_resource_limits(struct dmem_cgroup_pool_state *rpool) { - set_resource_min(rpool, 0); - set_resource_low(rpool, 0); - set_resource_max(rpool, PAGE_COUNTER_MAX); + set_resource_min(rpool, 0, false); + set_resource_low(rpool, 0, false); + /* nonblock: raising to max makes reclaim a no-op; sleeping is forbidden here. */ + set_resource_max(rpool, PAGE_COUNTER_MAX, true); } static void dmemcs_offline(struct cgroup_subsys_state *css) @@ -468,7 +516,10 @@ static void dmemcg_free_region(struct kref *ref) * dmem_cgroup_unregister_region() - Unregister a previously registered region. * @region: The region to unregister. * - * This function undoes dmem_cgroup_register_region. + * This function undoes dmem_cgroup_register_region. It drains any + * in-flight reclaim callbacks before returning, so the caller may safely + * free the resources pointed to by the @reclaim_priv that was passed at + * registration time. */ void dmem_cgroup_unregister_region(struct dmem_cgroup_region *region) { @@ -493,9 +544,11 @@ void dmem_cgroup_unregister_region(struct dmem_cgroup_region *region) * no new pools should be added to the dead region * by get_cg_pool_unlocked. */ - region->unregistered = true; + WRITE_ONCE(region->unregistered, true); spin_unlock(&dmemcg_lock); + synchronize_srcu(&dmemcg_srcu); + kref_put(®ion->ref, dmemcg_free_region); } EXPORT_SYMBOL_GPL(dmem_cgroup_unregister_region); @@ -537,6 +590,8 @@ dmem_cgroup_register_region(const struct dmem_cgroup_init *init, INIT_LIST_HEAD(&ret->pools); ret->name = region_name; ret->size = init->size; + ret->ops = init->ops; + ret->reclaim_priv = init->reclaim_priv; kref_init(&ret->ref); spin_lock(&dmemcg_lock); @@ -837,11 +892,12 @@ static int dmemcg_parse_limit(char *options, u64 *new_limit) static ssize_t dmemcg_limit_write(struct kernfs_open_file *of, char *buf, size_t nbytes, loff_t off, - void (*apply)(struct dmem_cgroup_pool_state *, u64)) + void (*apply)(struct dmem_cgroup_pool_state *, u64, bool)) { struct dmemcg_state *dmemcs = css_to_dmemcs(of_css(of)); struct dmem_cgroup_pool_state *pool; struct dmem_cgroup_region *region; + bool nonblock = of->file->f_flags & O_NONBLOCK; char *region_name; u64 new_limit; int err; @@ -867,7 +923,7 @@ static ssize_t dmemcg_limit_write(struct kernfs_open_file *of, goto out_put; } - apply(pool, new_limit); + apply(pool, new_limit, nonblock); dmemcg_pool_put(pool); out_put: From 425a783e1bc18bebd6f278b3ca55929e81f0fe0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Hellstr=C3=B6m?= Date: Sat, 25 Jul 2026 12:00:34 +0200 Subject: [PATCH 81/83] drm/ttm: Hook up a cgroup-aware reclaim callback for the dmem controller MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add ttm_bo_evict_cgroup() to evict buffer objects charged to a specific dmem cgroup pool from a resource manager's LRU until a byte target is met. Add ttm_resource_manager_set_dmem_region() to associate a dmem cgroup region with a resource manager; drivers supply their own dmem_cgroup_ops with ttm_resource_manager_dmem_reclaim as the reclaim function and the manager pointer as reclaim_priv in the dmem_cgroup_init to wire up TTM eviction as the reclaim callback. The eviction context is interruptible; signals abort the operation and propagate back through the write() syscall. Introduce a new mode for the bo LRU walker so that sleeping locks can be taken. This can be used when the caller doesn't hold any previous dma_resv locks, and where it intends to hold at most one lock at a time. Like the rest of the TTM eviction this should sooner than later be converted to full WW transactions. v3: - Fix ttm_resource_manager_set_dmem_region() storing an error pointer in man->cg unconditionally. (Sashiko-bot) - Fix kernel-doc function name format for ttm_bo_evict_cgroup() and ttm_resource_manager_set_dmem_region(). v5: - Rebased on the introduction of struct dmem_cgroup_init. - Handle NULL region in ttm_resource_manager_set_dmem_region() to clear the reclaim callback, preventing use-after-free when the manager is torn down while the dmem region outlives it. (Sashiko-bot) - Return 0 on any progress (even partial eviction), -ENOSPC only when nothing was freed; fixes callers that expected 0 on partial success. - Document that the reclaim callback should return 0 if some progress was made, -ENOSPC if no progress at all, or another error for fatal failures. v8: - Fix ttm_resource_manager_set_dmem_region() using IS_ERR_OR_NULL(), which skipped the assignment for a NULL region and thus never cleared man->cg. Use IS_ERR() so that a NULL region detaches the region as the kernel-doc and the v5 changelog intended. (Sashiko-bot) v9: - Don't leak cgroup charges for bos that may have survived dmemcg region fini. - Drop the misleading "Capture size before eviction in case res is cleared" comment in ttm_bo_evict_cb(). (Maarten Lankhorst) Assisted-by: GitHub_Copilot:claude-sonnet-4.6 Signed-off-by: Thomas Hellström Reviewed-by: Maarten Lankhorst #v7 Tested-by: Thadeu Lima de Souza Cascardo Link: https://patch.msgid.link/20260725100036.2372-5-thomas.hellstrom@linux.intel.com Signed-off-by: Maarten Lankhorst --- drivers/gpu/drm/ttm/ttm_bo.c | 97 +++++++++++++++++++++++++++++- drivers/gpu/drm/ttm/ttm_bo_util.c | 3 +- drivers/gpu/drm/ttm/ttm_resource.c | 54 ++++++++++++++++- include/drm/ttm/ttm_bo.h | 10 +++ include/drm/ttm/ttm_resource.h | 7 +++ 5 files changed, 166 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/ttm/ttm_bo.c b/drivers/gpu/drm/ttm/ttm_bo.c index 79c96aba4bf4..ef56c18ded1b 100644 --- a/drivers/gpu/drm/ttm/ttm_bo.c +++ b/drivers/gpu/drm/ttm/ttm_bo.c @@ -629,6 +629,7 @@ static s64 ttm_bo_evict_cb(struct ttm_lru_walk *walk, struct ttm_buffer_object * struct ttm_bo_evict_walk *evict_walk = container_of(walk, typeof(*evict_walk), walk); struct dmem_cgroup_pool_state *limit_pool, *ancestor = NULL; + s64 bo_size = bo->base.size; bool evict_valuable; s64 lret; @@ -672,6 +673,12 @@ static s64 ttm_bo_evict_cb(struct ttm_lru_walk *walk, struct ttm_buffer_object * if (!evict_valuable) return 0; + /* + * evict_walk->place is NULL in cgroup drain mode. Drivers' + * eviction_valuable() callbacks must handle a NULL place, treating it + * as "any placement": the TTM base implementation already does so via + * ttm_resource_intersects(). + */ if (bo->pin_count || !bo->bdev->funcs->eviction_valuable(bo, evict_walk->place)) return 0; @@ -687,13 +694,17 @@ static s64 ttm_bo_evict_cb(struct ttm_lru_walk *walk, struct ttm_buffer_object * goto out; evict_walk->evicted++; - if (evict_walk->res) + if (evict_walk->res) { lret = ttm_bo_alloc_at_place(evict_walk->evictor, evict_walk->place, false, evict_walk->res, evict_walk->alloc_state); - if (lret == 0) - return 1; + if (lret == 0) + return 1; + } else { + /* Cgroup drain: return bytes freed for byte-denominated progress. */ + return bo_size; + } out: /* Errors that should terminate the walk. */ if (lret == -ENOSPC) @@ -775,6 +786,86 @@ static int ttm_bo_evict_alloc(struct ttm_device *bdev, return 0; } +/** + * ttm_bo_evict_cgroup() - Evict buffer objects charged to a specific cgroup. + * @bdev: The TTM device. + * @man: The resource manager whose LRU to walk. + * @limit_pool: The cgroup pool state whose members should be evicted. + * @target_bytes: Number of bytes to free. + * @ctx: The TTM operation context. + * + * Walk the LRU of @man and evict buffer objects that are charged to the + * cgroup identified by @limit_pool, until at least @target_bytes have been + * freed. Mirrors the two-pass (trylock -> sleeping-lock, low-watermark) + * strategy used by ttm_bo_evict_alloc(). + * + * Return: >= @target_bytes on full success, 0..target_bytes-1 if partial, + * negative error code on fatal error. + */ +s64 ttm_bo_evict_cgroup(struct ttm_device *bdev, + struct ttm_resource_manager *man, + struct dmem_cgroup_pool_state *limit_pool, + s64 target_bytes, + struct ttm_operation_ctx *ctx) +{ + struct ttm_bo_evict_walk evict_walk = { + .walk = { + .ops = &ttm_evict_walk_ops, + .arg = { .ctx = ctx }, + }, + .alloc_state = &(struct ttm_bo_alloc_state) { + .limit_pool = limit_pool, + .in_evict = true, + }, + /* place, evictor, res left NULL: selects cgroup drain mode */ + }; + s64 lret, pass; + + evict_walk.walk.arg.trylock_only = true; + lret = ttm_lru_walk_for_evict(&evict_walk.walk, bdev, man, target_bytes); + if (lret < 0 || lret >= target_bytes) + return lret; + + /* Second pass: also evict BOs at the low watermark. */ + if (evict_walk.hit_low) { + evict_walk.try_low = true; + pass = ttm_lru_walk_for_evict(&evict_walk.walk, bdev, man, + target_bytes - lret); + if (pass < 0) + return pass; + lret += pass; + if (lret >= target_bytes) + return lret; + } + + /* Full sleeping-lock pass for remaining target. */ + evict_walk.try_low = evict_walk.hit_low = false; + evict_walk.walk.arg.trylock_only = false; + +retry: + evict_walk.walk.arg.sleeping_lock = true; + do { + evict_walk.evicted = 0; + pass = ttm_lru_walk_for_evict(&evict_walk.walk, bdev, man, + target_bytes - lret); + if (pass < 0) { + lret = pass; + goto out; + } + lret += pass; + } while (lret < target_bytes && evict_walk.evicted); + + /* One more attempt if we hit the low limit during sleeping-lock pass. */ + if (lret < target_bytes && evict_walk.hit_low && !evict_walk.try_low) { + evict_walk.try_low = true; + goto retry; + } + +out: + return lret; +} +EXPORT_SYMBOL(ttm_bo_evict_cgroup); + /** * ttm_bo_pin - Pin the buffer object. * @bo: The buffer object to pin diff --git a/drivers/gpu/drm/ttm/ttm_bo_util.c b/drivers/gpu/drm/ttm/ttm_bo_util.c index 3e3c201a0222..bd0b23ac2cc4 100644 --- a/drivers/gpu/drm/ttm/ttm_bo_util.c +++ b/drivers/gpu/drm/ttm/ttm_bo_util.c @@ -999,7 +999,8 @@ __ttm_bo_lru_cursor_next(struct ttm_bo_lru_cursor *curs) bo = res->bo; if (ttm_lru_walk_trylock(curs, bo)) bo_locked = true; - else if (!arg->ticket || arg->ctx->no_wait_gpu || arg->trylock_only) + else if ((!arg->ticket && !arg->sleeping_lock) || arg->ctx->no_wait_gpu || + arg->trylock_only) continue; if (!ttm_bo_get_unless_zero(bo)) { diff --git a/drivers/gpu/drm/ttm/ttm_resource.c b/drivers/gpu/drm/ttm/ttm_resource.c index 02eca679cb68..4e6d3d658d85 100644 --- a/drivers/gpu/drm/ttm/ttm_resource.c +++ b/drivers/gpu/drm/ttm/ttm_resource.c @@ -456,7 +456,7 @@ void ttm_resource_free(struct ttm_buffer_object *bo, struct ttm_resource **res) man = ttm_manager_type(bo->bdev, (*res)->mem_type); man->func->free(man, *res); *res = NULL; - if (man->cg) + if (pool) dmem_cgroup_uncharge(pool, bo->base.size); } EXPORT_SYMBOL(ttm_resource_free); @@ -972,3 +972,55 @@ void ttm_resource_manager_create_debugfs(struct ttm_resource_manager *man, #endif } EXPORT_SYMBOL(ttm_resource_manager_create_debugfs); + +/** + * ttm_resource_manager_dmem_reclaim() - dmem cgroup reclaim callback for TTM + * resource managers. + * @pool: The dmem cgroup pool state for the cgroup being reclaimed. + * @target_bytes: Number of bytes to try to free. + * @priv: The &ttm_resource_manager pointer, passed as @init.reclaim_priv to + * dmem_cgroup_register_region(). + * + * Drivers should use this as the @reclaim member of their own + * &struct dmem_cgroup_ops, with the &ttm_resource_manager pointer as + * @init.reclaim_priv. + * + * Return: 0 if some memory was freed, -ENOSPC if nothing was freed, or + * another negative error code on fatal failure. + */ +int ttm_resource_manager_dmem_reclaim(struct dmem_cgroup_pool_state *pool, + u64 target_bytes, void *priv) +{ + struct ttm_resource_manager *man = priv; + struct ttm_operation_ctx ctx = { .interruptible = true }; + s64 freed; + + freed = ttm_bo_evict_cgroup(man->bdev, man, pool, target_bytes, &ctx); + if (freed < 0) + return freed; + + return freed > 0 ? 0 : -ENOSPC; +} +EXPORT_SYMBOL(ttm_resource_manager_dmem_reclaim); + +/** + * ttm_resource_manager_set_dmem_region() - Associate a dmem cgroup region with a + * resource manager. + * @man: The resource manager. + * @region: The dmem cgroup region to associate, may be NULL or IS_ERR(). + * + * When @region is valid, stores it in @man->cg so that TTM can look up the + * associated pool during charging and eviction-target selection. When + * @region is %NULL, clears @man->cg to detach the region before teardown. + * An IS_ERR() @region is ignored, leaving @man->cg unchanged. + * The reclaim callback must be wired up using ttm_resource_manager_dmem_reclaim() + * in the driver's own &struct dmem_cgroup_ops, with the manager pointer as + * @init.reclaim_priv. + */ +void ttm_resource_manager_set_dmem_region(struct ttm_resource_manager *man, + struct dmem_cgroup_region *region) +{ + if (!IS_ERR(region)) + man->cg = region; +} +EXPORT_SYMBOL(ttm_resource_manager_set_dmem_region); diff --git a/include/drm/ttm/ttm_bo.h b/include/drm/ttm/ttm_bo.h index 8310bc3d55f9..32791c4db2a9 100644 --- a/include/drm/ttm/ttm_bo.h +++ b/include/drm/ttm/ttm_bo.h @@ -226,6 +226,11 @@ struct ttm_lru_walk_arg { struct ww_acquire_ctx *ticket; /** @trylock_only: Only use trylock for locking. */ bool trylock_only; + /** + * @sleeping_lock: Use sleeping locks even with %NULL @ticket. + * @trylock_only has precedence over this field. + */ + bool sleeping_lock; }; /** @@ -431,6 +436,11 @@ void ttm_bo_unpin(struct ttm_buffer_object *bo); int ttm_bo_evict_first(struct ttm_device *bdev, struct ttm_resource_manager *man, struct ttm_operation_ctx *ctx); +s64 ttm_bo_evict_cgroup(struct ttm_device *bdev, + struct ttm_resource_manager *man, + struct dmem_cgroup_pool_state *limit_pool, + s64 target_bytes, + struct ttm_operation_ctx *ctx); int ttm_bo_access(struct ttm_buffer_object *bo, unsigned long offset, void *buf, int len, int write); vm_fault_t ttm_bo_vm_reserve(struct ttm_buffer_object *bo, diff --git a/include/drm/ttm/ttm_resource.h b/include/drm/ttm/ttm_resource.h index e567b7ec8218..3f2812743a7e 100644 --- a/include/drm/ttm/ttm_resource.h +++ b/include/drm/ttm/ttm_resource.h @@ -39,6 +39,7 @@ struct dentry; struct dmem_cgroup_device; +struct dmem_cgroup_region; struct drm_printer; struct ttm_device; struct ttm_resource_manager; @@ -481,6 +482,12 @@ void ttm_resource_manager_init(struct ttm_resource_manager *man, struct ttm_device *bdev, uint64_t size); +void ttm_resource_manager_set_dmem_region(struct ttm_resource_manager *man, + struct dmem_cgroup_region *region); + +int ttm_resource_manager_dmem_reclaim(struct dmem_cgroup_pool_state *pool, + u64 target_bytes, void *priv); + int ttm_resource_manager_evict_all(struct ttm_device *bdev, struct ttm_resource_manager *man); From 042a1bea20db1a1542a75d23e0871343d9e70740 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Hellstr=C3=B6m?= Date: Sat, 25 Jul 2026 12:00:35 +0200 Subject: [PATCH 82/83] drm/xe: Wire up dmem cgroup reclaim for VRAM manager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Register the VRAM manager with the dmem cgroup reclaim infrastructure so that lowering dmem.max below current VRAM usage triggers TTM eviction rather than failing with -EBUSY. v4: - Rebased on drm-tip; dropped the XE_PL_STOLEN guard as stolen memory uses a separate TTM manager and never calls __xe_ttm_vram_mgr_init(). v5: - Rebased on the introduction of struct dmem_cgroup_init. - Register the fini drmm action before drmm_cgroup_register_region() so that devres LIFO teardown runs unregister_region() first (draining any in-flight reclaim callbacks via the rwsem) and xe_ttm_vram_mgr_fini() second, ensuring the manager is never accessed by a reclaim callback after teardown. (Sashiko-bot) - Wrap the reclaim callback in xe_ttm_vram_mgr_dmem_reclaim() using drm_dev_enter()/drm_dev_exit() to prevent TTM reclaim from running after driver unbind. v9: - Close ttm_resource_manager_set_used() races around vram manager init and fini. - Formatting fix. - Acquire a runtime PM reference around the TTM reclaim call in xe_ttm_vram_mgr_dmem_reclaim(). (Sashiko-bot) Assisted-by: GitHub_Copilot:claude-sonnet-4.6 Signed-off-by: Thomas Hellström Reviewed-by: Maarten Lankhorst #v8 Tested-by: Thadeu Lima de Souza Cascardo Link: https://patch.msgid.link/20260725100036.2372-6-thomas.hellstrom@linux.intel.com Signed-off-by: Maarten Lankhorst --- drivers/gpu/drm/xe/xe_ttm_vram_mgr.c | 72 +++++++++++++++++++++++----- 1 file changed, 60 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_ttm_vram_mgr.c b/drivers/gpu/drm/xe/xe_ttm_vram_mgr.c index 308fda4248eb..05911904c1f9 100644 --- a/drivers/gpu/drm/xe/xe_ttm_vram_mgr.c +++ b/drivers/gpu/drm/xe/xe_ttm_vram_mgr.c @@ -15,6 +15,7 @@ #include "xe_bo.h" #include "xe_device.h" +#include "xe_pm.h" #include "xe_res_cursor.h" #include "xe_ttm_vram_mgr.h" #include "xe_vram_types.h" @@ -276,14 +277,47 @@ static const struct ttm_resource_manager_func xe_ttm_vram_mgr_func = { .debug = xe_ttm_vram_mgr_debug }; +static const struct dmem_cgroup_ops xe_ttm_vram_mgr_dmem_ops; + +static int xe_ttm_vram_mgr_dmem_reclaim(struct dmem_cgroup_pool_state *pool, + u64 target_bytes, void *priv) +{ + struct ttm_resource_manager *man = priv; + struct xe_device *xe = ttm_to_xe_device(man->bdev); + int ret, idx; + + if (!drm_dev_enter(&xe->drm, &idx)) + return -ENODEV; + + { + ACQUIRE(xe_pm_runtime_ioctl, pm)(xe); + + ret = ACQUIRE_ERR(xe_pm_runtime_ioctl, &pm); + if (ret >= 0) + ret = ttm_resource_manager_dmem_reclaim(pool, target_bytes, priv); + } + + drm_dev_exit(idx); + return ret; +} + +static const struct dmem_cgroup_ops xe_ttm_vram_mgr_dmem_ops = { + .reclaim = xe_ttm_vram_mgr_dmem_reclaim, +}; + +static void xe_ttm_vram_mgr_set_unused(struct drm_device *dev, void *arg) +{ + struct ttm_resource_manager *man = arg; + + ttm_resource_manager_set_used(man, false); +} + static void xe_ttm_vram_mgr_fini(struct drm_device *dev, void *arg) { struct xe_device *xe = to_xe_device(dev); struct xe_ttm_vram_mgr *mgr = arg; struct ttm_resource_manager *man = &mgr->manager; - ttm_resource_manager_set_used(man, false); - if (ttm_resource_manager_evict_all(&xe->ttm, man)) return; @@ -301,17 +335,10 @@ int __xe_ttm_vram_mgr_init(struct xe_device *xe, struct xe_ttm_vram_mgr *mgr, u64 default_page_size) { struct ttm_resource_manager *man = &mgr->manager; + struct dmem_cgroup_region *cg; const char *name; int err; - name = mem_type == XE_PL_VRAM0 ? "vram0" : "vram1"; - man->cg = drmm_cgroup_register_region(&xe->drm, name, - &(struct dmem_cgroup_init){ - .size = size, - }); - if (IS_ERR(man->cg)) - return PTR_ERR(man->cg); - man->func = &xe_ttm_vram_mgr_func; mgr->mem_type = mem_type; err = drmm_mutex_init(&xe->drm, &mgr->lock); @@ -328,9 +355,30 @@ int __xe_ttm_vram_mgr_init(struct xe_device *xe, struct xe_ttm_vram_mgr *mgr, gpu_buddy_driver_set_lock(&mgr->mm, &mgr->lock); ttm_set_driver_manager(&xe->ttm, mem_type, &mgr->manager); - ttm_resource_manager_set_used(&mgr->manager, true); - return drmm_add_action_or_reset(&xe->drm, xe_ttm_vram_mgr_fini, mgr); + /* + * Register the fini action before the cgroup region so that devres + * LIFO teardown runs unregister_region before manager teardown + * (draining any in-flight reclaim callbacks) and the manager fini second. + */ + err = drmm_add_action_or_reset(&xe->drm, xe_ttm_vram_mgr_fini, mgr); + if (err) + return err; + + name = mem_type == XE_PL_VRAM0 ? "vram0" : "vram1"; + cg = drmm_cgroup_register_region(&xe->drm, name, + &(struct dmem_cgroup_init){ + .size = size, + .ops = &xe_ttm_vram_mgr_dmem_ops, + .reclaim_priv = man, + }); + if (IS_ERR(cg)) + return PTR_ERR(cg); + + ttm_resource_manager_set_dmem_region(man, cg); + ttm_resource_manager_set_used(man, true); + + return drmm_add_action_or_reset(&xe->drm, xe_ttm_vram_mgr_set_unused, man); } /** From bd4f284df04d76fd65e57141cb1e6e7a49e4c3cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Hellstr=C3=B6m?= Date: Sat, 25 Jul 2026 12:00:36 +0200 Subject: [PATCH 83/83] drm/amdgpu: Wire up dmem cgroup reclaim for VRAM manager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Register the VRAM manager with the dmem cgroup reclaim infrastructure so that lowering dmem.max below current VRAM usage triggers TTM eviction rather than failing with -EBUSY. Guard place->flags in amdgpu_ttm_bo_eviction_valuable() against NULL, as the TTM reclaim path passes a NULL place in cgroup drain mode. Use drmm_cgroup_register_region() so that the region is automatically unregistered at DRM device release, after drm_dev_unplug() has already made drm_dev_enter() return false. The drm_dev_enter/exit guard in the reclaim callback ensures no reclaim work touches the TTM manager after driver unbind, closing the window between vram_mgr_fini() (called from drm_driver.release) and the drmm cleanup that unregisters the region. v3: - Rebased on fix for uninitialized list and buddy allocator on the drmm_cgroup_register_region() error path. v5: - Rebased on the introduction of struct dmem_cgroup_init. - Clear the reclaim callback in amdgpu_vram_mgr_fini() to prevent use-after-free if cgroup reclaim is triggered after driver unbind while userspace holds an open DRM file descriptor. (Sashiko-bot) - Switch from drmm_cgroup_register_region() to the raw dmem_cgroup_register_region() and store the region in amdgpu_vram_mgr.cg_region. Call dmem_cgroup_unregister_region() in amdgpu_vram_mgr_fini() after ttm_resource_manager_evict_all() to drain in-flight reclaim callbacks, and clear man->cg afterwards. This is required because amdgpu's vram manager fini is called explicitly during driver unbind, which may precede the DRM device release and thus precede any drmm-based cleanup. (Sashiko-bot) v6: - Fix mgr->cg_region never being assigned, so dmem_cgroup_unregister_region() in fini silently no-ops on NULL and leaks the region. (Sashiko-bot) - Reorder fini to call set_used(false) and evict_all() before dmem_cgroup_unregister_region(), so ttm_resource_free() can uncharge via man->cg during eviction; clear man->cg after unregister. (Sashiko-bot) v7: - Move dmem_cgroup_unregister_region() before the early return on evict_all() failure; not doing so leaves a dangling reclaim callback pointing to the partially-torn-down VRAM manager, causing a use-after-free when the cgroup later triggers reclaim. (Sashiko-bot) - Switch back to drmm_cgroup_register_region() with a drm_dev_enter/ exit guard in the reclaim callback (matching xe), rather than manual register/unregister. drm_dev_unplug() fires before vram_mgr_fini(), so drm_dev_enter() returning false prevents any reclaim from touching the manager during teardown. This also fixes the "vram" name collision on multi-GPU systems, since drmm_cgroup_register_region() automatically prefixes with "drm//". (Sashiko-bot) v8: - Move the dmem cgroup region teardown back into amdgpu_vram_mgr_fini(): register the region with dmem_cgroup_register_region() (keeping the "drm//vram" prefix), store it in amdgpu_vram_mgr.cg_region, and unregister it explicitly after ttm_resource_manager_evict_all(). The v7 drmm-only approach left a use-after-free on the probe-error teardown path: drm_dev_unplug() is not called there, so the drm_dev_enter() guard stays open while drm_driver.release destroys the manager before the drmm action unregisters the region. The explicit unregister drains in-flight reclaim on every teardown path; the drm_dev_enter() guard is kept as defense against reclaim after unplug. (Sashiko-bot) v9: - Don't leak a gpu buddy manager on cgroup init failure. (Sashiko-bot) - Acquire a runtime PM reference (pm_runtime_get_sync/put_autosuspend) around the TTM reclaim call in amdgpu_vram_mgr_dmem_reclaim(). (Sashiko-bot) Assisted-by: GitHub_Copilot:claude-sonnet-4.6 Signed-off-by: Thomas Hellström Tested-by: Thadeu Lima de Souza Cascardo Link: https://patch.msgid.link/20260725100036.2372-7-thomas.hellstrom@linux.intel.com Acked-by: Christian König Signed-off-by: Maarten Lankhorst --- drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c | 2 +- drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.c | 64 ++++++++++++++++++-- drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.h | 3 + 3 files changed, 63 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c index b10b0878df37..27ba646f8ad7 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c @@ -1507,7 +1507,7 @@ static bool amdgpu_ttm_bo_eviction_valuable(struct ttm_buffer_object *bo, dma_resv_for_each_fence(&resv_cursor, bo->base.resv, DMA_RESV_USAGE_BOOKKEEP, f) { if (amdkfd_fence_check_mm(f, current->mm) && - !(place->flags & TTM_PL_FLAG_CONTIGUOUS)) + !(place && (place->flags & TTM_PL_FLAG_CONTIGUOUS))) return false; } diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.c index 08f05c3aed1d..249f07f30951 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.c @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -906,6 +907,38 @@ static const struct ttm_resource_manager_func amdgpu_vram_mgr_func = { .debug = amdgpu_vram_mgr_debug }; +static const struct dmem_cgroup_ops amdgpu_vram_mgr_dmem_ops; + +static int amdgpu_vram_mgr_dmem_reclaim(struct dmem_cgroup_pool_state *pool, + u64 target_bytes, void *priv) +{ + struct ttm_resource_manager *man = priv; + struct amdgpu_device *adev = amdgpu_ttm_adev(man->bdev); + int ret, idx; + + if (!drm_dev_enter(adev_to_drm(adev), &idx)) + return -ENODEV; + + ret = pm_runtime_get_sync(adev_to_drm(adev)->dev); + if (ret < 0) { + pm_runtime_put_autosuspend(adev_to_drm(adev)->dev); + goto out; + } + + ret = ttm_resource_manager_dmem_reclaim(pool, target_bytes, priv); + + pm_runtime_mark_last_busy(adev_to_drm(adev)->dev); + pm_runtime_put_autosuspend(adev_to_drm(adev)->dev); + +out: + drm_dev_exit(idx); + return ret; +} + +static const struct dmem_cgroup_ops amdgpu_vram_mgr_dmem_ops = { + .reclaim = amdgpu_vram_mgr_dmem_reclaim, +}; + /** * amdgpu_vram_mgr_init - init VRAM manager and DRM MM * @@ -917,6 +950,7 @@ int amdgpu_vram_mgr_init(struct amdgpu_device *adev) { struct amdgpu_vram_mgr *mgr = &adev->mman.vram_mgr; struct ttm_resource_manager *man = &mgr->manager; + struct dmem_cgroup_region *cg; int err; ttm_resource_manager_init(man, &adev->mman.bdev, @@ -933,12 +967,19 @@ int amdgpu_vram_mgr_init(struct amdgpu_device *adev) if (err) return err; - man->cg = drmm_cgroup_register_region(adev_to_drm(adev), "vram", - &(struct dmem_cgroup_init){ + cg = dmem_cgroup_register_region(&(struct dmem_cgroup_init){ .size = adev->gmc.real_vram_size, - }); - if (IS_ERR(man->cg)) - return PTR_ERR(man->cg); + .ops = &amdgpu_vram_mgr_dmem_ops, + .reclaim_priv = man, + }, + "drm/%s/vram", adev_to_drm(adev)->unique); + if (IS_ERR(cg)) { + gpu_buddy_fini(&mgr->mm); + return PTR_ERR(cg); + } + + mgr->cg_region = cg; + ttm_resource_manager_set_dmem_region(man, cg); ttm_set_driver_manager(&adev->mman.bdev, TTM_PL_VRAM, &mgr->manager); ttm_resource_manager_set_used(man, true); @@ -963,6 +1004,19 @@ void amdgpu_vram_mgr_fini(struct amdgpu_device *adev) ttm_resource_manager_set_used(man, false); ret = ttm_resource_manager_evict_all(&adev->mman.bdev, man); + + /* + * Unregister the dmem cgroup region regardless of the evict_all() + * result and before any further teardown. This drains in-flight + * reclaim callbacks and blocks new ones, so no reclaim can reference + * the manager once we start freeing it. It must run after evict_all() + * so that ttm_resource_free() can still uncharge via man->cg during + * eviction. Clear man->cg afterwards. + */ + dmem_cgroup_unregister_region(mgr->cg_region); + mgr->cg_region = NULL; + ttm_resource_manager_set_dmem_region(man, NULL); + if (ret) return; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.h index 429a21a2e9b2..e581cbf52fcb 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.h @@ -26,6 +26,8 @@ #include +struct dmem_cgroup_region; + struct amdgpu_vram_mgr { struct ttm_resource_manager manager; struct gpu_buddy mm; @@ -36,6 +38,7 @@ struct amdgpu_vram_mgr { atomic64_t vis_usage; u64 default_page_size; struct list_head allocated_vres_list; + struct dmem_cgroup_region *cg_region; }; struct amdgpu_vres_task {