From 45c7da8b15f6716f43143214eeb350d97623a608 Mon Sep 17 00:00:00 2001 From: Terry Hsiao Date: Thu, 10 Sep 2026 11:04:31 +0800 Subject: [PATCH 001/143] drm/panel-edp: Add BOE NV140FHM-T0A The raw EDID: BOE NV140FHM-T0A 00 ff ff ff ff ff ff 00 09 e5 ea 38 00 00 00 00 1c 24 01 04 95 1f 11 78 03 28 65 97 59 54 8e 27 1e 50 54 00 00 00 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 c0 39 80 18 71 38 28 40 30 20 35 00 35 ae 10 00 00 1a 80 26 80 18 71 38 28 40 30 20 35 00 35 ae 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 0d 40 ff 0a 3c 7d 0c 10 21 7d 00 00 00 01 38 70 20 79 02 00 25 01 09 7f 41 02 7f 41 02 28 3c 80 81 00 10 6f 1a 00 00 03 01 28 3c 00 00 4a 5a 4a 5a 3c 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 c8 90 Signed-off-by: Terry Hsiao Reviewed-by: Douglas Anderson Signed-off-by: Douglas Anderson Link: https://patch.msgid.link/20260910030432.1009658-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 bfb49b647b96..b66751df502c 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('B', 'O', 'E', 0x3879, &delay_200_500_e80, "NT116WHM-N21"), EDP_PANEL_ENTRY('B', 'O', 'E', 0x388b, &delay_200_500_e80, "NV116FH1-M31"), EDP_PANEL_ENTRY('B', 'O', 'E', 0x388c, &delay_200_500_e80, "NV116FH1-M30"), + EDP_PANEL_ENTRY('B', 'O', 'E', 0x38ea, &delay_200_500_e50, "NV140FHM-T0A"), EDP_PANEL_ENTRY('C', 'M', 'N', 0x1130, &delay_200_500_e50, "N116BGE-EB2"), EDP_PANEL_ENTRY('C', 'M', 'N', 0x1132, &delay_200_500_e80_d50, "N116BGE-EA2"), From 86d8b6147d945599b6d5b51d1dfd58d5e3885c0b Mon Sep 17 00:00:00 2001 From: Andrzej Kacprowski Date: Mon, 31 Aug 2026 14:16:17 +0200 Subject: [PATCH 002/143] accel/ivpu: Add support for getting and setting command queue priority Add a new DRM_IVPU_PARAM_CMDQ_PRIORITY parameter to query and update the scheduling priority of an existing command queue, identified by its ID via drm_ivpu_param.index. The priority can be changed on a queue that already has pending jobs; if the queue's doorbell is registered, the new priority is propagated to the firmware immediately, otherwise it is applied when the queue is next registered. The firmware picks up the change at the first opportunity. The feature requires HW scheduling mode and is advertised through the new DRM_IVPU_CAP_CMDQ_SET_PRIORITY capability. Legacy command queues are not supported. Signed-off-by: Andrzej Kacprowski Reviewed-by: Karol Wachowski Signed-off-by: Karol Wachowski Link: https://patch.msgid.link/20260831121617.395523-1-andrzej.kacprowski@linux.intel.com --- drivers/accel/ivpu/ivpu_drv.c | 13 ++++++ drivers/accel/ivpu/ivpu_job.c | 85 +++++++++++++++++++++++++++++++++++ drivers/accel/ivpu/ivpu_job.h | 5 ++- include/uapi/drm/ivpu_accel.h | 17 ++++++- 4 files changed, 118 insertions(+), 2 deletions(-) diff --git a/drivers/accel/ivpu/ivpu_drv.c b/drivers/accel/ivpu/ivpu_drv.c index 95120957f42a..0f215392d42d 100644 --- a/drivers/accel/ivpu/ivpu_drv.c +++ b/drivers/accel/ivpu/ivpu_drv.c @@ -205,6 +205,7 @@ bool ivpu_is_capable(struct ivpu_device *vdev, u32 capability) case DRM_IVPU_CAP_BO_CREATE_FROM_USERPTR: return true; case DRM_IVPU_CAP_MANAGE_CMDQ: + case DRM_IVPU_CAP_CMDQ_SET_PRIORITY: return vdev->fw->sched_mode == VPU_SCHEDULING_MODE_HW; default: return false; @@ -273,6 +274,9 @@ static int ivpu_get_param_ioctl(struct drm_device *dev, void *data, struct drm_f case DRM_IVPU_PARAM_PREEMPT_BUFFER_SIZE: args->value = ivpu_fw_preempt_buf_size(vdev); break; + case DRM_IVPU_PARAM_CMDQ_PRIORITY: + ret = ivpu_cmdq_get_priority(file_priv, args->index, &args->value); + break; default: ret = -EINVAL; break; @@ -284,14 +288,23 @@ static int ivpu_get_param_ioctl(struct drm_device *dev, void *data, struct drm_f static int ivpu_set_param_ioctl(struct drm_device *dev, void *data, struct drm_file *file) { + struct ivpu_file_priv *file_priv = file->driver_priv; struct drm_ivpu_param *args = data; int ret = 0; + int idx; + + if (!drm_dev_enter(dev, &idx)) + return -ENODEV; switch (args->param) { + case DRM_IVPU_PARAM_CMDQ_PRIORITY: + ret = ivpu_cmdq_set_priority(file_priv, args->index, args->value); + break; default: ret = -EINVAL; } + drm_dev_exit(idx); return ret; } diff --git a/drivers/accel/ivpu/ivpu_job.c b/drivers/accel/ivpu/ivpu_job.c index ebb2c865b09a..b3de5dd29d1e 100644 --- a/drivers/accel/ivpu/ivpu_job.c +++ b/drivers/accel/ivpu/ivpu_job.c @@ -323,6 +323,11 @@ static inline u8 ivpu_job_to_jsm_priority(u8 priority) return priority - 1; } +static inline u8 ivpu_jsm_to_job_priority(u8 priority) +{ + return priority + 1; +} + static void ivpu_cmdq_destroy(struct ivpu_file_priv *file_priv, struct ivpu_cmdq *cmdq) { lockdep_assert_held(&file_priv->lock); @@ -1127,6 +1132,86 @@ int ivpu_cmdq_destroy_ioctl(struct drm_device *dev, void *data, struct drm_file return ret; } +static int ivpu_cmdq_priority_args_check(struct ivpu_device *vdev, u32 cmdq_id, u64 priority) +{ + if (!ivpu_is_capable(vdev, DRM_IVPU_CAP_CMDQ_SET_PRIORITY)) { + ivpu_dbg(vdev, IOCTL, "Command queue priority not supported\n"); + return -ENODEV; + } + + if (cmdq_id < IVPU_CMDQ_MIN_ID || cmdq_id > IVPU_CMDQ_MAX_ID) { + ivpu_dbg(vdev, IOCTL, "Invalid command queue ID %u\n", cmdq_id); + return -EINVAL; + } + + if (priority > DRM_IVPU_JOB_PRIORITY_REALTIME) + return -EINVAL; + + return 0; +} + +int ivpu_cmdq_get_priority(struct ivpu_file_priv *file_priv, u32 cmdq_id, u64 *priority) +{ + struct ivpu_device *vdev = file_priv->vdev; + struct ivpu_cmdq *cmdq; + int ret; + + ret = ivpu_cmdq_priority_args_check(vdev, cmdq_id, DRM_IVPU_JOB_PRIORITY_NORMAL); + if (ret) + return ret; + + guard(mutex)(&file_priv->lock); + + cmdq = ivpu_cmdq_acquire(file_priv, cmdq_id); + if (!cmdq || cmdq->is_legacy) + return -EINVAL; + + *priority = ivpu_jsm_to_job_priority(cmdq->priority); + return 0; +} + +int ivpu_cmdq_set_priority(struct ivpu_file_priv *file_priv, u32 cmdq_id, u64 priority) +{ + struct ivpu_device *vdev = file_priv->vdev; + struct ivpu_cmdq *cmdq; + u8 jsm_priority; + int ret = 0; + + ret = ivpu_cmdq_priority_args_check(vdev, cmdq_id, priority); + if (ret) + return ret; + + ret = ivpu_rpm_get(vdev); + if (ret < 0) + return ret; + + mutex_lock(&file_priv->lock); + + cmdq = ivpu_cmdq_acquire(file_priv, cmdq_id); + if (!cmdq || cmdq->is_legacy) { + ret = -EINVAL; + goto out; + } + + jsm_priority = ivpu_job_to_jsm_priority(priority); + if (cmdq->priority == jsm_priority) + goto out; + + if (cmdq->db_id) { + ret = ivpu_jsm_hws_set_context_sched_properties(vdev, file_priv->ctx.id, cmdq_id, + jsm_priority); + if (ret) + goto out; + } + + cmdq->priority = jsm_priority; + +out: + mutex_unlock(&file_priv->lock); + ivpu_rpm_put(vdev); + return ret; +} + static void ivpu_job_done_callback(struct ivpu_device *vdev, struct ivpu_ipc_hdr *ipc_hdr, struct vpu_jsm_msg *jsm_msg) diff --git a/drivers/accel/ivpu/ivpu_job.h b/drivers/accel/ivpu/ivpu_job.h index d8dbce82447a..6e32fea1eb62 100644 --- a/drivers/accel/ivpu/ivpu_job.h +++ b/drivers/accel/ivpu/ivpu_job.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-only */ /* - * Copyright (C) 2020-2025 Intel Corporation + * Copyright (C) 2020-2026 Intel Corporation */ #ifndef __IVPU_JOB_H__ @@ -81,6 +81,9 @@ int ivpu_cmdq_create_ioctl(struct drm_device *dev, void *data, struct drm_file * int ivpu_cmdq_destroy_ioctl(struct drm_device *dev, void *data, struct drm_file *file); int ivpu_cmdq_submit_ioctl(struct drm_device *dev, void *data, struct drm_file *file); +int ivpu_cmdq_get_priority(struct ivpu_file_priv *file_priv, u32 cmdq_id, u64 *priority); +int ivpu_cmdq_set_priority(struct ivpu_file_priv *file_priv, u32 cmdq_id, u64 priority); + void ivpu_context_abort_locked(struct ivpu_file_priv *file_priv); void ivpu_cmdq_release_all_locked(struct ivpu_file_priv *file_priv); diff --git a/include/uapi/drm/ivpu_accel.h b/include/uapi/drm/ivpu_accel.h index 264505d54f93..9ce19d9c3131 100644 --- a/include/uapi/drm/ivpu_accel.h +++ b/include/uapi/drm/ivpu_accel.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */ /* - * Copyright (C) 2020-2025 Intel Corporation + * Copyright (C) 2020-2026 Intel Corporation */ #ifndef __UAPI_IVPU_DRM_H__ @@ -96,6 +96,7 @@ extern "C" { #define DRM_IVPU_PARAM_SKU 12 #define DRM_IVPU_PARAM_CAPABILITIES 13 #define DRM_IVPU_PARAM_PREEMPT_BUFFER_SIZE 14 +#define DRM_IVPU_PARAM_CMDQ_PRIORITY 15 #define DRM_IVPU_PLATFORM_TYPE_SILICON 0 @@ -140,6 +141,15 @@ extern "C" { */ #define DRM_IVPU_CAP_BO_CREATE_FROM_USERPTR 4 +/** + * DRM_IVPU_CAP_CMDQ_SET_PRIORITY + * + * Driver supports DRM_IVPU_PARAM_CMDQ_PRIORITY parameter to get/set command + * queue priority. + * This allows changing command queue priority after creation. + */ +#define DRM_IVPU_CAP_CMDQ_SET_PRIORITY 5 + /** * struct drm_ivpu_param - Get/Set VPU parameters */ @@ -192,6 +202,11 @@ struct drm_ivpu_param { * * %DRM_IVPU_PARAM_PREEMPT_BUFFER_SIZE: * Size of the preemption buffer (read-only) + * + * %DRM_IVPU_PARAM_CMDQ_PRIORITY: + * Get/Set command queue priority (see DRM_IVPU_JOB_PRIORITY_*). + * DRM_IVPU_JOB_PRIORITY_DEFAULT is treated as DRM_IVPU_JOB_PRIORITY_NORMAL. + * Command queue ID is given via drm_ivpu_param.index. */ __u32 param; From f9a70441dde5e79667469b77b192659c28d2e548 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Mon, 7 Sep 2026 12:29:40 +0300 Subject: [PATCH 003/143] dt-bindings: display: bridge: Document Renesas R-Car V4H DSC bindings The Renesas DSC Display Stream Compression is a bridge embedded in the Renesas R-Car V4H SoC. The bridge performs VESA DSC encoding of up to 8k or 400 Mpixel/s. Signed-off-by: Marek Vasut [tomi.valkeinen: fix the example] Reviewed-by: Laurent Pinchart Acked-by: Conor Dooley Reviewed-by: Geert Uytterhoeven Signed-off-by: Tomi Valkeinen Link: https://patch.msgid.link/20260907-rcar-du-dsc-v6-1-f8ef711a7494@ideasonboard.com Signed-off-by: Tomi Valkeinen --- .../display/bridge/renesas,r8a779g0-dsc.yaml | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/bridge/renesas,r8a779g0-dsc.yaml diff --git a/Documentation/devicetree/bindings/display/bridge/renesas,r8a779g0-dsc.yaml b/Documentation/devicetree/bindings/display/bridge/renesas,r8a779g0-dsc.yaml new file mode 100644 index 000000000000..6ce2444409a2 --- /dev/null +++ b/Documentation/devicetree/bindings/display/bridge/renesas,r8a779g0-dsc.yaml @@ -0,0 +1,96 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/display/bridge/renesas,r8a779g0-dsc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Renesas R-Car DSC Display Stream Compression + +maintainers: + - Marek Vasut + +description: | + This binding describes the VESA DSC Display Stream Compression encoder + embedded in the Renesas R-Car V4H SoC. The encoder supports all DSC1.1 + encoding mechanisms, configurable bits-per-pixel, resolution up to 8k. + +properties: + compatible: + const: renesas,r8a779g0-dsc + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + interrupts: + maxItems: 1 + + power-domains: + maxItems: 1 + + resets: + maxItems: 1 + + ports: + $ref: /schemas/graph.yaml#/properties/ports + + properties: + port@0: + $ref: /schemas/graph.yaml#/properties/port + description: R-Car DU input port + + port@1: + $ref: /schemas/graph.yaml#/properties/port + description: R-Car DSI output port + + required: + - port@0 + - port@1 + +required: + - compatible + - reg + - clocks + - interrupts + - power-domains + - resets + - ports + +unevaluatedProperties: false + +examples: + - | + #include + #include + #include + + bridge@feb8d000 { + compatible = "renesas,r8a779g0-dsc"; + reg = <0xfeb8d000 0x400>; + interrupts = ; + clocks = <&cpg CPG_MOD 2819>; + power-domains = <&sysc R8A779G0_PD_ALWAYS_ON>; + resets = <&cpg 2819>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + dsc_in_dsi1: endpoint { + remote-endpoint = <&du_out_dsi1>; + }; + }; + + port@1 { + reg = <1>; + dsc_out_dsi1: endpoint { + remote-endpoint = <&dsi1_in>; + }; + }; + }; + }; +... From c217ce005b2328996ab29d25483a7490beb8c249 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Mon, 7 Sep 2026 12:29:41 +0300 Subject: [PATCH 004/143] drm/rcar-du: dsc: Add rudimentary Renesas R-Car V4H DSC driver The Renesas DSC Display Stream Compression is a bridge embedded in the Renesas R-Car V4H SoC. The bridge performs VESA DSC encoding of up to 8k or 400 Mpixel/s. Add rudimentary driver, which currently acts as a pass-through bridge and allows DSI1 to be operational on R-Car V4H. Signed-off-by: Marek Vasut [tomi.valkeinen: use bridge->next_bridge, minor changes] Signed-off-by: Tomi Valkeinen Reviewed-by: Laurent Pinchart Link: https://patch.msgid.link/20260907-rcar-du-dsc-v6-2-f8ef711a7494@ideasonboard.com Signed-off-by: Tomi Valkeinen --- drivers/gpu/drm/renesas/rcar-du/Kconfig | 12 ++ drivers/gpu/drm/renesas/rcar-du/Makefile | 1 + drivers/gpu/drm/renesas/rcar-du/rcar_dsc.c | 153 +++++++++++++++++++++ 3 files changed, 166 insertions(+) create mode 100644 drivers/gpu/drm/renesas/rcar-du/rcar_dsc.c diff --git a/drivers/gpu/drm/renesas/rcar-du/Kconfig b/drivers/gpu/drm/renesas/rcar-du/Kconfig index 840305fdeb49..80bd770ae0f6 100644 --- a/drivers/gpu/drm/renesas/rcar-du/Kconfig +++ b/drivers/gpu/drm/renesas/rcar-du/Kconfig @@ -25,6 +25,18 @@ config DRM_RCAR_CMM def_tristate DRM_RCAR_DU depends on DRM_RCAR_USE_CMM +config DRM_RCAR_USE_DSC + bool "R-Car DU DSC Encoder Support" + depends on DRM_BRIDGE && OF + depends on DRM_RCAR_DU || COMPILE_TEST + default DRM_RCAR_DU + help + Enable support for the R-Car Display Unit embedded DSC encoder. + +config DRM_RCAR_DSC + def_tristate DRM_RCAR_DU + depends on DRM_RCAR_USE_DSC + config DRM_RCAR_DW_HDMI tristate "R-Car Gen3 and RZ/G2 DU HDMI Encoder Support" depends on DRM && OF diff --git a/drivers/gpu/drm/renesas/rcar-du/Makefile b/drivers/gpu/drm/renesas/rcar-du/Makefile index 6f132325c8b7..9ab7a0ac45d8 100644 --- a/drivers/gpu/drm/renesas/rcar-du/Makefile +++ b/drivers/gpu/drm/renesas/rcar-du/Makefile @@ -10,6 +10,7 @@ rcar-du-drm-$(CONFIG_DRM_RCAR_VSP) += rcar_du_vsp.o rcar-du-drm-$(CONFIG_DRM_RCAR_WRITEBACK) += rcar_du_writeback.o obj-$(CONFIG_DRM_RCAR_CMM) += rcar_cmm.o +obj-$(CONFIG_DRM_RCAR_DSC) += rcar_dsc.o obj-$(CONFIG_DRM_RCAR_DU) += rcar-du-drm.o obj-$(CONFIG_DRM_RCAR_DW_HDMI) += rcar_dw_hdmi.o obj-$(CONFIG_DRM_RCAR_LVDS) += rcar_lvds.o diff --git a/drivers/gpu/drm/renesas/rcar-du/rcar_dsc.c b/drivers/gpu/drm/renesas/rcar-du/rcar_dsc.c new file mode 100644 index 000000000000..1882ae34ce8f --- /dev/null +++ b/drivers/gpu/drm/renesas/rcar-du/rcar_dsc.c @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * R-Car DSC Encoder + * + * Copyright (C) 2025 Marek Vasut + * Copyright (C) 2025 Renesas Electronics Corporation + */ + +#include +#include +#include +#include +#include + +#include +#include + +struct rcar_dsc { + struct drm_bridge bridge; + + struct device *dev; +}; + +static inline struct rcar_dsc *bridge_to_rcar_dsc(struct drm_bridge *bridge) +{ + return container_of(bridge, struct rcar_dsc, bridge); +} + +/* ----------------------------------------------------------------------------- + * Bridge + */ + +static int rcar_dsc_attach(struct drm_bridge *bridge, + struct drm_encoder *encoder, + enum drm_bridge_attach_flags flags) +{ + struct rcar_dsc *dsc = bridge_to_rcar_dsc(bridge); + + if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) + return -EINVAL; + + return drm_bridge_attach(encoder, dsc->bridge.next_bridge, bridge, + DRM_BRIDGE_ATTACH_NO_CONNECTOR); +} + +static void rcar_dsc_atomic_enable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) +{ + struct rcar_dsc *dsc = bridge_to_rcar_dsc(bridge); + + WARN_ON(pm_runtime_get_sync(dsc->dev) < 0); +} + +static void rcar_dsc_atomic_disable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) +{ + struct rcar_dsc *dsc = bridge_to_rcar_dsc(bridge); + + pm_runtime_put(dsc->dev); +} + +static enum drm_mode_status +rcar_dsc_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + if (mode->hdisplay < 320 || mode->hdisplay > 8190) + return MODE_BAD_HVALUE; + + if (mode->vdisplay < 160 || mode->vdisplay > 8190) + return MODE_BAD_VVALUE; + + if (mode->clock > 400000) /* Really 400 Mpixel/s */ + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +static const struct drm_bridge_funcs rcar_dsc_bridge_ops = { + .attach = rcar_dsc_attach, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_enable = rcar_dsc_atomic_enable, + .atomic_disable = rcar_dsc_atomic_disable, + .mode_valid = rcar_dsc_bridge_mode_valid, +}; + +/* ----------------------------------------------------------------------------- + * Probe & Remove + */ + +static int rcar_dsc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct rcar_dsc *dsc; + int ret; + + dsc = devm_drm_bridge_alloc(dev, struct rcar_dsc, bridge, + &rcar_dsc_bridge_ops); + if (IS_ERR(dsc)) + return PTR_ERR(dsc); + + platform_set_drvdata(pdev, dsc); + + dsc->dev = &pdev->dev; + + dsc->bridge.next_bridge = of_drm_get_bridge_by_endpoint(dev->of_node, + 1, 0); + if (IS_ERR(dsc->bridge.next_bridge)) + return PTR_ERR(dsc->bridge.next_bridge); + + dsc->bridge.of_node = dev->of_node; + + pm_runtime_enable(&pdev->dev); + + ret = devm_drm_bridge_add(dev, &dsc->bridge); + if (ret) + goto err_runtime_disable; + + return 0; + +err_runtime_disable: + pm_runtime_disable(&pdev->dev); + + return ret; +} + +static void rcar_dsc_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); +} + +static const struct of_device_id rcar_dsc_of_table[] = { + { .compatible = "renesas,r8a779g0-dsc" }, + {} +}; + +MODULE_DEVICE_TABLE(of, rcar_dsc_of_table); + +static struct platform_driver rcar_dsc_platform_driver = { + .probe = rcar_dsc_probe, + .remove = rcar_dsc_remove, + .driver = { + .name = "rcar-dsc", + .of_match_table = rcar_dsc_of_table, + }, +}; + +module_platform_driver(rcar_dsc_platform_driver); + +MODULE_DESCRIPTION("Renesas R-Car DSC Encoder Driver"); +MODULE_LICENSE("GPL"); From 6cafbb6cf014eba5c0613645c0869096a770464a Mon Sep 17 00:00:00 2001 From: Tomi Valkeinen Date: Mon, 7 Sep 2026 12:29:42 +0300 Subject: [PATCH 005/143] drm/rcar-du: dsi: Support DSC in the pipeline Enabling DSI clocks on rcar-du needs some tricks as the DU dot clock is provided by the DSI. Thus, we call rcar_mipi_dsi_pclk_enable() from the crtc, when enabling the crtc. With DSC in the pipeline, between the DU and the DSI, the above call path is broken as the crtc tries to call rcar_mipi_dsi_pclk_enable() on the DSC. To solve this problem, make sure we store the DSI bridge to the rcdu->dsi[] array, instead of the first bridge in the DSI pipeline (which can be DSC), by checking the bridge's bridge->type. Reviewed-by: Laurent Pinchart Signed-off-by: Tomi Valkeinen Link: https://patch.msgid.link/20260907-rcar-du-dsc-v6-3-f8ef711a7494@ideasonboard.com Signed-off-by: Tomi Valkeinen --- .../gpu/drm/renesas/rcar-du/rcar_du_encoder.c | 17 +++++++++++++++-- drivers/gpu/drm/renesas/rcar-du/rcar_mipi_dsi.c | 1 + 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/renesas/rcar-du/rcar_du_encoder.c b/drivers/gpu/drm/renesas/rcar-du/rcar_du_encoder.c index d8e7e9877ba8..3f98eae9b6ac 100644 --- a/drivers/gpu/drm/renesas/rcar-du/rcar_du_encoder.c +++ b/drivers/gpu/drm/renesas/rcar-du/rcar_du_encoder.c @@ -90,9 +90,22 @@ int rcar_du_encoder_init(struct rcar_du_device *rcdu, drm_bridge_get(bridge); if (output == RCAR_DU_OUTPUT_DSI0 || - output == RCAR_DU_OUTPUT_DSI1) + output == RCAR_DU_OUTPUT_DSI1) { + struct drm_bridge *dsi_bridge; + + /* + * When we have a DSC block between the DU and the DSI, + * the "bridge" points to the DSC. Detect the DSC by looking + * at the bridge type, and skip the DSC if the bridge is not + * the DSI bridge. + */ + + dsi_bridge = bridge->type == DRM_MODE_CONNECTOR_DSI ? + bridge : bridge->next_bridge; + rcdu->dsi[output - RCAR_DU_OUTPUT_DSI0] = - drm_bridge_get(bridge); + drm_bridge_get(dsi_bridge); + } } /* diff --git a/drivers/gpu/drm/renesas/rcar-du/rcar_mipi_dsi.c b/drivers/gpu/drm/renesas/rcar-du/rcar_mipi_dsi.c index 6e46d6d99f3c..af4edb207486 100644 --- a/drivers/gpu/drm/renesas/rcar-du/rcar_mipi_dsi.c +++ b/drivers/gpu/drm/renesas/rcar-du/rcar_mipi_dsi.c @@ -957,6 +957,7 @@ static int rcar_mipi_dsi_host_attach(struct mipi_dsi_host *host, /* Initialize the DRM bridge. */ dsi->bridge.of_node = dsi->dev->of_node; + dsi->bridge.type = DRM_MODE_CONNECTOR_DSI; drm_bridge_add(&dsi->bridge); return 0; From 82f770ecf813dc5de2bbf8e2015b1932da1f3503 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Thu, 10 Sep 2026 20:19:45 +0200 Subject: [PATCH 006/143] drm/rcar-du: dsi: Implement DSI command TX using AXI memory access Currently, the driver supports register access mode for DSI command transfer, which limits both TX and RX to only 16 Bytes long packets. Implement support for DSI command TX using AXI memory access mode, which extends the packet transfer length up to 128 Bytes long in LP and 1024 Bytes long in HS. Support for DSI command RX using the AXI access mode is not implemented due to missing test hardware. The implementation allocates a DMAble 4k page. In case the command transfer is longer than 16 Bytes, the payload is copied into the page and sent out using the AXI access mode, otherwise the register access mode is used. Signed-off-by: Marek Vasut Reviewed-by: Tomi Valkeinen Link: https://patch.msgid.link/20260910182003.31612-1-marek.vasut+renesas@mailbox.org Signed-off-by: Tomi Valkeinen --- .../gpu/drm/renesas/rcar-du/rcar_mipi_dsi.c | 50 ++++++++++++++++--- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/renesas/rcar-du/rcar_mipi_dsi.c b/drivers/gpu/drm/renesas/rcar-du/rcar_mipi_dsi.c index af4edb207486..02a4bd791745 100644 --- a/drivers/gpu/drm/renesas/rcar-du/rcar_mipi_dsi.c +++ b/drivers/gpu/drm/renesas/rcar-du/rcar_mipi_dsi.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -75,6 +76,9 @@ struct rcar_mipi_dsi { unsigned long mode_flags; unsigned int num_data_lanes; unsigned int lanes; + + void *cmd_axi_cpu; + dma_addr_t cmd_axi_dma; }; struct dsi_setup_info { @@ -978,6 +982,7 @@ static ssize_t rcar_mipi_dsi_host_tx_transfer(struct mipi_dsi_host *host, bool is_rx_xfer) { const bool is_tx_long = mipi_dsi_packet_format_is_long(msg->type); + const bool is_tx_axi = !is_rx_xfer && is_tx_long && (msg->tx_len > 16); struct rcar_mipi_dsi *dsi = host_to_rcar_mipi_dsi(host); struct mipi_dsi_packet packet; u8 payload[16] = { 0 }; @@ -988,9 +993,14 @@ static ssize_t rcar_mipi_dsi_host_tx_transfer(struct mipi_dsi_host *host, if (ret) return ret; - /* Configure LP or HS command transfer. */ - rcar_mipi_dsi_write(dsi, TXCMSETR, (msg->flags & MIPI_DSI_MSG_USE_LPM) ? - TXCMSETR_SPDTYP : 0); + /* Configure LP or HS and register or AXI command transfer. */ + rcar_mipi_dsi_write(dsi, TXCMSETR, ((msg->flags & MIPI_DSI_MSG_USE_LPM) ? + TXCMSETR_SPDTYP : 0) | + (is_tx_axi ? TXCMSETR_LPPDACC : 0)); + + /* Configure DMA source address for AXI command transfer. */ + if (is_tx_axi) + rcar_mipi_dsi_write(dsi, TXCMADDRSET0R, dsi->cmd_axi_dma); /* Register access mode for RX transfer. */ if (is_rx_xfer) @@ -1012,7 +1022,10 @@ static ssize_t rcar_mipi_dsi_host_tx_transfer(struct mipi_dsi_host *host, TXCMPHDR_DATA1(packet.header[2]) | TXCMPHDR_DATA0(packet.header[1])); - if (is_tx_long) { + if (is_tx_axi) { + memcpy(dsi->cmd_axi_cpu, packet.payload, + min(msg->tx_len, 1024)); + } else if (is_tx_long) { memcpy(payload, packet.payload, min(msg->tx_len, sizeof(payload))); @@ -1163,10 +1176,16 @@ static ssize_t rcar_mipi_dsi_host_transfer(struct mipi_dsi_host *host, struct rcar_mipi_dsi *dsi = host_to_rcar_mipi_dsi(host); int ret; - if (msg->tx_len > 16 || msg->rx_len > 16) { - /* ToDo: Implement Memory on AXI bus command mode. */ + if (msg->tx_len > 1024 || msg->rx_len > 16) { + /* ToDo: Implement Memory on AXI bus RX command mode. */ dev_warn(dsi->dev, - "Register-based command mode supports only up to 16 Bytes long payload\n"); + "Command mode supports only up to 1024B long TX and 16B long RX payload\n"); + return -EOPNOTSUPP; + } + + if ((msg->flags & MIPI_DSI_MSG_USE_LPM) && msg->tx_len > 128) { + dev_warn(dsi->dev, + "Command mode in LP supports only up to 128B long TX payload\n"); return -EOPNOTSUPP; } @@ -1267,6 +1286,10 @@ static int rcar_mipi_dsi_probe(struct platform_device *pdev) struct rcar_mipi_dsi *dsi; int ret; + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + if (ret) + return dev_err_probe(&pdev->dev, ret, "No suitable DMA available\n"); + dsi = devm_drm_bridge_alloc(&pdev->dev, struct rcar_mipi_dsi, bridge, &rcar_mipi_dsi_bridge_ops); if (IS_ERR(dsi)) @@ -1296,12 +1319,21 @@ static int rcar_mipi_dsi_probe(struct platform_device *pdev) return PTR_ERR(dsi->rstc); } + dsi->cmd_axi_cpu = dma_alloc_coherent(&pdev->dev, SZ_4K, &dsi->cmd_axi_dma, + GFP_KERNEL); + if (!dsi->cmd_axi_cpu) { + return dev_err_probe(&pdev->dev, -ENOMEM, + "Failed to allocate DSI AXI Access command buffer\n"); + } + /* Initialize the DSI host. */ dsi->host.dev = dsi->dev; dsi->host.ops = &rcar_mipi_dsi_host_ops; ret = mipi_dsi_host_register(&dsi->host); - if (ret < 0) + if (ret < 0) { + dma_free_coherent(&pdev->dev, SZ_4K, dsi->cmd_axi_cpu, dsi->cmd_axi_dma); return ret; + } return 0; } @@ -1311,6 +1343,8 @@ static void rcar_mipi_dsi_remove(struct platform_device *pdev) struct rcar_mipi_dsi *dsi = platform_get_drvdata(pdev); mipi_dsi_host_unregister(&dsi->host); + + dma_free_coherent(&pdev->dev, SZ_4K, dsi->cmd_axi_cpu, dsi->cmd_axi_dma); } static const struct rcar_mipi_dsi_device_info v3u_data = { From 9b274a80c597ab00e04aa07837606030d28d1d5f Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Thu, 10 Sep 2026 14:45:37 +0900 Subject: [PATCH 007/143] drm/sched: Drop the unused entity argument from drm_sched_fence_alloc() In commit dbe48d030b28 ("drm/sched: Split drm_sched_job_init") the only use of the entity argument of drm_sched_fence_alloc(), fence->sched = entity->rq->sched, moved into drm_sched_fence_init(), and the now unused argument was left behind. Drop the argument. The sole caller still passes the entity to drm_sched_fence_init() at arm time, so behavior is unchanged. This also removes the parameter-name mismatch between the declaration (s_entity) and the definition (entity). Signed-off-by: Donggeun Yoo Assisted-by: Claude:claude-fable-5 Signed-off-by: Philipp Stanner Link: https://patch.msgid.link/20260910054537.633952-1-donggeunyoo.kernel@gmail.com --- drivers/gpu/drm/scheduler/sched_fence.c | 3 +-- drivers/gpu/drm/scheduler/sched_internal.h | 3 +-- drivers/gpu/drm/scheduler/sched_main.c | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/scheduler/sched_fence.c b/drivers/gpu/drm/scheduler/sched_fence.c index 096fe28aa9c9..b7a4cb921234 100644 --- a/drivers/gpu/drm/scheduler/sched_fence.c +++ b/drivers/gpu/drm/scheduler/sched_fence.c @@ -205,8 +205,7 @@ struct drm_sched_fence *to_drm_sched_fence(struct dma_fence *f) } EXPORT_SYMBOL(to_drm_sched_fence); -struct drm_sched_fence *drm_sched_fence_alloc(struct drm_sched_entity *entity, - void *owner, +struct drm_sched_fence *drm_sched_fence_alloc(void *owner, u64 drm_client_id) { struct drm_sched_fence *fence = NULL; diff --git a/drivers/gpu/drm/scheduler/sched_internal.h b/drivers/gpu/drm/scheduler/sched_internal.h index a901801fce85..32d3ddb820be 100644 --- a/drivers/gpu/drm/scheduler/sched_internal.h +++ b/drivers/gpu/drm/scheduler/sched_internal.h @@ -58,8 +58,7 @@ drm_sched_rq_select_entity(struct drm_gpu_scheduler *sched, void drm_sched_entity_select_rq(struct drm_sched_entity *entity); struct drm_sched_job *drm_sched_entity_pop_job(struct drm_sched_entity *entity); -struct drm_sched_fence *drm_sched_fence_alloc(struct drm_sched_entity *s_entity, - void *owner, u64 drm_client_id); +struct drm_sched_fence *drm_sched_fence_alloc(void *owner, u64 drm_client_id); void drm_sched_fence_init(struct drm_sched_fence *fence, struct drm_sched_entity *entity); void drm_sched_fence_free(struct drm_sched_fence *fence); diff --git a/drivers/gpu/drm/scheduler/sched_main.c b/drivers/gpu/drm/scheduler/sched_main.c index 6cb6f9546493..e4835b99ca60 100644 --- a/drivers/gpu/drm/scheduler/sched_main.c +++ b/drivers/gpu/drm/scheduler/sched_main.c @@ -612,7 +612,7 @@ int drm_sched_job_init(struct drm_sched_job *job, job->entity = entity; job->credits = credits; - job->s_fence = drm_sched_fence_alloc(entity, owner, drm_client_id); + job->s_fence = drm_sched_fence_alloc(owner, drm_client_id); if (!job->s_fence) return -ENOMEM; From f2aaaa8c6cddeba98a6e906581ceb34466e0c336 Mon Sep 17 00:00:00 2001 From: Ze Huang Date: Mon, 27 Jul 2026 03:45:18 +0800 Subject: [PATCH 008/143] drm/gm12u320: replace struct drm_simple_display_pipe with regular atomic helpers Convert gm12u320 to direct primary plane, CRTC and encoder setup. Keep shadow-plane helper state, framebuffer access helpers and no-scaling plane-state check from simple-KMS path. Reviewed-by: Thomas Zimmermann Tested-by: Thomas Zimmermann Signed-off-by: Ze Huang Signed-off-by: Thomas Zimmermann Link: https://patch.msgid.link/20260727-drm-simple-kms-removal-v3-5-cd5dc89858c6@oss.qualcomm.com --- drivers/gpu/drm/tiny/gm12u320.c | 138 +++++++++++++++++++++++++------- 1 file changed, 111 insertions(+), 27 deletions(-) diff --git a/drivers/gpu/drm/tiny/gm12u320.c b/drivers/gpu/drm/tiny/gm12u320.c index 4ad074337af0..754617722a0d 100644 --- a/drivers/gpu/drm/tiny/gm12u320.c +++ b/drivers/gpu/drm/tiny/gm12u320.c @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -27,7 +28,6 @@ #include #include #include -#include static bool eco_mode; module_param(eco_mode, bool, 0644); @@ -87,7 +87,9 @@ MODULE_PARM_DESC(eco_mode, "Turn on Eco mode (less bright, more silent)"); struct gm12u320_device { struct drm_device dev; - struct drm_simple_display_pipe pipe; + struct drm_plane plane; + struct drm_crtc crtc; + struct drm_encoder encoder; struct drm_connector conn; unsigned char *cmd_buf; unsigned char *data_buf[GM12U320_BLOCK_COUNT]; @@ -102,7 +104,10 @@ struct gm12u320_device { } fb_update; }; -#define to_gm12u320(__dev) container_of(__dev, struct gm12u320_device, dev) +static inline struct gm12u320_device *to_gm12u320(struct drm_device *__dev) +{ + return container_of(__dev, struct gm12u320_device, dev); +} static const char cmd_data[CMD_SIZE] = { 0x55, 0x53, 0x42, 0x43, 0x00, 0x00, 0x00, 0x00, @@ -555,43 +560,106 @@ static int gm12u320_conn_init(struct gm12u320_device *gm12u320) } /* ------------------------------------------------------------------ */ -/* gm12u320 (simple) display pipe */ +/* gm12u320 display pipe */ -static void gm12u320_pipe_enable(struct drm_simple_display_pipe *pipe, - struct drm_crtc_state *crtc_state, - struct drm_plane_state *plane_state) +static void gm12u320_crtc_helper_atomic_enable(struct drm_crtc *crtc, + struct drm_atomic_commit *commit) { struct drm_rect rect = { 0, 0, GM12U320_USER_WIDTH, GM12U320_HEIGHT }; - struct gm12u320_device *gm12u320 = to_gm12u320(pipe->crtc.dev); - struct drm_shadow_plane_state *shadow_plane_state = to_drm_shadow_plane_state(plane_state); + struct gm12u320_device *gm12u320 = to_gm12u320(crtc->dev); + struct drm_plane_state *pstate = drm_atomic_get_new_plane_state(commit, &gm12u320->plane); + struct drm_shadow_plane_state *shadow_plane_state = to_drm_shadow_plane_state(pstate); gm12u320->fb_update.draw_status_timeout = FIRST_FRAME_TIMEOUT; - gm12u320_fb_mark_dirty(plane_state->fb, &shadow_plane_state->data[0], &rect); + gm12u320_fb_mark_dirty(pstate->fb, &shadow_plane_state->data[0], &rect); } -static void gm12u320_pipe_disable(struct drm_simple_display_pipe *pipe) +static void gm12u320_crtc_helper_atomic_disable(struct drm_crtc *crtc, + struct drm_atomic_commit *commit) { - struct gm12u320_device *gm12u320 = to_gm12u320(pipe->crtc.dev); + struct gm12u320_device *gm12u320 = to_gm12u320(crtc->dev); gm12u320_stop_fb_update(gm12u320); } -static void gm12u320_pipe_update(struct drm_simple_display_pipe *pipe, - struct drm_plane_state *old_state) +static void gm12u320_plane_helper_atomic_update(struct drm_plane *plane, + struct drm_atomic_commit *commit) { - struct drm_plane_state *state = pipe->plane.state; + struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(commit, plane); + struct drm_plane_state *state = drm_atomic_get_new_plane_state(commit, plane); struct drm_shadow_plane_state *shadow_plane_state = to_drm_shadow_plane_state(state); struct drm_rect rect; + if (!state->fb) + return; + if (drm_atomic_helper_damage_merged(old_state, state, &rect)) gm12u320_fb_mark_dirty(state->fb, &shadow_plane_state->data[0], &rect); } -static const struct drm_simple_display_pipe_funcs gm12u320_pipe_funcs = { - .enable = gm12u320_pipe_enable, - .disable = gm12u320_pipe_disable, - .update = gm12u320_pipe_update, - DRM_GEM_SIMPLE_DISPLAY_PIPE_SHADOW_PLANE_FUNCS, +static const struct drm_plane_funcs gm12u320_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .destroy = drm_plane_cleanup, + DRM_GEM_SHADOW_PLANE_FUNCS, +}; + +static int gm12u320_plane_helper_atomic_check(struct drm_plane *plane, + struct drm_atomic_commit *commit) +{ + struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(commit, plane); + struct drm_crtc_state *crtc_state = NULL; + + if (plane_state->crtc) { + crtc_state = drm_atomic_get_crtc_state(commit, plane_state->crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + } + + return drm_atomic_helper_check_plane_state(plane_state, crtc_state, + DRM_PLANE_NO_SCALING, + DRM_PLANE_NO_SCALING, + false, false); +} + +static const struct drm_plane_helper_funcs gm12u320_plane_helper_funcs = { + DRM_GEM_SHADOW_PLANE_HELPER_FUNCS, + .atomic_check = gm12u320_plane_helper_atomic_check, + .atomic_update = gm12u320_plane_helper_atomic_update, +}; + +static int gm12u320_crtc_helper_atomic_check(struct drm_crtc *crtc, + struct drm_atomic_commit *commit) +{ + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(commit, crtc); + int ret; + + if (crtc_state->enable) { + ret = drm_atomic_helper_check_crtc_primary_plane(crtc_state); + if (ret) + return ret; + } + + return drm_atomic_add_affected_planes(commit, crtc); +} + +static const struct drm_crtc_helper_funcs gm12u320_crtc_helper_funcs = { + .atomic_check = gm12u320_crtc_helper_atomic_check, + .atomic_enable = gm12u320_crtc_helper_atomic_enable, + .atomic_disable = gm12u320_crtc_helper_atomic_disable, +}; + +static const struct drm_crtc_funcs gm12u320_crtc_funcs = { + .set_config = drm_atomic_helper_set_config, + .page_flip = drm_atomic_helper_page_flip, + .reset = drm_atomic_helper_crtc_reset, + .destroy = drm_crtc_cleanup, + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, +}; + +static const struct drm_encoder_funcs gm12u320_encoder_funcs = { + .destroy = drm_encoder_cleanup, }; static const uint32_t gm12u320_pipe_formats[] = { @@ -678,13 +746,29 @@ static int gm12u320_usb_probe(struct usb_interface *interface, if (ret) return ret; - ret = drm_simple_display_pipe_init(&gm12u320->dev, - &gm12u320->pipe, - &gm12u320_pipe_funcs, - gm12u320_pipe_formats, - ARRAY_SIZE(gm12u320_pipe_formats), - gm12u320_pipe_modifiers, - &gm12u320->conn); + ret = drm_universal_plane_init(dev, &gm12u320->plane, 0, + &gm12u320_plane_funcs, + gm12u320_pipe_formats, + ARRAY_SIZE(gm12u320_pipe_formats), + gm12u320_pipe_modifiers, + DRM_PLANE_TYPE_PRIMARY, NULL); + if (ret) + return ret; + drm_plane_helper_add(&gm12u320->plane, &gm12u320_plane_helper_funcs); + + ret = drm_crtc_init_with_planes(dev, &gm12u320->crtc, &gm12u320->plane, NULL, + &gm12u320_crtc_funcs, NULL); + if (ret) + return ret; + drm_crtc_helper_add(&gm12u320->crtc, &gm12u320_crtc_helper_funcs); + + ret = drm_encoder_init(dev, &gm12u320->encoder, &gm12u320_encoder_funcs, + DRM_MODE_ENCODER_NONE, NULL); + if (ret) + return ret; + gm12u320->encoder.possible_crtcs = drm_crtc_mask(&gm12u320->crtc); + + ret = drm_connector_attach_encoder(&gm12u320->conn, &gm12u320->encoder); if (ret) return ret; From 7e5d178cbc47b1df2f0b5c1865973534fbc4460b Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Fri, 4 Sep 2026 11:07:19 +0200 Subject: [PATCH 009/143] drm/crtc: Introduce hw_reset helper hook The CRTC reset hook conflates initial software state allocation with hardware reset. The atomic_create_state hook addresses the software state side, but drivers may still need to reset hardware to a known good state during drm_mode_config_reset(), for example during suspend/resume. Separating hardware reset from state allocation is also useful for the pending atomic state readout and userspace atomic reset flag series, which need to create pristine software state without affecting the hardware. Introduce a hw_reset hook in struct drm_crtc_helper_funcs that only resets the hardware, without touching the software state at all. Call it from drm_mode_config_crtc_reset_with_create_state() after the state has been successfully created. Reviewed-by: Thomas Zimmermann Reviewed-by: Javier Martinez Canillas Link: https://lore.kernel.org/r/20260904-drm-no-more-crtc-reset-v3-1-27d9891512ce@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/drm_mode_config.c | 13 ++++++++++++- include/drm/drm_modeset_helper_vtables.h | 14 ++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/drm_mode_config.c b/drivers/gpu/drm/drm_mode_config.c index 366f6d821242..f95cd9695b43 100644 --- a/drivers/gpu/drm/drm_mode_config.c +++ b/drivers/gpu/drm/drm_mode_config.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -230,12 +231,22 @@ static int drm_mode_config_crtc_create_state(struct drm_crtc *crtc) static int drm_mode_config_crtc_reset_with_create_state(struct drm_crtc *crtc) { + const struct drm_crtc_helper_funcs *helper_funcs = crtc->helper_private; + int ret; + if (crtc->state) { crtc->funcs->atomic_destroy_state(crtc, crtc->state); crtc->state = NULL; } - return drm_mode_config_crtc_create_state(crtc); + ret = drm_mode_config_crtc_create_state(crtc); + if (ret) + return ret; + + if (helper_funcs && helper_funcs->hw_reset) + helper_funcs->hw_reset(crtc); + + return 0; } static int drm_mode_config_connector_create_state(struct drm_connector *connector) diff --git a/include/drm/drm_modeset_helper_vtables.h b/include/drm/drm_modeset_helper_vtables.h index ca6268945c28..4965356a6314 100644 --- a/include/drm/drm_modeset_helper_vtables.h +++ b/include/drm/drm_modeset_helper_vtables.h @@ -281,6 +281,20 @@ struct drm_crtc_helper_funcs { */ void (*disable)(struct drm_crtc *crtc); + /** + * @hw_reset: + * + * Optional hook for CRTC hardware reset. + * + * Unlike @drm_crtc_funcs.reset, which both resets hardware and + * creates new software state, this hook only resets the + * hardware to a known good state without touching the software + * state at all. + * + * This hook is called by drm_mode_config_reset(). + */ + void (*hw_reset)(struct drm_crtc *crtc); + /** * @atomic_check: * From be34074931aa1bbd90685a90b9a12d74dbad5622 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Fri, 4 Sep 2026 11:07:20 +0200 Subject: [PATCH 010/143] drm/amdgpu: vkms: Switch to drm_atomic_helper_crtc_create_state The amdgpu vkms crtc implementation uses the deprecated drm_atomic_helper_crtc_reset() as its reset hook. Switch to drm_atomic_helper_crtc_create_state() instead. Reviewed-by: Thomas Zimmermann Reviewed-by: Javier Martinez Canillas Link: https://lore.kernel.org/r/20260904-drm-no-more-crtc-reset-v3-2-27d9891512ce@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/amd/amdgpu/amdgpu_vkms.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vkms.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vkms.c index c835504fdf2b..592c5c253938 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vkms.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vkms.c @@ -47,7 +47,7 @@ static const struct drm_crtc_funcs amdgpu_vkms_crtc_funcs = { .set_config = drm_atomic_helper_set_config, .destroy = drm_crtc_cleanup, .page_flip = drm_atomic_helper_page_flip, - .reset = drm_atomic_helper_crtc_reset, + .atomic_create_state = drm_atomic_helper_crtc_create_state, .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, DRM_CRTC_VBLANK_TIMER_FUNCS, From 3fe3b4104b4db397fd16e81d49bb56d034cddb2b Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Fri, 4 Sep 2026 11:07:21 +0200 Subject: [PATCH 011/143] drm/logicvc: Switch to drm_atomic_helper_crtc_create_state The logicvc crtc implementation uses the deprecated drm_atomic_helper_crtc_reset() as its reset hook. Switch to drm_atomic_helper_crtc_create_state() instead. Reviewed-by: Thomas Zimmermann Reviewed-by: Javier Martinez Canillas Link: https://lore.kernel.org/r/20260904-drm-no-more-crtc-reset-v3-3-27d9891512ce@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/logicvc/logicvc_crtc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/logicvc/logicvc_crtc.c b/drivers/gpu/drm/logicvc/logicvc_crtc.c index 81e5b80736fa..af92a573c3f2 100644 --- a/drivers/gpu/drm/logicvc/logicvc_crtc.c +++ b/drivers/gpu/drm/logicvc/logicvc_crtc.c @@ -213,10 +213,10 @@ static void logicvc_crtc_disable_vblank(struct drm_crtc *drm_crtc) } static const struct drm_crtc_funcs logicvc_crtc_funcs = { - .reset = drm_atomic_helper_crtc_reset, .destroy = drm_crtc_cleanup, .set_config = drm_atomic_helper_set_config, .page_flip = drm_atomic_helper_page_flip, + .atomic_create_state = drm_atomic_helper_crtc_create_state, .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, .enable_vblank = logicvc_crtc_enable_vblank, From 6e531fda1d6b60e961d7dca5213005d57f4cb94d Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Fri, 4 Sep 2026 11:07:22 +0200 Subject: [PATCH 012/143] drm/tilcdc: Move hardware reset to hw_reset hook The tilcdc crtc reset hook performs both atomic state initialization using drm_atomic_helper_crtc_reset() and hardware-level register writes to disable the raster and clear IRQ status. The hardware reset is not related to atomic state initialization, and the reset hook is being converted to atomic_create_state which only deals with state allocation. Move the hardware reset to the hw_reset helper hook instead. Reviewed-by: Thomas Zimmermann Reviewed-by: Javier Martinez Canillas Link: https://lore.kernel.org/r/20260904-drm-no-more-crtc-reset-v3-4-27d9891512ce@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/tilcdc/tilcdc_crtc.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/tilcdc/tilcdc_crtc.c b/drivers/gpu/drm/tilcdc/tilcdc_crtc.c index e07ecb36a27a..b299d73ce385 100644 --- a/drivers/gpu/drm/tilcdc/tilcdc_crtc.c +++ b/drivers/gpu/drm/tilcdc/tilcdc_crtc.c @@ -685,14 +685,12 @@ static void tilcdc_crtc_disable_vblank(struct drm_crtc *crtc) spin_unlock_irqrestore(&tilcdc_crtc->irq_lock, flags); } -static void tilcdc_crtc_reset(struct drm_crtc *crtc) +static void tilcdc_crtc_hw_reset(struct drm_crtc *crtc) { struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc); struct drm_device *dev = crtc->dev; int ret; - drm_atomic_helper_crtc_reset(crtc); - /* Turn the raster off if it for some reason is on. */ pm_runtime_get_sync(dev->dev); if (tilcdc_read(dev, LCDC_RASTER_CTRL_REG) & LCDC_RASTER_ENABLE) { @@ -716,7 +714,7 @@ static void tilcdc_crtc_reset(struct drm_crtc *crtc) static const struct drm_crtc_funcs tilcdc_crtc_funcs = { .set_config = drm_atomic_helper_set_config, .page_flip = drm_atomic_helper_page_flip, - .reset = tilcdc_crtc_reset, + .reset = drm_atomic_helper_crtc_reset, .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, .enable_vblank = tilcdc_crtc_enable_vblank, @@ -820,6 +818,7 @@ static const struct drm_crtc_helper_funcs tilcdc_crtc_helper_funcs = { .atomic_enable = tilcdc_crtc_atomic_enable, .atomic_disable = tilcdc_crtc_atomic_disable, .atomic_flush = tilcdc_crtc_atomic_flush, + .hw_reset = tilcdc_crtc_hw_reset, }; void tilcdc_crtc_update_clk(struct drm_crtc *crtc) From 0470f2047ed498bf4ebbc0351032d539f43f32ad Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Fri, 4 Sep 2026 11:07:23 +0200 Subject: [PATCH 013/143] drm/tilcdc: Switch to drm_atomic_helper_crtc_create_state The tilcdc crtc implementation uses the deprecated drm_atomic_helper_crtc_reset() as its reset hook. Switch to drm_atomic_helper_crtc_create_state() instead. Reviewed-by: Thomas Zimmermann Reviewed-by: Javier Martinez Canillas Link: https://lore.kernel.org/r/20260904-drm-no-more-crtc-reset-v3-5-27d9891512ce@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/tilcdc/tilcdc_crtc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/tilcdc/tilcdc_crtc.c b/drivers/gpu/drm/tilcdc/tilcdc_crtc.c index b299d73ce385..f619b7a6e70d 100644 --- a/drivers/gpu/drm/tilcdc/tilcdc_crtc.c +++ b/drivers/gpu/drm/tilcdc/tilcdc_crtc.c @@ -714,7 +714,7 @@ static void tilcdc_crtc_hw_reset(struct drm_crtc *crtc) static const struct drm_crtc_funcs tilcdc_crtc_funcs = { .set_config = drm_atomic_helper_set_config, .page_flip = drm_atomic_helper_page_flip, - .reset = drm_atomic_helper_crtc_reset, + .atomic_create_state = drm_atomic_helper_crtc_create_state, .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, .enable_vblank = tilcdc_crtc_enable_vblank, From 32fbec62a3e9f00a558f2a0fc9bd1bc0e7d9d0fb Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Fri, 4 Sep 2026 11:07:25 +0200 Subject: [PATCH 014/143] drm/amdgpu: dm: Convert to atomic_create_state The amdgpu display manager crtc implementation provides a custom reset hook. However, this hook only allocates the state, initializes it with __drm_atomic_helper_crtc_reset(), and frees the previous state. It does not perform any hardware reset. Since this is exactly what the atomic_create_state hook is meant to do, minus the old state cleanup which the caller handles, convert the implementation to use atomic_create_state with __drm_atomic_helper_crtc_state_init() instead. Reviewed-by: Thomas Zimmermann Reviewed-by: Leo Li Reviewed-by: Javier Martinez Canillas Link: https://lore.kernel.org/r/20260904-drm-no-more-crtc-reset-v3-7-27d9891512ce@kernel.org Signed-off-by: Maxime Ripard --- .../amd/display/amdgpu_dm/amdgpu_dm_crtc.c | 31 +++++++++++++------ .../amd/display/amdgpu_dm/amdgpu_dm_crtc.h | 2 +- .../amdgpu_dm/tests/amdgpu_dm_crtc_test.c | 23 +++++++------- 3 files changed, 33 insertions(+), 23 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c index 62eac6e65334..53910056da20 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c @@ -475,20 +475,19 @@ static void amdgpu_dm_crtc_destroy(struct drm_crtc *crtc) kfree(crtc); } -STATIC_IFN_KUNIT void amdgpu_dm_crtc_reset_state(struct drm_crtc *crtc) +STATIC_IFN_KUNIT struct drm_crtc_state *amdgpu_dm_crtc_create_state(struct drm_crtc *crtc) { struct dm_crtc_state *state; state = kzalloc_obj(*state); if (!state) - return; + return ERR_PTR(-ENOMEM); - if (crtc->state) - amdgpu_dm_crtc_destroy_state(crtc, crtc->state); + __drm_atomic_helper_crtc_state_init(&state->base, crtc); - __drm_atomic_helper_crtc_reset(crtc, &state->base); + return &state->base; } -EXPORT_IF_KUNIT(amdgpu_dm_crtc_reset_state); +EXPORT_IF_KUNIT(amdgpu_dm_crtc_create_state); #ifdef CONFIG_DEBUG_FS static int amdgpu_dm_crtc_late_register(struct drm_crtc *crtc) @@ -565,7 +564,7 @@ amdgpu_dm_atomic_crtc_get_property(struct drm_crtc *crtc, /* Implemented only the options currently available for the driver */ static const struct drm_crtc_funcs amdgpu_dm_crtc_funcs = { - .reset = amdgpu_dm_crtc_reset_state, + .atomic_create_state = amdgpu_dm_crtc_create_state, .destroy = amdgpu_dm_crtc_destroy, .set_config = drm_atomic_helper_set_config, .page_flip = drm_atomic_helper_page_flip, @@ -781,9 +780,18 @@ int amdgpu_dm_crtc_init(struct amdgpu_display_manager *dm, drm_crtc_helper_add(&acrtc->base, &amdgpu_dm_crtc_helper_funcs); - /* Create (reset) the plane state */ - if (acrtc->base.funcs->reset) - acrtc->base.funcs->reset(&acrtc->base); + /* Create the plane state */ + if (acrtc->base.funcs->atomic_create_state) { + struct drm_crtc_state *crtc_state; + + crtc_state = acrtc->base.funcs->atomic_create_state(&acrtc->base); + if (IS_ERR(crtc_state)) { + res = PTR_ERR(crtc_state); + goto error_ism_fini; + } + + acrtc->base.state = crtc_state; + } acrtc->max_cursor_width = dm->adev->dm.dc->caps.max_cursor_size; acrtc->max_cursor_height = dm->adev->dm.dc->caps.max_cursor_size; @@ -815,6 +823,9 @@ int amdgpu_dm_crtc_init(struct amdgpu_display_manager *dm, #endif return 0; +error_ism_fini: + amdgpu_dm_ism_fini(&acrtc->ism); + drm_crtc_cleanup(&acrtc->base); fail: kfree(acrtc); kfree(cursor_plane); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.h index 93c6d0d8d7fd..ad516aeb9798 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.h @@ -49,7 +49,7 @@ bool amdgpu_dm_crtc_helper_mode_fixup(struct drm_crtc *crtc, void amdgpu_dm_crtc_destroy_state(struct drm_crtc *crtc, struct drm_crtc_state *state); struct drm_crtc_state *amdgpu_dm_crtc_duplicate_state(struct drm_crtc *crtc); -void amdgpu_dm_crtc_reset_state(struct drm_crtc *crtc); +struct drm_crtc_state *amdgpu_dm_crtc_create_state(struct drm_crtc *crtc); int amdgpu_dm_crtc_count_crtc_active_planes(struct drm_crtc_state *new_crtc_state); void amdgpu_dm_crtc_update_crtc_active_planes(struct drm_crtc *crtc, struct drm_crtc_state *new_crtc_state); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c index 0d998f204250..17bd64611a82 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c @@ -1404,18 +1404,18 @@ static void dm_test_crtc_duplicate_state_copies_fields(struct kunit *test) amdgpu_dm_crtc_destroy_state(crtc, dup); } -/* Tests for amdgpu_dm_crtc_reset_state() */ +/* Tests for amdgpu_dm_crtc_create_state() */ /** - * dm_test_crtc_reset_state_allocates_state - Test reset installs a fresh state + * dm_test_crtc_create_state_allocates_state - Test create_state allocates a fresh state * @test: The KUnit test context * - * Resetting a CRTC with no existing state must allocate and install a new - * drm_crtc_state. + * Creating state for a CRTC must allocate a new drm_crtc_state. */ -static void dm_test_crtc_reset_state_allocates_state(struct kunit *test) +static void dm_test_crtc_create_state_allocates_state(struct kunit *test) { struct amdgpu_device *adev = dm_kunit_alloc_adev(test); + struct drm_crtc_state *crtc_state; struct drm_crtc *crtc; crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); @@ -1423,12 +1423,11 @@ static void dm_test_crtc_reset_state_allocates_state(struct kunit *test) crtc->dev = &adev->ddev; crtc->state = NULL; - amdgpu_dm_crtc_reset_state(crtc); + crtc_state = amdgpu_dm_crtc_create_state(crtc); + KUNIT_EXPECT_NOT_ERR_OR_NULL(test, crtc_state); - KUNIT_EXPECT_NOT_NULL(test, crtc->state); - - if (crtc->state) - amdgpu_dm_crtc_destroy_state(crtc, crtc->state); + if (!IS_ERR(crtc_state)) + amdgpu_dm_crtc_destroy_state(crtc, crtc_state); } /* Tests for amdgpu_dm_crtc_destroy_state() */ @@ -1907,8 +1906,8 @@ static struct kunit_case amdgpu_dm_crtc_tests[] = { KUNIT_CASE(dm_test_count_crtc_active_planes_mixed), /* amdgpu_dm_crtc_duplicate_state */ KUNIT_CASE(dm_test_crtc_duplicate_state_copies_fields), - /* amdgpu_dm_crtc_reset_state */ - KUNIT_CASE(dm_test_crtc_reset_state_allocates_state), + /* amdgpu_dm_crtc_create_state */ + KUNIT_CASE(dm_test_crtc_create_state_allocates_state), /* amdgpu_dm_crtc_destroy_state */ KUNIT_CASE(dm_test_crtc_destroy_state_no_stream), KUNIT_CASE(dm_test_crtc_destroy_state_releases_stream), From df456aa08b15fd65d5bdb62d5487b46b49423324 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Fri, 4 Sep 2026 11:07:26 +0200 Subject: [PATCH 015/143] drm/loongson: Move hardware reset to hw_reset hook The loongson crtc reset hook performs both atomic state initialization using __drm_atomic_helper_crtc_reset() and hardware-level register writes to reset the CRTC. The hardware reset is not related to atomic state initialization, and the reset hook is being converted to atomic_create_state which only deals with state allocation. Move the hardware reset to the hw_reset helper hook instead. Reviewed-by: Thomas Zimmermann Reviewed-by: Javier Martinez Canillas Link: https://lore.kernel.org/r/20260904-drm-no-more-crtc-reset-v3-8-27d9891512ce@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/loongson/lsdc_crtc.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/loongson/lsdc_crtc.c b/drivers/gpu/drm/loongson/lsdc_crtc.c index 16b8f36a4071..7cca574e3b6a 100644 --- a/drivers/gpu/drm/loongson/lsdc_crtc.c +++ b/drivers/gpu/drm/loongson/lsdc_crtc.c @@ -390,8 +390,6 @@ static const struct lsdc_crtc_hw_ops ls7a2000_crtc_hw_ops[2] = { static void lsdc_crtc_reset(struct drm_crtc *crtc) { - struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc); - const struct lsdc_crtc_hw_ops *ops = lcrtc->hw_ops; struct lsdc_crtc_state *priv_crtc_state; if (crtc->state) @@ -403,9 +401,6 @@ static void lsdc_crtc_reset(struct drm_crtc *crtc) __drm_atomic_helper_crtc_reset(crtc, NULL); else __drm_atomic_helper_crtc_reset(crtc, &priv_crtc_state->base); - - /* Reset the CRTC hardware, this is required for S3 support */ - ops->reset(lcrtc); } static void lsdc_crtc_atomic_destroy_state(struct drm_crtc *crtc, @@ -937,6 +932,15 @@ static bool lsdc_crtc_get_scanout_position(struct drm_crtc *crtc, return true; } +static void lsdc_crtc_hw_reset(struct drm_crtc *crtc) +{ + struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc); + + /* Reset the CRTC hardware, this is required for S3 support */ + if (lcrtc->hw_ops->reset) + lcrtc->hw_ops->reset(lcrtc); +} + static const struct drm_crtc_helper_funcs lsdc_crtc_helper_funcs = { .mode_valid = lsdc_crtc_mode_valid, .mode_set_nofb = lsdc_crtc_mode_set_nofb, @@ -945,6 +949,7 @@ static const struct drm_crtc_helper_funcs lsdc_crtc_helper_funcs = { .atomic_check = lsdc_crtc_helper_atomic_check, .atomic_flush = lsdc_crtc_atomic_flush, .get_scanout_position = lsdc_crtc_get_scanout_position, + .hw_reset = lsdc_crtc_hw_reset, }; int ls7a1000_crtc_init(struct drm_device *ddev, From 4288a0bb6d75c88eead8fcc5396aee8e3668faa6 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Fri, 4 Sep 2026 11:07:27 +0200 Subject: [PATCH 016/143] drm/loongson: Convert to atomic_create_state The loongson crtc implementation provides a custom reset hook. However, this hook only allocates the state, initializes it with __drm_atomic_helper_crtc_reset(), and frees the previous state. It does not perform any hardware reset. Since the atomic_create_state hook is only meant to deal with state allocation and initialization, convert it accordingly. Reviewed-by: Thomas Zimmermann Reviewed-by: Javier Martinez Canillas Link: https://lore.kernel.org/r/20260904-drm-no-more-crtc-reset-v3-9-27d9891512ce@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/loongson/lsdc_crtc.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/loongson/lsdc_crtc.c b/drivers/gpu/drm/loongson/lsdc_crtc.c index 7cca574e3b6a..a47816e3fa81 100644 --- a/drivers/gpu/drm/loongson/lsdc_crtc.c +++ b/drivers/gpu/drm/loongson/lsdc_crtc.c @@ -388,19 +388,18 @@ static const struct lsdc_crtc_hw_ops ls7a2000_crtc_hw_ops[2] = { }, }; -static void lsdc_crtc_reset(struct drm_crtc *crtc) +static struct drm_crtc_state *lsdc_crtc_create_state(struct drm_crtc *crtc) { struct lsdc_crtc_state *priv_crtc_state; - if (crtc->state) - crtc->funcs->atomic_destroy_state(crtc, crtc->state); - priv_crtc_state = kzalloc_obj(*priv_crtc_state); if (!priv_crtc_state) - __drm_atomic_helper_crtc_reset(crtc, NULL); - else - __drm_atomic_helper_crtc_reset(crtc, &priv_crtc_state->base); + return ERR_PTR(-ENOMEM); + + __drm_atomic_helper_crtc_state_init(&priv_crtc_state->base, crtc); + + return &priv_crtc_state->base; } static void lsdc_crtc_atomic_destroy_state(struct drm_crtc *crtc, @@ -707,7 +706,7 @@ static void lsdc_crtc_atomic_print_state(struct drm_printer *p, } static const struct drm_crtc_funcs ls7a1000_crtc_funcs = { - .reset = lsdc_crtc_reset, + .atomic_create_state = lsdc_crtc_create_state, .destroy = drm_crtc_cleanup, .set_config = drm_atomic_helper_set_config, .page_flip = drm_atomic_helper_page_flip, @@ -721,7 +720,7 @@ static const struct drm_crtc_funcs ls7a1000_crtc_funcs = { }; static const struct drm_crtc_funcs ls7a2000_crtc_funcs = { - .reset = lsdc_crtc_reset, + .atomic_create_state = lsdc_crtc_create_state, .destroy = drm_crtc_cleanup, .set_config = drm_atomic_helper_set_config, .page_flip = drm_atomic_helper_page_flip, From c60095d8cd01740b442fd992bb9037f494bf87f2 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Fri, 4 Sep 2026 11:07:28 +0200 Subject: [PATCH 017/143] drm/mediatek: Convert to atomic_create_state The mediatek crtc implementation provides a custom reset hook. However, this hook only allocates the state, initializes it with __drm_atomic_helper_crtc_reset(), and frees the previous state. It does not perform any hardware reset. Since this is exactly what the atomic_create_state hook is meant to do, minus the old state cleanup which the caller handles, convert the implementation to use atomic_create_state with __drm_atomic_helper_crtc_state_init() instead. Reviewed-by: Thomas Zimmermann Reviewed-by: Javier Martinez Canillas Link: https://lore.kernel.org/r/20260904-drm-no-more-crtc-reset-v3-10-27d9891512ce@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/mediatek/mtk_crtc.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/mediatek/mtk_crtc.c b/drivers/gpu/drm/mediatek/mtk_crtc.c index 97e3ff412e6e..2920f3198ef6 100644 --- a/drivers/gpu/drm/mediatek/mtk_crtc.c +++ b/drivers/gpu/drm/mediatek/mtk_crtc.c @@ -150,19 +150,17 @@ static void mtk_crtc_destroy(struct drm_crtc *crtc) drm_crtc_cleanup(crtc); } -static void mtk_crtc_reset(struct drm_crtc *crtc) +static struct drm_crtc_state *mtk_crtc_create_state(struct drm_crtc *crtc) { struct mtk_crtc_state *state; - if (crtc->state) { - __drm_atomic_helper_crtc_destroy_state(crtc->state); - kfree(to_mtk_crtc_state(crtc->state)); - } - crtc->state = NULL; - state = kzalloc_obj(*state); - if (state) - __drm_atomic_helper_crtc_reset(crtc, &state->base); + if (!state) + return ERR_PTR(-ENOMEM); + + __drm_atomic_helper_crtc_state_init(&state->base, crtc); + + return &state->base; } static struct drm_crtc_state *mtk_crtc_duplicate_state(struct drm_crtc *crtc) @@ -886,7 +884,7 @@ static const struct drm_crtc_funcs mtk_crtc_funcs = { .set_config = drm_atomic_helper_set_config, .page_flip = drm_atomic_helper_page_flip, .destroy = mtk_crtc_destroy, - .reset = mtk_crtc_reset, + .atomic_create_state = mtk_crtc_create_state, .atomic_duplicate_state = mtk_crtc_duplicate_state, .atomic_destroy_state = mtk_crtc_destroy_state, .enable_vblank = mtk_crtc_enable_vblank, From 18740b7fe4656ca6133c4e2d1dfa3ff6fee8f026 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Fri, 4 Sep 2026 11:07:29 +0200 Subject: [PATCH 018/143] drm/sitronix: st7920: Convert to atomic_create_state The st7920 crtc implementation provides a custom reset hook. However, this hook only allocates the state, initializes it with __drm_atomic_helper_crtc_reset(), and warns if there's a previous state. It does not perform any hardware reset. Since this is exactly what the atomic_create_state hook is meant to do, minus the old state cleanup which the caller handles, convert the implementation to use atomic_create_state with __drm_atomic_helper_crtc_state_init() instead. Reviewed-by: Thomas Zimmermann Reviewed-by: Iker Pedrosa Reviewed-by: Javier Martinez Canillas Link: https://lore.kernel.org/r/20260904-drm-no-more-crtc-reset-v3-11-27d9891512ce@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/sitronix/st7920.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/sitronix/st7920.c b/drivers/gpu/drm/sitronix/st7920.c index 0a75e9319080..9887ed746ceb 100644 --- a/drivers/gpu/drm/sitronix/st7920.c +++ b/drivers/gpu/drm/sitronix/st7920.c @@ -575,17 +575,17 @@ static void st7920_crtc_atomic_disable(struct drm_crtc *crtc, } /* Called during init to allocate the CRTC's atomic state. */ -static void st7920_crtc_reset(struct drm_crtc *crtc) +static struct drm_crtc_state *st7920_crtc_create_state(struct drm_crtc *crtc) { struct st7920_crtc_state *st7920_state; - drm_WARN_ON_ONCE(crtc->dev, crtc->state); - st7920_state = kzalloc_obj(*st7920_state); if (!st7920_state) - return; + return ERR_PTR(-ENOMEM); - __drm_atomic_helper_crtc_reset(crtc, &st7920_state->base); + __drm_atomic_helper_crtc_state_init(&st7920_state->base, crtc); + + return &st7920_state->base; } static struct drm_crtc_state *st7920_crtc_duplicate_state(struct drm_crtc *crtc) @@ -629,7 +629,7 @@ static const struct drm_crtc_helper_funcs st7920_crtc_helper_funcs = { }; static const struct drm_crtc_funcs st7920_crtc_funcs = { - .reset = st7920_crtc_reset, + .atomic_create_state = st7920_crtc_create_state, .destroy = drm_crtc_cleanup, .set_config = drm_atomic_helper_set_config, .page_flip = drm_atomic_helper_page_flip, From 38d8b5a35367246dee147d48ea6d5cd6644de5d5 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Tue, 8 Sep 2026 16:35:02 +0200 Subject: [PATCH 019/143] drm/atomic: colorop: Rename state to state_to_destroy The atomic state tracking structures used to have a generic state field to track the state to free when tearing down the drm_atomic_commit. It has since been renamed to state_to_destroy in __drm_planes_state, __drm_crtcs_state, __drm_connnectors_state, and __drm_private_objs_state to better describe its purpose. The colorop support has been added after that rename, but __drm_colorops_state still uses the old state name. Rename it to state_to_destroy for consistency, and add the matching kerneldoc. Reviewed-by: Thomas Zimmermann Link: https://patch.msgid.link/20260908-drm-reset-state-flag-v3-1-905570f387a4@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/drm_atomic.c | 6 +++--- drivers/gpu/drm/drm_atomic_helper.c | 2 +- include/drm/drm_atomic.h | 18 +++++++++++++++++- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c index 45df6376f408..5c0f7b7024d1 100644 --- a/drivers/gpu/drm/drm_atomic.c +++ b/drivers/gpu/drm/drm_atomic.c @@ -343,9 +343,9 @@ void drm_atomic_commit_default_clear(struct drm_atomic_commit *state) continue; drm_colorop_atomic_destroy_state(colorop, - state->colorops[i].state); + state->colorops[i].state_to_destroy); state->colorops[i].ptr = NULL; - state->colorops[i].state = NULL; + state->colorops[i].state_to_destroy = NULL; state->colorops[i].old_state = NULL; state->colorops[i].new_state = NULL; } @@ -708,7 +708,7 @@ drm_atomic_get_colorop_state(struct drm_atomic_commit *state, if (!colorop_state) return ERR_PTR(-ENOMEM); - state->colorops[index].state = colorop_state; + state->colorops[index].state_to_destroy = colorop_state; state->colorops[index].ptr = colorop; state->colorops[index].old_state = colorop->state; state->colorops[index].new_state = colorop_state; diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c index 285aac3554df..c4752bd7d999 100644 --- a/drivers/gpu/drm/drm_atomic_helper.c +++ b/drivers/gpu/drm/drm_atomic_helper.c @@ -3351,7 +3351,7 @@ int drm_atomic_helper_swap_state(struct drm_atomic_commit *state, old_colorop_state->state = state; new_colorop_state->state = NULL; - state->colorops[i].state = old_colorop_state; + state->colorops[i].state_to_destroy = old_colorop_state; colorop->state = new_colorop_state; } diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h index 88087910ab1a..00b3e9fc429a 100644 --- a/include/drm/drm_atomic.h +++ b/include/drm/drm_atomic.h @@ -159,7 +159,23 @@ struct drm_crtc_commit { struct __drm_colorops_state { struct drm_colorop *ptr; - struct drm_colorop_state *state, *old_state, *new_state; + + /** + * @state_to_destroy: + * + * Used to track the @drm_colorop_state we will need to free + * when tearing down the associated &drm_atomic_commit in + * $drm_mode_config_funcs.atomic_state_clear or + * drm_atomic_commit_default_clear(). + * + * Before a commit, and the call to + * drm_atomic_helper_swap_state() in particular, it points to + * the same state than @new_state. After a commit, it points to + * the same state than @old_state. + */ + struct drm_colorop_state *state_to_destroy; + + struct drm_colorop_state *old_state, *new_state; }; struct __drm_planes_state { From 138e7ff66a12ca62038ab26a24137000ac831308 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Thu, 9 Jul 2026 11:15:57 +0200 Subject: [PATCH 020/143] drm/edid: Include We currently get via . Include it explicitly. Signed-off-by: Thomas Zimmermann Acked-by: Maxime Ripard Acked-by: Helge Deller Link: https://patch.msgid.link/20260709092215.168172-2-tzimmermann@suse.de --- drivers/gpu/drm/drm_edid.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index 1331efb4876c..3148038747b6 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -32,6 +32,7 @@ #include #include #include +#include /* for KHZ2PICOS() */ #include #include #include From 4e089d048a4f169718934601b126c447ce6ef9c1 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Thu, 9 Jul 2026 11:15:58 +0200 Subject: [PATCH 021/143] drm/client: Add acquire_outputs callback; implement for fbdev emulation Add the callback acquire_outputs to drm_client_funcs to inform an internal DRM client that vga-switcheroo is about to switch the physical outputs to the client's device. Allows the client to prepare its internal state for the upcoming switch. Wire up the DRM client helpers to invoke the helper for a device's clients. Implement acquire_outputs for fbdev emulation. Invoke fb_switch_outputs(), which remaps framebuffers to virtual terminals in fbcon. Currently this is still being done by vga-switcheroo. With more DRM clients becoming available, vga-switcheroo needs to become client agonostic. Signed-off-by: Thomas Zimmermann Acked-by: Maxime Ripard Acked-by: Helge Deller Link: https://patch.msgid.link/20260709092215.168172-3-tzimmermann@suse.de --- drivers/gpu/drm/clients/drm_fbdev_client.c | 23 +++++++++++++++------- drivers/gpu/drm/drm_client_event.c | 18 +++++++++++++++++ include/drm/drm_client.h | 14 +++++++++++++ include/drm/drm_client_event.h | 3 +++ 4 files changed, 51 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/clients/drm_fbdev_client.c b/drivers/gpu/drm/clients/drm_fbdev_client.c index 91d196a397cf..827f32668714 100644 --- a/drivers/gpu/drm/clients/drm_fbdev_client.c +++ b/drivers/gpu/drm/clients/drm_fbdev_client.c @@ -47,6 +47,14 @@ static int drm_fbdev_client_restore(struct drm_client_dev *client, bool force) return 0; } +static void drm_fbdev_client_acquire_outputs(struct drm_client_dev *client) +{ + struct drm_fb_helper *fb_helper = drm_fb_helper_from_client(client); + + if (fb_helper->info) + fb_switch_outputs(fb_helper->info); +} + static int drm_fbdev_client_hotplug(struct drm_client_dev *client) { struct drm_fb_helper *fb_helper = drm_fb_helper_from_client(client); @@ -95,13 +103,14 @@ static int drm_fbdev_client_resume(struct drm_client_dev *client) } static const struct drm_client_funcs drm_fbdev_client_funcs = { - .owner = THIS_MODULE, - .free = drm_fbdev_client_free, - .unregister = drm_fbdev_client_unregister, - .restore = drm_fbdev_client_restore, - .hotplug = drm_fbdev_client_hotplug, - .suspend = drm_fbdev_client_suspend, - .resume = drm_fbdev_client_resume, + .owner = THIS_MODULE, + .free = drm_fbdev_client_free, + .unregister = drm_fbdev_client_unregister, + .restore = drm_fbdev_client_restore, + .acquire_outputs = drm_fbdev_client_acquire_outputs, + .hotplug = drm_fbdev_client_hotplug, + .suspend = drm_fbdev_client_suspend, + .resume = drm_fbdev_client_resume, }; /** diff --git a/drivers/gpu/drm/drm_client_event.c b/drivers/gpu/drm/drm_client_event.c index 7b3e362f7926..f0af584da23c 100644 --- a/drivers/gpu/drm/drm_client_event.c +++ b/drivers/gpu/drm/drm_client_event.c @@ -123,6 +123,24 @@ void drm_client_dev_restore(struct drm_device *dev, bool force) mutex_unlock(&dev->clientlist_mutex); } +void drm_client_dev_acquire_outputs(struct drm_device *dev) +{ + struct drm_client_dev *client; + + if (!drm_core_check_feature(dev, DRIVER_MODESET)) + return; + + mutex_lock(&dev->clientlist_mutex); + list_for_each_entry(client, &dev->clientlist, list) { + if (!client->funcs || !client->funcs->acquire_outputs) + continue; + + client->funcs->acquire_outputs(client); + } + mutex_unlock(&dev->clientlist_mutex); +} +EXPORT_SYMBOL(drm_client_dev_acquire_outputs); + static int drm_client_suspend(struct drm_client_dev *client) { struct drm_device *dev = client->dev; diff --git a/include/drm/drm_client.h b/include/drm/drm_client.h index 49a21f3dcb36..10a0cae3e48f 100644 --- a/include/drm/drm_client.h +++ b/include/drm/drm_client.h @@ -66,6 +66,20 @@ struct drm_client_funcs { */ int (*restore)(struct drm_client_dev *client, bool force); + /** + * @acquire_outputs: + * + * Called by vga-switcheroo. Informs the client that the outputs will + * be switched to its device. When @acquire_outputs runs, the outputs + * have not been switched yet. The client should only prepare the software + * state. After the switch happened, the client might get a hotplug + * event to update the hardware state. + * + * This callback exists for remapping framebuffers to virtual terminals + * in fbcon. + */ + void (*acquire_outputs)(struct drm_client_dev *client); + /** * @hotplug: * diff --git a/include/drm/drm_client_event.h b/include/drm/drm_client_event.h index 79369c755bc9..c93f404bae1d 100644 --- a/include/drm/drm_client_event.h +++ b/include/drm/drm_client_event.h @@ -11,6 +11,7 @@ struct drm_device; void drm_client_dev_unregister(struct drm_device *dev); void drm_client_dev_hotplug(struct drm_device *dev); void drm_client_dev_restore(struct drm_device *dev, bool force); +void drm_client_dev_acquire_outputs(struct drm_device *dev); void drm_client_dev_suspend(struct drm_device *dev); void drm_client_dev_resume(struct drm_device *dev); #else @@ -20,6 +21,8 @@ static inline void drm_client_dev_hotplug(struct drm_device *dev) { } static inline void drm_client_dev_restore(struct drm_device *dev, bool force) { } +static inline void drm_client_dev_acquire_outputs(struct drm_device *dev) +{ } static inline void drm_client_dev_suspend(struct drm_device *dev) { } static inline void drm_client_dev_resume(struct drm_device *dev) From bd33ce60cf1c5d3c055a00b92e8141a22f5ec2d7 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Thu, 9 Jul 2026 11:15:59 +0200 Subject: [PATCH 022/143] vga_switcheroo: Add pre_switch callback to client ops Add pre_switch to struct vga_switcheroo_client_ops to inform the switcheroo client about upcoming switches of the outputs. This callback is intended to replace the hard-coded call to fbdev's fb_switch_outputs(). With DRM supporting more clients than just fbdev emulation, something more flexible is required. v2: - remove non-sensical gpu_bound documentation (Sashiko) Signed-off-by: Thomas Zimmermann Acked-by: Maxime Ripard Acked-by: Helge Deller Link: https://patch.msgid.link/20260709092215.168172-4-tzimmermann@suse.de --- drivers/gpu/vga/vga_switcheroo.c | 4 +++- include/linux/vga_switcheroo.h | 11 ++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/vga/vga_switcheroo.c b/drivers/gpu/vga/vga_switcheroo.c index 22cf52b78b75..bdf1e56ae891 100644 --- a/drivers/gpu/vga/vga_switcheroo.c +++ b/drivers/gpu/vga/vga_switcheroo.c @@ -733,8 +733,10 @@ static int vga_switchto_stage2(struct vga_switcheroo_client *new_client) if (!active->driver_power_control) set_audio_state(active->id, VGA_SWITCHEROO_OFF); + if (new_client->ops->pre_switch) + new_client->ops->pre_switch(new_client->pdev); #if defined(CONFIG_FB) - if (new_client->fb_info) + else if (new_client->fb_info) fb_switch_outputs(new_client->fb_info); #endif diff --git a/include/linux/vga_switcheroo.h b/include/linux/vga_switcheroo.h index 7e6ac0114d55..4422daca9ceb 100644 --- a/include/linux/vga_switcheroo.h +++ b/include/linux/vga_switcheroo.h @@ -133,17 +133,22 @@ struct vga_switcheroo_handler { * @can_switch: check if the device is in a position to switch now. * Mandatory. The client should return false if a user space process * has one of its device files open + * @pre_switch: prepare switch + * Optional. This gets called before switching the outputs to the + * GPU. Allows drivers to prepare for the switch. * @gpu_bound: notify the client id to audio client when the GPU is bound. * * Client callbacks. A client can be either a GPU or an audio device on a GPU. - * The @set_gpu_state and @can_switch methods are mandatory, @reprobe may be - * set to NULL. For audio clients, the @reprobe member is bogus. - * OTOH, @gpu_bound is only for audio clients, and not used for GPU clients. + * The @set_gpu_state and @can_switch methods are mandatory, @pre_switch and + * @reprobe may be set to NULL. For audio clients, the @pre_switch and + * @reprobe members are bogus. OTOH, @gpu_bound is only for audio clients, + * and not used for GPU clients. */ struct vga_switcheroo_client_ops { void (*set_gpu_state)(struct pci_dev *dev, enum vga_switcheroo_state); void (*reprobe)(struct pci_dev *dev); bool (*can_switch)(struct pci_dev *dev); + void (*pre_switch)(struct pci_dev *dev); void (*gpu_bound)(struct pci_dev *dev, enum vga_switcheroo_client_id); }; From 649e96efe2caadb438c980459011a7f77e4d4959 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Thu, 9 Jul 2026 11:16:00 +0200 Subject: [PATCH 023/143] vga_switcheroo: Add post_switch callback to client ops Add post_switch to struct vga_switcheroo_client_ops to inform the switcheroo client about a completed switch of the output. This callback is intended to replace the reprobe client op. It is a rename of reprobe for consistency with pre_switch. Signed-off-by: Thomas Zimmermann Acked-by: Maxime Ripard Acked-by: Helge Deller Link: https://patch.msgid.link/20260709092215.168172-5-tzimmermann@suse.de --- drivers/gpu/vga/vga_switcheroo.c | 4 +++- include/linux/vga_switcheroo.h | 12 +++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/vga/vga_switcheroo.c b/drivers/gpu/vga/vga_switcheroo.c index bdf1e56ae891..7b0af4a8aa7d 100644 --- a/drivers/gpu/vga/vga_switcheroo.c +++ b/drivers/gpu/vga/vga_switcheroo.c @@ -746,7 +746,9 @@ static int vga_switchto_stage2(struct vga_switcheroo_client *new_client) if (ret) return ret; - if (new_client->ops->reprobe) + if (new_client->ops->post_switch) + new_client->ops->post_switch(new_client->pdev); + else if (new_client->ops->reprobe) new_client->ops->reprobe(new_client->pdev); if (vga_switcheroo_pwr_state(active) == VGA_SWITCHEROO_ON) diff --git a/include/linux/vga_switcheroo.h b/include/linux/vga_switcheroo.h index 4422daca9ceb..51851831d4c1 100644 --- a/include/linux/vga_switcheroo.h +++ b/include/linux/vga_switcheroo.h @@ -127,21 +127,22 @@ struct vga_switcheroo_handler { * @set_gpu_state: do the equivalent of suspend/resume for the card. * Mandatory. This should not cut power to the discrete GPU, * which is the job of the handler - * @reprobe: poll outputs. - * Optional. This gets called after waking the GPU and switching - * the outputs to it + * @reprobe: deprecated * @can_switch: check if the device is in a position to switch now. * Mandatory. The client should return false if a user space process * has one of its device files open * @pre_switch: prepare switch * Optional. This gets called before switching the outputs to the * GPU. Allows drivers to prepare for the switch. + * @post_switch: completes switch + * Optional. This gets called after waking the GPU and switching + * the outputs to it. Allows drivers to poll the switched outputs. * @gpu_bound: notify the client id to audio client when the GPU is bound. * * Client callbacks. A client can be either a GPU or an audio device on a GPU. * The @set_gpu_state and @can_switch methods are mandatory, @pre_switch and - * @reprobe may be set to NULL. For audio clients, the @pre_switch and - * @reprobe members are bogus. OTOH, @gpu_bound is only for audio clients, + * @post_switch may be set to NULL. For audio clients, the @pre_switch and + * @post_switch members are bogus. OTOH, @gpu_bound is only for audio clients, * and not used for GPU clients. */ struct vga_switcheroo_client_ops { @@ -149,6 +150,7 @@ struct vga_switcheroo_client_ops { void (*reprobe)(struct pci_dev *dev); bool (*can_switch)(struct pci_dev *dev); void (*pre_switch)(struct pci_dev *dev); + void (*post_switch)(struct pci_dev *dev); void (*gpu_bound)(struct pci_dev *dev, enum vga_switcheroo_client_id); }; From 0fbd06de08c18e37af0b20fc37be1fe108f1b881 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Thu, 9 Jul 2026 11:16:01 +0200 Subject: [PATCH 024/143] drm: Implement struct vga_switcheroo_client_ops.pre_switch Call drm_client_dev_acquire_outputs() from vga_switcheroo's pre_switch callback. Pushes fbcon updates from vga_switcheroo into DRM's fbdev emulation. This affects amdgpu, i915, nouveau and radeon. No other drivers implement vga_switcheroo. Also remove the calls to vga_switcheroo_client_fb_set() from fbcon. It is called from the DRM client's hotplug and sets the fbcon's framebuffer at vga_switcheroo. Running pre_switch and hotplug concurrently could result in a deadlock between clientlist_mutex and vgasr_mutex. Hence clean up fbcon here as well. Signed-off-by: Thomas Zimmermann Reviewed-by: Lyude Paul # nouveau Acked-by: Maxime Ripard Acked-by: Helge Deller Link: https://patch.msgid.link/20260709092215.168172-6-tzimmermann@suse.de --- drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 8 ++++++++ drivers/gpu/drm/i915/i915_switcheroo.c | 10 ++++++++++ drivers/gpu/drm/nouveau/nouveau_vga.c | 9 +++++++++ drivers/gpu/drm/radeon/radeon_device.c | 8 ++++++++ drivers/video/fbdev/core/fbcon.c | 8 -------- 5 files changed, 35 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index 104d1d2cbad9..0c8a94a22192 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -1734,10 +1734,18 @@ static bool amdgpu_switcheroo_can_switch(struct pci_dev *pdev) return atomic_read(&dev->open_count) == 0; } +static void amdgpu_switcheroo_pre_switch(struct pci_dev *pdev) +{ + struct drm_device *dev = pci_get_drvdata(pdev); + + drm_client_dev_acquire_outputs(dev); +} + static const struct vga_switcheroo_client_ops amdgpu_switcheroo_ops = { .set_gpu_state = amdgpu_switcheroo_set_state, .reprobe = NULL, .can_switch = amdgpu_switcheroo_can_switch, + .pre_switch = amdgpu_switcheroo_pre_switch, }; /** diff --git a/drivers/gpu/drm/i915/i915_switcheroo.c b/drivers/gpu/drm/i915/i915_switcheroo.c index 7e0791024282..6b306ece0556 100644 --- a/drivers/gpu/drm/i915/i915_switcheroo.c +++ b/drivers/gpu/drm/i915/i915_switcheroo.c @@ -5,6 +5,7 @@ #include +#include #include #include "display/intel_display_device.h" @@ -58,10 +59,19 @@ static bool i915_switcheroo_can_switch(struct pci_dev *pdev) atomic_read(&i915->drm.open_count) == 0; } +static void i915_switcheroo_pre_switch(struct pci_dev *pdev) +{ + struct drm_i915_private *i915 = pdev_to_i915(pdev); + + if (i915 && intel_display_device_present(i915->display)) + drm_client_dev_acquire_outputs(&i915->drm); +} + static const struct vga_switcheroo_client_ops i915_switcheroo_ops = { .set_gpu_state = i915_switcheroo_set_state, .reprobe = NULL, .can_switch = i915_switcheroo_can_switch, + .pre_switch = i915_switcheroo_pre_switch, }; int i915_switcheroo_register(struct drm_i915_private *i915) diff --git a/drivers/gpu/drm/nouveau/nouveau_vga.c b/drivers/gpu/drm/nouveau/nouveau_vga.c index a6c375a24154..2d2d08be8fbe 100644 --- a/drivers/gpu/drm/nouveau/nouveau_vga.c +++ b/drivers/gpu/drm/nouveau/nouveau_vga.c @@ -76,11 +76,20 @@ nouveau_switcheroo_can_switch(struct pci_dev *pdev) return atomic_read(&drm->dev->open_count) == 0; } +static void +nouveau_switcheroo_pre_switch(struct pci_dev *pdev) +{ + struct nouveau_drm *drm = pci_get_drvdata(pdev); + + drm_client_dev_acquire_outputs(drm->dev); +} + static const struct vga_switcheroo_client_ops nouveau_switcheroo_ops = { .set_gpu_state = nouveau_switcheroo_set_state, .reprobe = nouveau_switcheroo_reprobe, .can_switch = nouveau_switcheroo_can_switch, + .pre_switch = nouveau_switcheroo_pre_switch, }; void diff --git a/drivers/gpu/drm/radeon/radeon_device.c b/drivers/gpu/drm/radeon/radeon_device.c index 705c012fcf9e..8697a9eb5d13 100644 --- a/drivers/gpu/drm/radeon/radeon_device.c +++ b/drivers/gpu/drm/radeon/radeon_device.c @@ -1258,10 +1258,18 @@ static bool radeon_switcheroo_can_switch(struct pci_dev *pdev) return atomic_read(&dev->open_count) == 0; } +static void radeon_switcheroo_pre_switch(struct pci_dev *pdev) +{ + struct drm_device *dev = pci_get_drvdata(pdev); + + drm_client_dev_acquire_outputs(dev); +} + static const struct vga_switcheroo_client_ops radeon_switcheroo_ops = { .set_gpu_state = radeon_switcheroo_set_state, .reprobe = NULL, .can_switch = radeon_switcheroo_can_switch, + .pre_switch = radeon_switcheroo_pre_switch, }; /** diff --git a/drivers/video/fbdev/core/fbcon.c b/drivers/video/fbdev/core/fbcon.c index 23b3c536d53d..19b79fc8590b 100644 --- a/drivers/video/fbdev/core/fbcon.c +++ b/drivers/video/fbdev/core/fbcon.c @@ -78,7 +78,6 @@ #include #include /* For counting font checksums */ #include -#include #include #include "fbcon.h" @@ -2876,9 +2875,6 @@ void fbcon_fb_unregistered(struct fb_info *info) console_lock(); - if (info->device && dev_is_pci(info->device)) - vga_switcheroo_client_fb_set(to_pci_dev(info->device), NULL); - fbcon_registered_fb[info->node] = NULL; fbcon_num_registered_fb--; @@ -3012,10 +3008,6 @@ static int do_fb_registered(struct fb_info *info) } } - /* Set the fb info for vga_switcheroo clients. Does nothing otherwise. */ - if (info->device && dev_is_pci(info->device)) - vga_switcheroo_client_fb_set(to_pci_dev(info->device), info); - return ret; } From c9f2c30565da1bdb58064684d461b432e4384777 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Thu, 9 Jul 2026 11:16:02 +0200 Subject: [PATCH 025/143] drm: Implement vga_switcheroo_client_ops.post_switch An output's attached display might have changed while a DRM client's device did not have the output switched to it. The nouveau_switcheroo_reprobe() callback sends a hotplug notice to DRM's internal clients, so that they can reconfigure their display output if necessary. As post_switch callback replaces reprobe in vga_switcheroo, update nouveau accordingly. In the other drivers, remove the NULL-assignment to reprobe. No functional changes. Signed-off-by: Thomas Zimmermann Reviewed-by: Lyude Paul # nouveau Acked-by: Maxime Ripard Acked-by: Helge Deller Link: https://patch.msgid.link/20260709092215.168172-7-tzimmermann@suse.de --- drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 1 - drivers/gpu/drm/i915/i915_switcheroo.c | 1 - drivers/gpu/drm/nouveau/nouveau_vga.c | 19 +++++++++---------- drivers/gpu/drm/radeon/radeon_device.c | 1 - 4 files changed, 9 insertions(+), 13 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index 0c8a94a22192..62b5b4a23f17 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -1743,7 +1743,6 @@ static void amdgpu_switcheroo_pre_switch(struct pci_dev *pdev) static const struct vga_switcheroo_client_ops amdgpu_switcheroo_ops = { .set_gpu_state = amdgpu_switcheroo_set_state, - .reprobe = NULL, .can_switch = amdgpu_switcheroo_can_switch, .pre_switch = amdgpu_switcheroo_pre_switch, }; diff --git a/drivers/gpu/drm/i915/i915_switcheroo.c b/drivers/gpu/drm/i915/i915_switcheroo.c index 6b306ece0556..d97057722fde 100644 --- a/drivers/gpu/drm/i915/i915_switcheroo.c +++ b/drivers/gpu/drm/i915/i915_switcheroo.c @@ -69,7 +69,6 @@ static void i915_switcheroo_pre_switch(struct pci_dev *pdev) static const struct vga_switcheroo_client_ops i915_switcheroo_ops = { .set_gpu_state = i915_switcheroo_set_state, - .reprobe = NULL, .can_switch = i915_switcheroo_can_switch, .pre_switch = i915_switcheroo_pre_switch, }; diff --git a/drivers/gpu/drm/nouveau/nouveau_vga.c b/drivers/gpu/drm/nouveau/nouveau_vga.c index 2d2d08be8fbe..29a801124e56 100644 --- a/drivers/gpu/drm/nouveau/nouveau_vga.c +++ b/drivers/gpu/drm/nouveau/nouveau_vga.c @@ -54,15 +54,6 @@ nouveau_switcheroo_set_state(struct pci_dev *pdev, } } -static void -nouveau_switcheroo_reprobe(struct pci_dev *pdev) -{ - struct nouveau_drm *drm = pci_get_drvdata(pdev); - struct drm_device *dev = drm->dev; - - drm_client_dev_hotplug(dev); -} - static bool nouveau_switcheroo_can_switch(struct pci_dev *pdev) { @@ -84,12 +75,20 @@ nouveau_switcheroo_pre_switch(struct pci_dev *pdev) drm_client_dev_acquire_outputs(drm->dev); } +static void +nouveau_switcheroo_post_switch(struct pci_dev *pdev) +{ + struct nouveau_drm *drm = pci_get_drvdata(pdev); + + drm_client_dev_hotplug(drm->dev); +} + static const struct vga_switcheroo_client_ops nouveau_switcheroo_ops = { .set_gpu_state = nouveau_switcheroo_set_state, - .reprobe = nouveau_switcheroo_reprobe, .can_switch = nouveau_switcheroo_can_switch, .pre_switch = nouveau_switcheroo_pre_switch, + .post_switch = nouveau_switcheroo_post_switch, }; void diff --git a/drivers/gpu/drm/radeon/radeon_device.c b/drivers/gpu/drm/radeon/radeon_device.c index 8697a9eb5d13..9523240110a6 100644 --- a/drivers/gpu/drm/radeon/radeon_device.c +++ b/drivers/gpu/drm/radeon/radeon_device.c @@ -1267,7 +1267,6 @@ static void radeon_switcheroo_pre_switch(struct pci_dev *pdev) static const struct vga_switcheroo_client_ops radeon_switcheroo_ops = { .set_gpu_state = radeon_switcheroo_set_state, - .reprobe = NULL, .can_switch = radeon_switcheroo_can_switch, .pre_switch = radeon_switcheroo_pre_switch, }; From 2350f42c2f46b60bec79f2249f8bc5f3ff2807d4 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Thu, 9 Jul 2026 11:16:03 +0200 Subject: [PATCH 026/143] vga-switcheroo: Remove unused interfaces Remove all unused interfaces that used to implement the functionality of pre_switch and post_switch. For pre_switch, remove vga_switcheroo_client_fb_set(). This further allows for removing all symbols and data structures that refer to fbdev; such as fb_info and fb_switch_outputs(). For post_switch, remove the reprobe callback from the client ops. v2: - move changes to fbcon and drivers into other patches Signed-off-by: Thomas Zimmermann Acked-by: Maxime Ripard Acked-by: Helge Deller Link: https://patch.msgid.link/20260709092215.168172-8-tzimmermann@suse.de --- drivers/gpu/vga/vga_switcheroo.c | 37 ++++---------------------------- include/linux/vga_switcheroo.h | 12 +++++------ 2 files changed, 9 insertions(+), 40 deletions(-) diff --git a/drivers/gpu/vga/vga_switcheroo.c b/drivers/gpu/vga/vga_switcheroo.c index 7b0af4a8aa7d..9431d83b38a9 100644 --- a/drivers/gpu/vga/vga_switcheroo.c +++ b/drivers/gpu/vga/vga_switcheroo.c @@ -32,9 +32,9 @@ #include #include -#include +#include #include -#include +#include #include #include #include @@ -90,7 +90,6 @@ /** * struct vga_switcheroo_client - registered client * @pdev: client pci device - * @fb_info: framebuffer to which console is remapped on switching * @pwr_state: current power state if manual power control is used. * For driver power control, call vga_switcheroo_pwr_state(). * @ops: client callbacks @@ -105,12 +104,11 @@ * @vga_dev: pci device, indicate which GPU is bound to current audio client * * Registered client. A client can be either a GPU or an audio device on a GPU. - * For audio clients, the @fb_info and @active members are bogus. For GPU - * clients, the @vga_dev is bogus. + * For audio clients, the @active member is bogus. For GPU clients, the @vga_dev + * is bogus. */ struct vga_switcheroo_client { struct pci_dev *pdev; - struct fb_info *fb_info; enum vga_switcheroo_state pwr_state; const struct vga_switcheroo_client_ops *ops; enum vga_switcheroo_client_id id; @@ -514,27 +512,6 @@ void vga_switcheroo_unregister_client(struct pci_dev *pdev) } EXPORT_SYMBOL(vga_switcheroo_unregister_client); -/** - * vga_switcheroo_client_fb_set() - set framebuffer of a given client - * @pdev: client pci device - * @info: framebuffer - * - * Set framebuffer of a given client. The console will be remapped to this - * on switching. - */ -void vga_switcheroo_client_fb_set(struct pci_dev *pdev, - struct fb_info *info) -{ - struct vga_switcheroo_client *client; - - mutex_lock(&vgasr_mutex); - client = find_client_from_pci(&vgasr_priv.clients, pdev); - if (client) - client->fb_info = info; - mutex_unlock(&vgasr_mutex); -} -EXPORT_SYMBOL(vga_switcheroo_client_fb_set); - /** * vga_switcheroo_lock_ddc() - temporarily switch DDC lines to a given client * @pdev: client pci device @@ -735,10 +712,6 @@ static int vga_switchto_stage2(struct vga_switcheroo_client *new_client) if (new_client->ops->pre_switch) new_client->ops->pre_switch(new_client->pdev); -#if defined(CONFIG_FB) - else if (new_client->fb_info) - fb_switch_outputs(new_client->fb_info); -#endif mutex_lock(&vgasr_priv.mux_hw_lock); ret = vgasr_priv.handler->switchto(new_client->id); @@ -748,8 +721,6 @@ static int vga_switchto_stage2(struct vga_switcheroo_client *new_client) if (new_client->ops->post_switch) new_client->ops->post_switch(new_client->pdev); - else if (new_client->ops->reprobe) - new_client->ops->reprobe(new_client->pdev); if (vga_switcheroo_pwr_state(active) == VGA_SWITCHEROO_ON) vga_switchoff(active); diff --git a/include/linux/vga_switcheroo.h b/include/linux/vga_switcheroo.h index 51851831d4c1..d7eee65664ab 100644 --- a/include/linux/vga_switcheroo.h +++ b/include/linux/vga_switcheroo.h @@ -31,8 +31,12 @@ #ifndef _LINUX_VGA_SWITCHEROO_H_ #define _LINUX_VGA_SWITCHEROO_H_ -#include +#include +#include +struct device; +struct dev_pm_domain; +struct fb_info; struct pci_dev; /** @@ -127,7 +131,6 @@ struct vga_switcheroo_handler { * @set_gpu_state: do the equivalent of suspend/resume for the card. * Mandatory. This should not cut power to the discrete GPU, * which is the job of the handler - * @reprobe: deprecated * @can_switch: check if the device is in a position to switch now. * Mandatory. The client should return false if a user space process * has one of its device files open @@ -147,7 +150,6 @@ struct vga_switcheroo_handler { */ struct vga_switcheroo_client_ops { void (*set_gpu_state)(struct pci_dev *dev, enum vga_switcheroo_state); - void (*reprobe)(struct pci_dev *dev); bool (*can_switch)(struct pci_dev *dev); void (*pre_switch)(struct pci_dev *dev); void (*post_switch)(struct pci_dev *dev); @@ -163,9 +165,6 @@ int vga_switcheroo_register_audio_client(struct pci_dev *pdev, const struct vga_switcheroo_client_ops *ops, struct pci_dev *vga_dev); -void vga_switcheroo_client_fb_set(struct pci_dev *dev, - struct fb_info *info); - int vga_switcheroo_register_handler(const struct vga_switcheroo_handler *handler, enum vga_switcheroo_handler_flags_t handler_flags); void vga_switcheroo_unregister_handler(void); @@ -185,7 +184,6 @@ void vga_switcheroo_fini_domain_pm_ops(struct device *dev); static inline void vga_switcheroo_unregister_client(struct pci_dev *dev) {} static inline int vga_switcheroo_register_client(struct pci_dev *dev, const struct vga_switcheroo_client_ops *ops, bool driver_power_control) { return 0; } -static inline void vga_switcheroo_client_fb_set(struct pci_dev *dev, struct fb_info *info) {} static inline int vga_switcheroo_register_handler(const struct vga_switcheroo_handler *handler, enum vga_switcheroo_handler_flags_t handler_flags) { return 0; } static inline int vga_switcheroo_register_audio_client(struct pci_dev *pdev, From d0c0ffdba93a589f78a67c264f8a4b4998e103c3 Mon Sep 17 00:00:00 2001 From: Lizhi Hou Date: Wed, 2 Sep 2026 11:09:54 -0700 Subject: [PATCH 027/143] accel/amdxdna: Disable BO import via flink Accessing abo->client in amdxdna_gem_del_bo_usage() may result in a use-after-free when the BO is imported via flink. Disable flink import by verifying that filp->driver_priv matches the client stored in abo->client before accessing the client. Fixes: 3cc5d7a59519 ("accel/amdxdna: Add carveout memory support for non-IOMMU systems") Reviewed-by: Max Zhen Signed-off-by: Lizhi Hou Link: https://patch.msgid.link/20260902180955.3999242-1-lizhi.hou@amd.com --- drivers/accel/amdxdna/amdxdna_gem.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/accel/amdxdna/amdxdna_gem.c b/drivers/accel/amdxdna/amdxdna_gem.c index 3f05fa8811af..08e22829f72d 100644 --- a/drivers/accel/amdxdna/amdxdna_gem.c +++ b/drivers/accel/amdxdna/amdxdna_gem.c @@ -697,6 +697,8 @@ static int amdxdna_gem_obj_open(struct drm_gem_object *gobj, struct drm_file *fi int ret; guard(mutex)(&abo->lock); + if (abo->open_ref > 0 && filp->driver_priv != abo->client) + return -EPERM; abo->open_ref++; if (abo->open_ref > 1) return 0; From 1bc13a9d9366d68ebf7aa12137b362d539b4f0d0 Mon Sep 17 00:00:00 2001 From: Lizhi Hou Date: Wed, 2 Sep 2026 11:09:55 -0700 Subject: [PATCH 028/143] accel/amdxdna: Fix potential deadlock in BO open and close callbacks In amdxdna_gem_obj_open(), abo->lock is held when calling amdxdna_gem_add_bo_usage(), which then acquires client->mm_lock. However, the heap update path may acquire these locks in the reverse order, creating a potential deadlock. Fix this by saving the client pointer locally before acquiring abo->lock, and releasing abo->lock before calling amdxdna_gem_add_bo_usage(). Apply the same change to amdxdna_gem_obj_close(). Fixes: 1f513a3ec3a9 ("accel/amdxdna: Add per-process BO memory usage query support") Reviewed-by: Max Zhen Signed-off-by: Lizhi Hou Link: https://patch.msgid.link/20260902180955.3999242-2-lizhi.hou@amd.com --- drivers/accel/amdxdna/amdxdna_gem.c | 34 +++++++++++++++++++---------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/drivers/accel/amdxdna/amdxdna_gem.c b/drivers/accel/amdxdna/amdxdna_gem.c index 08e22829f72d..1dd3c0014d5a 100644 --- a/drivers/accel/amdxdna/amdxdna_gem.c +++ b/drivers/accel/amdxdna/amdxdna_gem.c @@ -641,10 +641,8 @@ amdxdna_gem_skip_bo_usage(struct amdxdna_gem_obj *abo) } static void -amdxdna_gem_add_bo_usage(struct amdxdna_gem_obj *abo) +amdxdna_gem_add_bo_usage(struct amdxdna_client *client, struct amdxdna_gem_obj *abo) { - struct amdxdna_client *client = abo->client; - if (amdxdna_gem_skip_bo_usage(abo)) return; @@ -656,10 +654,8 @@ amdxdna_gem_add_bo_usage(struct amdxdna_gem_obj *abo) } static void -amdxdna_gem_del_bo_usage(struct amdxdna_gem_obj *abo) +amdxdna_gem_del_bo_usage(struct amdxdna_client *client, struct amdxdna_gem_obj *abo) { - struct amdxdna_client *client = abo->client; - if (amdxdna_gem_skip_bo_usage(abo)) return; @@ -694,14 +690,20 @@ static int amdxdna_gem_obj_open(struct drm_gem_object *gobj, struct drm_file *fi { struct amdxdna_dev *xdna = to_xdna_dev(gobj->dev); struct amdxdna_gem_obj *abo = to_xdna_obj(gobj); + struct amdxdna_client *client; int ret; - guard(mutex)(&abo->lock); - if (abo->open_ref > 0 && filp->driver_priv != abo->client) + mutex_lock(&abo->lock); + if (abo->open_ref > 0 && filp->driver_priv != abo->client) { + mutex_unlock(&abo->lock); return -EPERM; + } + abo->open_ref++; - if (abo->open_ref > 1) + if (abo->open_ref > 1) { + mutex_unlock(&abo->lock); return 0; + } /* Attached to the client when first opened by it. */ abo->client = filp->driver_priv; @@ -712,26 +714,34 @@ static int amdxdna_gem_obj_open(struct drm_gem_object *gobj, struct drm_file *fi if (ret) { abo->open_ref--; abo->client = NULL; + mutex_unlock(&abo->lock); return ret; } } + client = abo->client; + mutex_unlock(&abo->lock); - amdxdna_gem_add_bo_usage(abo); + amdxdna_gem_add_bo_usage(client, abo); return 0; } static void amdxdna_gem_obj_close(struct drm_gem_object *gobj, struct drm_file *filp) { struct amdxdna_gem_obj *abo = to_xdna_obj(gobj); + struct amdxdna_client *client = NULL; - guard(mutex)(&abo->lock); + mutex_lock(&abo->lock); abo->open_ref--; if (abo->open_ref == 0) { - amdxdna_gem_del_bo_usage(abo); /* Detach from the client when last closed by it. */ + client = abo->client; abo->client = NULL; } + mutex_unlock(&abo->lock); + + if (client) + amdxdna_gem_del_bo_usage(client, abo); } static int amdxdna_gem_obj_vmap(struct drm_gem_object *obj, struct iosys_map *map) From df4d5352063ac48e1d3566f64ec94baa8b5af94f Mon Sep 17 00:00:00 2001 From: Lizhi Hou Date: Thu, 10 Sep 2026 14:13:38 -0700 Subject: [PATCH 029/143] accel/amdxdna: Fix unsafe use of handle_mm_fault() handle_mm_fault() must not be called from the mmap callback because the VMA has not yet been linked. The handle_mm_fault() API contract assumes that the VMA is already linked. Remove the handle_mm_fault() call from the mmap callback. For imported BOs, mark the mapping as invalid and rely on the first command submission to fault in the pages. For shmem BOs, set the VM_MIXEDMAP flag and use vm_insert_pages(). Implement amdxdna_gem_mixed_vm_ops to handle the page faults. Fixes: e486147c912f ("accel/amdxdna: Add BO import and export") Reviewed-by: Max Zhen Signed-off-by: Lizhi Hou Link: https://patch.msgid.link/20260910211338.1102315-1-lizhi.hou@amd.com --- drivers/accel/amdxdna/amdxdna_gem.c | 151 ++++++++++++++++++++++------ 1 file changed, 118 insertions(+), 33 deletions(-) diff --git a/drivers/accel/amdxdna/amdxdna_gem.c b/drivers/accel/amdxdna/amdxdna_gem.c index 1dd3c0014d5a..5a1a2ee261b7 100644 --- a/drivers/accel/amdxdna/amdxdna_gem.c +++ b/drivers/accel/amdxdna/amdxdna_gem.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include "amdxdna_cbuf.h" @@ -490,16 +491,16 @@ static int amdxdna_insert_pages(struct amdxdna_gem_obj *abo, { struct amdxdna_dev *xdna = to_xdna_dev(to_gobj(abo)->dev); unsigned long num_pages = vma_pages(vma); - unsigned long offset = 0; int ret; - if (!is_import_bo(abo)) { - ret = drm_gem_shmem_mmap(&abo->base, vma); - if (ret) { - XDNA_ERR(xdna, "Failed shmem mmap %d", ret); - return ret; - } - } else { + /* + * Until today there is not any use case to mmap with non-zero + * offset. Put an explicit check here. + */ + if (vma->vm_pgoff - drm_vma_node_start(&to_gobj(abo)->vma_node)) + return -EINVAL; + + if (is_import_bo(abo)) { vma->vm_private_data = NULL; vma->vm_ops = NULL; ret = dma_buf_mmap(abo->dma_buf, vma, 0); @@ -508,23 +509,28 @@ static int amdxdna_insert_pages(struct amdxdna_gem_obj *abo, return ret; } + amdxdna_mark_mapp_invalid(abo, vma); + /* Drop the reference drm_gem_mmap_obj() acquired.*/ drm_gem_object_put(to_gobj(abo)); + return 0; } - do { - vm_fault_t fault_ret; + ret = drm_gem_shmem_mmap(&abo->base, vma); + if (ret) { + XDNA_ERR(xdna, "Failed shmem mmap %d", ret); + return ret; + } - fault_ret = handle_mm_fault(vma, vma->vm_start + offset, - FAULT_FLAG_WRITE, NULL); - if (fault_ret & VM_FAULT_ERROR) { - XDNA_ERR(xdna, "Fault in page failed"); - amdxdna_mark_mapp_invalid(abo, vma); - break; - } - - offset += PAGE_SIZE; - } while (--num_pages); + vm_flags_mod(vma, VM_MIXEDMAP, VM_PFNMAP); + ret = vm_insert_pages(vma, vma->vm_start, abo->base.pages, &num_pages); + if (ret) { + XDNA_ERR(xdna, "Failed to insert pages %d", ret); + dma_resv_lock(to_gobj(abo)->resv, NULL); + drm_gem_shmem_put_pages_locked(&abo->base); + dma_resv_unlock(to_gobj(abo)->resv); + return ret; + } return 0; } @@ -536,6 +542,10 @@ static int amdxdna_gem_obj_mmap(struct drm_gem_object *gobj, struct amdxdna_gem_obj *abo = to_xdna_obj(gobj); int ret; + XDNA_DBG(xdna, "BO map_offset 0x%llx type %d userptr 0x%lx size 0x%lx", + drm_vma_node_offset_addr(&gobj->vma_node), abo->type, + vma->vm_start, gobj->size); + ret = amdxdna_hmm_register(abo, vma); if (ret) return ret; @@ -546,9 +556,6 @@ static int amdxdna_gem_obj_mmap(struct drm_gem_object *gobj, goto hmm_unreg; } - XDNA_DBG(xdna, "BO map_offset 0x%llx type %d userptr 0x%lx size 0x%lx", - drm_vma_node_offset_addr(&gobj->vma_node), abo->type, - vma->vm_start, gobj->size); return 0; hmm_unreg: @@ -556,14 +563,99 @@ static int amdxdna_gem_obj_mmap(struct drm_gem_object *gobj, return ret; } +/* + * VM operations for amdxdna shmem VMAs that use VM_MIXEDMAP. + * + * drm_gem_shmem_vm_ops cannot be used on VM_MIXEDMAP VMAs because its fault + * handler calls vmf_insert_pfn() → vmf_insert_pfn_prot() which contains: + * BUG_ON((vma->vm_flags & VM_MIXEDMAP) && pfn_valid(pfn)) + * All amdxdna shmem pages are ordinary struct pages so pfn_valid() is always + * true, making the combination fatal. + * + * These ops use vmf_insert_page() (struct-page based) instead, which is the + * correct API for VM_MIXEDMAP VMAs backed by real struct pages. The open and + * close handlers replicate drm_gem_shmem_vm_open/close using only exported + * symbols. + */ +static vm_fault_t amdxdna_gem_mixedmap_fault(struct vm_fault *vmf) +{ + struct vm_area_struct *vma = vmf->vma; + struct drm_gem_object *gobj = vma->vm_private_data; + struct drm_gem_shmem_object *shmem = to_drm_gem_shmem_obj(gobj); + loff_t num_pages = gobj->size >> PAGE_SHIFT; + vm_fault_t ret = VM_FAULT_SIGBUS; + pgoff_t page_offset; + struct page *page; + + /* + * Partial free of vma is unexpected. Otherwise, the wrong page + * will be faulted in and the user application may crash itself. + */ + page_offset = linear_page_delta(vma, vmf->address); + + dma_resv_lock(gobj->resv, NULL); + + if (!shmem->pages || shmem->madv < 0 || page_offset >= num_pages) + goto out; + + page = shmem->pages[page_offset]; + if (WARN_ON_ONCE(!page)) + goto out; + + /* + * Use vmf_insert_page() (struct-page path) not vmf_insert_pfn() + * (PFN path) because this VMA carries VM_MIXEDMAP. + */ + ret = vmf_insert_page(vma, vmf->address, page); + if (ret == VM_FAULT_NOPAGE) + folio_mark_accessed(page_folio(page)); + +out: + dma_resv_unlock(gobj->resv); + return ret; +} + +static void amdxdna_gem_mixedmap_vm_open(struct vm_area_struct *vma) +{ + struct drm_gem_object *gobj = vma->vm_private_data; + struct drm_gem_shmem_object *shmem = to_drm_gem_shmem_obj(gobj); + + /* + * Bump pages_use_count so the page array stays alive for the new + * mapping copy created by fork(). Mirrors drm_gem_shmem_vm_open(). + */ + dma_resv_lock(gobj->resv, NULL); + drm_WARN_ON_ONCE(gobj->dev, !refcount_inc_not_zero(&shmem->pages_use_count)); + dma_resv_unlock(gobj->resv); + + drm_gem_vm_open(vma); +} + +static void amdxdna_gem_mixedmap_vm_close(struct vm_area_struct *vma) +{ + struct drm_gem_object *gobj = vma->vm_private_data; + struct drm_gem_shmem_object *shmem = to_drm_gem_shmem_obj(gobj); + + dma_resv_lock(gobj->resv, NULL); + drm_gem_shmem_put_pages_locked(shmem); + dma_resv_unlock(gobj->resv); + + drm_gem_vm_close(vma); +} + +static const struct vm_operations_struct amdxdna_gem_mixedmap_vm_ops = { + .fault = amdxdna_gem_mixedmap_fault, + .open = amdxdna_gem_mixedmap_vm_open, + .close = amdxdna_gem_mixedmap_vm_close, +}; + static int amdxdna_gem_dmabuf_mmap(struct dma_buf *dma_buf, struct vm_area_struct *vma) { struct drm_gem_object *gobj = dma_buf->priv; struct amdxdna_gem_obj *abo = to_xdna_obj(gobj); - unsigned long num_pages = vma_pages(vma); int ret; - vma->vm_ops = &drm_gem_shmem_vm_ops; + vma->vm_ops = &amdxdna_gem_mixedmap_vm_ops; vma->vm_private_data = gobj; drm_gem_object_get(gobj); @@ -573,16 +665,9 @@ static int amdxdna_gem_dmabuf_mmap(struct dma_buf *dma_buf, struct vm_area_struc /* The buffer is based on memory pages. Fix the flag. */ vm_flags_mod(vma, VM_MIXEDMAP, VM_PFNMAP); - ret = vm_insert_pages(vma, vma->vm_start, abo->base.pages, - &num_pages); - if (ret) - goto close_vma; return 0; -close_vma: - vma->vm_ops->close(vma); - return ret; put_obj: drm_gem_object_put(gobj); return ret; @@ -878,7 +963,7 @@ static const struct drm_gem_object_funcs amdxdna_gem_shmem_funcs = { .vmap = amdxdna_gem_obj_vmap, .vunmap = amdxdna_gem_obj_vunmap, .mmap = amdxdna_gem_obj_mmap, - .vm_ops = &drm_gem_shmem_vm_ops, + .vm_ops = &amdxdna_gem_mixedmap_vm_ops, .export = amdxdna_gem_prime_export, }; From 9d4c98219598e77ca7044c453ae6994519ac0a96 Mon Sep 17 00:00:00 2001 From: Roshan Kumar Date: Fri, 11 Sep 2026 00:55:26 +0530 Subject: [PATCH 030/143] drm/pl111: drop alpha formats the hardware cannot scan out The PL110/PL111 controller scans out a single framebuffer and never blends its alpha channel with anything. In every pixel-format table each alpha-carrying entry (ARGB/ABGR in 8888, 1555 and 4444) maps to exactly the same register configuration as its XRGB/XBGR counterpart, so the alpha byte was never used by the hardware. Commit 860e748bddcc ("drm: ensure blend mode supported if pixel format with alpha exposed") added validation that warns when a plane exposes alpha formats without a pixel blend mode property, and PL111 does exactly that. With panic_on_warn enabled the warning prevents the syzkaller vexpress-a15 manager from booting. Drivers should not report formats they do not support, so remove the alpha formats from all variant tables: the generic PL110, PL111 and Nomadik tables in pl111_drv.c and the board-specific Integrator, IM-PD1, Versatile and RealView/Versatile-Express tables in pl111_versatile.c, plus the now dead case labels in the display setup and the Versatile syscon connector switch, instead of advertising a blend mode property. Behavior is unchanged for userspace that picks an XRGB/XBGR/RGB format; alpha-picking clients fall back to the identical X variant. Link: https://github.com/google/syzkaller/issues/7805 Fixes: 860e748bddcc ("drm: ensure blend mode supported if pixel format with alpha exposed") Signed-off-by: Roshan Kumar Changes in v2: drop the alpha formats entirely instead of advertising a DRM_MODE_BLEND_PIXEL_NONE property, per review feedback from Thomas Zimmermann. Leandro's Reviewed-by from v1 is not carried as the approach changed. Changes in v3: v2 missed the board-specific variant tables in pl111_versatile.c (Integrator, IM-PD1, Versatile, RealView and Versatile Express), which still exposed alpha formats and would fall through to the WARN_ONCE default in pl111_display_enable() on those boards. Pointed out by the sashiko AI reviewer. Reviewed-by: Thomas Zimmermann Link: https://github.com/google/syzkaller/issues/7805 Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260910192526.137057-1-roshaen09@gmail.com --- drivers/gpu/drm/pl111/pl111_display.c | 6 ------ drivers/gpu/drm/pl111/pl111_drv.c | 16 ---------------- drivers/gpu/drm/pl111/pl111_versatile.c | 18 ------------------ 3 files changed, 40 deletions(-) diff --git a/drivers/gpu/drm/pl111/pl111_display.c b/drivers/gpu/drm/pl111/pl111_display.c index 5d10bc5fdf1f..e1b513d36c27 100644 --- a/drivers/gpu/drm/pl111/pl111_display.c +++ b/drivers/gpu/drm/pl111/pl111_display.c @@ -267,14 +267,12 @@ static void pl111_display_enable(struct drm_simple_display_pipe *pipe, if (priv->variant->st_bitmux_control) cntl |= CNTL_ST_LCDBPP24_PACKED; break; - case DRM_FORMAT_ABGR8888: case DRM_FORMAT_XBGR8888: if (priv->variant->st_bitmux_control) cntl |= CNTL_LCDBPP24 | CNTL_BGR; else cntl |= CNTL_LCDBPP24; break; - case DRM_FORMAT_ARGB8888: case DRM_FORMAT_XRGB8888: if (priv->variant->st_bitmux_control) cntl |= CNTL_LCDBPP24; @@ -297,13 +295,11 @@ static void pl111_display_enable(struct drm_simple_display_pipe *pipe, else cntl |= CNTL_LCDBPP16_565 | CNTL_BGR; break; - case DRM_FORMAT_ABGR1555: case DRM_FORMAT_XBGR1555: cntl |= CNTL_LCDBPP16; if (priv->variant->st_bitmux_control) cntl |= CNTL_ST_1XBPP_5551 | CNTL_BGR; break; - case DRM_FORMAT_ARGB1555: case DRM_FORMAT_XRGB1555: cntl |= CNTL_LCDBPP16; if (priv->variant->st_bitmux_control) @@ -311,13 +307,11 @@ static void pl111_display_enable(struct drm_simple_display_pipe *pipe, else cntl |= CNTL_BGR; break; - case DRM_FORMAT_ABGR4444: case DRM_FORMAT_XBGR4444: cntl |= CNTL_LCDBPP16_444; if (priv->variant->st_bitmux_control) cntl |= CNTL_ST_1XBPP_444 | CNTL_BGR; break; - case DRM_FORMAT_ARGB4444: case DRM_FORMAT_XRGB4444: cntl |= CNTL_LCDBPP16_444; if (priv->variant->st_bitmux_control) diff --git a/drivers/gpu/drm/pl111/pl111_drv.c b/drivers/gpu/drm/pl111/pl111_drv.c index 8ec659b3c08e..0e97261e988d 100644 --- a/drivers/gpu/drm/pl111/pl111_drv.c +++ b/drivers/gpu/drm/pl111/pl111_drv.c @@ -342,13 +342,9 @@ static void pl111_amba_shutdown(struct amba_device *amba_dev) * This early variant lacks the 565 and 444 pixel formats. */ static const u32 pl110_pixel_formats[] = { - DRM_FORMAT_ABGR8888, DRM_FORMAT_XBGR8888, - DRM_FORMAT_ARGB8888, DRM_FORMAT_XRGB8888, - DRM_FORMAT_ABGR1555, DRM_FORMAT_XBGR1555, - DRM_FORMAT_ARGB1555, DRM_FORMAT_XRGB1555, }; @@ -362,19 +358,13 @@ static const struct pl111_variant_data pl110_variant = { /* RealView, Versatile Express etc use this modern variant */ static const u32 pl111_pixel_formats[] = { - DRM_FORMAT_ABGR8888, DRM_FORMAT_XBGR8888, - DRM_FORMAT_ARGB8888, DRM_FORMAT_XRGB8888, DRM_FORMAT_BGR565, DRM_FORMAT_RGB565, - DRM_FORMAT_ABGR1555, DRM_FORMAT_XBGR1555, - DRM_FORMAT_ARGB1555, DRM_FORMAT_XRGB1555, - DRM_FORMAT_ABGR4444, DRM_FORMAT_XBGR4444, - DRM_FORMAT_ARGB4444, DRM_FORMAT_XRGB4444, }; @@ -388,19 +378,13 @@ static const struct pl111_variant_data pl111_variant = { static const u32 pl110_nomadik_pixel_formats[] = { DRM_FORMAT_RGB888, DRM_FORMAT_BGR888, - DRM_FORMAT_ABGR8888, DRM_FORMAT_XBGR8888, - DRM_FORMAT_ARGB8888, DRM_FORMAT_XRGB8888, DRM_FORMAT_BGR565, DRM_FORMAT_RGB565, - DRM_FORMAT_ABGR1555, DRM_FORMAT_XBGR1555, - DRM_FORMAT_ARGB1555, DRM_FORMAT_XRGB1555, - DRM_FORMAT_ABGR4444, DRM_FORMAT_XBGR4444, - DRM_FORMAT_ARGB4444, DRM_FORMAT_XRGB4444, }; diff --git a/drivers/gpu/drm/pl111/pl111_versatile.c b/drivers/gpu/drm/pl111/pl111_versatile.c index 0d8331a3909f..9a60f10231b1 100644 --- a/drivers/gpu/drm/pl111/pl111_versatile.c +++ b/drivers/gpu/drm/pl111/pl111_versatile.c @@ -209,9 +209,7 @@ static void pl111_versatile_enable(struct drm_device *drm, u32 format) drm_info(drm, "enable Versatile CLCD connectors\n"); switch (format) { - case DRM_FORMAT_ABGR8888: case DRM_FORMAT_XBGR8888: - case DRM_FORMAT_ARGB8888: case DRM_FORMAT_XRGB8888: val |= SYS_CLCD_MODE_888; break; @@ -221,9 +219,7 @@ static void pl111_versatile_enable(struct drm_device *drm, u32 format) case DRM_FORMAT_RGB565: val |= SYS_CLCD_MODE_565_B_LSB; break; - case DRM_FORMAT_ABGR1555: case DRM_FORMAT_XBGR1555: - case DRM_FORMAT_ARGB1555: case DRM_FORMAT_XRGB1555: val |= SYS_CLCD_MODE_5551; break; @@ -266,44 +262,30 @@ static void pl111_realview_clcd_enable(struct drm_device *drm, u32 format) /* PL110 pixel formats for Integrator, vanilla PL110 */ static const u32 pl110_integrator_pixel_formats[] = { - DRM_FORMAT_ABGR8888, DRM_FORMAT_XBGR8888, - DRM_FORMAT_ARGB8888, DRM_FORMAT_XRGB8888, - DRM_FORMAT_ABGR1555, DRM_FORMAT_XBGR1555, - DRM_FORMAT_ARGB1555, DRM_FORMAT_XRGB1555, }; /* Extended PL110 pixel formats for Integrator and Versatile */ static const u32 pl110_versatile_pixel_formats[] = { - DRM_FORMAT_ABGR8888, DRM_FORMAT_XBGR8888, - DRM_FORMAT_ARGB8888, DRM_FORMAT_XRGB8888, DRM_FORMAT_BGR565, /* Uses external PLD */ DRM_FORMAT_RGB565, /* Uses external PLD */ - DRM_FORMAT_ABGR1555, DRM_FORMAT_XBGR1555, - DRM_FORMAT_ARGB1555, DRM_FORMAT_XRGB1555, }; static const u32 pl111_realview_pixel_formats[] = { - DRM_FORMAT_ABGR8888, DRM_FORMAT_XBGR8888, - DRM_FORMAT_ARGB8888, DRM_FORMAT_XRGB8888, DRM_FORMAT_BGR565, DRM_FORMAT_RGB565, - DRM_FORMAT_ABGR1555, DRM_FORMAT_XBGR1555, - DRM_FORMAT_ARGB1555, DRM_FORMAT_XRGB1555, - DRM_FORMAT_ABGR4444, DRM_FORMAT_XBGR4444, - DRM_FORMAT_ARGB4444, DRM_FORMAT_XRGB4444, }; From 5052ea2e6bfc43c64b851c596d3675d17e05e631 Mon Sep 17 00:00:00 2001 From: Ze Huang Date: Mon, 27 Jul 2026 03:45:17 +0800 Subject: [PATCH 031/143] drm/pl111: replace struct drm_simple_display_pipe with regular atomic helpers Replace the PL111 simple display pipe with explicit plane, CRTC and encoder objects. Move the existing timing, format and pitch validation into explicit atomic check paths. Use commit-local plane state in the CRTC enable path when reading framebuffer format state. Move page-flip event handling to the CRTC commit path. Signed-off-by: Ze Huang Tested-by: Roshan Kumar # Ze Huang's patch, QEMU vexpress Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260727-drm-simple-kms-removal-v3-4-cd5dc89858c6@oss.qualcomm.com --- drivers/gpu/drm/pl111/pl111_display.c | 201 +++++++++++++++++++------- drivers/gpu/drm/pl111/pl111_drm.h | 5 +- drivers/gpu/drm/pl111/pl111_drv.c | 3 +- 3 files changed, 149 insertions(+), 60 deletions(-) diff --git a/drivers/gpu/drm/pl111/pl111_display.c b/drivers/gpu/drm/pl111/pl111_display.c index e1b513d36c27..8e5d662c23c8 100644 --- a/drivers/gpu/drm/pl111/pl111_display.c +++ b/drivers/gpu/drm/pl111/pl111_display.c @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -37,7 +38,7 @@ irqreturn_t pl111_irq(int irq, void *data) return IRQ_NONE; if (irq_stat & CLCD_IRQ_NEXTBASE_UPDATE) { - drm_crtc_handle_vblank(&priv->pipe.crtc); + drm_crtc_handle_vblank(&priv->crtc); status = IRQ_HANDLED; } @@ -49,10 +50,10 @@ irqreturn_t pl111_irq(int irq, void *data) } static enum drm_mode_status -pl111_mode_valid(struct drm_simple_display_pipe *pipe, - const struct drm_display_mode *mode) +pl111_crtc_helper_mode_valid(struct drm_crtc *crtc, + const struct drm_display_mode *mode) { - struct drm_device *drm = pipe->crtc.dev; + struct drm_device *drm = crtc->dev; struct pl111_drm_dev_private *priv = drm->dev_private; u32 cpp = DIV_ROUND_UP(priv->variant->fb_depth, 8); u64 bw; @@ -83,13 +84,34 @@ pl111_mode_valid(struct drm_simple_display_pipe *pipe, return MODE_OK; } -static int pl111_display_check(struct drm_simple_display_pipe *pipe, - struct drm_plane_state *pstate, - struct drm_crtc_state *cstate) +static int pl111_plane_helper_atomic_check(struct drm_plane *plane, + struct drm_atomic_commit *commit) { - const struct drm_display_mode *mode = &cstate->mode; - struct drm_framebuffer *old_fb = pipe->plane.state->fb; + struct drm_plane_state *pstate = drm_atomic_get_new_plane_state(commit, plane); + struct drm_plane_state *old_pstate = drm_atomic_get_old_plane_state(commit, plane); + struct drm_crtc_state *cstate = NULL; + const struct drm_display_mode *mode; + struct drm_framebuffer *old_fb = old_pstate->fb; struct drm_framebuffer *fb = pstate->fb; + int ret; + + if (pstate->crtc) { + cstate = drm_atomic_get_crtc_state(commit, pstate->crtc); + if (IS_ERR(cstate)) + return PTR_ERR(cstate); + } + + ret = drm_atomic_helper_check_plane_state(pstate, cstate, + DRM_PLANE_NO_SCALING, + DRM_PLANE_NO_SCALING, + false, false); + if (ret) + return ret; + + if (!pstate->visible) + return 0; + + mode = &cstate->mode; if (mode->hdisplay % 16) return -EINVAL; @@ -117,16 +139,15 @@ static int pl111_display_check(struct drm_simple_display_pipe *pipe, return 0; } -static void pl111_display_enable(struct drm_simple_display_pipe *pipe, - struct drm_crtc_state *cstate, - struct drm_plane_state *plane_state) +static void pl111_crtc_helper_atomic_enable(struct drm_crtc *crtc, + struct drm_atomic_commit *commit) { - struct drm_crtc *crtc = &pipe->crtc; - struct drm_plane *plane = &pipe->plane; struct drm_device *drm = crtc->dev; struct pl111_drm_dev_private *priv = drm->dev_private; + struct drm_crtc_state *cstate = drm_atomic_get_new_crtc_state(commit, crtc); + struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(commit, &priv->plane); const struct drm_display_mode *mode = &cstate->mode; - struct drm_framebuffer *fb = plane->state->fb; + struct drm_framebuffer *fb = plane_state->fb; struct drm_connector *connector = priv->connector; struct drm_bridge *bridge = priv->bridge; bool grayscale = false; @@ -349,9 +370,9 @@ static void pl111_display_enable(struct drm_simple_display_pipe *pipe, drm_crtc_vblank_on(crtc); } -static void pl111_display_disable(struct drm_simple_display_pipe *pipe) +static void pl111_crtc_helper_atomic_disable(struct drm_crtc *crtc, + struct drm_atomic_commit *commit) { - struct drm_crtc *crtc = &pipe->crtc; struct drm_device *drm = crtc->dev; struct pl111_drm_dev_private *priv = drm->dev_private; u32 cntl; @@ -381,38 +402,43 @@ static void pl111_display_disable(struct drm_simple_display_pipe *pipe) clk_disable_unprepare(priv->clk); } -static void pl111_display_update(struct drm_simple_display_pipe *pipe, - struct drm_plane_state *old_pstate) +static void pl111_plane_helper_atomic_update(struct drm_plane *plane, + struct drm_atomic_commit *commit) { - struct drm_crtc *crtc = &pipe->crtc; - struct drm_device *drm = crtc->dev; + struct drm_device *drm = plane->dev; struct pl111_drm_dev_private *priv = drm->dev_private; - struct drm_pending_vblank_event *event = crtc->state->event; - struct drm_plane *plane = &pipe->plane; - struct drm_plane_state *pstate = plane->state; + struct drm_plane_state *pstate = drm_atomic_get_new_plane_state(commit, plane); struct drm_framebuffer *fb = pstate->fb; - if (fb) { - u32 addr = drm_fb_dma_get_gem_addr(fb, pstate, 0); + if (!fb) + return; - writel(addr, priv->regs + CLCD_UBAS); - } + u32 addr = drm_fb_dma_get_gem_addr(fb, pstate, 0); - if (event) { - crtc->state->event = NULL; - - spin_lock_irq(&crtc->dev->event_lock); - if (crtc->state->active && drm_crtc_vblank_get(crtc) == 0) - drm_crtc_arm_vblank_event(crtc, event); - else - drm_crtc_send_vblank_event(crtc, event); - spin_unlock_irq(&crtc->dev->event_lock); - } + writel(addr, priv->regs + CLCD_UBAS); } -static int pl111_display_enable_vblank(struct drm_simple_display_pipe *pipe) +static void pl111_crtc_helper_atomic_flush(struct drm_crtc *crtc, + struct drm_atomic_commit *commit) +{ + struct drm_crtc_state *cstate = drm_atomic_get_new_crtc_state(commit, crtc); + struct drm_pending_vblank_event *event = cstate->event; + + if (!event) + return; + + cstate->event = NULL; + + spin_lock_irq(&crtc->dev->event_lock); + if (cstate->active && drm_crtc_vblank_get(crtc) == 0) + drm_crtc_arm_vblank_event(crtc, event); + else + drm_crtc_send_vblank_event(crtc, event); + spin_unlock_irq(&crtc->dev->event_lock); +} + +static int pl111_display_enable_vblank(struct drm_crtc *crtc) { - struct drm_crtc *crtc = &pipe->crtc; struct drm_device *drm = crtc->dev; struct pl111_drm_dev_private *priv = drm->dev_private; @@ -421,21 +447,62 @@ static int pl111_display_enable_vblank(struct drm_simple_display_pipe *pipe) return 0; } -static void pl111_display_disable_vblank(struct drm_simple_display_pipe *pipe) +static void pl111_display_disable_vblank(struct drm_crtc *crtc) { - struct drm_crtc *crtc = &pipe->crtc; struct drm_device *drm = crtc->dev; struct pl111_drm_dev_private *priv = drm->dev_private; writel(0, priv->regs + priv->ienb); } -static struct drm_simple_display_pipe_funcs pl111_display_funcs = { - .mode_valid = pl111_mode_valid, - .check = pl111_display_check, - .enable = pl111_display_enable, - .disable = pl111_display_disable, - .update = pl111_display_update, +static int pl111_crtc_helper_atomic_check(struct drm_crtc *crtc, struct drm_atomic_commit *commit) +{ + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(commit, crtc); + int ret; + + if (crtc_state->enable) { + ret = drm_atomic_helper_check_crtc_primary_plane(crtc_state); + if (ret) + return ret; + } + + return drm_atomic_add_affected_planes(commit, crtc); +} + +static struct drm_crtc_funcs pl111_crtc_funcs = { + .reset = drm_atomic_helper_crtc_reset, + .destroy = drm_crtc_cleanup, + .set_config = drm_atomic_helper_set_config, + .page_flip = drm_atomic_helper_page_flip, + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, +}; + +static const struct drm_crtc_helper_funcs pl111_crtc_helper_funcs = { + .mode_valid = pl111_crtc_helper_mode_valid, + .atomic_check = pl111_crtc_helper_atomic_check, + .atomic_enable = pl111_crtc_helper_atomic_enable, + .atomic_disable = pl111_crtc_helper_atomic_disable, + .atomic_flush = pl111_crtc_helper_atomic_flush, +}; + +static const struct drm_plane_funcs pl111_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .reset = drm_atomic_helper_plane_reset, + .destroy = drm_plane_cleanup, + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, +}; + +static const struct drm_plane_helper_funcs pl111_plane_helper_funcs = { + .prepare_fb = drm_gem_plane_helper_prepare_fb, + .atomic_check = pl111_plane_helper_atomic_check, + .atomic_update = pl111_plane_helper_atomic_update, +}; + +static const struct drm_encoder_funcs pl111_encoder_funcs = { + .destroy = drm_encoder_cleanup, }; static int pl111_clk_div_choose_div(struct clk_hw *hw, unsigned long rate, @@ -577,18 +644,40 @@ int pl111_display_init(struct drm_device *drm) return ret; if (!priv->variant->broken_vblank) { - pl111_display_funcs.enable_vblank = pl111_display_enable_vblank; - pl111_display_funcs.disable_vblank = pl111_display_disable_vblank; + pl111_crtc_funcs.enable_vblank = pl111_display_enable_vblank; + pl111_crtc_funcs.disable_vblank = pl111_display_disable_vblank; } - ret = drm_simple_display_pipe_init(drm, &priv->pipe, - &pl111_display_funcs, - priv->variant->formats, - priv->variant->nformats, - NULL, - priv->connector); + ret = drm_universal_plane_init(drm, &priv->plane, 0, + &pl111_plane_funcs, + priv->variant->formats, + priv->variant->nformats, + NULL, DRM_PLANE_TYPE_PRIMARY, NULL); if (ret) return ret; + drm_plane_helper_add(&priv->plane, &pl111_plane_helper_funcs); + + ret = drm_crtc_init_with_planes(drm, &priv->crtc, &priv->plane, + NULL, &pl111_crtc_funcs, NULL); + if (ret) + return ret; + + drm_crtc_helper_add(&priv->crtc, &pl111_crtc_helper_funcs); + + ret = drm_encoder_init(drm, &priv->encoder, &pl111_encoder_funcs, + DRM_MODE_ENCODER_NONE, NULL); + if (ret) + return ret; + + priv->encoder.possible_crtcs = drm_crtc_mask(&priv->crtc); + + if (priv->connector) { + ret = drm_connector_attach_encoder(priv->connector, + &priv->encoder); + if (ret) + return ret; + } + return 0; } diff --git a/drivers/gpu/drm/pl111/pl111_drm.h b/drivers/gpu/drm/pl111/pl111_drm.h index d1fe756444ee..ec92a5a180a8 100644 --- a/drivers/gpu/drm/pl111/pl111_drm.h +++ b/drivers/gpu/drm/pl111/pl111_drm.h @@ -21,7 +21,6 @@ #include #include #include -#include /* * CLCD Controller Internal Register addresses @@ -135,7 +134,9 @@ struct pl111_drm_dev_private { struct drm_connector *connector; struct drm_panel *panel; struct drm_bridge *bridge; - struct drm_simple_display_pipe pipe; + struct drm_plane plane; + struct drm_crtc crtc; + struct drm_encoder encoder; void *regs; u32 memory_bw; diff --git a/drivers/gpu/drm/pl111/pl111_drv.c b/drivers/gpu/drm/pl111/pl111_drv.c index 0e97261e988d..89d44e970510 100644 --- a/drivers/gpu/drm/pl111/pl111_drv.c +++ b/drivers/gpu/drm/pl111/pl111_drv.c @@ -169,8 +169,7 @@ static int pl111_modeset_init(struct drm_device *dev) goto out_bridge; } - ret = drm_simple_display_pipe_attach_bridge(&priv->pipe, - bridge); + ret = drm_bridge_attach(&priv->encoder, bridge, NULL, 0); if (ret) return ret; From b990db9a01769457e39fd34250dbbfbe8ffc0e3e Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Thu, 3 Sep 2026 23:21:19 +0200 Subject: [PATCH 032/143] drm/panel: samsung: Add shared test key helpers Several Samsung display controllers use identical commands to enable and disable their three levels of test keys. The drivers currently duplicate these commands in local macros. Move the commands to typed helpers in a private Samsung DSI header and use them from the drivers which already identify the commands by their test key level. Assisted-by: LLM Reviewed-by: Neil Armstrong Acked-by: David Heidelberg Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260903-drm-panels-sofef0-v1-1-9e841645beba@kernel.org --- drivers/gpu/drm/panel/panel-samsung-dsi.h | 38 ++++++++++ .../gpu/drm/panel/panel-samsung-s6e3fc2x01.c | 71 ++++++++----------- drivers/gpu/drm/panel/panel-samsung-s6e3ha8.c | 70 ++++++++---------- .../drm/panel/panel-samsung-s6e8fc0-m1906f9.c | 23 +++--- drivers/gpu/drm/panel/panel-samsung-sofef00.c | 15 ++-- 5 files changed, 112 insertions(+), 105 deletions(-) create mode 100644 drivers/gpu/drm/panel/panel-samsung-dsi.h diff --git a/drivers/gpu/drm/panel/panel-samsung-dsi.h b/drivers/gpu/drm/panel/panel-samsung-dsi.h new file mode 100644 index 000000000000..65c74dcbb21b --- /dev/null +++ b/drivers/gpu/drm/panel/panel-samsung-dsi.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef _PANEL_SAMSUNG_DSI_H +#define _PANEL_SAMSUNG_DSI_H + +#include + +static inline void samsung_dsi_test_key_on_lvl1(struct mipi_dsi_multi_context *ctx) +{ + mipi_dsi_dcs_write_seq_multi(ctx, 0x9f, 0xa5, 0xa5); +} + +static inline void samsung_dsi_test_key_off_lvl1(struct mipi_dsi_multi_context *ctx) +{ + mipi_dsi_dcs_write_seq_multi(ctx, 0x9f, 0x5a, 0x5a); +} + +static inline void samsung_dsi_test_key_on_lvl2(struct mipi_dsi_multi_context *ctx) +{ + mipi_dsi_dcs_write_seq_multi(ctx, 0xf0, 0x5a, 0x5a); +} + +static inline void samsung_dsi_test_key_off_lvl2(struct mipi_dsi_multi_context *ctx) +{ + mipi_dsi_dcs_write_seq_multi(ctx, 0xf0, 0xa5, 0xa5); +} + +static inline void samsung_dsi_test_key_on_lvl3(struct mipi_dsi_multi_context *ctx) +{ + mipi_dsi_dcs_write_seq_multi(ctx, 0xfc, 0x5a, 0x5a); +} + +static inline void samsung_dsi_test_key_off_lvl3(struct mipi_dsi_multi_context *ctx) +{ + mipi_dsi_dcs_write_seq_multi(ctx, 0xfc, 0xa5, 0xa5); +} + +#endif /* _PANEL_SAMSUNG_DSI_H */ diff --git a/drivers/gpu/drm/panel/panel-samsung-s6e3fc2x01.c b/drivers/gpu/drm/panel/panel-samsung-s6e3fc2x01.c index 8dfe654f36ec..b8f5e2f4f9e9 100644 --- a/drivers/gpu/drm/panel/panel-samsung-s6e3fc2x01.c +++ b/drivers/gpu/drm/panel/panel-samsung-s6e3fc2x01.c @@ -20,6 +20,8 @@ #include #include +#include "panel-samsung-dsi.h" + #define MCS_ELVSS_ON 0xb1 struct samsung_s6e3fc2x01 { @@ -41,19 +43,6 @@ struct samsung_s6e3fc2x01 *to_samsung_s6e3fc2x01(struct drm_panel *panel) return container_of(panel, struct samsung_s6e3fc2x01, panel); } -#define s6e3fc2x01_test_key_on_lvl1(ctx) \ - mipi_dsi_dcs_write_seq_multi(ctx, 0x9f, 0xa5, 0xa5) -#define s6e3fc2x01_test_key_off_lvl1(ctx) \ - mipi_dsi_dcs_write_seq_multi(ctx, 0x9f, 0x5a, 0x5a) -#define s6e3fc2x01_test_key_on_lvl2(ctx) \ - mipi_dsi_dcs_write_seq_multi(ctx, 0xf0, 0x5a, 0x5a) -#define s6e3fc2x01_test_key_off_lvl2(ctx) \ - mipi_dsi_dcs_write_seq_multi(ctx, 0xf0, 0xa5, 0xa5) -#define s6e3fc2x01_test_key_on_lvl3(ctx) \ - mipi_dsi_dcs_write_seq_multi(ctx, 0xfc, 0x5a, 0x5a) -#define s6e3fc2x01_test_key_off_lvl3(ctx) \ - mipi_dsi_dcs_write_seq_multi(ctx, 0xfc, 0xa5, 0xa5) - static void s6e3fc2x01_reset(struct samsung_s6e3fc2x01 *ctx) { gpiod_set_value_cansleep(ctx->reset_gpio, 1); @@ -66,7 +55,7 @@ static int s6e3fc2x01_on(struct samsung_s6e3fc2x01 *ctx) { struct mipi_dsi_multi_context dsi_ctx = { .dsi = ctx->dsi }; - s6e3fc2x01_test_key_on_lvl1(&dsi_ctx); + samsung_dsi_test_key_on_lvl1(&dsi_ctx); mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); @@ -75,64 +64,64 @@ static int s6e3fc2x01_on(struct samsung_s6e3fc2x01 *ctx) mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0x0a); mipi_dsi_usleep_range(&dsi_ctx, 10000, 11000); - s6e3fc2x01_test_key_off_lvl1(&dsi_ctx); + samsung_dsi_test_key_off_lvl1(&dsi_ctx); - s6e3fc2x01_test_key_on_lvl2(&dsi_ctx); + samsung_dsi_test_key_on_lvl2(&dsi_ctx); mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0x01); mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xcd, 0x01); - s6e3fc2x01_test_key_off_lvl2(&dsi_ctx); + samsung_dsi_test_key_off_lvl2(&dsi_ctx); mipi_dsi_usleep_range(&dsi_ctx, 15000, 16000); mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0x0f); mipi_dsi_usleep_range(&dsi_ctx, 10000, 11000); - s6e3fc2x01_test_key_on_lvl1(&dsi_ctx); + samsung_dsi_test_key_on_lvl1(&dsi_ctx); mipi_dsi_dcs_set_tear_on_multi(&dsi_ctx, MIPI_DSI_DCS_TEAR_MODE_VBLANK); - s6e3fc2x01_test_key_off_lvl1(&dsi_ctx); + samsung_dsi_test_key_off_lvl1(&dsi_ctx); - s6e3fc2x01_test_key_on_lvl2(&dsi_ctx); + samsung_dsi_test_key_on_lvl2(&dsi_ctx); mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xeb, 0x17, 0x41, 0x92, 0x0e, 0x10, 0x82, 0x5a); - s6e3fc2x01_test_key_off_lvl2(&dsi_ctx); + samsung_dsi_test_key_off_lvl2(&dsi_ctx); /* Column & Page Address Setting */ mipi_dsi_dcs_set_column_address_multi(&dsi_ctx, 0x0000, 0x0437); mipi_dsi_dcs_set_page_address_multi(&dsi_ctx, 0x0000, 0x0923); /* Horizontal & Vertical sync Setting */ - s6e3fc2x01_test_key_on_lvl2(&dsi_ctx); + samsung_dsi_test_key_on_lvl2(&dsi_ctx); mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0x09); mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe8, 0x10, 0x30); - s6e3fc2x01_test_key_off_lvl2(&dsi_ctx); + samsung_dsi_test_key_off_lvl2(&dsi_ctx); - s6e3fc2x01_test_key_on_lvl3(&dsi_ctx); + samsung_dsi_test_key_on_lvl3(&dsi_ctx); mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0x01); mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe3, 0x88); mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0x07); mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xed, 0x67); - s6e3fc2x01_test_key_off_lvl3(&dsi_ctx); + samsung_dsi_test_key_off_lvl3(&dsi_ctx); - s6e3fc2x01_test_key_on_lvl2(&dsi_ctx); + samsung_dsi_test_key_on_lvl2(&dsi_ctx); mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0x07); mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb7, 0x01); mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0x08); mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb7, 0x12); - s6e3fc2x01_test_key_off_lvl2(&dsi_ctx); + samsung_dsi_test_key_off_lvl2(&dsi_ctx); mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x20); mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_POWER_SAVE, 0x00); mipi_dsi_usleep_range(&dsi_ctx, 1000, 2000); - s6e3fc2x01_test_key_on_lvl2(&dsi_ctx); + samsung_dsi_test_key_on_lvl2(&dsi_ctx); mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_ELVSS_ON, 0x00, 0x01); - s6e3fc2x01_test_key_off_lvl2(&dsi_ctx); + samsung_dsi_test_key_off_lvl2(&dsi_ctx); - s6e3fc2x01_test_key_on_lvl2(&dsi_ctx); + samsung_dsi_test_key_on_lvl2(&dsi_ctx); mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb3, 0x00, 0xc1); - s6e3fc2x01_test_key_off_lvl2(&dsi_ctx); + samsung_dsi_test_key_off_lvl2(&dsi_ctx); mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0x78); mipi_dsi_usleep_range(&dsi_ctx, 10000, 11000); @@ -140,14 +129,14 @@ static int s6e3fc2x01_on(struct samsung_s6e3fc2x01 *ctx) mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x81, 0x90); mipi_dsi_usleep_range(&dsi_ctx, 10000, 11000); - s6e3fc2x01_test_key_on_lvl2(&dsi_ctx); + samsung_dsi_test_key_on_lvl2(&dsi_ctx); mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0x02); mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_ELVSS_ON, 0xc6, 0x00, 0x00, 0x21, 0xed, 0x02, 0x08, 0x06, 0xc1, 0x27, 0xfc, 0xdc, 0xe4, 0x00, 0xd9, 0xe6, 0xe7, 0x00, 0xfc, 0xff, 0xea); mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MCS_ELVSS_ON, 0x00, 0x00); - s6e3fc2x01_test_key_off_lvl2(&dsi_ctx); + samsung_dsi_test_key_off_lvl2(&dsi_ctx); mipi_dsi_usleep_range(&dsi_ctx, 10000, 11000); @@ -159,9 +148,9 @@ static int s6e3fc2x01_enable(struct drm_panel *panel) struct samsung_s6e3fc2x01 *ctx = to_samsung_s6e3fc2x01(panel); struct mipi_dsi_multi_context dsi_ctx = { .dsi = ctx->dsi }; - s6e3fc2x01_test_key_on_lvl1(&dsi_ctx); + samsung_dsi_test_key_on_lvl1(&dsi_ctx); mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); - s6e3fc2x01_test_key_off_lvl1(&dsi_ctx); + samsung_dsi_test_key_off_lvl1(&dsi_ctx); return dsi_ctx.accum_err; } @@ -170,27 +159,27 @@ static int s6e3fc2x01_off(struct samsung_s6e3fc2x01 *ctx) { struct mipi_dsi_multi_context dsi_ctx = { .dsi = ctx->dsi }; - s6e3fc2x01_test_key_on_lvl1(&dsi_ctx); + samsung_dsi_test_key_on_lvl1(&dsi_ctx); mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); mipi_dsi_usleep_range(&dsi_ctx, 10000, 11000); - s6e3fc2x01_test_key_on_lvl2(&dsi_ctx); + samsung_dsi_test_key_on_lvl2(&dsi_ctx); mipi_dsi_usleep_range(&dsi_ctx, 16000, 17000); mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0x50); mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb9, 0x82); - s6e3fc2x01_test_key_off_lvl2(&dsi_ctx); + samsung_dsi_test_key_off_lvl2(&dsi_ctx); mipi_dsi_usleep_range(&dsi_ctx, 16000, 17000); mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); - s6e3fc2x01_test_key_off_lvl1(&dsi_ctx); + samsung_dsi_test_key_off_lvl1(&dsi_ctx); - s6e3fc2x01_test_key_on_lvl2(&dsi_ctx); + samsung_dsi_test_key_on_lvl2(&dsi_ctx); mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0x05); mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf4, 0x01); - s6e3fc2x01_test_key_off_lvl2(&dsi_ctx); + samsung_dsi_test_key_off_lvl2(&dsi_ctx); mipi_dsi_msleep(&dsi_ctx, 160); return dsi_ctx.accum_err; diff --git a/drivers/gpu/drm/panel/panel-samsung-s6e3ha8.c b/drivers/gpu/drm/panel/panel-samsung-s6e3ha8.c index c09ff71b9c39..078523df4886 100644 --- a/drivers/gpu/drm/panel/panel-samsung-s6e3ha8.c +++ b/drivers/gpu/drm/panel/panel-samsung-s6e3ha8.c @@ -16,6 +16,8 @@ #include #include +#include "panel-samsung-dsi.h" + struct s6e3ha8 { struct drm_panel panel; struct mipi_dsi_device *dsi; @@ -36,18 +38,6 @@ struct s6e3ha8 *to_s6e3ha8_amb577px01_wqhd(struct drm_panel *panel) return container_of(panel, struct s6e3ha8, panel); } -#define s6e3ha8_test_key_on_lvl2(ctx) \ - mipi_dsi_dcs_write_seq_multi(ctx, 0xf0, 0x5a, 0x5a) -#define s6e3ha8_test_key_off_lvl2(ctx) \ - mipi_dsi_dcs_write_seq_multi(ctx, 0xf0, 0xa5, 0xa5) -#define s6e3ha8_test_key_on_lvl3(ctx) \ - mipi_dsi_dcs_write_seq_multi(ctx, 0xfc, 0x5a, 0x5a) -#define s6e3ha8_test_key_off_lvl3(ctx) \ - mipi_dsi_dcs_write_seq_multi(ctx, 0xfc, 0xa5, 0xa5) -#define s6e3ha8_test_key_on_lvl1(ctx) \ - mipi_dsi_dcs_write_seq_multi(ctx, 0x9f, 0xa5, 0xa5) -#define s6e3ha8_test_key_off_lvl1(ctx) \ - mipi_dsi_dcs_write_seq_multi(ctx, 0x9f, 0x5a, 0x5a) #define s6e3ha8_afc_off(ctx) \ mipi_dsi_dcs_write_seq_multi(ctx, 0xe2, 0x00, 0x00) @@ -68,26 +58,26 @@ static int s6e3ha8_amb577px01_wqhd_on(struct s6e3ha8 *priv) dsi->mode_flags |= MIPI_DSI_MODE_LPM; - s6e3ha8_test_key_on_lvl1(&ctx); + samsung_dsi_test_key_on_lvl1(&ctx); - s6e3ha8_test_key_on_lvl2(&ctx); + samsung_dsi_test_key_on_lvl2(&ctx); mipi_dsi_compression_mode_multi(&ctx, true); - s6e3ha8_test_key_off_lvl2(&ctx); + samsung_dsi_test_key_off_lvl2(&ctx); mipi_dsi_dcs_exit_sleep_mode_multi(&ctx); usleep_range(5000, 6000); - s6e3ha8_test_key_on_lvl2(&ctx); + samsung_dsi_test_key_on_lvl2(&ctx); mipi_dsi_dcs_write_seq_multi(&ctx, 0xf2, 0x13); - s6e3ha8_test_key_off_lvl2(&ctx); + samsung_dsi_test_key_off_lvl2(&ctx); usleep_range(10000, 11000); - s6e3ha8_test_key_on_lvl2(&ctx); + samsung_dsi_test_key_on_lvl2(&ctx); mipi_dsi_dcs_write_seq_multi(&ctx, 0xf2, 0x13); - s6e3ha8_test_key_off_lvl2(&ctx); + samsung_dsi_test_key_off_lvl2(&ctx); /* OMOK setting 1 (Initial setting) - Scaler Latch Setting Guide */ - s6e3ha8_test_key_on_lvl2(&ctx); + samsung_dsi_test_key_on_lvl2(&ctx); mipi_dsi_dcs_write_seq_multi(&ctx, 0xb0, 0x07); /* latch setting 1 : Scaler on/off & address setting & PPS setting -> Image update latch */ mipi_dsi_dcs_write_seq_multi(&ctx, 0xf2, 0x3c, 0x10); @@ -98,31 +88,31 @@ static int s6e3ha8_amb577px01_wqhd_on(struct s6e3ha8 *priv) mipi_dsi_dcs_write_seq_multi(&ctx, 0x2a, 0x00, 0x00, 0x05, 0x9f); /* CASET */ mipi_dsi_dcs_write_seq_multi(&ctx, 0x2b, 0x00, 0x00, 0x0b, 0x8f); /* PASET */ mipi_dsi_dcs_write_seq_multi(&ctx, 0xba, 0x01); /* scaler setup : scaler off */ - s6e3ha8_test_key_off_lvl2(&ctx); + samsung_dsi_test_key_off_lvl2(&ctx); mipi_dsi_dcs_write_seq_multi(&ctx, 0x35, 0x00); /* TE Vsync ON */ - s6e3ha8_test_key_on_lvl2(&ctx); + samsung_dsi_test_key_on_lvl2(&ctx); mipi_dsi_dcs_write_seq_multi(&ctx, 0xed, 0x4c); /* ERR_FG */ - s6e3ha8_test_key_off_lvl2(&ctx); + samsung_dsi_test_key_off_lvl2(&ctx); - s6e3ha8_test_key_on_lvl3(&ctx); + samsung_dsi_test_key_on_lvl3(&ctx); /* FFC Setting 897.6Mbps */ mipi_dsi_dcs_write_seq_multi(&ctx, 0xc5, 0x0d, 0x10, 0xb4, 0x3e, 0x01); - s6e3ha8_test_key_off_lvl3(&ctx); + samsung_dsi_test_key_off_lvl3(&ctx); - s6e3ha8_test_key_on_lvl2(&ctx); + samsung_dsi_test_key_on_lvl2(&ctx); mipi_dsi_dcs_write_seq_multi(&ctx, 0xb9, 0x00, 0xb0, 0x81, 0x09, 0x00, 0x00, 0x00, 0x11, 0x03); /* TSP HSYNC Setting */ - s6e3ha8_test_key_off_lvl2(&ctx); + samsung_dsi_test_key_off_lvl2(&ctx); - s6e3ha8_test_key_on_lvl2(&ctx); + samsung_dsi_test_key_on_lvl2(&ctx); mipi_dsi_dcs_write_seq_multi(&ctx, 0xb0, 0x03); mipi_dsi_dcs_write_seq_multi(&ctx, 0xf6, 0x43); - s6e3ha8_test_key_off_lvl2(&ctx); + samsung_dsi_test_key_off_lvl2(&ctx); - s6e3ha8_test_key_on_lvl2(&ctx); + samsung_dsi_test_key_on_lvl2(&ctx); /* Brightness condition set */ mipi_dsi_dcs_write_seq_multi(&ctx, 0xca, 0x07, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, @@ -138,9 +128,9 @@ static int s6e3ha8_amb577px01_wqhd_on(struct s6e3ha8 *priv) 0x10); /* MPS/ELVSS Setting */ mipi_dsi_dcs_write_seq_multi(&ctx, 0xf4, 0xeb, 0x28); /* VINT */ mipi_dsi_dcs_write_seq_multi(&ctx, 0xf7, 0x03); /* Gamma, LTPS(AID) update */ - s6e3ha8_test_key_off_lvl2(&ctx); + samsung_dsi_test_key_off_lvl2(&ctx); - s6e3ha8_test_key_off_lvl1(&ctx); + samsung_dsi_test_key_off_lvl1(&ctx); return ctx.accum_err; } @@ -151,9 +141,9 @@ static int s6e3ha8_enable(struct drm_panel *panel) struct mipi_dsi_device *dsi = priv->dsi; struct mipi_dsi_multi_context ctx = { .dsi = dsi }; - s6e3ha8_test_key_on_lvl1(&ctx); + samsung_dsi_test_key_on_lvl1(&ctx); mipi_dsi_dcs_set_display_on_multi(&ctx); - s6e3ha8_test_key_off_lvl1(&ctx); + samsung_dsi_test_key_off_lvl1(&ctx); return ctx.accum_err; } @@ -164,14 +154,14 @@ static int s6e3ha8_disable(struct drm_panel *panel) struct mipi_dsi_device *dsi = priv->dsi; struct mipi_dsi_multi_context ctx = { .dsi = dsi }; - s6e3ha8_test_key_on_lvl1(&ctx); + samsung_dsi_test_key_on_lvl1(&ctx); mipi_dsi_dcs_set_display_off_multi(&ctx); - s6e3ha8_test_key_off_lvl1(&ctx); + samsung_dsi_test_key_off_lvl1(&ctx); mipi_dsi_msleep(&ctx, 20); - s6e3ha8_test_key_on_lvl2(&ctx); + samsung_dsi_test_key_on_lvl2(&ctx); s6e3ha8_afc_off(&ctx); - s6e3ha8_test_key_off_lvl2(&ctx); + samsung_dsi_test_key_off_lvl2(&ctx); mipi_dsi_msleep(&ctx, 160); @@ -200,9 +190,9 @@ static int s6e3ha8_amb577px01_wqhd_prepare(struct drm_panel *panel) drm_dsc_pps_payload_pack(&pps, &priv->dsc); - s6e3ha8_test_key_on_lvl1(&ctx); + samsung_dsi_test_key_on_lvl1(&ctx); mipi_dsi_picture_parameter_set_multi(&ctx, &pps); - s6e3ha8_test_key_off_lvl1(&ctx); + samsung_dsi_test_key_off_lvl1(&ctx); mipi_dsi_msleep(&ctx, 28); diff --git a/drivers/gpu/drm/panel/panel-samsung-s6e8fc0-m1906f9.c b/drivers/gpu/drm/panel/panel-samsung-s6e8fc0-m1906f9.c index 0e797a2ec14f..1d2635c8cc3b 100644 --- a/drivers/gpu/drm/panel/panel-samsung-s6e8fc0-m1906f9.c +++ b/drivers/gpu/drm/panel/panel-samsung-s6e8fc0-m1906f9.c @@ -17,6 +17,8 @@ #include #include +#include "panel-samsung-dsi.h" + struct s6e8fc0_ctx { struct drm_panel panel; struct mipi_dsi_device *dsi; @@ -45,20 +47,11 @@ static void s6e8fc0_m1906f9_reset(struct s6e8fc0_ctx *ctx) usleep_range(10000, 11000); } -#define s6e8fc0_test_key_on_lvl2(ctx) \ - mipi_dsi_dcs_write_seq_multi(ctx, 0xf0, 0x5a, 0x5a) -#define s6e8fc0_test_key_off_lvl2(ctx) \ - mipi_dsi_dcs_write_seq_multi(ctx, 0xf0, 0xa5, 0xa5) -#define s6e8fc0_test_key_on_lvl3(ctx) \ - mipi_dsi_dcs_write_seq_multi(ctx, 0xfc, 0x5a, 0x5a) -#define s6e8fc0_test_key_off_lvl3(ctx) \ - mipi_dsi_dcs_write_seq_multi(ctx, 0xfc, 0xa5, 0xa5) - static int s6e8fc0_m1906f9_on(struct s6e8fc0_ctx *ctx) { struct mipi_dsi_multi_context dsi_ctx = { .dsi = ctx->dsi }; - s6e8fc0_test_key_on_lvl3(&dsi_ctx); + samsung_dsi_test_key_on_lvl3(&dsi_ctx); mipi_dsi_dcs_set_display_brightness_multi(&dsi_ctx, 0x0000); mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_CONTROL_DISPLAY, @@ -71,15 +64,15 @@ static int s6e8fc0_m1906f9_on(struct s6e8fc0_ctx *ctx) mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xed, 0xe4, 0x08, 0x96, 0xa4, 0x2a, 0x72, 0xe2, 0xca, 0x00); - s6e8fc0_test_key_off_lvl3(&dsi_ctx); - s6e8fc0_test_key_on_lvl2(&dsi_ctx); - s6e8fc0_test_key_on_lvl3(&dsi_ctx); + samsung_dsi_test_key_off_lvl3(&dsi_ctx); + samsung_dsi_test_key_on_lvl2(&dsi_ctx); + samsung_dsi_test_key_on_lvl3(&dsi_ctx); mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe1, 0x93); mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0x05, 0xf4); mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf4, 0x03); mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xed, 0x01, 0x81, 0x04); - s6e8fc0_test_key_off_lvl2(&dsi_ctx); - s6e8fc0_test_key_off_lvl3(&dsi_ctx); + samsung_dsi_test_key_off_lvl2(&dsi_ctx); + samsung_dsi_test_key_off_lvl3(&dsi_ctx); return dsi_ctx.accum_err; } diff --git a/drivers/gpu/drm/panel/panel-samsung-sofef00.c b/drivers/gpu/drm/panel/panel-samsung-sofef00.c index da02955f5f42..913be42e7975 100644 --- a/drivers/gpu/drm/panel/panel-samsung-sofef00.c +++ b/drivers/gpu/drm/panel/panel-samsung-sofef00.c @@ -18,6 +18,8 @@ #include #include +#include "panel-samsung-dsi.h" + struct sofef00_panel { struct drm_panel panel; struct mipi_dsi_device *dsi; @@ -37,11 +39,6 @@ struct sofef00_panel *to_sofef00_panel(struct drm_panel *panel) return container_of(panel, struct sofef00_panel, panel); } -#define sofef00_test_key_on_lvl2(ctx) \ - mipi_dsi_dcs_write_seq_multi(ctx, 0xf0, 0x5a, 0x5a) -#define sofef00_test_key_off_lvl2(ctx) \ - mipi_dsi_dcs_write_seq_multi(ctx, 0xf0, 0xa5, 0xa5) - static void sofef00_panel_reset(struct sofef00_panel *ctx) { gpiod_set_value_cansleep(ctx->reset_gpio, 0); @@ -62,14 +59,14 @@ static int sofef00_panel_on(struct sofef00_panel *ctx) mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); mipi_dsi_usleep_range(&dsi_ctx, 10000, 11000); - sofef00_test_key_on_lvl2(&dsi_ctx); + samsung_dsi_test_key_on_lvl2(&dsi_ctx); mipi_dsi_dcs_set_tear_on_multi(&dsi_ctx, MIPI_DSI_DCS_TEAR_MODE_VBLANK); - sofef00_test_key_off_lvl2(&dsi_ctx); + samsung_dsi_test_key_off_lvl2(&dsi_ctx); - sofef00_test_key_on_lvl2(&dsi_ctx); + samsung_dsi_test_key_on_lvl2(&dsi_ctx); mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb0, 0x07); mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb6, 0x12); - sofef00_test_key_off_lvl2(&dsi_ctx); + samsung_dsi_test_key_off_lvl2(&dsi_ctx); mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x20); mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_POWER_SAVE, 0x00); From 920237dde7fb1e41e63ea87e323d8d3fa2ee51b1 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Thu, 3 Sep 2026 23:21:20 +0200 Subject: [PATCH 033/143] drm/panel: samsung-s6e88a0-ams452ef01: Use test key helpers The F0 command pair in this driver is already documented as enabling and disabling level 2 commands. Replace the raw writes with the shared level 2 test key helpers. Assisted-by: LLM Reviewed-by: Neil Armstrong Acked-by: David Heidelberg Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260903-drm-panels-sofef0-v1-2-9e841645beba@kernel.org --- drivers/gpu/drm/panel/panel-samsung-s6e88a0-ams452ef01.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/panel/panel-samsung-s6e88a0-ams452ef01.c b/drivers/gpu/drm/panel/panel-samsung-s6e88a0-ams452ef01.c index bbb86ea86d5e..2178e03e4772 100644 --- a/drivers/gpu/drm/panel/panel-samsung-s6e88a0-ams452ef01.c +++ b/drivers/gpu/drm/panel/panel-samsung-s6e88a0-ams452ef01.c @@ -13,6 +13,8 @@ #include #include +#include "panel-samsung-dsi.h" + struct s6e88a0_ams452ef01 { struct drm_panel panel; struct mipi_dsi_device *dsi; @@ -43,7 +45,7 @@ static int s6e88a0_ams452ef01_on(struct s6e88a0_ams452ef01 *ctx) dsi->mode_flags |= MIPI_DSI_MODE_LPM; - mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf0, 0x5a, 0x5a); // enable LEVEL2 commands + samsung_dsi_test_key_on_lvl2(&dsi_ctx); mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xcc, 0x4c); // set Pixel Clock Divider polarity mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); @@ -66,7 +68,7 @@ static int s6e88a0_ams452ef01_on(struct s6e88a0_ams452ef01 *ctx) mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xb6, 0x2c, 0x0b); // set default elvss voltage mipi_dsi_dcs_write_seq_multi(&dsi_ctx, MIPI_DCS_WRITE_POWER_SAVE, 0x00); mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf7, 0x03); // gamma/aor update - mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf0, 0xa5, 0xa5); // disable LEVEL2 commands + samsung_dsi_test_key_off_lvl2(&dsi_ctx); mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); From 766bfba0f3bae329f99b42dcabc3ef11fa368f0b Mon Sep 17 00:00:00 2001 From: Luca Ceresoli Date: Fri, 14 Aug 2026 16:05:56 +0200 Subject: [PATCH 034/143] drm/mcde: dsi: simplify device_node management using scoped for_each variant Before further modifying this function, simpify the struct device_node management using the scoped variat of for_each_available_child_of_node(). Signed-off-by: Luca Ceresoli Reviewed-by: Linus Walleij Reviewed-by: Maxime Ripard Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260814-drm-bridge-every-panel-v1-8-19cd5277cc8d@bootlin.com --- drivers/gpu/drm/mcde/mcde_dsi.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/mcde/mcde_dsi.c b/drivers/gpu/drm/mcde/mcde_dsi.c index 694372581840..ce078aa3910f 100644 --- a/drivers/gpu/drm/mcde/mcde_dsi.c +++ b/drivers/gpu/drm/mcde/mcde_dsi.c @@ -1076,7 +1076,6 @@ static int mcde_dsi_bind(struct device *dev, struct device *master, struct drm_device *drm = data; struct mcde *mcde = to_mcde(drm); struct mcde_dsi *d = dev_get_drvdata(dev); - struct device_node *child; struct drm_panel *panel = NULL; struct drm_bridge *bridge __free(drm_bridge_put) = NULL; @@ -1104,7 +1103,7 @@ static int mcde_dsi_bind(struct device *dev, struct device *master, } /* Look for a panel as a child to this node */ - for_each_available_child_of_node(dev->of_node, child) { + for_each_available_child_of_node_scoped(dev->of_node, child) { panel = of_drm_find_panel(child); if (IS_ERR(panel)) { dev_err(dev, "failed to find panel try bridge (%ld)\n", @@ -1114,15 +1113,12 @@ static int mcde_dsi_bind(struct device *dev, struct device *master, bridge = of_drm_find_and_get_bridge(child); if (!bridge) { dev_err(dev, "failed to find bridge\n"); - of_node_put(child); return -EINVAL; } } - if (panel || bridge) { - of_node_put(child); + if (panel || bridge) break; - } } if (panel) { bridge = drm_panel_bridge_add_typed(panel, From ab26ed3f07e856b9dc29682ec1a7297e1a1d95b7 Mon Sep 17 00:00:00 2001 From: Amit Barzilai Date: Mon, 14 Sep 2026 11:59:26 +0300 Subject: [PATCH 035/143] dt-bindings: display: Add Solomon SSD1351 OLED controller Add a device tree binding for the Solomon SSD1351, a 128x128 65k-color RGB OLED display controller driven over a 4-wire SPI bus. The binding builds on the shared solomon,ssd-common.yaml properties already used by the other Solomon display controllers. Assisted-by: LLM Signed-off-by: Amit Barzilai Reviewed-by: Javier Martinez Canillas Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260914085931.66857-2-amit.barzilai22@gmail.com Signed-off-by: Javier Martinez Canillas --- .../bindings/display/solomon,ssd1351.yaml | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/solomon,ssd1351.yaml diff --git a/Documentation/devicetree/bindings/display/solomon,ssd1351.yaml b/Documentation/devicetree/bindings/display/solomon,ssd1351.yaml new file mode 100644 index 000000000000..80850c2ab5b3 --- /dev/null +++ b/Documentation/devicetree/bindings/display/solomon,ssd1351.yaml @@ -0,0 +1,42 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/display/solomon,ssd1351.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Solomon SSD1351 OLED Display Controller + +maintainers: + - Amit Barzilai + - Javier Martinez Canillas + +allOf: + - $ref: solomon,ssd-common.yaml# + +properties: + compatible: + enum: + - solomon,ssd1351 + +required: + - compatible + - reg + +unevaluatedProperties: false + +examples: + - | + #include + + spi { + #address-cells = <1>; + #size-cells = <0>; + + oled@0 { + compatible = "solomon,ssd1351"; + reg = <0x0>; + reset-gpios = <&gpio2 7 GPIO_ACTIVE_LOW>; + dc-gpios = <&gpio2 8 GPIO_ACTIVE_HIGH>; + spi-max-frequency = <10000000>; + }; + }; From e9bebd4b411193dfd77a9682f1715da1478c5bd4 Mon Sep 17 00:00:00 2001 From: Amit Barzilai Date: Mon, 14 Sep 2026 11:59:27 +0300 Subject: [PATCH 036/143] drm/ssd130x: Change SSD133X color format to RGB565 from RGB332 SSD133X screens were driven at 8bpp RGB332 despite supporting 16bpp RGB565. Switch the SSD133X data path to RGB565. Assisted-by: LLM Signed-off-by: Amit Barzilai Reviewed-by: Javier Martinez Canillas Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/20260914085931.66857-3-amit.barzilai22@gmail.com Signed-off-by: Javier Martinez Canillas --- drivers/gpu/drm/solomon/ssd130x.c | 48 ++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/drivers/gpu/drm/solomon/ssd130x.c b/drivers/gpu/drm/solomon/ssd130x.c index feec4933e3e6..cbf77a74c4fa 100644 --- a/drivers/gpu/drm/solomon/ssd130x.c +++ b/drivers/gpu/drm/solomon/ssd130x.c @@ -146,6 +146,11 @@ #define SSD133X_DEFAULT_CONTRAST_B 0x50 #define SSD133X_DEFAULT_CONTRAST_C 0x7d +/* ssd133x remap byte (data of SSD13XX_SET_SEG_REMAP) */ +#define SSD133X_SET_REMAP_COM_SPLIT BIT(5) +#define SSD133X_SET_REMAP_COLOR_DEPTH_MASK GENMASK(7, 6) +#define SSD133X_COLOR_DEPTH_65K 0x1 + #define MAX_CONTRAST 255 const struct ssd130x_deviceinfo ssd130x_variants[] = { @@ -618,17 +623,17 @@ static int ssd133x_set_contrast(struct ssd130x_device *ssd130x, u32 brightness) static int ssd133x_init(struct ssd130x_device *ssd130x) { int ret; + /* + * Horizontal address increment, normal SA,SB,SC (e.g. RGB) sub-pixel + * order, COM split odd even and 65k (RGB565) color depth. + */ + const u8 remap = SSD133X_SET_REMAP_COM_SPLIT | + FIELD_PREP(SSD133X_SET_REMAP_COLOR_DEPTH_MASK, SSD133X_COLOR_DEPTH_65K); 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, - /* - * Horizontal Address Increment - * Normal order SA,SB,SC (e.g. RGB) - * COM Split Odd Even - * 256 color format - */ - 2, SSD13XX_SET_SEG_REMAP, 0x20, + 2, SSD13XX_SET_SEG_REMAP, remap, 2, SSD133X_SET_DISPLAY_START, 0x00, 2, SSD133X_SET_DISPLAY_OFFSET, 0x00, 1, SSD133X_SET_DISPLAY_NORMAL, @@ -829,14 +834,20 @@ static int ssd133x_update_rect(struct ssd130x_device *ssd130x, * COM0 to COM[N - 1] are the rows and SEG0 to SEG[M - 1] are * the columns. * - * Each Segment has a 8-bit pixel and each Common output has a - * row of pixels. When using the (default) horizontal address - * increment mode, each byte of data sent to the controller has - * a Segment (e.g: SEG0). + * Each Segment holds one pixel and each Common output has a row + * of pixels. A pixel is 8 bits (one byte) in the 256 color + * (RGB332) format or 16 bits (two bytes) in the 65k color + * (RGB565) format. When using the (default) horizontal address + * increment mode, the pixel data is sent Segment by Segment + * (e.g: SEG0 first). * * When using the 256 color depth format, each pixel contains 3 - * sub-pixels for color A, B and C. These have 3 bit, 3 bit and - * 2 bits respectively. + * sub-pixels for color A, B and C. These have 3, 3 and 2 bits + * respectively. + * + * When using the 65k color depth format, each pixel contains 3 + * sub-pixels for color A, B and C. These have 5, 6 and 5 bits + * respectively. */ /* Set column start and end */ @@ -909,9 +920,10 @@ static void ssd132x_clear_screen(struct ssd130x_device *ssd130x, u8 *data_array) static void ssd133x_clear_screen(struct ssd130x_device *ssd130x, u8 *data_array) { - const struct drm_format_info *fi = drm_format_info(DRM_FORMAT_RGB332); + const struct drm_format_info *fi; unsigned int pitch; + fi = drm_format_info(DRM_FORMAT_RGB565); if (!fi) return; @@ -978,17 +990,18 @@ static int ssd133x_fb_blit_rect(struct drm_framebuffer *fb, struct drm_format_conv_state *fmtcnv_state) { struct ssd130x_device *ssd130x = drm_to_ssd130x(fb->dev); - const struct drm_format_info *fi = drm_format_info(DRM_FORMAT_RGB332); + const struct drm_format_info *fi; unsigned int dst_pitch; struct iosys_map dst; + fi = drm_format_info(DRM_FORMAT_RGB565); if (!fi) return -EINVAL; dst_pitch = drm_format_info_min_pitch(fi, 0, drm_rect_width(rect)); iosys_map_set_vaddr(&dst, data_array); - drm_fb_xrgb8888_to_rgb332(&dst, &dst_pitch, vmap, fb, rect, fmtcnv_state); + drm_fb_xrgb8888_to_rgb565be(&dst, &dst_pitch, vmap, fb, rect, fmtcnv_state); ssd133x_update_rect(ssd130x, rect, data_array, dst_pitch); @@ -1446,10 +1459,11 @@ static int ssd133x_crtc_atomic_check(struct drm_crtc *crtc, struct ssd130x_device *ssd130x = drm_to_ssd130x(drm); struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc); struct ssd130x_crtc_state *ssd130x_state = to_ssd130x_crtc_state(crtc_state); - const struct drm_format_info *fi = drm_format_info(DRM_FORMAT_RGB332); + const struct drm_format_info *fi; unsigned int pitch; int ret; + fi = drm_format_info(DRM_FORMAT_RGB565); if (!fi) return -EINVAL; From d89a37ba57d7ace46bb30947f4666c72dafcf4be Mon Sep 17 00:00:00 2001 From: Amit Barzilai Date: Mon, 14 Sep 2026 11:59:28 +0300 Subject: [PATCH 037/143] drm/ssd130x: Constify ssd130x_write_data() 'values' parameter At no point does ssd130x_write_data() change the bytes it receives via the 'values' parameter. Constify the parameter so const-qualified buffers pass through without casting away const. Signed-off-by: Amit Barzilai Reviewed-by: Javier Martinez Canillas Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/20260914085931.66857-4-amit.barzilai22@gmail.com Signed-off-by: Javier Martinez Canillas --- drivers/gpu/drm/solomon/ssd130x.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/solomon/ssd130x.c b/drivers/gpu/drm/solomon/ssd130x.c index cbf77a74c4fa..1d834be6243b 100644 --- a/drivers/gpu/drm/solomon/ssd130x.c +++ b/drivers/gpu/drm/solomon/ssd130x.c @@ -252,7 +252,7 @@ static inline struct ssd130x_device *drm_to_ssd130x(struct drm_device *drm) /* * Helper to write data (SSD13XX_DATA) to the device. */ -static int ssd130x_write_data(struct ssd130x_device *ssd130x, u8 *values, int count) +static int ssd130x_write_data(struct ssd130x_device *ssd130x, const u8 *values, int count) { return regmap_bulk_write(ssd130x->regmap, SSD13XX_DATA, values, count); } From 19713453f0ca509d36a5e82a56ba9a009d411ed9 Mon Sep 17 00:00:00 2001 From: Amit Barzilai Date: Mon, 14 Sep 2026 11:59:29 +0300 Subject: [PATCH 038/143] drm/ssd130x: Replace positional ssd130x_spi_id[] initialization with C99 ssd130x_spi_id[] is initialized with positional initializers, which easily create easy-to-miss bugs when changing the members of the target struct (struct spi_device_id in this example). Change this to C99 initializers to guarantee each member is initialized with the correct value. Signed-off-by: Amit Barzilai Reviewed-by: Andy Shevchenko Reviewed-by: Javier Martinez Canillas Link: https://patch.msgid.link/20260914085931.66857-5-amit.barzilai22@gmail.com Signed-off-by: Javier Martinez Canillas --- drivers/gpu/drm/solomon/ssd130x-spi.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/solomon/ssd130x-spi.c b/drivers/gpu/drm/solomon/ssd130x-spi.c index b52f5fd592a1..be50e5aa2c87 100644 --- a/drivers/gpu/drm/solomon/ssd130x-spi.c +++ b/drivers/gpu/drm/solomon/ssd130x-spi.c @@ -160,17 +160,17 @@ MODULE_DEVICE_TABLE(of, ssd130x_of_match); */ static const struct spi_device_id ssd130x_spi_id[] = { /* ssd130x family */ - { "sh1106", SH1106_ID }, - { "ssd1305", SSD1305_ID }, - { "ssd1306", SSD1306_ID }, - { "ssd1307", SSD1307_ID }, - { "ssd1309", SSD1309_ID }, + { .name = "sh1106", .driver_data = SH1106_ID }, + { .name = "ssd1305", .driver_data = SSD1305_ID }, + { .name = "ssd1306", .driver_data = SSD1306_ID }, + { .name = "ssd1307", .driver_data = SSD1307_ID }, + { .name = "ssd1309", .driver_data = SSD1309_ID }, /* ssd132x family */ - { "ssd1322", SSD1322_ID }, - { "ssd1325", SSD1325_ID }, - { "ssd1327", SSD1327_ID }, + { .name = "ssd1322", .driver_data = SSD1322_ID }, + { .name = "ssd1325", .driver_data = SSD1325_ID }, + { .name = "ssd1327", .driver_data = SSD1327_ID }, /* ssd133x family */ - { "ssd1331", SSD1331_ID }, + { .name = "ssd1331", .driver_data = SSD1331_ID }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(spi, ssd130x_spi_id); From 3a1de89b73d44231531871050e31e7196e89ecef Mon Sep 17 00:00:00 2001 From: Amit Barzilai Date: Mon, 14 Sep 2026 11:59:30 +0300 Subject: [PATCH 039/143] drm/ssd130x: Implement ssd130x_write_cmd() on top of ssd130x_write_cmds() ssd130x_write_cmd() and ssd130x_write_cmds() each carried their own regmap_write() loop over SSD13XX_COMMAND, differing only in how the caller supplies the bytes. Turn ssd130x_write_cmd() into a thin variadic wrapper that collects its arguments into a small stack buffer and defers to ssd130x_write_cmds(), and move the protocol comment onto the latter, which is where the loop now lives. No functional change: the bytes sent and the bus transactions used to send them are identical for every chip on both the I2C and SPI transports. Assisted-by: LLM Signed-off-by: Amit Barzilai Reviewed-by: Javier Martinez Canillas Link: https://patch.msgid.link/20260914085931.66857-6-amit.barzilai22@gmail.com Signed-off-by: Javier Martinez Canillas --- drivers/gpu/drm/solomon/ssd130x.c | 65 +++++++++++++++---------------- 1 file changed, 31 insertions(+), 34 deletions(-) diff --git a/drivers/gpu/drm/solomon/ssd130x.c b/drivers/gpu/drm/solomon/ssd130x.c index 1d834be6243b..fd13250ef56a 100644 --- a/drivers/gpu/drm/solomon/ssd130x.c +++ b/drivers/gpu/drm/solomon/ssd130x.c @@ -258,41 +258,12 @@ static int ssd130x_write_data(struct ssd130x_device *ssd130x, const u8 *values, } /* - * Helper to write command (SSD13XX_COMMAND). The fist variadic argument - * is the command to write and the following are the command options. + * Helper to write a command (SSD13XX_COMMAND) from a buffer. The first byte + * is the command opcode and the following ones are its parameters. * - * Note that the ssd13xx protocol requires each command and option to be - * written as a SSD13XX_COMMAND device register value. That is why a call - * to regmap_write(..., SSD13XX_COMMAND, ...) is done for each argument. - */ -static int ssd130x_write_cmd(struct ssd130x_device *ssd130x, int count, - /* u8 cmd, u8 option, ... */...) -{ - va_list ap; - u8 value; - int ret; - - va_start(ap, count); - - do { - value = va_arg(ap, int); - ret = regmap_write(ssd130x->regmap, SSD13XX_COMMAND, value); - if (ret) - goto out_end; - } while (--count); - -out_end: - va_end(ap); - - return ret; -} - -/* - * Write a command byte sequence from a buffer. - * - * Like ssd130x_write_cmd() but takes a pre-built byte array instead of - * variadic arguments, handy when the command is already in an array or - * when the caller wants to use sizeof() for the length. + * Note that the ssd13xx protocol requires the opcode and each parameter to + * be written as a SSD13XX_COMMAND device register value. That is why a call + * to regmap_write(..., SSD13XX_COMMAND, ...) is done for each byte. */ static int ssd130x_write_cmds(struct ssd130x_device *ssd130x, const u8 *cmd, size_t len) @@ -309,6 +280,32 @@ static int ssd130x_write_cmds(struct ssd130x_device *ssd130x, const u8 *cmd, return 0; } +/* + * Variadic wrapper around ssd130x_write_cmds(). The first variadic argument + * is the command opcode and the following are its parameters. + * + * The arguments are gathered into a fixed size buffer, so at most 8 bytes + * can be sent per call, i.e. an opcode and seven parameters. That covers + * every command this driver sends through it. Commands taking more parameters, + * such as the grey scale tables, must use ssd130x_write_cmds() instead. + */ +static int ssd130x_write_cmd(struct ssd130x_device *ssd130x, int count, + /* u8 cmd, u8 param, ... */...) +{ + u8 buf[8]; + va_list ap; + + if (drm_WARN_ON(&ssd130x->drm, count > sizeof(buf))) + return -EINVAL; + + va_start(ap, count); + for (int i = 0; i < count; i++) + buf[i] = va_arg(ap, int); + va_end(ap); + + return ssd130x_write_cmds(ssd130x, buf, count); +} + /* * Run a packed command sequence. The format is a flat byte array where each * entry starts with a length byte followed by that many command bytes. A From 3e391b0b516b007dafcbbd8742886717ebaf8cf9 Mon Sep 17 00:00:00 2001 From: Amit Barzilai Date: Mon, 14 Sep 2026 11:59:31 +0300 Subject: [PATCH 040/143] drm/ssd130x: Add SSD135X_FAMILY and SSD1351 support The Solomon SSD1351 is a 128x128 RGB color OLED controller. It shares the SSD133X pixel layout: one 65k color (RGB565) pixel per Segment, written as a bulk transfer once a column/row addressing window has been programmed. Add it as a new SSD135X_FAMILY rather than as a separate driver, so that the ssd130x plane, CRTC and encoder infrastructure is reused. Give the family its own primary plane update and disable, encoder enable and backlight callbacks instead of teaching the ssd133x ones about a second family. Only the callbacks that carry no family specific logic are reused as is: ssd133x_primary_plane_atomic_check(), ssd133x_crtc_atomic_check() and ssd130x_encoder_atomic_disable(). The data path differs from the ssd133x family in one respect. The SSD1351 only starts accepting pixel data after an explicit Write RAM command (0x5c), while the SSD133X enters data mode as soon as the address window has been programmed. Emit it from ssd135x_update_rect(), which both the damage update and the clear screen paths go through. SSD1351 differs from previous controllers in the command protocol. While the opcode is still sent on the command path, the parameters are sent on the data path. Introduce the cmd_params_are_data flag to struct ssd130x_deviceinfo and let ssd130x_write_cmds() split the buffer in accordance to the device specifications. The SSD1351 also needs its own init sequence (ssd135x_init). The remap byte is fixed at horizontal address increment, COM split, reversed COM scan direction, BGR sub-pixel order and 65k color depth; rotation is not supported. Contrast is calibrated per color channel as for the ssd133x family, but the three channels are parameters of a single command (0xc1) instead of one command per channel. Add ssd135x_set_contrast() for that and use it from both the init and the backlight update paths. The SSD1351 is SPI-only, so only the SPI transport match tables gain an entry; no new config symbol is needed. Assisted-by: LLM Signed-off-by: Amit Barzilai Reviewed-by: Javier Martinez Canillas Link: https://patch.msgid.link/20260914085931.66857-7-amit.barzilai22@gmail.com Signed-off-by: Javier Martinez Canillas --- drivers/gpu/drm/solomon/ssd130x-spi.c | 7 + drivers/gpu/drm/solomon/ssd130x.c | 312 +++++++++++++++++++++++++- drivers/gpu/drm/solomon/ssd130x.h | 10 +- 3 files changed, 322 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/solomon/ssd130x-spi.c b/drivers/gpu/drm/solomon/ssd130x-spi.c index be50e5aa2c87..c41064aa8221 100644 --- a/drivers/gpu/drm/solomon/ssd130x-spi.c +++ b/drivers/gpu/drm/solomon/ssd130x-spi.c @@ -146,6 +146,11 @@ static const struct of_device_id ssd130x_of_match[] = { .compatible = "solomon,ssd1331", .data = &ssd130x_variants[SSD1331_ID], }, + /* ssd135x family */ + { + .compatible = "solomon,ssd1351", + .data = &ssd130x_variants[SSD1351_ID], + }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, ssd130x_of_match); @@ -171,6 +176,8 @@ static const struct spi_device_id ssd130x_spi_id[] = { { .name = "ssd1327", .driver_data = SSD1327_ID }, /* ssd133x family */ { .name = "ssd1331", .driver_data = SSD1331_ID }, + /* ssd135x family */ + { .name = "ssd1351", .driver_data = SSD1351_ID }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(spi, ssd130x_spi_id); diff --git a/drivers/gpu/drm/solomon/ssd130x.c b/drivers/gpu/drm/solomon/ssd130x.c index fd13250ef56a..324467ae9309 100644 --- a/drivers/gpu/drm/solomon/ssd130x.c +++ b/drivers/gpu/drm/solomon/ssd130x.c @@ -151,6 +151,37 @@ #define SSD133X_SET_REMAP_COLOR_DEPTH_MASK GENMASK(7, 6) #define SSD133X_COLOR_DEPTH_65K 0x1 +/* ssd135x commands */ +#define SSD135X_SET_COL_RANGE 0x15 +#define SSD135X_WRITE_RAM 0x5c +#define SSD135X_SET_ROW_RANGE 0x75 +#define SSD135X_SET_DISPLAY_START 0xa1 +#define SSD135X_SET_DISPLAY_OFFSET 0xa2 +#define SSD135X_SET_DISPLAY_NORMAL 0xa6 +#define SSD135X_SET_FUNCTION 0xab +#define SSD135X_SET_PHASE_LENGTH 0xb1 +#define SSD135X_SET_CLOCK_FREQ 0xb3 +#define SSD135X_SET_VSL 0xb4 +#define SSD135X_SET_GPIO 0xb5 +#define SSD135X_SET_PRECHARGE2 0xb6 +#define SSD135X_SET_PRECHARGE_VOLTAGE 0xbb +#define SSD135X_SET_VCOMH_VOLTAGE 0xbe +#define SSD135X_SET_CONTRAST 0xc1 +#define SSD135X_SET_CONTRAST_MASTER 0xc7 +#define SSD135X_SET_MUX_RATIO 0xca +#define SSD135X_SET_COMMAND_LOCK 0xfd + +/* ssd135x A/B/C channel contrast at full brightness (white balance) */ +#define SSD135X_DEFAULT_CONTRAST_A 0xc8 +#define SSD135X_DEFAULT_CONTRAST_B 0x80 +#define SSD135X_DEFAULT_CONTRAST_C 0xc8 + +/* ssd135x remap byte (data of SSD13XX_SET_SEG_REMAP) */ +#define SSD135X_SET_REMAP_COLOR_BGR BIT(2) +#define SSD135X_SET_REMAP_COM_SCAN BIT(4) +#define SSD135X_SET_REMAP_COM_SPLIT BIT(5) +#define SSD135X_SET_REMAP_65K BIT(6) + #define MAX_CONTRAST 255 const struct ssd130x_deviceinfo ssd130x_variants[] = { @@ -218,7 +249,14 @@ const struct ssd130x_deviceinfo ssd130x_variants[] = { .default_width = 96, .default_height = 64, .family_id = SSD133X_FAMILY, - } + }, + /* ssd135x family */ + [SSD1351_ID] = { + .default_width = 128, + .default_height = 128, + .family_id = SSD135X_FAMILY, + .cmd_params_are_data = true, + }, }; EXPORT_SYMBOL_NS_GPL(ssd130x_variants, "DRM_SSD130X"); @@ -261,9 +299,10 @@ static int ssd130x_write_data(struct ssd130x_device *ssd130x, const u8 *values, * Helper to write a command (SSD13XX_COMMAND) from a buffer. The first byte * is the command opcode and the following ones are its parameters. * - * Note that the ssd13xx protocol requires the opcode and each parameter to - * be written as a SSD13XX_COMMAND device register value. That is why a call - * to regmap_write(..., SSD13XX_COMMAND, ...) is done for each byte. + * By default every byte is written as a SSD13XX_COMMAND register value, hence + * the regmap_write() per byte. Controllers that set cmd_params_are_data expect + * the parameters on the data path instead, so only the opcode is written as + * SSD13XX_COMMAND and the rest as SSD13XX_DATA. */ static int ssd130x_write_cmds(struct ssd130x_device *ssd130x, const u8 *cmd, size_t len) @@ -271,6 +310,18 @@ static int ssd130x_write_cmds(struct ssd130x_device *ssd130x, const u8 *cmd, unsigned int i; int ret; + if (!len) + return 0; + + if (ssd130x->device_info->cmd_params_are_data) { + ret = regmap_write(ssd130x->regmap, SSD13XX_COMMAND, cmd[0]); + /* A command with no parameters is complete after its opcode. */ + if (ret || len == 1) + return ret; + + return ssd130x_write_data(ssd130x, cmd + 1, len - 1); + } + for (i = 0; i < len; i++) { ret = regmap_write(ssd130x->regmap, SSD13XX_COMMAND, cmd[i]); if (ret) @@ -617,6 +668,20 @@ static int ssd133x_set_contrast(struct ssd130x_device *ssd130x, u32 brightness) return ssd130x_run_cmd_seq(ssd130x, cmds); } +/* + * Same white balance calibration as for the ssd133x family, except that the + * ssd135x controllers take the three channels as parameters of a single + * command instead of one command per channel. + */ +static int ssd135x_set_contrast(struct ssd130x_device *ssd130x, u32 brightness) +{ + u8 a = ssd130x_scale_contrast(SSD135X_DEFAULT_CONTRAST_A, brightness); + u8 b = ssd130x_scale_contrast(SSD135X_DEFAULT_CONTRAST_B, brightness); + u8 c = ssd130x_scale_contrast(SSD135X_DEFAULT_CONTRAST_C, brightness); + + return ssd130x_write_cmd(ssd130x, 4, SSD135X_SET_CONTRAST, a, b, c); +} + static int ssd133x_init(struct ssd130x_device *ssd130x) { int ret; @@ -654,6 +719,47 @@ static int ssd133x_init(struct ssd130x_device *ssd130x) return ssd130x_run_cmd_seq(ssd130x, cmds); } +static int ssd135x_init(struct ssd130x_device *ssd130x) +{ + /* + * Horizontal address increment, COM split, reversed COM scan direction, + * BGR sub-pixel order and 65k (RGB565) color depth. Rotation is not + * supported, so the remap byte is fixed. + */ + const u8 remap = SSD135X_SET_REMAP_65K | SSD135X_SET_REMAP_COM_SPLIT | + SSD135X_SET_REMAP_COLOR_BGR | SSD135X_SET_REMAP_COM_SCAN; + const u8 cmds[] = { + /* Unlock the controller, then the extended command set */ + 2, SSD135X_SET_COMMAND_LOCK, 0x12, + 2, SSD135X_SET_COMMAND_LOCK, 0xb1, + 1, SSD13XX_DISPLAY_OFF, + 2, SSD135X_SET_CLOCK_FREQ, 0xf1, + 2, SSD135X_SET_MUX_RATIO, ssd130x->height - 1, + 3, SSD135X_SET_COL_RANGE, 0x00, ssd130x->width - 1, + 3, SSD135X_SET_ROW_RANGE, 0x00, ssd130x->height - 1, + 2, SSD135X_SET_DISPLAY_START, 0x00, + 2, SSD135X_SET_DISPLAY_OFFSET, 0x00, + 2, SSD135X_SET_GPIO, 0x00, + 2, SSD135X_SET_FUNCTION, 0x01, + 2, SSD135X_SET_PHASE_LENGTH, 0x32, + 4, SSD135X_SET_VSL, 0xa0, 0xb5, 0x55, + 2, SSD135X_SET_PRECHARGE_VOLTAGE, 0x17, + 2, SSD135X_SET_VCOMH_VOLTAGE, 0x05, + 2, SSD135X_SET_CONTRAST_MASTER, 0x0f, + 2, SSD135X_SET_PRECHARGE2, 0x01, + 1, SSD135X_SET_DISPLAY_NORMAL, + 2, SSD13XX_SET_SEG_REMAP, remap, + 0, + }; + int ret; + + ret = ssd130x_run_cmd_seq(ssd130x, cmds); + if (ret < 0) + return ret; + + return ssd135x_set_contrast(ssd130x, ssd130x->contrast); +} + static int ssd130x_update_rect(struct ssd130x_device *ssd130x, struct drm_rect *rect, u8 *buf, u8 *data_array) @@ -863,6 +969,44 @@ static int ssd133x_update_rect(struct ssd130x_device *ssd130x, return ret; } +static int ssd135x_update_rect(struct ssd130x_device *ssd130x, + struct drm_rect *rect, u8 *data_array, + unsigned int pitch) +{ + unsigned int x = rect->x1; + unsigned int y = rect->y1; + unsigned int columns = drm_rect_width(rect); + unsigned int rows = drm_rect_height(rect); + int ret; + + /* + * The pixel layout is the same as for the ssd133x family: one 65k + * color (RGB565) pixel per Segment, sent Segment by Segment when the + * (default) horizontal address increment mode is used. + * + * But unlike the ssd133x family, which starts accepting pixel data as + * soon as the address window has been programmed, the ssd135x family + * needs an explicit Write RAM command before the data is written. + */ + + /* Set column start and end */ + ret = ssd130x_write_cmd(ssd130x, 3, SSD135X_SET_COL_RANGE, x, x + columns - 1); + if (ret < 0) + return ret; + + /* Set row start and end */ + ret = ssd130x_write_cmd(ssd130x, 3, SSD135X_SET_ROW_RANGE, y, y + rows - 1); + if (ret < 0) + return ret; + + ret = ssd130x_write_cmd(ssd130x, 1, SSD135X_WRITE_RAM); + if (ret < 0) + return ret; + + /* Write out update in one go since horizontal addressing mode is used */ + return ssd130x_write_data(ssd130x, data_array, pitch * rows); +} + static void ssd130x_clear_screen(struct ssd130x_device *ssd130x, u8 *data_array) { unsigned int pages = DIV_ROUND_UP(ssd130x->height, SSD130X_PAGE_HEIGHT); @@ -932,6 +1076,23 @@ static void ssd133x_clear_screen(struct ssd130x_device *ssd130x, u8 *data_array) ssd130x_write_data(ssd130x, data_array, pitch * ssd130x->height); } +static void ssd135x_clear_screen(struct ssd130x_device *ssd130x, u8 *data_array) +{ + struct drm_rect screen = DRM_RECT_INIT(0, 0, ssd130x->width, ssd130x->height); + const struct drm_format_info *fi; + unsigned int pitch; + + fi = drm_format_info(DRM_FORMAT_RGB565); + if (!fi) + return; + + pitch = drm_format_info_min_pitch(fi, 0, ssd130x->width); + + memset(data_array, 0, pitch * ssd130x->height); + + ssd135x_update_rect(ssd130x, &screen, data_array, pitch); +} + static int ssd130x_fb_blit_rect(struct drm_framebuffer *fb, const struct iosys_map *vmap, struct drm_rect *rect, @@ -1005,6 +1166,30 @@ static int ssd133x_fb_blit_rect(struct drm_framebuffer *fb, return 0; } +static int ssd135x_fb_blit_rect(struct drm_framebuffer *fb, + const struct iosys_map *vmap, + struct drm_rect *rect, u8 *data_array, + struct drm_format_conv_state *fmtcnv_state) +{ + struct ssd130x_device *ssd130x = drm_to_ssd130x(fb->dev); + const struct drm_format_info *fi; + unsigned int dst_pitch; + struct iosys_map dst; + + fi = drm_format_info(DRM_FORMAT_RGB565); + if (!fi) + return -EINVAL; + + dst_pitch = drm_format_info_min_pitch(fi, 0, drm_rect_width(rect)); + + iosys_map_set_vaddr(&dst, data_array); + drm_fb_xrgb8888_to_rgb565be(&dst, &dst_pitch, vmap, fb, rect, fmtcnv_state); + + ssd135x_update_rect(ssd130x, rect, data_array, dst_pitch); + + return 0; +} + static int ssd130x_primary_plane_atomic_check(struct drm_plane *plane, struct drm_atomic_commit *state) { @@ -1247,6 +1432,45 @@ static void ssd133x_primary_plane_atomic_update(struct drm_plane *plane, drm_dev_exit(idx); } +static void ssd135x_primary_plane_atomic_update(struct drm_plane *plane, + struct drm_atomic_commit *state) +{ + struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(state, plane); + struct drm_plane_state *old_plane_state = drm_atomic_get_old_plane_state(state, plane); + struct drm_shadow_plane_state *shadow_plane_state = to_drm_shadow_plane_state(plane_state); + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, plane_state->crtc); + struct ssd130x_crtc_state *ssd130x_crtc_state = to_ssd130x_crtc_state(crtc_state); + struct drm_framebuffer *fb = plane_state->fb; + struct drm_atomic_helper_damage_iter iter; + struct drm_device *drm = plane->dev; + struct drm_rect dst_clip; + struct drm_rect damage; + int idx; + + if (!drm_dev_enter(drm, &idx)) + return; + + if (drm_gem_fb_begin_cpu_access(fb, DMA_FROM_DEVICE)) + goto out_drm_dev_exit; + + drm_atomic_helper_damage_iter_init(&iter, old_plane_state, plane_state); + drm_atomic_for_each_plane_damage(&iter, &damage) { + dst_clip = plane_state->dst; + + if (!drm_rect_intersect(&dst_clip, &damage)) + continue; + + ssd135x_fb_blit_rect(fb, &shadow_plane_state->data[0], &dst_clip, + ssd130x_crtc_state->data_array, + &shadow_plane_state->fmtcnv_state); + } + + drm_gem_fb_end_cpu_access(fb, DMA_FROM_DEVICE); + +out_drm_dev_exit: + drm_dev_exit(idx); +} + static void ssd130x_primary_plane_atomic_disable(struct drm_plane *plane, struct drm_atomic_commit *state) { @@ -1319,6 +1543,31 @@ static void ssd133x_primary_plane_atomic_disable(struct drm_plane *plane, drm_dev_exit(idx); } +static void ssd135x_primary_plane_atomic_disable(struct drm_plane *plane, + struct drm_atomic_commit *state) +{ + struct drm_device *drm = plane->dev; + struct ssd130x_device *ssd130x = drm_to_ssd130x(drm); + struct drm_plane_state *plane_state; + struct drm_crtc_state *crtc_state; + struct ssd130x_crtc_state *ssd130x_crtc_state; + int idx; + + plane_state = drm_atomic_get_new_plane_state(state, plane); + if (!plane_state->crtc) + return; + + crtc_state = drm_atomic_get_new_crtc_state(state, plane_state->crtc); + ssd130x_crtc_state = to_ssd130x_crtc_state(crtc_state); + + if (!drm_dev_enter(drm, &idx)) + return; + + ssd135x_clear_screen(ssd130x, ssd130x_crtc_state->data_array); + + drm_dev_exit(idx); +} + /* Called during init to allocate the plane's atomic state. */ static struct drm_plane_state *ssd130x_primary_plane_create_state(struct drm_plane *plane) { @@ -1387,7 +1636,13 @@ static const struct drm_plane_helper_funcs ssd130x_primary_plane_helper_funcs[] .atomic_check = ssd133x_primary_plane_atomic_check, .atomic_update = ssd133x_primary_plane_atomic_update, .atomic_disable = ssd133x_primary_plane_atomic_disable, - } + }, + [SSD135X_FAMILY] = { + DRM_GEM_SHADOW_PLANE_HELPER_FUNCS, + .atomic_check = ssd133x_primary_plane_atomic_check, + .atomic_update = ssd135x_primary_plane_atomic_update, + .atomic_disable = ssd135x_primary_plane_atomic_disable, + }, }; static const struct drm_plane_funcs ssd130x_primary_plane_funcs = { @@ -1544,6 +1799,10 @@ static const struct drm_crtc_helper_funcs ssd130x_crtc_helper_funcs[] = { .mode_valid = ssd130x_crtc_mode_valid, .atomic_check = ssd133x_crtc_atomic_check, }, + [SSD135X_FAMILY] = { + .mode_valid = ssd130x_crtc_mode_valid, + .atomic_check = ssd133x_crtc_atomic_check, + }, }; static const struct drm_crtc_funcs ssd130x_crtc_funcs = { @@ -1631,6 +1890,31 @@ static void ssd133x_encoder_atomic_enable(struct drm_encoder *encoder, ssd130x_power_off(ssd130x); } +static void ssd135x_encoder_atomic_enable(struct drm_encoder *encoder, + struct drm_atomic_commit *state) +{ + struct drm_device *drm = encoder->dev; + struct ssd130x_device *ssd130x = drm_to_ssd130x(drm); + int ret; + + ret = ssd130x_power_on(ssd130x); + if (ret) + return; + + ret = ssd135x_init(ssd130x); + if (ret) + goto power_off; + + ssd130x_write_cmd(ssd130x, 1, SSD13XX_DISPLAY_ON); + + backlight_enable(ssd130x->bl_dev); + + return; + +power_off: + ssd130x_power_off(ssd130x); +} + static void ssd130x_encoder_atomic_disable(struct drm_encoder *encoder, struct drm_atomic_commit *state) { @@ -1656,7 +1940,11 @@ static const struct drm_encoder_helper_funcs ssd130x_encoder_helper_funcs[] = { [SSD133X_FAMILY] = { .atomic_enable = ssd133x_encoder_atomic_enable, .atomic_disable = ssd130x_encoder_atomic_disable, - } + }, + [SSD135X_FAMILY] = { + .atomic_enable = ssd135x_encoder_atomic_enable, + .atomic_disable = ssd130x_encoder_atomic_disable, + }, }; static const struct drm_encoder_funcs ssd130x_encoder_funcs = { @@ -1733,6 +2021,15 @@ static int ssd133x_update_bl(struct backlight_device *bdev) return ssd133x_set_contrast(ssd130x, ssd130x->contrast); } +static int ssd135x_update_bl(struct backlight_device *bdev) +{ + struct ssd130x_device *ssd130x = bl_get_data(bdev); + + ssd130x->contrast = backlight_get_brightness(bdev); + + return ssd135x_set_contrast(ssd130x, ssd130x->contrast); +} + static const struct backlight_ops ssd130xfb_bl_ops[] = { [SSD130X_FAMILY] = { .update_status = ssd130x_update_bl, @@ -1743,6 +2040,9 @@ static const struct backlight_ops ssd130xfb_bl_ops[] = { [SSD133X_FAMILY] = { .update_status = ssd133x_update_bl, }, + [SSD135X_FAMILY] = { + .update_status = ssd135x_update_bl, + }, }; static void ssd130x_parse_properties(struct ssd130x_device *ssd130x) diff --git a/drivers/gpu/drm/solomon/ssd130x.h b/drivers/gpu/drm/solomon/ssd130x.h index a4554018bb2a..fd81262f2ceb 100644 --- a/drivers/gpu/drm/solomon/ssd130x.h +++ b/drivers/gpu/drm/solomon/ssd130x.h @@ -26,7 +26,8 @@ enum ssd130x_family_ids { SSD130X_FAMILY, SSD132X_FAMILY, - SSD133X_FAMILY + SSD133X_FAMILY, + SSD135X_FAMILY, }; enum ssd130x_variants { @@ -42,6 +43,8 @@ enum ssd130x_variants { SSD1327_ID, /* ssd133x family */ SSD1331_ID, + /* ssd135x family */ + SSD1351_ID, NR_SSD130X_VARIANTS }; @@ -54,6 +57,11 @@ struct ssd130x_deviceinfo { bool need_pwm; bool need_chargepump; bool page_mode_only; + /* + * Whether the controller takes a command's parameters on the data path + * instead of as further command bytes. + */ + bool cmd_params_are_data; enum ssd130x_family_ids family_id; }; From b7b87ed3611414328710ac9a6ed8cf5d07e4a77e Mon Sep 17 00:00:00 2001 From: Ze Huang Date: Mon, 27 Jul 2026 03:45:14 +0800 Subject: [PATCH 041/143] drm/arcpgu: replace struct drm_simple_display_pipe with regular atomic helpers Instantiate the primary plane, CRTC and encoder directly and wire them up with standard atomic helpers. Move the simple-pipe callbacks into explicit plane and CRTC helper callbacks. Use commit-local CRTC and plane state in the CRTC enable path for mode and pixel-format programming. Handle missing remote encoder nodes explicitly and take the CRTC modeset lock while reading CRTC state from debugfs. Signed-off-by: Ze Huang Reviewed-by: Thomas Zimmermann Signed-off-by: Thomas Zimmermann Link: https://patch.msgid.link/20260727-drm-simple-kms-removal-v3-1-cd5dc89858c6@oss.qualcomm.com --- drivers/gpu/drm/tiny/arcpgu.c | 201 +++++++++++++++++++++++++--------- 1 file changed, 152 insertions(+), 49 deletions(-) diff --git a/drivers/gpu/drm/tiny/arcpgu.c b/drivers/gpu/drm/tiny/arcpgu.c index c93d61ac0bb7..19a943f5f792 100644 --- a/drivers/gpu/drm/tiny/arcpgu.c +++ b/drivers/gpu/drm/tiny/arcpgu.c @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -17,12 +18,13 @@ #include #include #include +#include #include #include #include #include +#include #include -#include #include #include #include @@ -52,13 +54,16 @@ struct arcpgu_drm_private { struct drm_device drm; void __iomem *regs; struct clk *clk; - struct drm_simple_display_pipe pipe; + struct drm_plane plane; + struct drm_crtc crtc; + struct drm_encoder encoder; struct drm_connector sim_conn; }; -#define dev_to_arcpgu(x) container_of(x, struct arcpgu_drm_private, drm) - -#define pipe_to_arcpgu_priv(x) container_of(x, struct arcpgu_drm_private, pipe) +static inline struct arcpgu_drm_private *dev_to_arcpgu(struct drm_device *drm) +{ + return container_of(drm, struct arcpgu_drm_private, drm); +} static inline void arc_pgu_write(struct arcpgu_drm_private *arcpgu, unsigned int reg, u32 value) @@ -115,9 +120,10 @@ static const u32 arc_pgu_supported_formats[] = { DRM_FORMAT_ARGB8888, }; -static void arc_pgu_set_pxl_fmt(struct arcpgu_drm_private *arcpgu) +static void arc_pgu_set_pxl_fmt(struct arcpgu_drm_private *arcpgu, + const struct drm_plane_state *plane_state) { - const struct drm_framebuffer *fb = arcpgu->pipe.plane.state->fb; + const struct drm_framebuffer *fb = plane_state->fb; uint32_t pixel_format = fb->format->format; u32 format = DRM_FORMAT_INVALID; int i; @@ -139,10 +145,10 @@ static void arc_pgu_set_pxl_fmt(struct arcpgu_drm_private *arcpgu) arc_pgu_write(arcpgu, ARCPGU_REG_CTRL, reg_ctrl); } -static enum drm_mode_status arc_pgu_mode_valid(struct drm_simple_display_pipe *pipe, - const struct drm_display_mode *mode) +static enum drm_mode_status arcpgu_crtc_helper_mode_valid(struct drm_crtc *crtc, + const struct drm_display_mode *mode) { - struct arcpgu_drm_private *arcpgu = pipe_to_arcpgu_priv(pipe); + struct arcpgu_drm_private *arcpgu = dev_to_arcpgu(crtc->dev); long rate, clk_rate = mode->clock * 1000; long diff = clk_rate / 200; /* +-0.5% allowed by HDMI spec */ @@ -153,9 +159,11 @@ static enum drm_mode_status arc_pgu_mode_valid(struct drm_simple_display_pipe *p return MODE_NOCLOCK; } -static void arc_pgu_mode_set(struct arcpgu_drm_private *arcpgu) +static void arc_pgu_mode_set(struct arcpgu_drm_private *arcpgu, + struct drm_crtc_state *crtc_state, + struct drm_plane_state *plane_state) { - struct drm_display_mode *m = &arcpgu->pipe.crtc.state->adjusted_mode; + struct drm_display_mode *m = &crtc_state->adjusted_mode; u32 val; arc_pgu_write(arcpgu, ARCPGU_REG_FMT, @@ -189,18 +197,19 @@ static void arc_pgu_mode_set(struct arcpgu_drm_private *arcpgu) arc_pgu_write(arcpgu, ARCPGU_REG_STRIDE, 0); arc_pgu_write(arcpgu, ARCPGU_REG_START_SET, 1); - arc_pgu_set_pxl_fmt(arcpgu); + arc_pgu_set_pxl_fmt(arcpgu, plane_state); clk_set_rate(arcpgu->clk, m->crtc_clock * 1000); } -static void arc_pgu_enable(struct drm_simple_display_pipe *pipe, - struct drm_crtc_state *crtc_state, - struct drm_plane_state *plane_state) +static void arcpgu_crtc_helper_atomic_enable(struct drm_crtc *crtc, + struct drm_atomic_commit *commit) { - struct arcpgu_drm_private *arcpgu = pipe_to_arcpgu_priv(pipe); + struct arcpgu_drm_private *arcpgu = dev_to_arcpgu(crtc->dev); + struct drm_plane_state *pstate = drm_atomic_get_new_plane_state(commit, &arcpgu->plane); + struct drm_crtc_state *cstate = drm_atomic_get_new_crtc_state(commit, crtc); - arc_pgu_mode_set(arcpgu); + arc_pgu_mode_set(arcpgu, cstate, pstate); clk_prepare_enable(arcpgu->clk); arc_pgu_write(arcpgu, ARCPGU_REG_CTRL, @@ -208,9 +217,10 @@ static void arc_pgu_enable(struct drm_simple_display_pipe *pipe, ARCPGU_CTRL_ENABLE_MASK); } -static void arc_pgu_disable(struct drm_simple_display_pipe *pipe) +static void arcpgu_crtc_helper_atomic_disable(struct drm_crtc *crtc, + struct drm_atomic_commit *commit) { - struct arcpgu_drm_private *arcpgu = pipe_to_arcpgu_priv(pipe); + struct arcpgu_drm_private *arcpgu = dev_to_arcpgu(crtc->dev); clk_disable_unprepare(arcpgu->clk); arc_pgu_write(arcpgu, ARCPGU_REG_CTRL, @@ -218,27 +228,21 @@ static void arc_pgu_disable(struct drm_simple_display_pipe *pipe) ~ARCPGU_CTRL_ENABLE_MASK); } -static void arc_pgu_update(struct drm_simple_display_pipe *pipe, - struct drm_plane_state *state) +static void arcpgu_plane_helper_atomic_update(struct drm_plane *plane, + struct drm_atomic_commit *commit) { + struct drm_plane_state *pstate = drm_atomic_get_new_plane_state(commit, plane); struct arcpgu_drm_private *arcpgu; struct drm_gem_dma_object *gem; - if (!pipe->plane.state->fb) + if (!pstate->fb) return; - arcpgu = pipe_to_arcpgu_priv(pipe); - gem = drm_fb_dma_get_gem_obj(pipe->plane.state->fb, 0); + arcpgu = dev_to_arcpgu(plane->dev); + gem = drm_fb_dma_get_gem_obj(pstate->fb, 0); arc_pgu_write(arcpgu, ARCPGU_REG_BUF0_ADDR, gem->dma_addr); } -static const struct drm_simple_display_pipe_funcs arc_pgu_pipe_funcs = { - .update = arc_pgu_update, - .mode_valid = arc_pgu_mode_valid, - .enable = arc_pgu_enable, - .disable = arc_pgu_disable, -}; - static const struct drm_mode_config_funcs arcpgu_drm_modecfg_funcs = { .fb_create = drm_gem_fb_create, .atomic_check = drm_atomic_helper_check, @@ -247,6 +251,74 @@ static const struct drm_mode_config_funcs arcpgu_drm_modecfg_funcs = { DEFINE_DRM_GEM_DMA_FOPS(arcpgu_drm_ops); +static int arcpgu_plane_helper_atomic_check(struct drm_plane *plane, + struct drm_atomic_commit *commit) +{ + struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(commit, plane); + struct drm_crtc_state *crtc_state = NULL; + + if (plane_state->crtc) { + crtc_state = drm_atomic_get_crtc_state(commit, plane_state->crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + } + + return drm_atomic_helper_check_plane_state(plane_state, crtc_state, + DRM_PLANE_NO_SCALING, + DRM_PLANE_NO_SCALING, + false, false); +} + +static const struct drm_plane_helper_funcs arcpgu_plane_helper_funcs = { + .prepare_fb = drm_gem_plane_helper_prepare_fb, + .atomic_check = arcpgu_plane_helper_atomic_check, + .atomic_update = arcpgu_plane_helper_atomic_update, +}; + +static const struct drm_plane_funcs arcpgu_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .destroy = drm_plane_cleanup, + .reset = drm_atomic_helper_plane_reset, + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, +}; + +static int arcpgu_crtc_helper_atomic_check(struct drm_crtc *crtc, + struct drm_atomic_commit *commit) +{ + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(commit, crtc); + int ret; + + if (crtc_state->enable) { + ret = drm_atomic_helper_check_crtc_primary_plane(crtc_state); + if (ret) + return ret; + } + + return drm_atomic_add_affected_planes(commit, crtc); +} + +static const struct drm_crtc_helper_funcs arcpgu_crtc_helper_funcs = { + .mode_valid = arcpgu_crtc_helper_mode_valid, + .atomic_check = arcpgu_crtc_helper_atomic_check, + .atomic_enable = arcpgu_crtc_helper_atomic_enable, + .atomic_disable = arcpgu_crtc_helper_atomic_disable, +}; + +static const struct drm_crtc_funcs arcpgu_crtc_funcs = { + .reset = drm_atomic_helper_crtc_reset, + .destroy = drm_crtc_cleanup, + .set_config = drm_atomic_helper_set_config, + .page_flip = drm_atomic_helper_page_flip, + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, +}; + +static const struct drm_encoder_funcs arcpgu_encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + static int arcpgu_load(struct arcpgu_drm_private *arcpgu) { struct platform_device *pdev = to_platform_device(arcpgu->drm.dev); @@ -254,6 +326,9 @@ static int arcpgu_load(struct arcpgu_drm_private *arcpgu) struct device_node *endpoint_node = NULL; struct drm_connector *connector = NULL; struct drm_device *drm = &arcpgu->drm; + struct drm_plane *plane; + struct drm_encoder *encoder; + struct drm_crtc *crtc; int ret; arcpgu->clk = devm_clk_get(drm->dev, "pxlclk"); @@ -285,6 +360,30 @@ static int arcpgu_load(struct arcpgu_drm_private *arcpgu) if (dma_set_mask_and_coherent(drm->dev, DMA_BIT_MASK(32))) return -ENODEV; + plane = &arcpgu->plane; + ret = drm_universal_plane_init(drm, plane, 0, + &arcpgu_plane_funcs, + arc_pgu_supported_formats, + ARRAY_SIZE(arc_pgu_supported_formats), + NULL, + DRM_PLANE_TYPE_PRIMARY, NULL); + if (ret) + return ret; + drm_plane_helper_add(plane, &arcpgu_plane_helper_funcs); + + crtc = &arcpgu->crtc; + ret = drm_crtc_init_with_planes(drm, crtc, plane, NULL, + &arcpgu_crtc_funcs, NULL); + if (ret) + return ret; + drm_crtc_helper_add(crtc, &arcpgu_crtc_helper_funcs); + + encoder = &arcpgu->encoder; + ret = drm_encoder_init(drm, encoder, &arcpgu_encoder_funcs, DRM_MODE_ENCODER_NONE, NULL); + if (ret) + return ret; + encoder->possible_crtcs = drm_crtc_mask(crtc); + /* * There is only one output port inside each device. It is linked with * encoder endpoint. @@ -293,29 +392,26 @@ static int arcpgu_load(struct arcpgu_drm_private *arcpgu) if (endpoint_node) { encoder_node = of_graph_get_remote_port_parent(endpoint_node); of_node_put(endpoint_node); - } else { - connector = &arcpgu->sim_conn; - dev_info(drm->dev, "no encoder found. Assumed virtual LCD on simulation platform\n"); - ret = arcpgu_drm_sim_init(drm, connector); - if (ret < 0) - return ret; - } + if (!encoder_node) + return -ENODEV; - ret = drm_simple_display_pipe_init(drm, &arcpgu->pipe, &arc_pgu_pipe_funcs, - arc_pgu_supported_formats, - ARRAY_SIZE(arc_pgu_supported_formats), - NULL, connector); - if (ret) - return ret; - - if (encoder_node) { /* Locate drm bridge from the hdmi encoder DT node */ struct drm_bridge *bridge __free(drm_bridge_put) = of_drm_find_and_get_bridge(encoder_node); if (!bridge) return -EPROBE_DEFER; - ret = drm_simple_display_pipe_attach_bridge(&arcpgu->pipe, bridge); + ret = drm_bridge_attach(encoder, bridge, NULL, 0); + if (ret) + return ret; + } else { + connector = &arcpgu->sim_conn; + dev_info(drm->dev, "no encoder found. Assumed virtual LCD on simulation platform\n"); + ret = arcpgu_drm_sim_init(drm, connector); + if (ret < 0) + return ret; + + ret = drm_connector_attach_encoder(connector, encoder); if (ret) return ret; } @@ -342,7 +438,14 @@ static int arcpgu_show_pxlclock(struct seq_file *m, void *arg) struct drm_device *drm = node->minor->dev; struct arcpgu_drm_private *arcpgu = dev_to_arcpgu(drm); unsigned long clkrate = clk_get_rate(arcpgu->clk); - unsigned long mode_clock = arcpgu->pipe.crtc.mode.crtc_clock * 1000; + unsigned long mode_clock; + int ret; + + ret = drm_modeset_lock_single_interruptible(&arcpgu->crtc.mutex); + if (ret) + return ret; + mode_clock = arcpgu->crtc.state->mode.crtc_clock * 1000; + drm_modeset_unlock(&arcpgu->crtc.mutex); seq_printf(m, "hw : %lu\n", clkrate); seq_printf(m, "mode: %lu\n", mode_clock); From fccd486a7e0c4fb58b8357087c9618c217532d0d Mon Sep 17 00:00:00 2001 From: Ze Huang Date: Mon, 27 Jul 2026 03:45:15 +0800 Subject: [PATCH 042/143] drm/aspeed: replace struct drm_simple_display_pipe with regular atomic helpers Replace the simple display pipe with explicit plane, CRTC and encoder objects. Move callbacks to plane and CRTC helpers, and wire vblank handling through drm_crtc_funcs. Move page-flip event handling from plane atomic_update() to CRTC atomic_flush(). Use commit-local CRTC and plane state when programming mode and pixel format in the CRTC enable path. Signed-off-by: Ze Huang Reviewed-by: Thomas Zimmermann Signed-off-by: Thomas Zimmermann Link: https://patch.msgid.link/20260727-drm-simple-kms-removal-v3-2-cd5dc89858c6@oss.qualcomm.com --- drivers/gpu/drm/aspeed/aspeed_gfx.h | 11 +- drivers/gpu/drm/aspeed/aspeed_gfx_crtc.c | 203 +++++++++++++++++------ drivers/gpu/drm/aspeed/aspeed_gfx_drv.c | 3 +- 3 files changed, 157 insertions(+), 60 deletions(-) diff --git a/drivers/gpu/drm/aspeed/aspeed_gfx.h b/drivers/gpu/drm/aspeed/aspeed_gfx.h index 4e6a442c3886..273528d30b4c 100644 --- a/drivers/gpu/drm/aspeed/aspeed_gfx.h +++ b/drivers/gpu/drm/aspeed/aspeed_gfx.h @@ -2,7 +2,6 @@ /* Copyright 2018 IBM Corporation */ #include -#include struct aspeed_gfx { struct drm_device drm; @@ -17,10 +16,16 @@ struct aspeed_gfx { u32 throd_val; u32 scan_line_max; - struct drm_simple_display_pipe pipe; + struct drm_plane plane; + struct drm_crtc crtc; + struct drm_encoder encoder; struct drm_connector connector; }; -#define to_aspeed_gfx(x) container_of(x, struct aspeed_gfx, drm) + +static inline struct aspeed_gfx *to_aspeed_gfx(struct drm_device *drm) +{ + return container_of(drm, struct aspeed_gfx, drm); +} int aspeed_gfx_create_pipe(struct drm_device *drm); int aspeed_gfx_create_output(struct drm_device *drm); diff --git a/drivers/gpu/drm/aspeed/aspeed_gfx_crtc.c b/drivers/gpu/drm/aspeed/aspeed_gfx_crtc.c index 7877a57b8e26..e8c9a6040d75 100644 --- a/drivers/gpu/drm/aspeed/aspeed_gfx_crtc.c +++ b/drivers/gpu/drm/aspeed/aspeed_gfx_crtc.c @@ -5,6 +5,8 @@ #include #include +#include +#include #include #include #include @@ -12,22 +14,17 @@ #include #include #include -#include #include #include "aspeed_gfx.h" -static struct aspeed_gfx * -drm_pipe_to_aspeed_gfx(struct drm_simple_display_pipe *pipe) +static int aspeed_gfx_set_pixel_fmt(struct aspeed_gfx *priv, + struct drm_plane_state *plane_state, + u32 *bpp) { - return container_of(pipe, struct aspeed_gfx, pipe); -} - -static int aspeed_gfx_set_pixel_fmt(struct aspeed_gfx *priv, u32 *bpp) -{ - struct drm_crtc *crtc = &priv->pipe.crtc; + struct drm_crtc *crtc = &priv->crtc; struct drm_device *drm = crtc->dev; - const u32 format = crtc->primary->state->fb->format->format; + const u32 format = plane_state->fb->format->format; u32 ctrl1; ctrl1 = readl(priv->base + CRT_CTRL1); @@ -77,13 +74,15 @@ static void aspeed_gfx_disable_controller(struct aspeed_gfx *priv) regmap_update_bits(priv->scu, priv->dac_reg, BIT(16), 0); } -static void aspeed_gfx_crtc_mode_set_nofb(struct aspeed_gfx *priv) +static void aspeed_gfx_crtc_mode_set_nofb(struct aspeed_gfx *priv, + struct drm_crtc_state *crtc_state, + struct drm_plane_state *plane_state) { - struct drm_display_mode *m = &priv->pipe.crtc.state->adjusted_mode; + struct drm_display_mode *m = &crtc_state->adjusted_mode; u32 ctrl1, d_offset, t_count, bpp; int err; - err = aspeed_gfx_set_pixel_fmt(priv, &bpp); + err = aspeed_gfx_set_pixel_fmt(priv, plane_state, &bpp); if (err) return; @@ -139,48 +138,35 @@ static void aspeed_gfx_crtc_mode_set_nofb(struct aspeed_gfx *priv) writel(priv->throd_val, priv->base + CRT_THROD); } -static void aspeed_gfx_pipe_enable(struct drm_simple_display_pipe *pipe, - struct drm_crtc_state *crtc_state, - struct drm_plane_state *plane_state) +static void aspeed_gfx_crtc_helper_atomic_enable(struct drm_crtc *crtc, + struct drm_atomic_commit *commit) { - struct aspeed_gfx *priv = drm_pipe_to_aspeed_gfx(pipe); - struct drm_crtc *crtc = &pipe->crtc; + struct aspeed_gfx *priv = to_aspeed_gfx(crtc->dev); + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(commit, crtc); + struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(commit, &priv->plane); - aspeed_gfx_crtc_mode_set_nofb(priv); + aspeed_gfx_crtc_mode_set_nofb(priv, crtc_state, plane_state); aspeed_gfx_enable_controller(priv); drm_crtc_vblank_on(crtc); } -static void aspeed_gfx_pipe_disable(struct drm_simple_display_pipe *pipe) +static void aspeed_gfx_crtc_helper_atomic_disable(struct drm_crtc *crtc, + struct drm_atomic_commit *commit) { - struct aspeed_gfx *priv = drm_pipe_to_aspeed_gfx(pipe); - struct drm_crtc *crtc = &pipe->crtc; + struct aspeed_gfx *priv = to_aspeed_gfx(crtc->dev); drm_crtc_vblank_off(crtc); aspeed_gfx_disable_controller(priv); } -static void aspeed_gfx_pipe_update(struct drm_simple_display_pipe *pipe, - struct drm_plane_state *plane_state) +static void aspeed_gfx_plane_helper_atomic_update(struct drm_plane *plane, + struct drm_atomic_commit *commit) { - struct aspeed_gfx *priv = drm_pipe_to_aspeed_gfx(pipe); - struct drm_crtc *crtc = &pipe->crtc; - struct drm_framebuffer *fb = pipe->plane.state->fb; - struct drm_pending_vblank_event *event; + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(commit, plane); + struct aspeed_gfx *priv = to_aspeed_gfx(plane->dev); + struct drm_framebuffer *fb = new_plane_state->fb; struct drm_gem_dma_object *gem; - spin_lock_irq(&crtc->dev->event_lock); - event = crtc->state->event; - if (event) { - crtc->state->event = NULL; - - if (drm_crtc_vblank_get(crtc) == 0) - drm_crtc_arm_vblank_event(crtc, event); - else - drm_crtc_send_vblank_event(crtc, event); - } - spin_unlock_irq(&crtc->dev->event_lock); - if (!fb) return; @@ -190,9 +176,9 @@ static void aspeed_gfx_pipe_update(struct drm_simple_display_pipe *pipe, writel(gem->dma_addr, priv->base + CRT_ADDR); } -static int aspeed_gfx_enable_vblank(struct drm_simple_display_pipe *pipe) +static int aspeed_gfx_crtc_enable_vblank(struct drm_crtc *crtc) { - struct aspeed_gfx *priv = drm_pipe_to_aspeed_gfx(pipe); + struct aspeed_gfx *priv = to_aspeed_gfx(crtc->dev); u32 reg = readl(priv->base + CRT_CTRL1); /* Clear pending VBLANK IRQ */ @@ -204,9 +190,9 @@ static int aspeed_gfx_enable_vblank(struct drm_simple_display_pipe *pipe) return 0; } -static void aspeed_gfx_disable_vblank(struct drm_simple_display_pipe *pipe) +static void aspeed_gfx_crtc_disable_vblank(struct drm_crtc *crtc) { - struct aspeed_gfx *priv = drm_pipe_to_aspeed_gfx(pipe); + struct aspeed_gfx *priv = to_aspeed_gfx(crtc->dev); u32 reg = readl(priv->base + CRT_CTRL1); reg &= ~CRT_CTRL_VERTICAL_INTR_EN; @@ -216,12 +202,93 @@ static void aspeed_gfx_disable_vblank(struct drm_simple_display_pipe *pipe) writel(reg | CRT_CTRL_VERTICAL_INTR_STS, priv->base + CRT_CTRL1); } -static const struct drm_simple_display_pipe_funcs aspeed_gfx_funcs = { - .enable = aspeed_gfx_pipe_enable, - .disable = aspeed_gfx_pipe_disable, - .update = aspeed_gfx_pipe_update, - .enable_vblank = aspeed_gfx_enable_vblank, - .disable_vblank = aspeed_gfx_disable_vblank, +static int aspeed_gfx_plane_helper_atomic_check(struct drm_plane *plane, + struct drm_atomic_commit *commit) +{ + struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(commit, plane); + struct drm_crtc_state *crtc_state = NULL; + + if (plane_state->crtc) { + crtc_state = drm_atomic_get_crtc_state(commit, plane_state->crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + } + + return drm_atomic_helper_check_plane_state(plane_state, crtc_state, + DRM_PLANE_NO_SCALING, + DRM_PLANE_NO_SCALING, + false, false); +} + +static const struct drm_plane_helper_funcs aspeed_gfx_plane_helper_funcs = { + .prepare_fb = drm_gem_plane_helper_prepare_fb, + .atomic_check = aspeed_gfx_plane_helper_atomic_check, + .atomic_update = aspeed_gfx_plane_helper_atomic_update, +}; + +static const struct drm_plane_funcs aspeed_gfx_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .destroy = drm_plane_cleanup, + .reset = drm_atomic_helper_plane_reset, + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, +}; + +static int aspeed_gfx_crtc_helper_atomic_check(struct drm_crtc *crtc, + struct drm_atomic_commit *commit) +{ + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(commit, crtc); + int ret; + + if (crtc_state->enable) { + ret = drm_atomic_helper_check_crtc_primary_plane(crtc_state); + if (ret) + return ret; + } + + return drm_atomic_add_affected_planes(commit, crtc); +} + +static void aspeed_gfx_crtc_helper_atomic_flush(struct drm_crtc *crtc, + struct drm_atomic_commit *commit) +{ + struct drm_crtc_state *new_crtc_state = drm_atomic_get_new_crtc_state(commit, crtc); + struct drm_pending_vblank_event *event = new_crtc_state->event; + + if (!event) + return; + + new_crtc_state->event = NULL; + + spin_lock_irq(&crtc->dev->event_lock); + if (drm_crtc_vblank_get(crtc) == 0) + drm_crtc_arm_vblank_event(crtc, event); + else + drm_crtc_send_vblank_event(crtc, event); + spin_unlock_irq(&crtc->dev->event_lock); +} + +static const struct drm_crtc_helper_funcs aspeed_gfx_crtc_helper_funcs = { + .atomic_check = aspeed_gfx_crtc_helper_atomic_check, + .atomic_enable = aspeed_gfx_crtc_helper_atomic_enable, + .atomic_disable = aspeed_gfx_crtc_helper_atomic_disable, + .atomic_flush = aspeed_gfx_crtc_helper_atomic_flush, +}; + +static const struct drm_crtc_funcs aspeed_gfx_crtc_funcs = { + .reset = drm_atomic_helper_crtc_reset, + .destroy = drm_crtc_cleanup, + .set_config = drm_atomic_helper_set_config, + .page_flip = drm_atomic_helper_page_flip, + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, + .enable_vblank = aspeed_gfx_crtc_enable_vblank, + .disable_vblank = aspeed_gfx_crtc_disable_vblank, +}; + +static const struct drm_encoder_funcs aspeed_gfx_encoder_funcs = { + .destroy = drm_encoder_cleanup, }; static const uint32_t aspeed_gfx_formats[] = { @@ -232,10 +299,36 @@ static const uint32_t aspeed_gfx_formats[] = { int aspeed_gfx_create_pipe(struct drm_device *drm) { struct aspeed_gfx *priv = to_aspeed_gfx(drm); + struct drm_plane *plane = &priv->plane; + struct drm_crtc *crtc = &priv->crtc; + struct drm_encoder *encoder = &priv->encoder; + int ret; - return drm_simple_display_pipe_init(drm, &priv->pipe, &aspeed_gfx_funcs, - aspeed_gfx_formats, - ARRAY_SIZE(aspeed_gfx_formats), - NULL, - &priv->connector); + ret = drm_universal_plane_init(drm, plane, 0, + &aspeed_gfx_plane_funcs, + aspeed_gfx_formats, + ARRAY_SIZE(aspeed_gfx_formats), + NULL, + DRM_PLANE_TYPE_PRIMARY, NULL); + if (ret) + return ret; + drm_plane_helper_add(plane, &aspeed_gfx_plane_helper_funcs); + + ret = drm_crtc_init_with_planes(drm, crtc, plane, NULL, + &aspeed_gfx_crtc_funcs, NULL); + if (ret) + return ret; + drm_crtc_helper_add(crtc, &aspeed_gfx_crtc_helper_funcs); + + ret = drm_encoder_init(drm, encoder, &aspeed_gfx_encoder_funcs, + DRM_MODE_ENCODER_NONE, NULL); + if (ret) + return ret; + encoder->possible_crtcs = drm_crtc_mask(crtc); + + ret = drm_connector_attach_encoder(&priv->connector, encoder); + if (ret) + return ret; + + return 0; } diff --git a/drivers/gpu/drm/aspeed/aspeed_gfx_drv.c b/drivers/gpu/drm/aspeed/aspeed_gfx_drv.c index d4577663a1f0..c8746766f258 100644 --- a/drivers/gpu/drm/aspeed/aspeed_gfx_drv.c +++ b/drivers/gpu/drm/aspeed/aspeed_gfx_drv.c @@ -20,7 +20,6 @@ #include #include #include -#include #include #include @@ -129,7 +128,7 @@ static irqreturn_t aspeed_gfx_irq_handler(int irq, void *data) reg = readl(priv->base + CRT_CTRL1); if (reg & CRT_CTRL_VERTICAL_INTR_STS) { - drm_crtc_handle_vblank(&priv->pipe.crtc); + drm_crtc_handle_vblank(&priv->crtc); writel(reg, priv->base + priv->int_clr_reg); return IRQ_HANDLED; } From 8bc1946ab3723cbef79341af98eaf8002aba71ed Mon Sep 17 00:00:00 2001 From: Ze Huang Date: Mon, 27 Jul 2026 03:45:16 +0800 Subject: [PATCH 043/143] drm/mcde: replace struct drm_simple_display_pipe with regular atomic helpers Convert MCDE to explicit plane, CRTC and encoder objects. Handle pending page-flip events from the CRTC atomic flush path using the commit-local CRTC state, and avoid manual connector/encoder attachment before the connector has been created by the bridge path. Signed-off-by: Ze Huang Reviewed-by: Thomas Zimmermann Signed-off-by: Thomas Zimmermann Link: https://patch.msgid.link/20260727-drm-simple-kms-removal-v3-3-cd5dc89858c6@oss.qualcomm.com --- drivers/gpu/drm/mcde/mcde_display.c | 272 ++++++++++++++++++---------- drivers/gpu/drm/mcde/mcde_drm.h | 12 +- drivers/gpu/drm/mcde/mcde_drv.c | 3 +- 3 files changed, 184 insertions(+), 103 deletions(-) diff --git a/drivers/gpu/drm/mcde/mcde_display.c b/drivers/gpu/drm/mcde/mcde_display.c index 257a6e84dd58..50c90cf84a9a 100644 --- a/drivers/gpu/drm/mcde/mcde_display.c +++ b/drivers/gpu/drm/mcde/mcde_display.c @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -18,7 +19,6 @@ #include #include #include -#include #include #include #include