From ec902de0221747094f9ccbb61454e9633fbf282f Mon Sep 17 00:00:00 2001 From: Raveendra Talabattula Date: Mon, 3 Aug 2026 15:21:21 +0100 Subject: [PATCH 001/213] drm/komeda: Fix bits parsing of GLB_CORE_ID GLB_CORE_ID contains the product and version information returned by the hardware. The current macros parses VERSION_MINOR as a 4-bit field and VERSION_STATUS as an 8-bit field, which does not match the register layout. As a result, the driver reports an incorrect version number. Fix the parsing macros to match the hardware specification: - VERSION_MINOR is 8 bits at [11:4] - VERSION_STATUS is 4 bits at [3:0] Fixes: bd628c1bed79 ("drm/komeda: komeda_dev/pipeline/component definition and initialzation") Signed-off-by: Raveendra Talabattula Reviewed-by: Liviu Dudau Signed-off-by: Liviu Dudau Link: https://patch.msgid.link/20260803142122.4127851-2-raveendra.talabattula@arm.com --- drivers/gpu/drm/arm/display/include/malidp_product.h | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/arm/display/include/malidp_product.h b/drivers/gpu/drm/arm/display/include/malidp_product.h index 6f954bcdf40e..f9a3ee7ba4b7 100644 --- a/drivers/gpu/drm/arm/display/include/malidp_product.h +++ b/drivers/gpu/drm/arm/display/include/malidp_product.h @@ -8,14 +8,18 @@ #define _MALIDP_PRODUCT_H_ /* Product identification */ +/* GLB_CORE_ID fields as per HW specification: + * MINOR is 8 bits ([11:4]) and STATUS is 4 bits ([3:0]). + * Update masks/shifts accordingly. + */ #define MALIDP_CORE_ID(__product, __major, __minor, __status) \ ((((__product) & 0xFFFF) << 16) | (((__major) & 0xF) << 12) | \ - (((__minor) & 0xF) << 8) | ((__status) & 0xFF)) + (((__minor) & 0xFF) << 4) | ((__status) & 0xF)) #define MALIDP_CORE_ID_PRODUCT_ID(__core_id) ((__u32)(__core_id) >> 16) #define MALIDP_CORE_ID_MAJOR(__core_id) (((__u32)(__core_id) >> 12) & 0xF) -#define MALIDP_CORE_ID_MINOR(__core_id) (((__u32)(__core_id) >> 8) & 0xF) -#define MALIDP_CORE_ID_STATUS(__core_id) (((__u32)(__core_id)) & 0xFF) +#define MALIDP_CORE_ID_MINOR(__core_id) (((__u32)(__core_id) >> 4) & 0xFF) +#define MALIDP_CORE_ID_STATUS(__core_id) ((__u32)(__core_id) & 0xF) /* Mali-display product IDs */ #define MALIDP_D71_PRODUCT_ID 0x0071 From dc2f9f7fed1a8ea5290f9f60c6310d497e85e666 Mon Sep 17 00:00:00 2001 From: Vincenzo Frascino Date: Mon, 3 Aug 2026 15:21:22 +0100 Subject: [PATCH 002/213] drm/komeda: Set display/writeback clone masks Since commit 41b4b11da0215 ("drm: Add valid clones check"), all encoders attached to the same CRTC must advertise each other through possible_clones. Komeda creates one writeback connector per CRTC. Allow the writeback encoder to be cloned only with encoders that can drive the same CRTC, and update those encoders reciprocally. Signed-off-by: Asad Malik Signed-off-by: Vincenzo Frascino Co-developed-by: Raveendra Talabattula Signed-off-by: Raveendra Talabattula Reviewed-by: Liviu Dudau Signed-off-by: Liviu Dudau Link: https://patch.msgid.link/20260803142122.4127851-3-raveendra.talabattula@arm.com --- .../arm/display/komeda/komeda_wb_connector.c | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_wb_connector.c b/drivers/gpu/drm/arm/display/komeda/komeda_wb_connector.c index 41cc3e080dc9..4872253a2449 100644 --- a/drivers/gpu/drm/arm/display/komeda/komeda_wb_connector.c +++ b/drivers/gpu/drm/arm/display/komeda/komeda_wb_connector.c @@ -143,12 +143,17 @@ static int komeda_wb_connector_add(struct komeda_kms_dev *kms, struct komeda_wb_connector *kwb_conn; struct drm_writeback_connector *wb_conn; struct drm_display_info *info; + struct drm_encoder *encoder; + struct drm_encoder *wb_encoder; u32 *formats, n_formats = 0; + u32 crtc_mask; int err; if (!kcrtc->master->wb_layer) return 0; + crtc_mask = drm_crtc_mask(&kcrtc->base); + kwb_conn = kzalloc_obj(*kwb_conn); if (!kwb_conn) return -ENOMEM; @@ -176,6 +181,34 @@ static int komeda_wb_connector_add(struct komeda_kms_dev *kms, return err; } + wb_encoder = &wb_conn->encoder; + + /* + * The writeback connector is associated with a single CRTC. Make its + * encoder clone-compatible only with encoders that can drive that CRTC. + * + * possible_clones must contain the encoder's own bit whenever it is + * non-zero. Add both the encoder itself and the writeback encoder when + * updating the reciprocal clone relationship. + */ + wb_encoder->possible_clones = drm_encoder_mask(wb_encoder); + + drm_for_each_encoder(encoder, &kms->base) { + u32 encoder_mask; + + if (encoder == wb_encoder) + continue; + + if (!(encoder->possible_crtcs & crtc_mask)) + continue; + + encoder_mask = drm_encoder_mask(encoder); + + wb_encoder->possible_clones |= encoder_mask; + encoder->possible_clones |= encoder_mask | + drm_encoder_mask(wb_encoder); + } + drm_connector_helper_add(&wb_conn->base, &komeda_wb_conn_helper_funcs); info = &kwb_conn->base.base.display_info; From 5c1a6c9736a62e8339a4028142a46c133276204d Mon Sep 17 00:00:00 2001 From: Alex Huang Date: Tue, 4 Aug 2026 10:33:36 -0400 Subject: [PATCH 003/213] drm/edid: Parse AMD VSDB for FreeSync refresh range Commit 118362a96286 ("drm/edid: Parse AMD Vendor-Specific Data Block") added parsing of the AMD VSDB, but only for version 3 and only for the replay/panel-type/luminance fields. Monitors that only advertise FreeSync through a v1 or v2 AMD VSDB therefore get no variable refresh rate range from the common EDID parser. Restructure drm_parse_amd_vsdb() and implement parsing for the FreeSync refresh range fields provided in the AMD VSDB. Additionally, version > 3 will be parsed as if they were version 3 (with a warning of this occurring) as future versions will likely be backwards compatible. Signed-off-by: Alex Huang Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20260804143339.714548-2-Alex.Huang2@amd.com Signed-off-by: Mario Limonciello --- drivers/gpu/drm/drm_edid.c | 147 +++++++++++++++++++++++++++++------- include/drm/drm_connector.h | 22 +++++- 2 files changed, 139 insertions(+), 30 deletions(-) diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index 07970e5b5f65..98dd72d986d5 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -101,20 +101,42 @@ enum drm_edid_internal_quirk { #define MICROSOFT_IEEE_OUI 0xca125c #define AMD_IEEE_OUI 0x00001A +#define AMD_VSDB_V1_PAYLOAD_LEN 8 +#define AMD_VSDB_V2_PAYLOAD_LEN 13 #define AMD_VSDB_V3_PAYLOAD_MIN_LEN 15 -#define AMD_VSDB_V3_PAYLOAD_MAX_LEN 20 +#define AMD_VSDB_V3_PAYLOAD_MAX_LEN 21 -struct amd_vsdb_v3_payload { +#define AMD_VSDB_V3_MAX_VFREQ_EXT2_LEN 21 + +struct amd_vsdb_common_payload { u8 oui[3]; u8 version; +} __packed; + +struct amd_vsdb_v1_payload { + struct amd_vsdb_common_payload common; u8 feature_caps; - u8 rsvd0[3]; + u8 min_vfreq; + u8 max_vfreq; + u8 freesync_vcp_code; +} __packed; + +struct amd_vsdb_v2_payload { + struct amd_vsdb_v1_payload v1; u8 cs_eotf_support; u8 lum1_max; u8 lum1_min; u8 lum2_max; u8 lum2_min; - u8 rsvd1[2]; +} __packed; + +struct amd_vsdb_v3_payload { + struct amd_vsdb_v2_payload v2; + u8 max_vfreq_ext_low; + /* + * byte 14: bits 0-1 extend MSB of max refresh rate + */ + u8 max_vfreq_ext_high; /* * Bytes beyond AMD_VSDB_V3_PAYLOAD_MIN_LEN are optional; a * monitor may provide a payload as short as 15 bytes. Always @@ -123,6 +145,12 @@ struct amd_vsdb_v3_payload { u8 extra[AMD_VSDB_V3_PAYLOAD_MAX_LEN - AMD_VSDB_V3_PAYLOAD_MIN_LEN]; } __packed; +static const u8 amd_vsdb_min_len[] = { + [1] = AMD_VSDB_V1_PAYLOAD_LEN, + [2] = AMD_VSDB_V2_PAYLOAD_LEN, + [3] = AMD_VSDB_V3_PAYLOAD_MIN_LEN, +}; + struct detailed_mode_closure { struct drm_connector *connector; const struct drm_edid *drm_edid; @@ -5231,7 +5259,7 @@ static bool cea_db_is_microsoft_vsdb(const struct cea_db *db) static bool cea_db_is_amd_vsdb(const struct cea_db *db) { return cea_db_is_vendor(db, AMD_IEEE_OUI) && - cea_db_payload_len(db) >= AMD_VSDB_V3_PAYLOAD_MIN_LEN && + cea_db_payload_len(db) >= AMD_VSDB_V1_PAYLOAD_LEN && cea_db_payload_len(db) <= AMD_VSDB_V3_PAYLOAD_MAX_LEN; } @@ -6431,43 +6459,104 @@ static void drm_parse_microsoft_vsdb(struct drm_connector *connector, connector->base.id, connector->name, version, db[5]); } +static void drm_parse_amd_vsdb_common(struct drm_display_info *info, + const struct amd_vsdb_common_payload *data) +{ + info->amd_vsdb.version = data->version; +} + +static void drm_parse_amd_vsdb_v1(struct drm_display_info *info, + const struct amd_vsdb_v1_payload *data) +{ + info->amd_vsdb.freesync_supported = data->feature_caps & 0x1; + info->amd_vsdb.min_frame_rate = data->min_vfreq; + info->amd_vsdb.max_frame_rate = data->max_vfreq; + info->amd_vsdb.freesync_vcp_code = data->freesync_vcp_code; + + drm_parse_amd_vsdb_common(info, &data->common); +} + +static void drm_parse_amd_vsdb_v2(struct drm_display_info *info, + const struct amd_vsdb_v2_payload *data) +{ + info->amd_vsdb.luminance_range1.max_luminance = data->lum1_max; + info->amd_vsdb.luminance_range1.min_luminance = data->lum1_min; + info->amd_vsdb.luminance_range2.max_luminance = data->lum2_max; + info->amd_vsdb.luminance_range2.min_luminance = data->lum2_min; + + drm_parse_amd_vsdb_v1(info, &data->v1); +} + +static void drm_parse_amd_vsdb_v3(struct drm_display_info *info, + const struct amd_vsdb_v3_payload *data, + int payload_len) +{ + u16 max_frame_rate; + + info->amd_vsdb.replay_mode = data->v2.v1.feature_caps & 0x40; + info->amd_vsdb.panel_type = (data->v2.cs_eotf_support & 0xC0) >> 6; + + drm_parse_amd_vsdb_v2(info, &data->v2); + + /* vfreq is provided in a different set of fields for v3. */ + max_frame_rate = data->max_vfreq_ext_low | + (data->max_vfreq_ext_high & 0x3) << 8; + + /* + * The AMD VSDB v3 payload length is variable (15..21 bytes). + * All fields through max_vfreq_ext_high (byte 14) are always + * present, but data->extra[] (bytes 15+) may not be, so any access + * to extra[] must be guarded with a runtime length check to + * avoid out-of-bounds reads on shorter (but spec-valid) payloads. + */ + if (payload_len >= AMD_VSDB_V3_MAX_VFREQ_EXT2_LEN) + max_frame_rate |= (data->extra[5] & 0x3) << 10; + + info->amd_vsdb.max_frame_rate = max_frame_rate; +} + static void drm_parse_amd_vsdb(struct drm_connector *connector, const struct cea_db *db) { struct drm_display_info *info = &connector->display_info; const u8 *data = cea_db_data(db); - const struct amd_vsdb_v3_payload *p; + int payload_len = cea_db_payload_len(db); + u8 version; - p = (const struct amd_vsdb_v3_payload *)data; + version = ((const struct amd_vsdb_common_payload *)data)->version; - if (p->version != 0x03) { + if (version < 1) { drm_dbg_kms(connector->dev, "[CONNECTOR:%d:%s] Unsupported AMD VSDB version %u\n", - connector->base.id, connector->name, p->version); + connector->base.id, connector->name, version); return; } - info->amd_vsdb.version = p->version; - info->amd_vsdb.replay_mode = p->feature_caps & 0x40; - info->amd_vsdb.panel_type = (p->cs_eotf_support & 0xC0) >> 6; - info->amd_vsdb.luminance_range1.max_luminance = p->lum1_max; - info->amd_vsdb.luminance_range1.min_luminance = p->lum1_min; - info->amd_vsdb.luminance_range2.max_luminance = p->lum2_max; - info->amd_vsdb.luminance_range2.min_luminance = p->lum2_min; + if ((version <= 3 && payload_len < amd_vsdb_min_len[version]) || + (version > 3 && payload_len < AMD_VSDB_V3_PAYLOAD_MIN_LEN)) { + drm_dbg_kms(connector->dev, + "[CONNECTOR:%d:%s] AMD VSDB v%u payload too short (%d bytes)\n", + connector->base.id, connector->name, version, payload_len); + return; + } - /* - * The AMD VSDB v3 payload length is variable (15..20 bytes). - * All fields through p->rsvd1 (byte 14) are always present, - * but p->extra[] (bytes 15+) may not be. Any future access to - * extra[] must be guarded with a runtime length check to avoid - * out-of-bounds reads on shorter (but spec-valid) payloads. - * For example: - * - * int len = cea_db_payload_len(db); - * - * if (len > AMD_VSDB_V3_PAYLOAD_MIN_LEN) - * info->amd_vsdb.foo = p->extra[0]; - */ + switch (version) { + default: + drm_dbg_kms(connector->dev, + "[CONNECTOR:%d:%s] AMD VSDB v%u is unsupported, VSDB will be parsed as v3.\n", + connector->base.id, connector->name, version); + fallthrough; + case 3: + drm_parse_amd_vsdb_v3(info, (const struct amd_vsdb_v3_payload *)data, + payload_len); + break; + case 2: + drm_parse_amd_vsdb_v2(info, (const struct amd_vsdb_v2_payload *)data); + break; + case 1: + drm_parse_amd_vsdb_v1(info, (const struct amd_vsdb_v1_payload *)data); + break; + } } static void drm_parse_cea_ext(struct drm_connector *connector, diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h index a0cf0268de48..48059e7073e5 100644 --- a/include/drm/drm_connector.h +++ b/include/drm/drm_connector.h @@ -765,7 +765,7 @@ enum drm_bus_flags { * struct drm_amd_vsdb_info - AMD-specific VSDB information * * This structure holds information parsed from the AMD Vendor-Specific Data - * Block (VSDB) version 3. + * Block (VSDB) versions 1 to 3. */ struct drm_amd_vsdb_info { /** @@ -773,6 +773,26 @@ struct drm_amd_vsdb_info { */ u8 version; + /** + * @freesync_supported: FreeSync (variable refresh rate) supported by panel + */ + bool freesync_supported; + + /** + * @min_frame_rate: FreeSync minimum refresh rate in Hz + */ + u16 min_frame_rate; + + /** + * @max_frame_rate: FreeSync maximum refresh rate in Hz + */ + u16 max_frame_rate; + + /** + * @freesync_vcp_code: MCCS VCP code for FreeSync + */ + u8 freesync_vcp_code; + /** * @replay_mode: Panel Replay supported */ From f65198b3d07389afdcb48e4ad00fea2aded95dfd Mon Sep 17 00:00:00 2001 From: Alex Huang Date: Tue, 4 Aug 2026 10:33:37 -0400 Subject: [PATCH 004/213] drm/amd/display: Use HDMI FreeSync range from common EDID parser HDMI FreeSync on amdgpu is detected using the AMD VSDB parser in the DMUB firmware, now that the common DRM EDID parser can do the same, use the common parser. Extend get_amd_vsdb() to copy the relevant flags and data now being extracted from the AMD VSDB. Additionally, parse_hdmi_amd_vsdb() and the parse_edid_cea() firmware helpers now have no callers; remove them. Signed-off-by: Alex Huang Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20260804143339.714548-3-Alex.Huang2@amd.com (Adjust for movement to amdgpu_dm_connector.c) Signed-off-by: Mario Limonciello --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 1 - .../display/amdgpu_dm/amdgpu_dm_connector.c | 178 +----------------- 2 files changed, 8 insertions(+), 171 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index 67b825cbb88f..38cffe127989 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -36,7 +36,6 @@ #include "dc/inc/hw/dmcu.h" #include "dc/inc/hw/abm.h" #include "dc/dc_dmub_srv.h" -#include "dc/dc_edid_parser.h" #include "dc/dc_stat.h" #include "dc/dc_state.h" #include "amdgpu_dm_trace.h" diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index 88f938a8f24f..0badbb27fac8 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c @@ -27,7 +27,6 @@ #include "dm_services_types.h" #include "dc.h" #include "dc/dc_dmub_srv.h" -#include "dc/dc_edid_parser.h" #include "dc/dc_stat.h" #include "dc/dc_state.h" #include "dc/dc_stream.h" @@ -3292,139 +3291,6 @@ void dm_restore_drm_connector_state(struct drm_device *dev, dm_force_atomic_commit(&aconnector->base); } -static bool dm_edid_parser_send_cea(struct amdgpu_display_manager *dm, - unsigned int offset, - unsigned int total_length, - u8 *data, - unsigned int length, - struct amdgpu_hdmi_vsdb_info *vsdb) -{ - bool res; - union dmub_rb_cmd cmd; - struct dmub_cmd_send_edid_cea *input; - struct dmub_cmd_edid_cea_output *output; - - if (length > DMUB_EDID_CEA_DATA_CHUNK_BYTES) - return false; - - memset(&cmd, 0, sizeof(cmd)); - - input = &cmd.edid_cea.data.input; - - cmd.edid_cea.header.type = DMUB_CMD__EDID_CEA; - cmd.edid_cea.header.sub_type = 0; - cmd.edid_cea.header.payload_bytes = - sizeof(cmd.edid_cea) - sizeof(cmd.edid_cea.header); - input->offset = offset; - input->length = length; - input->cea_total_length = total_length; - memcpy(input->payload, data, length); - - res = dc_wake_and_execute_dmub_cmd(dm->dc->ctx, &cmd, DM_DMUB_WAIT_TYPE_WAIT_WITH_REPLY); - if (!res) { - drm_err(adev_to_drm(dm->adev), "EDID CEA parser failed\n"); - return false; - } - - output = &cmd.edid_cea.data.output; - - if (output->type == DMUB_CMD__EDID_CEA_ACK) { - if (!output->ack.success) { - drm_err(adev_to_drm(dm->adev), "EDID CEA ack failed at offset %d\n", - output->ack.offset); - } - } else if (output->type == DMUB_CMD__EDID_CEA_AMD_VSDB) { - if (!output->amd_vsdb.vsdb_found) - return false; - - vsdb->freesync_supported = output->amd_vsdb.freesync_supported; - vsdb->amd_vsdb_version = output->amd_vsdb.amd_vsdb_version; - vsdb->min_refresh_rate_hz = output->amd_vsdb.min_frame_rate; - vsdb->max_refresh_rate_hz = output->amd_vsdb.max_frame_rate; - vsdb->freesync_mccs_vcp_code = output->amd_vsdb.freesync_mccs_vcp_code; - } else { - drm_warn(adev_to_drm(dm->adev), "Unknown EDID CEA parser results\n"); - return false; - } - - return true; -} - -static bool parse_edid_cea_dmcu(struct amdgpu_display_manager *dm, - u8 *edid_ext, int len, - struct amdgpu_hdmi_vsdb_info *vsdb_info) -{ - int i; - - /* send extension block to DMCU for parsing */ - for (i = 0; i < len; i += 8) { - bool res; - int offset; - - /* send 8 bytes a time */ - if (!dc_edid_parser_send_cea(dm->dc, i, len, &edid_ext[i], 8)) - return false; - - if (i+8 == len) { - /* EDID block sent completed, expect result */ - int version, min_rate, max_rate; - - res = dc_edid_parser_recv_amd_vsdb(dm->dc, &version, &min_rate, &max_rate); - if (res) { - /* amd vsdb found */ - vsdb_info->freesync_supported = 1; - vsdb_info->amd_vsdb_version = version; - vsdb_info->min_refresh_rate_hz = min_rate; - vsdb_info->max_refresh_rate_hz = max_rate; - /* Not enabled on DMCU*/ - vsdb_info->freesync_mccs_vcp_code = 0; - return true; - } - /* not amd vsdb */ - return false; - } - - /* check for ack*/ - res = dc_edid_parser_recv_cea_ack(dm->dc, &offset); - if (!res) - return false; - } - - return false; -} - -static bool parse_edid_cea_dmub(struct amdgpu_display_manager *dm, - u8 *edid_ext, int len, - struct amdgpu_hdmi_vsdb_info *vsdb_info) -{ - int i; - - /* send extension block to DMCU for parsing */ - for (i = 0; i < len; i += 8) { - /* send 8 bytes a time */ - if (!dm_edid_parser_send_cea(dm, i, len, &edid_ext[i], 8, vsdb_info)) - return false; - } - - return vsdb_info->freesync_supported; -} - -static bool parse_edid_cea(struct amdgpu_dm_connector *aconnector, - u8 *edid_ext, int len, - struct amdgpu_hdmi_vsdb_info *vsdb_info) -{ - struct amdgpu_device *adev = drm_to_adev(aconnector->base.dev); - bool ret; - - mutex_lock(&adev->dm.dc_lock); - if (adev->dm.dmub_srv) - ret = parse_edid_cea_dmub(&adev->dm, edid_ext, len, vsdb_info); - else - ret = parse_edid_cea_dmcu(&adev->dm, edid_ext, len, vsdb_info); - mutex_unlock(&adev->dm.dc_lock); - return ret; -} - static void parse_edid_displayid_vrr(struct drm_connector *connector, const struct edid *edid) { @@ -3475,42 +3341,14 @@ static int get_amd_vsdb(struct amdgpu_dm_connector *aconnector, vsdb_info->replay_mode = connector->display_info.amd_vsdb.replay_mode; vsdb_info->amd_vsdb_version = connector->display_info.amd_vsdb.version; + vsdb_info->freesync_supported = connector->display_info.amd_vsdb.freesync_supported; + vsdb_info->min_refresh_rate_hz = connector->display_info.amd_vsdb.min_frame_rate; + vsdb_info->max_refresh_rate_hz = connector->display_info.amd_vsdb.max_frame_rate; + vsdb_info->freesync_mccs_vcp_code = connector->display_info.amd_vsdb.freesync_vcp_code; return connector->display_info.amd_vsdb.version != 0; } -static int parse_hdmi_amd_vsdb(struct amdgpu_dm_connector *aconnector, - const struct edid *edid, - struct amdgpu_hdmi_vsdb_info *vsdb_info) -{ - u8 *edid_ext = NULL; - int i; - bool valid_vsdb_found = false; - - /*----- drm_find_cea_extension() -----*/ - /* No EDID or EDID extensions */ - if (edid == NULL || edid->extensions == 0) - return -ENODEV; - - /* Find CEA extension */ - for (i = 0; i < edid->extensions; i++) { - edid_ext = (uint8_t *)edid + EDID_LENGTH * (i + 1); - if (edid_ext[0] == CEA_EXT) - break; - } - - if (i == edid->extensions) - return -ENODEV; - - /*----- cea_db_offsets() -----*/ - if (edid_ext[0] != CEA_EXT) - return -ENODEV; - - valid_vsdb_found = parse_edid_cea(aconnector, edid_ext, EDID_LENGTH, vsdb_info); - - return valid_vsdb_found ? i : -ENODEV; -} - /** * amdgpu_dm_update_freesync_caps - Update Freesync capabilities * @@ -3593,8 +3431,8 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, } } else if (drm_edid && sink->sink_signal == SIGNAL_TYPE_HDMI_TYPE_A) { - i = parse_hdmi_amd_vsdb(amdgpu_dm_connector, edid, &vsdb_info); - if (i >= 0) { + i = get_amd_vsdb(amdgpu_dm_connector, &vsdb_info); + if (i) { amdgpu_dm_connector->vsdb_info = vsdb_info; sink->edid_caps.freesync_vcp_code = vsdb_info.freesync_mccs_vcp_code; @@ -3614,8 +3452,8 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, as_type = dm_get_adaptive_sync_support_type(amdgpu_dm_connector->dc_link); if (as_type == FREESYNC_TYPE_PCON_IN_WHITELIST) { - i = parse_hdmi_amd_vsdb(amdgpu_dm_connector, edid, &vsdb_info); - if (i >= 0) { + i = get_amd_vsdb(amdgpu_dm_connector, &vsdb_info); + if (i) { amdgpu_dm_connector->vsdb_info = vsdb_info; sink->edid_caps.freesync_vcp_code = vsdb_info.freesync_mccs_vcp_code; From ee28f3446dde3049c23ff647fcdab2f9c4944876 Mon Sep 17 00:00:00 2001 From: Alex Huang Date: Tue, 4 Aug 2026 10:33:38 -0400 Subject: [PATCH 005/213] drm/amd/display: Clean up FreeSync capability detection Many duplicate codes can be extracted to common code, fill amdgpu_dm_connector fields outside of the DP/eDP, HDMI branches, pull the VSDB data once using get_amd_vsdb() and then derive the refresh rate ranges. When a monitor advertises support for FreeSync, the refresh rate from the AMD VSDB is used, otherwise, falls back on the monitor_range, either EDID refresh rate range field or DisplayID VRR data. Signed-off-by: Alex Huang Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20260804143339.714548-4-Alex.Huang2@amd.com (Adjust for movement to amdgpu_dm_connector.c) Signed-off-by: Mario Limonciello --- .../display/amdgpu_dm/amdgpu_dm_connector.c | 84 +++++++++---------- 1 file changed, 39 insertions(+), 45 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index 0badbb27fac8..0a2277ee3713 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c @@ -3367,7 +3367,7 @@ static int get_amd_vsdb(struct amdgpu_dm_connector *aconnector, void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, const struct drm_edid *drm_edid, bool do_mccs) { - int i = 0; + bool has_vsdb = 0; struct amdgpu_dm_connector *amdgpu_dm_connector = to_amdgpu_dm_connector(connector); struct dm_connector_state *dm_con_state = NULL; @@ -3412,63 +3412,57 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, connector->display_info.monitor_range.max_vfreq == 0)) parse_edid_displayid_vrr(connector, edid); - if (edid && (sink->sink_signal == SIGNAL_TYPE_DISPLAY_PORT || - sink->sink_signal == SIGNAL_TYPE_EDP)) { - if (amdgpu_dm_connector->dc_link && - amdgpu_dm_connector->dc_link->dpcd_caps.allow_invalid_MSA_timing_param) { - amdgpu_dm_connector->min_vfreq = connector->display_info.monitor_range.min_vfreq; - amdgpu_dm_connector->max_vfreq = connector->display_info.monitor_range.max_vfreq; - if (amdgpu_dm_connector->max_vfreq - amdgpu_dm_connector->min_vfreq > 10) - freesync_capable = true; - } + has_vsdb = get_amd_vsdb(amdgpu_dm_connector, &vsdb_info) != 0; - get_amd_vsdb(amdgpu_dm_connector, &vsdb_info); + if (has_vsdb) { + amdgpu_dm_connector->vsdb_info = vsdb_info; + + /* copy refresh rate info as long as VSDB advertises FreeSync */ + if (vsdb_info.freesync_supported) { + amdgpu_dm_connector->min_vfreq = vsdb_info.min_refresh_rate_hz; + amdgpu_dm_connector->max_vfreq = vsdb_info.max_refresh_rate_hz; + + connector->display_info.monitor_range.min_vfreq = + vsdb_info.min_refresh_rate_hz; + connector->display_info.monitor_range.max_vfreq = + vsdb_info.max_refresh_rate_hz; + } + } else { + /* fall back to the base EDID range when there is no VSDB */ + amdgpu_dm_connector->min_vfreq = connector->display_info.monitor_range.min_vfreq; + amdgpu_dm_connector->max_vfreq = connector->display_info.monitor_range.max_vfreq; + } + + if (sink->sink_signal == SIGNAL_TYPE_DISPLAY_PORT || + sink->sink_signal == SIGNAL_TYPE_EDP) { + if (amdgpu_dm_connector->dc_link && + amdgpu_dm_connector->dc_link->dpcd_caps.allow_invalid_MSA_timing_param && + amdgpu_dm_connector->max_vfreq - amdgpu_dm_connector->min_vfreq > 10) + freesync_capable = true; if (vsdb_info.replay_mode) { - amdgpu_dm_connector->vsdb_info.replay_mode = vsdb_info.replay_mode; - amdgpu_dm_connector->vsdb_info.amd_vsdb_version = vsdb_info.amd_vsdb_version; amdgpu_dm_connector->as_type = ADAPTIVE_SYNC_TYPE_EDP; } + } else if (has_vsdb && sink->sink_signal == SIGNAL_TYPE_HDMI_TYPE_A) { + sink->edid_caps.freesync_vcp_code = vsdb_info.freesync_mccs_vcp_code; - } else if (drm_edid && sink->sink_signal == SIGNAL_TYPE_HDMI_TYPE_A) { - i = get_amd_vsdb(amdgpu_dm_connector, &vsdb_info); - if (i) { - amdgpu_dm_connector->vsdb_info = vsdb_info; - sink->edid_caps.freesync_vcp_code = vsdb_info.freesync_mccs_vcp_code; - - if (vsdb_info.freesync_supported) { - amdgpu_dm_connector->min_vfreq = vsdb_info.min_refresh_rate_hz; - amdgpu_dm_connector->max_vfreq = vsdb_info.max_refresh_rate_hz; - if (amdgpu_dm_connector->max_vfreq - amdgpu_dm_connector->min_vfreq > 10) - freesync_capable = true; - - connector->display_info.monitor_range.min_vfreq = vsdb_info.min_refresh_rate_hz; - connector->display_info.monitor_range.max_vfreq = vsdb_info.max_refresh_rate_hz; - } - } + if (vsdb_info.freesync_supported && + amdgpu_dm_connector->max_vfreq - amdgpu_dm_connector->min_vfreq > 10) + freesync_capable = true; } if (amdgpu_dm_connector->dc_link) as_type = dm_get_adaptive_sync_support_type(amdgpu_dm_connector->dc_link); - if (as_type == FREESYNC_TYPE_PCON_IN_WHITELIST) { - i = get_amd_vsdb(amdgpu_dm_connector, &vsdb_info); - if (i) { - amdgpu_dm_connector->vsdb_info = vsdb_info; - sink->edid_caps.freesync_vcp_code = vsdb_info.freesync_mccs_vcp_code; + if (has_vsdb && as_type == FREESYNC_TYPE_PCON_IN_WHITELIST) { + sink->edid_caps.freesync_vcp_code = vsdb_info.freesync_mccs_vcp_code; - if (vsdb_info.freesync_supported && vsdb_info.amd_vsdb_version > 0) { - amdgpu_dm_connector->pack_sdp_v1_3 = true; - amdgpu_dm_connector->as_type = as_type; + if (vsdb_info.freesync_supported && vsdb_info.amd_vsdb_version > 0) { + amdgpu_dm_connector->pack_sdp_v1_3 = true; + amdgpu_dm_connector->as_type = as_type; - amdgpu_dm_connector->min_vfreq = vsdb_info.min_refresh_rate_hz; - amdgpu_dm_connector->max_vfreq = vsdb_info.max_refresh_rate_hz; - if (amdgpu_dm_connector->max_vfreq - amdgpu_dm_connector->min_vfreq > 10) - freesync_capable = true; - - connector->display_info.monitor_range.min_vfreq = vsdb_info.min_refresh_rate_hz; - connector->display_info.monitor_range.max_vfreq = vsdb_info.max_refresh_rate_hz; - } + if (amdgpu_dm_connector->max_vfreq - amdgpu_dm_connector->min_vfreq > 10) + freesync_capable = true; } } From b961eb36d7b04147104cff2fd8bc0e94f4713324 Mon Sep 17 00:00:00 2001 From: Alex Huang Date: Tue, 4 Aug 2026 10:33:39 -0400 Subject: [PATCH 006/213] drm/amd/display: Remove unused DMCU/DMUB EDID CEA parser With HDMI FreeSync detection now sourced from the common DRM EDID parser, the DMUB parsing for AMD VSDB is no longer used. Remove all relevant fields and files for this flow, it is no longer necessary to ever take firmware round-trip for this. Signed-off-by: Alex Huang Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20260804143339.714548-5-Alex.Huang2@amd.com Signed-off-by: Mario Limonciello --- drivers/gpu/drm/amd/display/dc/Makefile | 1 - .../gpu/drm/amd/display/dc/dc_edid_parser.c | 80 ------------ .../gpu/drm/amd/display/dc/dc_edid_parser.h | 44 ------- drivers/gpu/drm/amd/display/dc/dce/dce_dmcu.c | 121 ------------------ drivers/gpu/drm/amd/display/dc/inc/hw/dmcu.h | 10 -- .../gpu/drm/amd/display/dmub/inc/dmub_cmd.h | 71 ---------- 6 files changed, 327 deletions(-) delete mode 100644 drivers/gpu/drm/amd/display/dc/dc_edid_parser.c delete mode 100644 drivers/gpu/drm/amd/display/dc/dc_edid_parser.h diff --git a/drivers/gpu/drm/amd/display/dc/Makefile b/drivers/gpu/drm/amd/display/dc/Makefile index 93d02956c5eb..e88a9cea8d88 100644 --- a/drivers/gpu/drm/amd/display/dc/Makefile +++ b/drivers/gpu/drm/amd/display/dc/Makefile @@ -63,7 +63,6 @@ include $(AMD_DC) FILES = FILES += dc_dmub_srv.o -FILES += dc_edid_parser.o FILES += dc_fused_io.o FILES += dc_helper.o FILES += core/dc.o diff --git a/drivers/gpu/drm/amd/display/dc/dc_edid_parser.c b/drivers/gpu/drm/amd/display/dc/dc_edid_parser.c deleted file mode 100644 index 0db5b49e9d5e..000000000000 --- a/drivers/gpu/drm/amd/display/dc/dc_edid_parser.c +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2021 Advanced Micro Devices, Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR - * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - * - * Authors: AMD - * - */ - -#include "dce/dce_dmcu.h" -#include "dc_edid_parser.h" - -bool dc_edid_parser_send_cea(struct dc *dc, - int offset, - int total_length, - uint8_t *data, - int length) -{ - struct dmcu *dmcu = dc->res_pool->dmcu; - - if (dmcu && - dmcu->funcs->is_dmcu_initialized(dmcu) && - dmcu->funcs->send_edid_cea) { - return dmcu->funcs->send_edid_cea(dmcu, - offset, - total_length, - data, - length); - } - - return false; -} - -bool dc_edid_parser_recv_cea_ack(struct dc *dc, int *offset) -{ - struct dmcu *dmcu = dc->res_pool->dmcu; - - if (dmcu && - dmcu->funcs->is_dmcu_initialized(dmcu) && - dmcu->funcs->recv_edid_cea_ack) { - return dmcu->funcs->recv_edid_cea_ack(dmcu, offset); - } - - return false; -} - -bool dc_edid_parser_recv_amd_vsdb(struct dc *dc, - int *version, - int *min_frame_rate, - int *max_frame_rate) -{ - struct dmcu *dmcu = dc->res_pool->dmcu; - - if (dmcu && - dmcu->funcs->is_dmcu_initialized(dmcu) && - dmcu->funcs->recv_amd_vsdb) { - return dmcu->funcs->recv_amd_vsdb(dmcu, - version, - min_frame_rate, - max_frame_rate); - } - - return false; -} diff --git a/drivers/gpu/drm/amd/display/dc/dc_edid_parser.h b/drivers/gpu/drm/amd/display/dc/dc_edid_parser.h deleted file mode 100644 index da67ec06f0a2..000000000000 --- a/drivers/gpu/drm/amd/display/dc/dc_edid_parser.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2021 Advanced Micro Devices, Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR - * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - * - * Authors: AMD - * - */ - -#ifndef _DC_EDID_PARSER_H_ -#define _DC_EDID_PARSER_H_ - -#include "core_types.h" - -bool dc_edid_parser_send_cea(struct dc *dc, - int offset, - int total_length, - uint8_t *data, - int length); - -bool dc_edid_parser_recv_cea_ack(struct dc *dc, int *offset); - -bool dc_edid_parser_recv_amd_vsdb(struct dc *dc, - int *version, - int *min_frame_rate, - int *max_frame_rate); - -#endif /* _DC_EDID_PARSER_H_ */ diff --git a/drivers/gpu/drm/amd/display/dc/dce/dce_dmcu.c b/drivers/gpu/drm/amd/display/dc/dce/dce_dmcu.c index 25ebd8a52ae4..23de1c793ec3 100644 --- a/drivers/gpu/drm/amd/display/dc/dce/dce_dmcu.c +++ b/drivers/gpu/drm/amd/display/dc/dce/dce_dmcu.c @@ -56,9 +56,6 @@ #define MCP_BL_SET_PWM_FRAC 0x6A /* Enable or disable Fractional PWM */ #define CRC_WIN_NOTIFY 0x92 #define CRC_STOP_UPDATE 0x93 -#define MCP_SEND_EDID_CEA 0xA0 -#define EDID_CEA_CMD_ACK 1 -#define EDID_CEA_CMD_NACK 2 #define MASTER_COMM_CNTL_REG__MASTER_COMM_INTERRUPT_MASK 0x00000001L // PSP FW version @@ -812,121 +809,6 @@ static bool dcn20_unlock_phy(struct dmcu *dmcu) return true; } -static bool dcn10_send_edid_cea(struct dmcu *dmcu, - int offset, - int total_length, - uint8_t *data, - int length) -{ - struct dce_dmcu *dmcu_dce = TO_DCE_DMCU(dmcu); - uint32_t header, data1, data2; - - /* If microcontroller is not running, do nothing */ - if (dmcu->dmcu_state != DMCU_RUNNING) - return false; - - if (length > 8 || length <= 0) - return false; - - header = ((uint32_t)offset & 0xFFFF) << 16 | (total_length & 0xFFFF); - data1 = (((uint32_t)data[0]) << 24) | (((uint32_t)data[1]) << 16) | - (((uint32_t)data[2]) << 8) | ((uint32_t)data[3]); - data2 = (((uint32_t)data[4]) << 24) | (((uint32_t)data[5]) << 16) | - (((uint32_t)data[6]) << 8) | ((uint32_t)data[7]); - - /* waitDMCUReadyForCmd */ - REG_WAIT(MASTER_COMM_CNTL_REG, MASTER_COMM_INTERRUPT, 0, 1, 10000); - - /* setDMCUParam_Cmd */ - REG_UPDATE(MASTER_COMM_CMD_REG, MASTER_COMM_CMD_REG_BYTE0, MCP_SEND_EDID_CEA); - - REG_WRITE(MASTER_COMM_DATA_REG1, header); - REG_WRITE(MASTER_COMM_DATA_REG2, data1); - REG_WRITE(MASTER_COMM_DATA_REG3, data2); - - /* notifyDMCUMsg */ - REG_UPDATE(MASTER_COMM_CNTL_REG, MASTER_COMM_INTERRUPT, 1); - - /* waitDMCUReadyForCmd */ - REG_WAIT(MASTER_COMM_CNTL_REG, MASTER_COMM_INTERRUPT, 0, 1, 10000); - - return true; -} - -static bool dcn10_get_scp_results(struct dmcu *dmcu, - uint32_t *cmd, - uint32_t *data1, - uint32_t *data2, - uint32_t *data3) -{ - struct dce_dmcu *dmcu_dce = TO_DCE_DMCU(dmcu); - - /* If microcontroller is not running, do nothing */ - if (dmcu->dmcu_state != DMCU_RUNNING) - return false; - - *cmd = REG_READ(SLAVE_COMM_CMD_REG); - *data1 = REG_READ(SLAVE_COMM_DATA_REG1); - *data2 = REG_READ(SLAVE_COMM_DATA_REG2); - *data3 = REG_READ(SLAVE_COMM_DATA_REG3); - - /* clear SCP interrupt */ - REG_UPDATE(SLAVE_COMM_CNTL_REG, SLAVE_COMM_INTERRUPT, 0); - - return true; -} - -static bool dcn10_recv_amd_vsdb(struct dmcu *dmcu, - int *version, - int *min_frame_rate, - int *max_frame_rate) -{ - uint32_t data[4]; - int cmd, ack, len; - - if (!dcn10_get_scp_results(dmcu, &data[0], &data[1], &data[2], &data[3])) - return false; - - cmd = data[0] & 0x3FF; - len = (data[0] >> 10) & 0x3F; - ack = data[1]; - - if (cmd != MCP_SEND_EDID_CEA || ack != EDID_CEA_CMD_ACK || len != 12) - return false; - - if ((data[2] & 0xFF)) { - *version = (data[2] >> 8) & 0xFF; - *min_frame_rate = (data[3] >> 16) & 0xFFFF; - *max_frame_rate = data[3] & 0xFFFF; - return true; - } - - return false; -} - -static bool dcn10_recv_edid_cea_ack(struct dmcu *dmcu, int *offset) -{ - uint32_t data[4]; - int cmd, ack; - - if (!dcn10_get_scp_results(dmcu, - &data[0], &data[1], &data[2], &data[3])) - return false; - - cmd = data[0] & 0x3FF; - ack = data[1]; - - if (cmd != MCP_SEND_EDID_CEA) - return false; - - if (ack == EDID_CEA_CMD_ACK) - return true; - - *offset = data[2]; /* nack */ - return false; -} - - #if defined(CONFIG_DRM_AMD_SECURE_DISPLAY) static void dcn10_forward_crc_window(struct dmcu *dmcu, struct rect *rect, @@ -1030,9 +912,6 @@ static const struct dmcu_funcs dcn10_funcs = { .get_psr_state = dcn10_get_dmcu_psr_state, .set_psr_wait_loop = dcn10_psr_wait_loop, .get_psr_wait_loop = dcn10_get_psr_wait_loop, - .send_edid_cea = dcn10_send_edid_cea, - .recv_amd_vsdb = dcn10_recv_amd_vsdb, - .recv_edid_cea_ack = dcn10_recv_edid_cea_ack, #if defined(CONFIG_DRM_AMD_SECURE_DISPLAY) .forward_crc_window = dcn10_forward_crc_window, .stop_crc_win_update = dcn10_stop_crc_win_update, diff --git a/drivers/gpu/drm/amd/display/dc/inc/hw/dmcu.h b/drivers/gpu/drm/amd/display/dc/inc/hw/dmcu.h index de3113ecbc77..eebfd177463d 100644 --- a/drivers/gpu/drm/amd/display/dc/inc/hw/dmcu.h +++ b/drivers/gpu/drm/amd/display/dc/inc/hw/dmcu.h @@ -74,16 +74,6 @@ struct dmcu_funcs { bool (*is_dmcu_initialized)(struct dmcu *dmcu); bool (*lock_phy)(struct dmcu *dmcu); bool (*unlock_phy)(struct dmcu *dmcu); - bool (*send_edid_cea)(struct dmcu *dmcu, - int offset, - int total_length, - uint8_t *data, - int length); - bool (*recv_amd_vsdb)(struct dmcu *dmcu, - int *version, - int *min_frame_rate, - int *max_frame_rate); - bool (*recv_edid_cea_ack)(struct dmcu *dmcu, int *offset); #if defined(CONFIG_DRM_AMD_SECURE_DISPLAY) void (*forward_crc_window)(struct dmcu *dmcu, struct rect *rect, diff --git a/drivers/gpu/drm/amd/display/dmub/inc/dmub_cmd.h b/drivers/gpu/drm/amd/display/dmub/inc/dmub_cmd.h index e7879bd81f83..0ca4ff82ea1f 100644 --- a/drivers/gpu/drm/amd/display/dmub/inc/dmub_cmd.h +++ b/drivers/gpu/drm/amd/display/dmub/inc/dmub_cmd.h @@ -1887,10 +1887,6 @@ enum dmub_cmd_type { * Command type used for interfacing with DPIA. */ DMUB_CMD__DPIA = 77, - /** - * Command type used for EDID CEA parsing - */ - DMUB_CMD__EDID_CEA = 79, /** * Command type used for getting usbc cable ID */ @@ -6665,69 +6661,6 @@ struct dmub_rb_cmd_transmitter_set_phy_fsm { struct dmub_rb_cmd_transmitter_set_phy_fsm_data data; /**< payload */ }; -/** - * Maximum number of bytes a chunk sent to DMUB for parsing - */ -#define DMUB_EDID_CEA_DATA_CHUNK_BYTES 8 - -/** - * Represent a chunk of CEA blocks sent to DMUB for parsing - */ -struct dmub_cmd_send_edid_cea { - uint16_t offset; /**< offset into the CEA block */ - uint8_t length; /**< number of bytes in payload to copy as part of CEA block */ - uint16_t cea_total_length; /**< total length of the CEA block */ - uint8_t payload[DMUB_EDID_CEA_DATA_CHUNK_BYTES]; /**< data chunk of the CEA block */ - uint8_t pad[3]; /**< padding and for future expansion */ -}; - -/** - * Result of VSDB parsing from CEA block - */ -struct dmub_cmd_edid_cea_amd_vsdb { - uint8_t vsdb_found; /**< 1 if parsing has found valid AMD VSDB */ - uint8_t freesync_supported; /**< 1 if Freesync is supported */ - uint16_t amd_vsdb_version; /**< AMD VSDB version */ - uint16_t min_frame_rate; /**< Maximum frame rate */ - uint16_t max_frame_rate; /**< Minimum frame rate */ - uint8_t freesync_mccs_vcp_code; /**< Freesync MCCS VCP code */ -}; - -/** - * Result of sending a CEA chunk - */ -struct dmub_cmd_edid_cea_ack { - uint16_t offset; /**< offset of the chunk into the CEA block */ - uint8_t success; /**< 1 if this sending of chunk succeeded */ - uint8_t pad; /**< padding and for future expansion */ -}; - -/** - * Specify whether the result is an ACK/NACK or the parsing has finished - */ -enum dmub_cmd_edid_cea_reply_type { - DMUB_CMD__EDID_CEA_AMD_VSDB = 1, /**< VSDB parsing has finished */ - DMUB_CMD__EDID_CEA_ACK = 2, /**< acknowledges the CEA sending is OK or failing */ -}; - -/** - * Definition of a DMUB_CMD__EDID_CEA command. - */ -struct dmub_rb_cmd_edid_cea { - struct dmub_cmd_header header; /**< Command header */ - union dmub_cmd_edid_cea_data { - struct dmub_cmd_send_edid_cea input; /**< input to send CEA chunks */ - struct dmub_cmd_edid_cea_output { /**< output with results */ - uint8_t type; /**< dmub_cmd_edid_cea_reply_type */ - union { - struct dmub_cmd_edid_cea_amd_vsdb amd_vsdb; - struct dmub_cmd_edid_cea_ack ack; - }; - } output; /**< output to retrieve ACK/NACK or VSDB parsing results */ - } data; /**< Command data */ - -}; - /** * struct dmub_cmd_cable_id_input - Defines the input of DMUB_CMD_GET_USBC_CABLE_ID command. */ @@ -7579,10 +7512,6 @@ union dmub_rb_cmd { * Definition of a DMUB_CMD__DPIA_SET_TPS_NOTIFICATION command. */ struct dmub_rb_cmd_set_tps_notification set_tps_notification; - /** - * Definition of a DMUB_CMD__EDID_CEA command. - */ - struct dmub_rb_cmd_edid_cea edid_cea; /** * Definition of a DMUB_CMD_GET_USBC_CABLE_ID command. */ From 2132c69fa0a05b66824714fc17dbfd1d2f93ef86 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 23 Jul 2026 21:16:33 +0800 Subject: [PATCH 007/213] drm/bridge: analogix_dp: Remove redundant dev_err() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_threaded_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err() call. Signed-off-by: Pan Chuang Reviewed-by: Damon Ding Reviewed-by: Luca Ceresoli Tested-by: Luca Ceresoli Link: https://patch.msgid.link/20260723131649.134127-2-panchuang@vivo.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/analogix/analogix_dp_core.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c index 8d3d4a6e6ca2..1a1a22f12d86 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c @@ -1410,10 +1410,8 @@ analogix_dp_probe(struct device *dev, struct analogix_dp_plat_data *plat_data) analogix_dp_hardirq, analogix_dp_irq_thread, irq_flags, "analogix-dp", dp); - if (ret) { - dev_err(&pdev->dev, "failed to request irq\n"); + if (ret) return ERR_PTR(ret); - } dp->aux.name = "DP-AUX"; dp->aux.transfer = analogix_dpaux_transfer; From ee4cb1090310e56f5a1a5cf13bbedafb4f786751 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 23 Jul 2026 21:16:34 +0800 Subject: [PATCH 008/213] drm/bridge: cdns-mhdp8546: Remove redundant dev_err() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_threaded_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err() call. Signed-off-by: Pan Chuang Reviewed-by: Luca Ceresoli Tested-by: Luca Ceresoli Link: https://patch.msgid.link/20260723131649.134127-3-panchuang@vivo.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c index 504a3186ebb3..f47717bd1079 100644 --- a/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c +++ b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c @@ -2343,7 +2343,6 @@ static int cdns_mhdp_probe(struct platform_device *pdev) cdns_mhdp_irq_handler, IRQF_ONESHOT, "mhdp8546", mhdp); if (ret) { - dev_err(dev, "cannot install IRQ %d\n", irq); ret = -EIO; goto plat_fini; } From fa5cbeccd5e49ac165af3487741b61f94b5a5d06 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 23 Jul 2026 21:16:35 +0800 Subject: [PATCH 009/213] drm/bridge: lt9611: Remove redundant dev_err() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_threaded_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err() call. Signed-off-by: Pan Chuang Reviewed-by: Luca Ceresoli Tested-by: Luca Ceresoli Link: https://patch.msgid.link/20260723131649.134127-4-panchuang@vivo.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/lontium-lt9611.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/gpu/drm/bridge/lontium-lt9611.c b/drivers/gpu/drm/bridge/lontium-lt9611.c index fb34f661ee0a..3ebd8992cd94 100644 --- a/drivers/gpu/drm/bridge/lontium-lt9611.c +++ b/drivers/gpu/drm/bridge/lontium-lt9611.c @@ -1156,10 +1156,8 @@ static int lt9611_probe(struct i2c_client *client) ret = devm_request_threaded_irq(dev, client->irq, NULL, lt9611_irq_thread_handler, IRQF_ONESHOT, "lt9611", lt9611); - if (ret) { - dev_err(dev, "failed to request irq\n"); + if (ret) goto err_disable_regulators; - } i2c_set_clientdata(client, lt9611); From 27860b5f974f412ee92ca44538f402d1cf1141c5 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 23 Jul 2026 21:16:36 +0800 Subject: [PATCH 010/213] drm/bridge: samsung-dsim: Remove redundant dev_err() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_threaded_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err() call. Signed-off-by: Pan Chuang Reviewed-by: Luca Ceresoli Tested-by: Luca Ceresoli Link: https://patch.msgid.link/20260723131649.134127-5-panchuang@vivo.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/samsung-dsim.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/gpu/drm/bridge/samsung-dsim.c b/drivers/gpu/drm/bridge/samsung-dsim.c index e2fc69fc51b6..5457a7b02a33 100644 --- a/drivers/gpu/drm/bridge/samsung-dsim.c +++ b/drivers/gpu/drm/bridge/samsung-dsim.c @@ -2184,10 +2184,8 @@ int samsung_dsim_probe(struct platform_device *pdev) samsung_dsim_irq, IRQF_ONESHOT | IRQF_NO_AUTOEN, dev_name(dev), dsi); - if (ret) { - dev_err(dev, "failed to request dsi irq\n"); + if (ret) return ret; - } ret = samsung_dsim_parse_dt(dsi); if (ret) From c4d6efe7364de4f68ed54fd654bd79b4c14979f3 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 23 Jul 2026 21:16:37 +0800 Subject: [PATCH 011/213] drm/bridge: synopsys: dw-dp: Remove redundant dev_err_probe() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_threaded_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err_probe() call. Signed-off-by: Pan Chuang Reviewed-by: Luca Ceresoli Tested-by: Luca Ceresoli Link: https://patch.msgid.link/20260723131649.134127-6-panchuang@vivo.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/synopsys/dw-dp.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c index 8b8529e1723c..86a20a0b9c01 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c @@ -2079,10 +2079,8 @@ struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder, ret = devm_request_threaded_irq(dev, dp->irq, NULL, dw_dp_irq, IRQF_ONESHOT, dev_name(dev), dp); - if (ret) { - dev_err_probe(dev, ret, "failed to request irq\n"); + if (ret) goto unregister_aux; - } return dp; From d265c80cbfc5a0df93c64106d3db1689d09b2ea3 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 23 Jul 2026 21:16:38 +0800 Subject: [PATCH 012/213] drm/bridge: tc358767: Remove redundant dev_err() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_threaded_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err() call. Signed-off-by: Pan Chuang Reviewed-by: Luca Ceresoli Tested-by: Luca Ceresoli Link: https://patch.msgid.link/20260723131649.134127-7-panchuang@vivo.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/tc358767.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/gpu/drm/bridge/tc358767.c b/drivers/gpu/drm/bridge/tc358767.c index e3a57f8228da..ac83af7902cd 100644 --- a/drivers/gpu/drm/bridge/tc358767.c +++ b/drivers/gpu/drm/bridge/tc358767.c @@ -2530,10 +2530,8 @@ static int tc_probe(struct i2c_client *client) NULL, tc_irq_handler, IRQF_ONESHOT, "tc358767-irq", tc); - if (ret) { - dev_err(dev, "failed to register dp interrupt\n"); + if (ret) return ret; - } tc->have_irq = true; } From d81e374f453de961662296905e77260048446f2d Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 23 Jul 2026 21:16:39 +0800 Subject: [PATCH 013/213] drm/bridge: it6505: Remove redundant dev_err() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_threaded_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err() call. Signed-off-by: Pan Chuang Reviewed-by: Luca Ceresoli Tested-by: Luca Ceresoli Link: https://patch.msgid.link/20260723131649.134127-8-panchuang@vivo.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/ite-it6505.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/gpu/drm/bridge/ite-it6505.c b/drivers/gpu/drm/bridge/ite-it6505.c index 025a19c1e530..e136ac71edbb 100644 --- a/drivers/gpu/drm/bridge/ite-it6505.c +++ b/drivers/gpu/drm/bridge/ite-it6505.c @@ -3607,10 +3607,8 @@ static int it6505_i2c_probe(struct i2c_client *client) IRQF_TRIGGER_LOW | IRQF_ONESHOT | IRQF_NO_AUTOEN, "it6505-intp", it6505); - if (err) { - dev_err(dev, "Failed to request INTP threaded IRQ: %d", err); + if (err) return err; - } INIT_WORK(&it6505->link_works, it6505_link_training_work); INIT_WORK(&it6505->hdcp_wait_ksv_list, it6505_hdcp_wait_ksv_list); From b0899db1a492a6830a6cf5cfe12349f45f866549 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 23 Jul 2026 21:16:40 +0800 Subject: [PATCH 014/213] drm/bridge/sii8620: Remove redundant dev_err_probe() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_threaded_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err_probe() call. Signed-off-by: Pan Chuang Reviewed-by: Luca Ceresoli Tested-by: Luca Ceresoli Link: https://patch.msgid.link/20260723131649.134127-9-panchuang@vivo.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/sil-sii8620.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/gpu/drm/bridge/sil-sii8620.c b/drivers/gpu/drm/bridge/sil-sii8620.c index b452748ad9db..88f9cd9ba6db 100644 --- a/drivers/gpu/drm/bridge/sil-sii8620.c +++ b/drivers/gpu/drm/bridge/sil-sii8620.c @@ -2320,8 +2320,7 @@ static int sii8620_probe(struct i2c_client *client) IRQF_TRIGGER_HIGH | IRQF_ONESHOT, "sii8620", ctx); if (ret < 0) - return dev_err_probe(dev, ret, - "failed to install IRQ handler\n"); + return ret; ctx->gpio_reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); if (IS_ERR(ctx->gpio_reset)) From c9a2c8ddcd65e96e7f095a87c0cc0b3211f740ea Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 23 Jul 2026 21:16:41 +0800 Subject: [PATCH 015/213] drm/bridge: ti-sn65dsi83: Remove redundant dev_err_probe() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_threaded_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err_probe() call. Signed-off-by: Pan Chuang Reviewed-by: Luca Ceresoli Tested-by: Luca Ceresoli Link: https://patch.msgid.link/20260723131649.134127-10-panchuang@vivo.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/ti-sn65dsi83.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi83.c b/drivers/gpu/drm/bridge/ti-sn65dsi83.c index f9fdbf48c6b3..da17a18e89f7 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi83.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi83.c @@ -1040,7 +1040,7 @@ static int sn65dsi83_probe(struct i2c_client *client) ret = devm_request_threaded_irq(ctx->dev, ctx->irq, NULL, sn65dsi83_irq, IRQF_ONESHOT, dev_name(ctx->dev), ctx); if (ret) - return dev_err_probe(dev, ret, "failed to request irq\n"); + return ret; } dev_set_drvdata(dev, ctx); From d85a5e9dfb42449d9f37b0ddc6ec30b129f481ce Mon Sep 17 00:00:00 2001 From: Wojciech Dubowik Date: Tue, 28 Jul 2026 14:20:29 +0200 Subject: [PATCH 016/213] drm/bridge: ti-sn65dsi83: Simplify error condition logic Simplify error condition logic in lvds device tree property parsing. It makes it easier to add extra properties without handling complex goto entries. Reviewed-by: Luca Ceresoli Tested-by: Luca Ceresoli Signed-off-by: Wojciech Dubowik Link: https://patch.msgid.link/20260728122032.692482-2-wojciech.dubowik@mt.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/ti-sn65dsi83.c | 43 ++++++++------------------- 1 file changed, 13 insertions(+), 30 deletions(-) diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi83.c b/drivers/gpu/drm/bridge/ti-sn65dsi83.c index da17a18e89f7..41c217074cc3 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi83.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi83.c @@ -825,48 +825,35 @@ static int sn65dsi83_select_lvds_vod_swing(struct device *dev, static int sn65dsi83_parse_lvds_endpoint(struct sn65dsi83 *ctx, int channel) { struct device *dev = ctx->dev; - struct device_node *endpoint; - int endpoint_reg; + int endpoint_reg = (channel == CHANNEL_A) ? 2 : 3; + struct device_node *endpoint __free(device_node) = + of_graph_get_endpoint_by_regs(dev->of_node, endpoint_reg, -1); /* Set so the property can be freely selected if not defined */ u32 lvds_vod_swing_data[2] = { 0, 1000000 }; u32 lvds_vod_swing_clk[2] = { 0, 1000000 }; /* Set default near end terminataion to 200 Ohm */ u32 lvds_term = 200; int lvds_vod_swing_conf; - int ret = 0; int ret_data; int ret_clock; - if (channel == CHANNEL_A) - endpoint_reg = 2; - else - endpoint_reg = 3; - - endpoint = of_graph_get_endpoint_by_regs(dev->of_node, endpoint_reg, -1); - of_property_read_u32(endpoint, "ti,lvds-termination-ohms", &lvds_term); if (lvds_term == 100) ctx->lvds_term_conf[channel] = OHM_100; else if (lvds_term == 200) ctx->lvds_term_conf[channel] = OHM_200; - else { - ret = -EINVAL; - goto exit; - } + else + return -EINVAL; ret_data = of_property_read_u32_array(endpoint, "ti,lvds-vod-swing-data-microvolt", lvds_vod_swing_data, ARRAY_SIZE(lvds_vod_swing_data)); - if (ret_data != 0 && ret_data != -EINVAL) { - ret = ret_data; - goto exit; - } + if (ret_data != 0 && ret_data != -EINVAL) + return ret_data; ret_clock = of_property_read_u32_array(endpoint, "ti,lvds-vod-swing-clock-microvolt", lvds_vod_swing_clk, ARRAY_SIZE(lvds_vod_swing_clk)); - if (ret_clock != 0 && ret_clock != -EINVAL) { - ret = ret_clock; - goto exit; - } + if (ret_clock != 0 && ret_clock != -EINVAL) + return ret_clock; /* Use default value if both properties are NOT defined. */ if (ret_data == -EINVAL && ret_clock == -EINVAL) @@ -876,17 +863,13 @@ static int sn65dsi83_parse_lvds_endpoint(struct sn65dsi83 *ctx, int channel) if (!ret_data || !ret_clock) { lvds_vod_swing_conf = sn65dsi83_select_lvds_vod_swing(dev, lvds_vod_swing_data, lvds_vod_swing_clk, ctx->lvds_term_conf[channel]); - if (lvds_vod_swing_conf < 0) { - ret = lvds_vod_swing_conf; - goto exit; - } + if (lvds_vod_swing_conf < 0) + return lvds_vod_swing_conf; } ctx->lvds_vod_swing_conf[channel] = lvds_vod_swing_conf; - ret = 0; -exit: - of_node_put(endpoint); - return ret; + + return 0; } static int sn65dsi83_parse_dt(struct sn65dsi83 *ctx, enum sn65dsi83_model model) From 0d46312808f3267ee73d15200ef7e551aba34b16 Mon Sep 17 00:00:00 2001 From: Shixiong Ou Date: Wed, 29 Jul 2026 16:48:23 +0800 Subject: [PATCH 017/213] drm/log: Batch vmap/vunmap and flush for record drawing drm_log_draw_new_line() calls drm_log_clear_line() and drm_log_draw_line(), which independently call drm_client_buffer_vmap_local(), drm_client_buffer_vunmap_local(), and drm_client_buffer_flush(). For each call to drm_log_draw_new_line(), this results in 2 vmap/vunmap pairs and 2 flush calls, even though vmap_local maps the entire framebuffer each time. Refactor drm_log_clear_line() and drm_log_draw_line() to accept a pre-mapped iosys_map by value, removing the per-line vmap/vunmap/flush calls. Move the single vmap/vunmap pair and flush up to drm_log_draw_new_line(), which now maps once before clearing and drawing, then issues a single flush after unmapping. Signed-off-by: Shixiong Ou Link: https://patch.msgid.link/20260729084823.693723-1-oushixiong1025@163.com Signed-off-by: Jocelyn Falempe --- drivers/gpu/drm/clients/drm_log.c | 54 +++++++++++++++++++------------ 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/drivers/gpu/drm/clients/drm_log.c b/drivers/gpu/drm/clients/drm_log.c index e3e02c84a4cf..d446988eecc9 100644 --- a/drivers/gpu/drm/clients/drm_log.c +++ b/drivers/gpu/drm/clients/drm_log.c @@ -89,25 +89,21 @@ static void drm_log_blit(struct iosys_map *dst, unsigned int dst_pitch, } } -static void drm_log_clear_line(struct drm_log_scanout *scanout, u32 line) +static void drm_log_clear_line(struct drm_log_scanout *scanout, u32 line, + struct iosys_map map) { struct drm_framebuffer *fb = scanout->buffer->fb; unsigned long height = scanout->scaled_font_h; - struct iosys_map map; struct drm_rect r = DRM_RECT_INIT(0, line * height, fb->width, height); - if (drm_client_buffer_vmap_local(scanout->buffer, &map)) - return; iosys_map_memset(&map, r.y1 * fb->pitches[0], 0, height * fb->pitches[0]); - drm_client_buffer_vunmap_local(scanout->buffer); - drm_client_buffer_flush(scanout->buffer, &r); } static void drm_log_draw_line(struct drm_log_scanout *scanout, const char *s, - unsigned int len, unsigned int prefix_len) + unsigned int len, unsigned int prefix_len, + struct iosys_map map) { struct drm_framebuffer *fb = scanout->buffer->fb; - struct iosys_map map; const struct font_desc *font = scanout->font; size_t font_pitch = DIV_ROUND_UP(font->width, 8); const u8 *src; @@ -116,10 +112,9 @@ static void drm_log_draw_line(struct drm_log_scanout *scanout, const char *s, fb->width, (scanout->line + 1) * scanout->scaled_font_h); u32 i; - if (drm_client_buffer_vmap_local(scanout->buffer, &map)) - return; iosys_map_incr(&map, r.y1 * fb->pitches[0]); + for (i = 0; i < len && i < scanout->columns; i++) { u32 color = (i < prefix_len) ? scanout->prefix_color : scanout->front_color; src = font_data_glyph_buf(font->data, font->width, font->height, @@ -134,21 +129,40 @@ static void drm_log_draw_line(struct drm_log_scanout *scanout, const char *s, scanout->line++; if (scanout->line >= scanout->rows) scanout->line = 0; - drm_client_buffer_vunmap_local(scanout->buffer); - drm_client_buffer_flush(scanout->buffer, &r); } static void drm_log_draw_new_line(struct drm_log_scanout *scanout, - const char *s, unsigned int len, unsigned int prefix_len) + const char *s, unsigned int len, + unsigned int prefix_len) { - if (scanout->line == 0) { - drm_log_clear_line(scanout, 0); - drm_log_clear_line(scanout, 1); - drm_log_clear_line(scanout, 2); - } else if (scanout->line + 2 < scanout->rows) - drm_log_clear_line(scanout, scanout->line + 2); + struct iosys_map map; + struct drm_framebuffer *fb = scanout->buffer->fb; + u32 height = scanout->scaled_font_h; + u32 line = scanout->line; + u32 y2; + struct drm_rect dirty; - drm_log_draw_line(scanout, s, len, prefix_len); + if (drm_client_buffer_vmap_local(scanout->buffer, &map)) + return; + + if (scanout->line == 0) { + drm_log_clear_line(scanout, 0, map); + drm_log_clear_line(scanout, 1, map); + drm_log_clear_line(scanout, 2, map); + y2 = min(3, scanout->rows) * height; + } else if (scanout->line + 2 < scanout->rows) { + drm_log_clear_line(scanout, scanout->line + 2, map); + y2 = (line + 3) * height; + } else { + y2 = (line + 1) * height; + } + + drm_log_draw_line(scanout, s, len, prefix_len, map); + + drm_client_buffer_vunmap_local(scanout->buffer); + + dirty = DRM_RECT_INIT(0, line * height, fb->width, y2 - line * height); + drm_client_buffer_flush(scanout->buffer, &dirty); } /* From ed4a19460fb9ff47d01f02c2375499c1110dd1b3 Mon Sep 17 00:00:00 2001 From: Nicolas Frattaroli Date: Fri, 24 Jul 2026 16:22:36 +0200 Subject: [PATCH 018/213] drm/scdc-helper: Don't use ssize_t return type for scdc_read/write drm_scdc_read and drm_scdc_write, both of which are only used within drm_scdc_helper (although exported), use a ssize_t as their return type. This would make sense if they returned the number of bytes read/written on success, and negative errno otherwise. However, they return 0 on success. Demote them to "int" as their return type, in order to avoid needlessly using 64 bits when less suffices. No functional change. Reviewed-by: Luca Ceresoli Reviewed-by: Hans Verkuil Signed-off-by: Nicolas Frattaroli Tested-by: Hans Verkuil Link: https://patch.msgid.link/20260724-scdc-link-health-v9-1-bdda406d016d@collabora.com Signed-off-by: Daniel Stone --- drivers/gpu/drm/display/drm_scdc_helper.c | 8 ++++---- include/drm/display/drm_scdc_helper.h | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/display/drm_scdc_helper.c b/drivers/gpu/drm/display/drm_scdc_helper.c index df878aad4a36..8403f2390ab6 100644 --- a/drivers/gpu/drm/display/drm_scdc_helper.c +++ b/drivers/gpu/drm/display/drm_scdc_helper.c @@ -67,8 +67,8 @@ * Returns: * 0 on success, negative error code on failure. */ -ssize_t drm_scdc_read(struct i2c_adapter *adapter, u8 offset, void *buffer, - size_t size) +int drm_scdc_read(struct i2c_adapter *adapter, u8 offset, void *buffer, + size_t size) { int ret; struct i2c_msg msgs[2] = { @@ -107,8 +107,8 @@ EXPORT_SYMBOL(drm_scdc_read); * Returns: * 0 on success, negative error code on failure. */ -ssize_t drm_scdc_write(struct i2c_adapter *adapter, u8 offset, - const void *buffer, size_t size) +int drm_scdc_write(struct i2c_adapter *adapter, u8 offset, const void *buffer, + size_t size) { struct i2c_msg msg = { .addr = SCDC_I2C_SLAVE_ADDRESS, diff --git a/include/drm/display/drm_scdc_helper.h b/include/drm/display/drm_scdc_helper.h index 34600476a1b9..e9ccaeba56dd 100644 --- a/include/drm/display/drm_scdc_helper.h +++ b/include/drm/display/drm_scdc_helper.h @@ -31,10 +31,10 @@ struct drm_connector; struct i2c_adapter; -ssize_t drm_scdc_read(struct i2c_adapter *adapter, u8 offset, void *buffer, - size_t size); -ssize_t drm_scdc_write(struct i2c_adapter *adapter, u8 offset, - const void *buffer, size_t size); +int drm_scdc_read(struct i2c_adapter *adapter, u8 offset, void *buffer, + size_t size); +int drm_scdc_write(struct i2c_adapter *adapter, u8 offset, const void *buffer, + size_t size); /** * drm_scdc_readb - read a single byte from SCDC From 6c5121962ec0cb486fa189a0c5dc17441c2f9e9c Mon Sep 17 00:00:00 2001 From: Nicolas Frattaroli Date: Fri, 24 Jul 2026 16:22:37 +0200 Subject: [PATCH 019/213] drm/scdc-helper: Add scdc_status debugfs entry SCDC provides status information on the current display link. At the very least, it may be useful to expose this info through debugfs. Add a debugfs entry for it under the connector, which displays a few more details parsed out of the SCDC registers. A new drm_scdc_debugfs_init function can be called by the connector implementation to initialise the debugfs file. Signed-off-by: Nicolas Frattaroli Reviewed-by: Daniel Stone Acked-by: Maxime Ripard Tested-by: Hans Verkuil Link: https://patch.msgid.link/20260724-scdc-link-health-v9-2-bdda406d016d@collabora.com Signed-off-by: Daniel Stone --- drivers/gpu/drm/display/drm_scdc_helper.c | 186 ++++++++++++++++++++++ include/drm/display/drm_scdc_helper.h | 32 ++++ 2 files changed, 218 insertions(+) diff --git a/drivers/gpu/drm/display/drm_scdc_helper.c b/drivers/gpu/drm/display/drm_scdc_helper.c index 8403f2390ab6..129a2b85527b 100644 --- a/drivers/gpu/drm/display/drm_scdc_helper.c +++ b/drivers/gpu/drm/display/drm_scdc_helper.c @@ -24,7 +24,9 @@ #include #include #include +#include #include +#include #include #include @@ -276,3 +278,187 @@ bool drm_scdc_set_high_tmds_clock_ratio(struct drm_connector *connector, return true; } EXPORT_SYMBOL(drm_scdc_set_high_tmds_clock_ratio); + +static void +drm_scdc_parse_status0_flags(u8 val, struct drm_scdc_status_flags *flags) +{ + flags->clock_detected = val & SCDC_CLOCK_DETECT; + flags->ch0_locked = val & SCDC_CH0_LOCK; + flags->ch1_locked = val & SCDC_CH1_LOCK; + flags->ch2_locked = val & SCDC_CH2_LOCK; +} + +static int drm_scdc_parse_error_counters(const u8 scdc[256], u16 counter[3]) +{ + u8 sum = 0; + int i; + + for (i = SCDC_ERR_DET_0_L; i <= SCDC_ERR_DET_CHECKSUM ; i++) + sum = wrapping_add(u8, sum, scdc[i]); + + if (sum) + return -EPROTO; + + for (i = 0; i < 3; i++) { + if (scdc[SCDC_ERR_DET_0_H + i * 2] & SCDC_CHANNEL_VALID) + counter[i] = (scdc[SCDC_ERR_DET_0_H + i * 2] & + ~SCDC_CHANNEL_VALID) << 8 | + scdc[SCDC_ERR_DET_0_L + i * 2]; + else + counter[i] = 0; + } + + return 0; +} + +/** + * drm_scdc_read_state - Update state from SCDC + * @connector: pointer to a &struct drm_connector on which to operate on + * @state: pointer to a &struct drm_scdc_state to fill + * + * Reads the entire 256 byte SCDC state and parses it. + * + * Returns: %0 on success, negative errno on failure. + */ +int drm_scdc_read_state(struct drm_connector *connector, struct drm_scdc_state *state) +{ + struct i2c_adapter *ddc; + struct drm_scdc *scdc; + u8 *buf = state->scdc; + int ret; + + if (!state || !connector) + return -ENODEV; + + scdc = &connector->display_info.hdmi.scdc; + ddc = connector->ddc; + + if (!scdc->supported || !ddc) + return -EOPNOTSUPP; + + /* Read in 128-byte chunks, to work around DP<->HDMI converters with issues. */ + ret = drm_scdc_read(ddc, 0, buf, 128); + if (ret) + return ret; + + ret = drm_scdc_read(ddc, 128, &buf[128], 128); + if (ret) + return ret; + + state->scrambling_enabled = buf[SCDC_TMDS_CONFIG] & SCDC_SCRAMBLING_ENABLE; + state->tmds_bclk_x40 = buf[SCDC_TMDS_CONFIG] & SCDC_TMDS_BIT_CLOCK_RATIO_BY_40; + + state->scrambling_detected = buf[SCDC_SCRAMBLER_STATUS] & SCDC_SCRAMBLING_STATUS; + + drm_scdc_parse_status0_flags(buf[SCDC_STATUS_FLAGS_0], &state->stf); + ret = drm_scdc_parse_error_counters(buf, state->error_count); + if (ret) + return ret; + + return 0; +} +EXPORT_SYMBOL(drm_scdc_read_state); + +#define scdc_print_str(_f, key, s) \ + (seq_printf((_f), "%-30s: %s\n", (key), (s))) +#define scdc_print_flag(_f, key, val) \ + (scdc_print_str((_f), (key), str_yes_no((val)))) +#define scdc_print_dec(_f, key, val) \ + (seq_printf((_f), "%-30s: %d\n", (key), (val))) + +static int scdc_status_show(struct seq_file *m, void *data) +{ + struct drm_connector *connector = m->private; + struct drm_scdc *scdc = &connector->display_info.hdmi.scdc; + struct drm_scdc_state *st; + int i, ret; + + drm_connector_get(connector); + + ret = mutex_lock_interruptible(&connector->dev->mode_config.mutex); + if (ret) + goto err_conn_put; + + if (connector->status != connector_status_connected) { + ret = -ENODEV; + goto err_unlock; + } + + if (scdc->supported) { + st = kzalloc_obj(*st); + if (!st) { + ret = -ENOMEM; + goto err_unlock; + } + ret = drm_scdc_read_state(connector, st); + if (ret) + goto err_free_state; + + for (i = 0; i < ARRAY_SIZE(st->scdc); i += 16) + seq_printf(m, "%*ph\n", 16, &st->scdc[i]); + + seq_puts(m, "\n----------------\n\n"); + } + + scdc_print_flag(m, "SCDC Supported", scdc->supported); + if (!scdc->supported) { + ret = 0; + goto err_unlock; + } + + scdc_print_flag(m, "Sink Read Request Capable", scdc->read_request); + scdc_print_flag(m, "Scrambling Supported", scdc->scrambling.supported); + scdc_print_flag(m, "Low Rate Scrambling Supported", scdc->scrambling.low_rates); + + mutex_unlock(&connector->dev->mode_config.mutex); + + drm_connector_put(connector); + + scdc_print_flag(m, "Scrambling Enabled", st->scrambling_enabled); + scdc_print_flag(m, "Scrambling Detected", st->scrambling_detected); + + if (st->tmds_bclk_x40) + scdc_print_str(m, "TMDS Bit Clock Ratio", "1/40"); + else + scdc_print_str(m, "TMDS Bit Clock Ratio", "1/10"); + + scdc_print_flag(m, "Clock Detected", st->stf.clock_detected); + scdc_print_flag(m, "Channel 0 Locked", st->stf.ch0_locked); + scdc_print_flag(m, "Channel 1 Locked", st->stf.ch1_locked); + scdc_print_flag(m, "Channel 2 Locked", st->stf.ch2_locked); + + scdc_print_dec(m, "Channel 0 Errors", st->error_count[0]); + scdc_print_dec(m, "Channel 1 Errors", st->error_count[1]); + scdc_print_dec(m, "Channel 2 Errors", st->error_count[2]); + + kfree(st); + + return 0; + +err_free_state: + kfree(st); +err_unlock: + mutex_unlock(&connector->dev->mode_config.mutex); +err_conn_put: + drm_connector_put(connector); + + return ret; +} +DEFINE_SHOW_ATTRIBUTE(scdc_status); + +/** + * drm_scdc_debugfs_init - Initialize scdc files in connector debugfs + * @connector: pointer to &struct drm_connector to operate on + * @root: debugfs &struct dentry for the debugfs root of @connector + * + * Creates SCDC-related debugfs files for @connector. Must be called after + * @root is already created. + */ +void drm_scdc_debugfs_init(struct drm_connector *connector, struct dentry *root) +{ + if (!root || !connector) + return; + + debugfs_create_file("scdc_status", 0444, root, connector, &scdc_status_fops); +} +EXPORT_SYMBOL(drm_scdc_debugfs_init); diff --git a/include/drm/display/drm_scdc_helper.h b/include/drm/display/drm_scdc_helper.h index e9ccaeba56dd..e0b79d79e1ff 100644 --- a/include/drm/display/drm_scdc_helper.h +++ b/include/drm/display/drm_scdc_helper.h @@ -30,6 +30,34 @@ struct drm_connector; struct i2c_adapter; +struct dentry; + +struct drm_scdc_status_flags { + /* Status Register 0 */ + bool clock_detected; + bool ch0_locked; + bool ch1_locked; + bool ch2_locked; +}; + +struct drm_scdc_state { + /** @stf: contents of the status flag registers */ + struct drm_scdc_status_flags stf; + /** @scramling_enabled: true if TMDS scrambling is on */ + bool scrambling_enabled; + /** @scrambling_detected: true if the sink actually detected scrambling */ + bool scrambling_detected; + /** + * @tmds_bclk_x40: true if TMDS bit period is 1/40th of the TMDS + * clock period, false if it's 1/10th of the clock period. + */ + bool tmds_bclk_x40; + /** @error_count: character error counts for each channel */ + u16 error_count[3]; + + /** @scdc: raw SCDC data buffer */ + u8 scdc[256]; +}; int drm_scdc_read(struct i2c_adapter *adapter, u8 offset, void *buffer, size_t size); @@ -77,4 +105,8 @@ bool drm_scdc_get_scrambling_status(struct drm_connector *connector); bool drm_scdc_set_scrambling(struct drm_connector *connector, bool enable); bool drm_scdc_set_high_tmds_clock_ratio(struct drm_connector *connector, bool set); +int drm_scdc_read_state(struct drm_connector *connector, + struct drm_scdc_state *state); +void drm_scdc_debugfs_init(struct drm_connector *connector, struct dentry *root); + #endif From c8faf89e97a8e95cb2f0e171d88c982c9595befe Mon Sep 17 00:00:00 2001 From: Nicolas Frattaroli Date: Fri, 24 Jul 2026 16:22:38 +0200 Subject: [PATCH 020/213] drm/scdc-helper: Implement parsing and printing HDMI 2.1 fields HDMI 2.1 redefines previously reserved fields in SCDC for various new uses. No version check needs to be performed, as an HDMI 2.0 sink's reserved SCDC fields are well-defined to be 0, and any zero-ness of these fields for an HDMI 2.0 sink is not a surprise for SCDC parsers for HDMI 2.1. Implement reading and outputting these fields over debugfs. Reviewed-by: Maxime Ripard Signed-off-by: Nicolas Frattaroli Tested-by: Hans Verkuil Link: https://patch.msgid.link/20260724-scdc-link-health-v9-3-bdda406d016d@collabora.com Signed-off-by: Daniel Stone --- drivers/gpu/drm/display/drm_scdc_helper.c | 103 +++++++++++++++++++++- include/drm/display/drm_scdc.h | 21 ++++- include/drm/display/drm_scdc_helper.h | 69 ++++++++++++++- 3 files changed, 185 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/display/drm_scdc_helper.c b/drivers/gpu/drm/display/drm_scdc_helper.c index 129a2b85527b..d00da20862fc 100644 --- a/drivers/gpu/drm/display/drm_scdc_helper.c +++ b/drivers/gpu/drm/display/drm_scdc_helper.c @@ -21,6 +21,7 @@ * DEALINGS IN THE SOFTWARE. */ +#include #include #include #include @@ -57,6 +58,38 @@ #define SCDC_I2C_SLAVE_ADDRESS 0x54 +static const char *drm_scdc_frl_rate_str(enum drm_scdc_frl_rate rate) +{ + switch (rate) { + case SCDC_FRL_RATE_OFF: + return "Off"; + case SCDC_FRL_RATE_3X3: + return "3 Gbit/s x 3 lanes"; + case SCDC_FRL_RATE_6X3: + return "6 Gbit/s x 3 lanes"; + case SCDC_FRL_RATE_6X4: + return "6 Gbit/s x 4 lanes"; + case SCDC_FRL_RATE_8X4: + return "8 Gbit/s x 4 lanes"; + case SCDC_FRL_RATE_10X4: + return "10 Gbit/s x 4 lanes"; + case SCDC_FRL_RATE_12X4: + return "12 Gbit/s x 4 lanes"; + case SCDC_FRL_RATE_RESV_7: + case SCDC_FRL_RATE_RESV_8: + case SCDC_FRL_RATE_RESV_9: + case SCDC_FRL_RATE_RESV_10: + case SCDC_FRL_RATE_RESV_11: + case SCDC_FRL_RATE_RESV_12: + case SCDC_FRL_RATE_RESV_13: + case SCDC_FRL_RATE_RESV_14: + case SCDC_FRL_RATE_RESV_15: + return "(Reserved)"; + default: + return NULL; + } +} + /** * drm_scdc_read - read a block of data from SCDC * @adapter: I2C controller @@ -286,14 +319,41 @@ drm_scdc_parse_status0_flags(u8 val, struct drm_scdc_status_flags *flags) flags->ch0_locked = val & SCDC_CH0_LOCK; flags->ch1_locked = val & SCDC_CH1_LOCK; flags->ch2_locked = val & SCDC_CH2_LOCK; + flags->ln3_locked = val & SCDC_LN3_LOCK; + flags->flt_ready = val & SCDC_FLT_READY; + flags->dsc_fail = val & SCDC_DSC_FAIL; } -static int drm_scdc_parse_error_counters(const u8 scdc[256], u16 counter[3]) +static void +drm_scdc_parse_status1_2_flags(u8 val_flag1, u8 val_flag2, + struct drm_scdc_status_flags *flags) { + flags->ln0_training_pattern = FIELD_GET(SCDC_LN_EVEN_TRAIN_PTRN, val_flag1); + flags->ln1_training_pattern = FIELD_GET(SCDC_LN_ODD_TRAIN_PTRN, val_flag1); + + flags->ln2_training_pattern = FIELD_GET(SCDC_LN_EVEN_TRAIN_PTRN, val_flag2); + flags->ln3_training_pattern = FIELD_GET(SCDC_LN_ODD_TRAIN_PTRN, val_flag2); +} + +static int drm_scdc_parse_error_counters(const u8 scdc[256], u16 counter[4], + unsigned int num_lanes) +{ + u8 end_reg; u8 sum = 0; int i; - for (i = SCDC_ERR_DET_0_L; i <= SCDC_ERR_DET_CHECKSUM ; i++) + switch (num_lanes) { + case 3: + end_reg = SCDC_ERR_DET_CHECKSUM; + break; + case 4: + end_reg = SCDC_ERR_DET_3_H; + break; + default: + return -EINVAL; + } + + for (i = SCDC_ERR_DET_0_L; i <= end_reg; i++) sum = wrapping_add(u8, sum, scdc[i]); if (sum) @@ -308,6 +368,12 @@ static int drm_scdc_parse_error_counters(const u8 scdc[256], u16 counter[3]) counter[i] = 0; } + if (num_lanes == 4 && scdc[SCDC_ERR_DET_3_H] & SCDC_CHANNEL_VALID) + counter[3] = (scdc[SCDC_ERR_DET_3_H] & ~SCDC_CHANNEL_VALID) << 8 | + scdc[SCDC_ERR_DET_3_L]; + else + counter[3] = 0; + return 0; } @@ -325,6 +391,7 @@ int drm_scdc_read_state(struct drm_connector *connector, struct drm_scdc_state * struct i2c_adapter *ddc; struct drm_scdc *scdc; u8 *buf = state->scdc; + int num_lanes; int ret; if (!state || !connector) @@ -350,11 +417,26 @@ int drm_scdc_read_state(struct drm_connector *connector, struct drm_scdc_state * state->scrambling_detected = buf[SCDC_SCRAMBLER_STATUS] & SCDC_SCRAMBLING_STATUS; + state->rate = FIELD_GET(SCDC_FRL_RATE, buf[SCDC_CONFIG_1]); + num_lanes = drm_scdc_num_frl_lanes(state->rate); + if (num_lanes < 0) + return num_lanes; + if (!num_lanes) + num_lanes = 3; + + state->ffe_levels = FIELD_GET(SCDC_FFE_LEVELS, buf[SCDC_CONFIG_1]); + drm_scdc_parse_status0_flags(buf[SCDC_STATUS_FLAGS_0], &state->stf); - ret = drm_scdc_parse_error_counters(buf, state->error_count); + drm_scdc_parse_status1_2_flags(buf[SCDC_STATUS_FLAGS_1], + buf[SCDC_STATUS_FLAGS_2], &state->stf); + ret = drm_scdc_parse_error_counters(buf, state->error_count, num_lanes); if (ret) return ret; + if (state->rate && (buf[SCDC_ERR_DET_RS_H] & SCDC_CHANNEL_VALID)) + state->rs_corrections = (buf[SCDC_ERR_DET_RS_H] & ~SCDC_CHANNEL_VALID) << 8 | + buf[SCDC_ERR_DET_RS_L]; + return 0; } EXPORT_SYMBOL(drm_scdc_read_state); @@ -371,7 +453,7 @@ static int scdc_status_show(struct seq_file *m, void *data) struct drm_connector *connector = m->private; struct drm_scdc *scdc = &connector->display_info.hdmi.scdc; struct drm_scdc_state *st; - int i, ret; + int i, ret, frl_lanes; drm_connector_get(connector); @@ -414,8 +496,12 @@ static int scdc_status_show(struct seq_file *m, void *data) drm_connector_put(connector); + frl_lanes = drm_scdc_num_frl_lanes(st->rate); + scdc_print_flag(m, "Scrambling Enabled", st->scrambling_enabled); scdc_print_flag(m, "Scrambling Detected", st->scrambling_detected); + scdc_print_str(m, "FRL Rate", drm_scdc_frl_rate_str(st->rate)); + scdc_print_dec(m, "FFE Levels", st->ffe_levels); if (st->tmds_bclk_x40) scdc_print_str(m, "TMDS Bit Clock Ratio", "1/40"); @@ -426,10 +512,19 @@ static int scdc_status_show(struct seq_file *m, void *data) scdc_print_flag(m, "Channel 0 Locked", st->stf.ch0_locked); scdc_print_flag(m, "Channel 1 Locked", st->stf.ch1_locked); scdc_print_flag(m, "Channel 2 Locked", st->stf.ch2_locked); + if (frl_lanes == 4) + scdc_print_flag(m, "Lane 3 Locked", st->stf.ln3_locked); + + scdc_print_flag(m, "Sink Ready For Link Training", st->stf.flt_ready); + scdc_print_flag(m, "Sink Failed To Decode DSC", st->stf.dsc_fail); scdc_print_dec(m, "Channel 0 Errors", st->error_count[0]); scdc_print_dec(m, "Channel 1 Errors", st->error_count[1]); scdc_print_dec(m, "Channel 2 Errors", st->error_count[2]); + if (frl_lanes == 4) + scdc_print_dec(m, "Lane 3 Errors", st->error_count[3]); + if (frl_lanes > 0) + scdc_print_dec(m, "Reed-Solomon Corrections", st->rs_corrections); kfree(st); diff --git a/include/drm/display/drm_scdc.h b/include/drm/display/drm_scdc.h index 3d58f37e8ed8..7f0b05b2f280 100644 --- a/include/drm/display/drm_scdc.h +++ b/include/drm/display/drm_scdc.h @@ -29,6 +29,8 @@ #define SCDC_SOURCE_VERSION 0x02 #define SCDC_UPDATE_0 0x10 +#define SCDC_RSED_UPDATE (1 << 6) +#define SCDC_FLT_UPDATE (1 << 5) #define SCDC_READ_REQUEST_TEST (1 << 2) #define SCDC_CED_UPDATE (1 << 1) #define SCDC_STATUS_UPDATE (1 << 0) @@ -46,14 +48,25 @@ #define SCDC_CONFIG_0 0x30 #define SCDC_READ_REQUEST_ENABLE (1 << 0) +#define SCDC_CONFIG_1 0x31 +#define SCDC_FRL_RATE 0x0f +#define SCDC_FFE_LEVELS 0xf0 + #define SCDC_STATUS_FLAGS_0 0x40 +#define SCDC_DSC_FAIL (1 << 7) +#define SCDC_FLT_READY (1 << 6) +#define SCDC_LN3_LOCK (1 << 4) #define SCDC_CH2_LOCK (1 << 3) #define SCDC_CH1_LOCK (1 << 2) #define SCDC_CH0_LOCK (1 << 1) -#define SCDC_CH_LOCK_MASK (SCDC_CH2_LOCK | SCDC_CH1_LOCK | SCDC_CH0_LOCK) +#define SCDC_CH_LOCK_MASK (SCDC_LN3_LOCK | SCDC_CH2_LOCK | SCDC_CH1_LOCK | \ + SCDC_CH0_LOCK) #define SCDC_CLOCK_DETECT (1 << 0) #define SCDC_STATUS_FLAGS_1 0x41 +#define SCDC_LN_EVEN_TRAIN_PTRN 0x0f +#define SCDC_LN_ODD_TRAIN_PTRN 0xf0 +#define SCDC_STATUS_FLAGS_2 0x42 #define SCDC_ERR_DET_0_L 0x50 #define SCDC_ERR_DET_0_H 0x51 @@ -65,6 +78,12 @@ #define SCDC_ERR_DET_CHECKSUM 0x56 +#define SCDC_ERR_DET_3_L 0x57 +#define SCDC_ERR_DET_3_H 0x58 + +#define SCDC_ERR_DET_RS_L 0x59 +#define SCDC_ERR_DET_RS_H 0x5a + #define SCDC_TEST_CONFIG_0 0xc0 #define SCDC_TEST_READ_REQUEST (1 << 7) #define SCDC_TEST_READ_REQUEST_DELAY(x) ((x) & 0x7f) diff --git a/include/drm/display/drm_scdc_helper.h b/include/drm/display/drm_scdc_helper.h index e0b79d79e1ff..a3b20adaac7e 100644 --- a/include/drm/display/drm_scdc_helper.h +++ b/include/drm/display/drm_scdc_helper.h @@ -24,6 +24,7 @@ #ifndef DRM_SCDC_HELPER_H #define DRM_SCDC_HELPER_H +#include #include #include @@ -38,8 +39,65 @@ struct drm_scdc_status_flags { bool ch0_locked; bool ch1_locked; bool ch2_locked; + bool ln3_locked; + bool flt_ready; + bool dsc_fail; + + /* Status Register 1 */ + u8 ln0_training_pattern : 4; + u8 ln1_training_pattern : 4; + + /* Status Register 2 */ + u8 ln2_training_pattern : 4; + u8 ln3_training_pattern : 4; }; +enum drm_scdc_frl_rate { + SCDC_FRL_RATE_OFF = 0, + SCDC_FRL_RATE_3X3 = 1, + SCDC_FRL_RATE_6X3 = 2, + SCDC_FRL_RATE_6X4 = 3, + SCDC_FRL_RATE_8X4 = 4, + SCDC_FRL_RATE_10X4 = 5, + SCDC_FRL_RATE_12X4 = 6, + SCDC_FRL_RATE_RESV_7 = 7, + SCDC_FRL_RATE_RESV_8 = 8, + SCDC_FRL_RATE_RESV_9 = 9, + SCDC_FRL_RATE_RESV_10 = 10, + SCDC_FRL_RATE_RESV_11 = 11, + SCDC_FRL_RATE_RESV_12 = 12, + SCDC_FRL_RATE_RESV_13 = 13, + SCDC_FRL_RATE_RESV_14 = 14, + SCDC_FRL_RATE_RESV_15 = 15 +}; + +/** + * drm_scdc_num_frl_lanes - get number of lanes for a given FRL rate + * @rate: one of &enum drm_scdc_frl_rate + * + * For a given @rate, return the number of lanes it uses. + * + * Returns: %-EINVAL if @rate is not a valid FRL rate, or the number of lanes + * for a given &enum drm_scdc_frl_rate on success (including %0 for "off") + */ +static inline __pure int drm_scdc_num_frl_lanes(enum drm_scdc_frl_rate rate) +{ + switch (rate) { + case SCDC_FRL_RATE_OFF: + return 0; + case SCDC_FRL_RATE_3X3: + case SCDC_FRL_RATE_6X3: + return 3; + case SCDC_FRL_RATE_6X4: + case SCDC_FRL_RATE_8X4: + case SCDC_FRL_RATE_10X4: + case SCDC_FRL_RATE_12X4: + return 4; + default: + return -EINVAL; + } +} + struct drm_scdc_state { /** @stf: contents of the status flag registers */ struct drm_scdc_status_flags stf; @@ -52,9 +110,14 @@ struct drm_scdc_state { * clock period, false if it's 1/10th of the clock period. */ bool tmds_bclk_x40; - /** @error_count: character error counts for each channel */ - u16 error_count[3]; - + /** @rate: FRL rate set by the source */ + enum drm_scdc_frl_rate rate : 4; + /** @ffe_levels: The FFE levels for @rate set by the source */ + u8 ffe_levels : 4; + /** @error_count: character error counts for each channel/link */ + u16 error_count[4]; + /** @rs_corrections: number of Reed-Solomon Corrections */ + u16 rs_corrections; /** @scdc: raw SCDC data buffer */ u8 scdc[256]; }; From c72198cceeae040d23f5a50e2830c84c296ec551 Mon Sep 17 00:00:00 2001 From: Nicolas Frattaroli Date: Fri, 24 Jul 2026 16:22:39 +0200 Subject: [PATCH 021/213] drm/debugfs: Move HDMI debugfs registration to state helper The HDMI debugfs files are only useful for connectors that already make use of the HDMI state helpers. In addition, relying on HDMI-specific functions in drm_debugfs.c can lead to circular linking problems later down the line. Move hdmi_debugfs_add() and friends to a new helper in drm_hdmi_state_helper.c, and make all current users (vc4, sun4i, bridge) either use it as a debugfs_init func, or call it directly in its debugfs_init func. Suggested-by: Maxime Ripard Reviewed-by: Dmitry Baryshkov Acked-by: Maxime Ripard Signed-off-by: Nicolas Frattaroli Tested-by: Hans Verkuil Link: https://patch.msgid.link/20260724-scdc-link-health-v9-4-bdda406d016d@collabora.com Signed-off-by: Daniel Stone --- .../gpu/drm/display/drm_bridge_connector.c | 3 + .../gpu/drm/display/drm_hdmi_state_helper.c | 154 +++++++++++++++++ drivers/gpu/drm/drm_debugfs.c | 157 ------------------ drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c | 1 + drivers/gpu/drm/vc4/vc4_hdmi.c | 1 + include/drm/display/drm_hdmi_state_helper.h | 3 + 6 files changed, 162 insertions(+), 157 deletions(-) diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c index 8b54069fa53a..00feb73e63d7 100644 --- a/drivers/gpu/drm/display/drm_bridge_connector.c +++ b/drivers/gpu/drm/display/drm_bridge_connector.c @@ -263,6 +263,9 @@ static void drm_bridge_connector_debugfs_init(struct drm_connector *connector, if (bridge->funcs->debugfs_init) bridge->funcs->debugfs_init(bridge, root); } + + if (bridge_connector->bridge_hdmi) + drm_hdmi_connector_debugfs_init(connector, root); } static struct drm_connector_state * diff --git a/drivers/gpu/drm/display/drm_hdmi_state_helper.c b/drivers/gpu/drm/display/drm_hdmi_state_helper.c index ce17eeefc2da..8d5ee10ceb87 100644 --- a/drivers/gpu/drm/display/drm_hdmi_state_helper.c +++ b/drivers/gpu/drm/display/drm_hdmi_state_helper.c @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT +#include #include #include @@ -965,6 +966,159 @@ drm_hdmi_connector_mode_valid(struct drm_connector *connector, } EXPORT_SYMBOL(drm_hdmi_connector_mode_valid); +static ssize_t +audio_infoframe_read(struct file *filp, char __user *ubuf, size_t count, loff_t *ppos) +{ + struct drm_connector_hdmi_infoframe *infoframe; + struct drm_connector *connector; + union hdmi_infoframe *frame; + u8 buf[HDMI_INFOFRAME_SIZE(AUDIO)]; + ssize_t len = 0; + + connector = filp->private_data; + mutex_lock(&connector->hdmi.infoframes.lock); + + infoframe = &connector->hdmi.infoframes.audio; + if (!infoframe->set) + goto out; + + frame = &infoframe->data; + len = hdmi_infoframe_pack(frame, buf, sizeof(buf)); + if (len < 0) + goto out; + + len = simple_read_from_buffer(ubuf, count, ppos, buf, len); + +out: + mutex_unlock(&connector->hdmi.infoframes.lock); + return len; +} + +static const struct file_operations audio_infoframe_fops = { + .owner = THIS_MODULE, + .open = simple_open, + .read = audio_infoframe_read, +}; + +static int create_hdmi_audio_infoframe_file(struct drm_connector *connector, + struct dentry *parent) +{ + struct dentry *file; + + if (!connector->hdmi.funcs || + !connector->hdmi.funcs->audio.write_infoframe) + return 0; + + file = debugfs_create_file("audio", 0400, parent, connector, &audio_infoframe_fops); + if (IS_ERR(file)) + return PTR_ERR(file); + + return 0; +} + +#define DEFINE_INFOFRAME_FILE(_f) \ +static ssize_t _f##_read_infoframe(struct file *filp, \ + char __user *ubuf, \ + size_t count, \ + loff_t *ppos) \ +{ \ + struct drm_connector_hdmi_infoframe *infoframe; \ + struct drm_connector_state *conn_state; \ + struct drm_connector *connector; \ + union hdmi_infoframe *frame; \ + struct drm_device *dev; \ + u8 buf[HDMI_INFOFRAME_SIZE(MAX)]; \ + ssize_t len = 0; \ + \ + connector = filp->private_data; \ + dev = connector->dev; \ + \ + drm_modeset_lock(&dev->mode_config.connection_mutex, NULL); \ + \ + conn_state = connector->state; \ + infoframe = &conn_state->hdmi.infoframes._f; \ + if (!infoframe->set) \ + goto out; \ + \ + frame = &infoframe->data; \ + len = hdmi_infoframe_pack(frame, buf, sizeof(buf)); \ + if (len < 0) \ + goto out; \ + \ + len = simple_read_from_buffer(ubuf, count, ppos, buf, len); \ + \ +out: \ + drm_modeset_unlock(&dev->mode_config.connection_mutex); \ + return len; \ +} \ +\ +static const struct file_operations _f##_infoframe_fops = { \ + .owner = THIS_MODULE, \ + .open = simple_open, \ + .read = _f##_read_infoframe, \ +}; \ +\ +static int create_hdmi_## _f ## _infoframe_file(struct drm_connector *connector, \ + struct dentry *parent) \ +{ \ + struct dentry *file; \ + \ + if (!connector->hdmi.funcs || \ + !connector->hdmi.funcs->_f.write_infoframe) \ + return 0; \ + file = debugfs_create_file(#_f, 0400, parent, connector, &_f ## _infoframe_fops); \ + if (IS_ERR(file)) \ + return PTR_ERR(file); \ + \ + return 0; \ +} + +DEFINE_INFOFRAME_FILE(avi); +DEFINE_INFOFRAME_FILE(hdmi); +DEFINE_INFOFRAME_FILE(hdr_drm); +DEFINE_INFOFRAME_FILE(spd); + +static int create_hdmi_infoframe_files(struct drm_connector *connector, + struct dentry *parent) +{ + int ret; + + ret = create_hdmi_audio_infoframe_file(connector, parent); + if (ret) + return ret; + + ret = create_hdmi_avi_infoframe_file(connector, parent); + if (ret) + return ret; + + ret = create_hdmi_hdmi_infoframe_file(connector, parent); + if (ret) + return ret; + + ret = create_hdmi_hdr_drm_infoframe_file(connector, parent); + if (ret) + return ret; + + ret = create_hdmi_spd_infoframe_file(connector, parent); + if (ret) + return ret; + + return 0; +} + +void drm_hdmi_connector_debugfs_init(struct drm_connector *connector, + struct dentry *root) +{ + struct dentry *dir; + + dir = debugfs_create_dir("infoframes", root); + if (IS_ERR(dir)) + return; + + create_hdmi_infoframe_files(connector, dir); +} +EXPORT_SYMBOL(drm_hdmi_connector_debugfs_init); + static int clear_infoframe(struct drm_connector *connector, const struct drm_connector_infoframe_funcs *funcs, const char *type) diff --git a/drivers/gpu/drm/drm_debugfs.c b/drivers/gpu/drm/drm_debugfs.c index ae1c6126c2c5..a72f9723d3a9 100644 --- a/drivers/gpu/drm/drm_debugfs.c +++ b/drivers/gpu/drm/drm_debugfs.c @@ -633,161 +633,6 @@ static const struct file_operations drm_connector_fops = { .write = connector_write }; -static ssize_t -audio_infoframe_read(struct file *filp, char __user *ubuf, size_t count, loff_t *ppos) -{ - struct drm_connector_hdmi_infoframe *infoframe; - struct drm_connector *connector; - union hdmi_infoframe *frame; - u8 buf[HDMI_INFOFRAME_SIZE(AUDIO)]; - ssize_t len = 0; - - connector = filp->private_data; - mutex_lock(&connector->hdmi.infoframes.lock); - - infoframe = &connector->hdmi.infoframes.audio; - if (!infoframe->set) - goto out; - - frame = &infoframe->data; - len = hdmi_infoframe_pack(frame, buf, sizeof(buf)); - if (len < 0) - goto out; - - len = simple_read_from_buffer(ubuf, count, ppos, buf, len); - -out: - mutex_unlock(&connector->hdmi.infoframes.lock); - return len; -} - -static const struct file_operations audio_infoframe_fops = { - .owner = THIS_MODULE, - .open = simple_open, - .read = audio_infoframe_read, -}; - -static int create_hdmi_audio_infoframe_file(struct drm_connector *connector, - struct dentry *parent) -{ - struct dentry *file; - - if (!connector->hdmi.funcs || - !connector->hdmi.funcs->audio.write_infoframe) - return 0; - - file = debugfs_create_file("audio", 0400, parent, connector, &audio_infoframe_fops); - if (IS_ERR(file)) - return PTR_ERR(file); - - return 0; -} - -#define DEFINE_INFOFRAME_FILE(_f) \ -static ssize_t _f##_read_infoframe(struct file *filp, \ - char __user *ubuf, \ - size_t count, \ - loff_t *ppos) \ -{ \ - struct drm_connector_hdmi_infoframe *infoframe; \ - struct drm_connector_state *conn_state; \ - struct drm_connector *connector; \ - union hdmi_infoframe *frame; \ - struct drm_device *dev; \ - u8 buf[HDMI_INFOFRAME_SIZE(MAX)]; \ - ssize_t len = 0; \ - \ - connector = filp->private_data; \ - dev = connector->dev; \ - \ - drm_modeset_lock(&dev->mode_config.connection_mutex, NULL); \ - \ - conn_state = connector->state; \ - infoframe = &conn_state->hdmi.infoframes._f; \ - if (!infoframe->set) \ - goto out; \ - \ - frame = &infoframe->data; \ - len = hdmi_infoframe_pack(frame, buf, sizeof(buf)); \ - if (len < 0) \ - goto out; \ - \ - len = simple_read_from_buffer(ubuf, count, ppos, buf, len); \ - \ -out: \ - drm_modeset_unlock(&dev->mode_config.connection_mutex); \ - return len; \ -} \ -\ -static const struct file_operations _f##_infoframe_fops = { \ - .owner = THIS_MODULE, \ - .open = simple_open, \ - .read = _f##_read_infoframe, \ -}; \ -\ -static int create_hdmi_## _f ## _infoframe_file(struct drm_connector *connector, \ - struct dentry *parent) \ -{ \ - struct dentry *file; \ - \ - if (!connector->hdmi.funcs || \ - !connector->hdmi.funcs->_f.write_infoframe) \ - return 0; \ - file = debugfs_create_file(#_f, 0400, parent, connector, &_f ## _infoframe_fops); \ - if (IS_ERR(file)) \ - return PTR_ERR(file); \ - \ - return 0; \ -} - -DEFINE_INFOFRAME_FILE(avi); -DEFINE_INFOFRAME_FILE(hdmi); -DEFINE_INFOFRAME_FILE(hdr_drm); -DEFINE_INFOFRAME_FILE(spd); - -static int create_hdmi_infoframe_files(struct drm_connector *connector, - struct dentry *parent) -{ - int ret; - - ret = create_hdmi_audio_infoframe_file(connector, parent); - if (ret) - return ret; - - ret = create_hdmi_avi_infoframe_file(connector, parent); - if (ret) - return ret; - - ret = create_hdmi_hdmi_infoframe_file(connector, parent); - if (ret) - return ret; - - ret = create_hdmi_hdr_drm_infoframe_file(connector, parent); - if (ret) - return ret; - - ret = create_hdmi_spd_infoframe_file(connector, parent); - if (ret) - return ret; - - return 0; -} - -static void hdmi_debugfs_add(struct drm_connector *connector) -{ - struct dentry *dir; - - if (!(connector->connector_type == DRM_MODE_CONNECTOR_HDMIA || - connector->connector_type == DRM_MODE_CONNECTOR_HDMIB)) - return; - - dir = debugfs_create_dir("infoframes", connector->debugfs_entry); - if (IS_ERR(dir)) - return; - - create_hdmi_infoframe_files(connector, dir); -} - void drm_debugfs_connector_add(struct drm_connector *connector) { struct drm_device *dev = connector->dev; @@ -815,8 +660,6 @@ void drm_debugfs_connector_add(struct drm_connector *connector) debugfs_create_file("output_bpc", 0444, root, connector, &output_bpc_fops); - hdmi_debugfs_add(connector); - if (connector->funcs->debugfs_init) connector->funcs->debugfs_init(connector, root); } diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c index 97253d631b2b..91844a396a2b 100644 --- a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c @@ -292,6 +292,7 @@ static void sun4i_hdmi_connector_reset(struct drm_connector *connector) } static const struct drm_connector_funcs sun4i_hdmi_connector_funcs = { + .debugfs_init = drm_hdmi_connector_debugfs_init, .detect = sun4i_hdmi_connector_detect, .fill_modes = drm_helper_probe_single_connector_modes, .reset = sun4i_hdmi_connector_reset, diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index 17c8635c5afa..ea1f2013b20e 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -513,6 +513,7 @@ static void vc4_hdmi_connector_reset(struct drm_connector *connector) } static const struct drm_connector_funcs vc4_hdmi_connector_funcs = { + .debugfs_init = drm_hdmi_connector_debugfs_init, .force = drm_atomic_helper_connector_hdmi_force, .fill_modes = drm_helper_probe_single_connector_modes, .reset = vc4_hdmi_connector_reset, diff --git a/include/drm/display/drm_hdmi_state_helper.h b/include/drm/display/drm_hdmi_state_helper.h index 13375bd0f4ae..596c57d9d250 100644 --- a/include/drm/display/drm_hdmi_state_helper.h +++ b/include/drm/display/drm_hdmi_state_helper.h @@ -3,6 +3,7 @@ #ifndef DRM_HDMI_STATE_HELPER_H_ #define DRM_HDMI_STATE_HELPER_H_ +struct dentry; struct drm_atomic_commit; struct drm_connector; struct drm_connector_state; @@ -29,5 +30,7 @@ void drm_atomic_helper_connector_hdmi_force(struct drm_connector *connector); enum drm_mode_status drm_hdmi_connector_mode_valid(struct drm_connector *connector, const struct drm_display_mode *mode); +void drm_hdmi_connector_debugfs_init(struct drm_connector *connector, + struct dentry *root); #endif // DRM_HDMI_STATE_HELPER_H_ From 9b388fc3d0e63a4f5b022d326151e176e8d82e25 Mon Sep 17 00:00:00 2001 From: Nicolas Frattaroli Date: Fri, 24 Jul 2026 16:22:40 +0200 Subject: [PATCH 022/213] drm/display: hdmi-state-helper: Init SCDC debugfs for HDMI Initialise the SCDC debugfs file for connectors that call the HDMI state helper's debugfs_init helper. This makes all connectors that use the helper expose the scdc_status file in their connector debugfs. Acked-by: Maxime Ripard Signed-off-by: Nicolas Frattaroli Tested-by: Hans Verkuil Link: https://patch.msgid.link/20260724-scdc-link-health-v9-5-bdda406d016d@collabora.com Signed-off-by: Daniel Stone --- drivers/gpu/drm/display/drm_hdmi_state_helper.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/display/drm_hdmi_state_helper.c b/drivers/gpu/drm/display/drm_hdmi_state_helper.c index 8d5ee10ceb87..709524b40c26 100644 --- a/drivers/gpu/drm/display/drm_hdmi_state_helper.c +++ b/drivers/gpu/drm/display/drm_hdmi_state_helper.c @@ -13,6 +13,7 @@ #include #include #include +#include /** * DOC: hdmi helpers @@ -1116,6 +1117,7 @@ void drm_hdmi_connector_debugfs_init(struct drm_connector *connector, return; create_hdmi_infoframe_files(connector, dir); + drm_scdc_debugfs_init(connector, root); } EXPORT_SYMBOL(drm_hdmi_connector_debugfs_init); From 70a9079a8288dc60098536a3660d07c521fbb114 Mon Sep 17 00:00:00 2001 From: Akash Sukhavasi Date: Mon, 10 Aug 2026 18:45:40 -0500 Subject: [PATCH 023/213] drm/panel: samsung-s6d16d0: Use mipi_dsi_*_multi(); fix minor bugs The mipi_dsi_dcs_*() functions used by this driver are deprecated in favour of their _multi() counterparts, as noted in Documentation/gpu/todo.rst. The _multi() variants record the first error in a context structure and skip subsequent calls once an error is set, removing the need to check the return value after each command. They also log failures internally, making the per-call dev_err() calls redundant. Convert prepare(), enable(), disable(), and unprepare() to use mipi_dsi_dcs_*_multi(). unprepare() previously returned an error if mipi_dsi_dcs_enter_sleep_mode() failed, skipping RESET assertion and regulator_disable(). Because drm_panel_unprepare() does not clear panel->prepared when the callback returns an error, drm_panel_prepare() would then return early on the next call, leaving the panel powered and unable to be re-initialised. The converted code always asserts RESET, disables the regulator, and returns 0. Also fix a typo in a comment ("Enabe" -> "Enable"). Signed-off-by: Akash Sukhavasi Reviewed-by: Neil Armstrong Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260810-mipi-dsi-s6d16d0-multi-v2-1-8fcdfbf9bbd2@gmail.com --- drivers/gpu/drm/panel/panel-samsung-s6d16d0.c | 55 ++++++------------- 1 file changed, 17 insertions(+), 38 deletions(-) diff --git a/drivers/gpu/drm/panel/panel-samsung-s6d16d0.c b/drivers/gpu/drm/panel/panel-samsung-s6d16d0.c index 54a65abf7e89..e734fffa45a4 100644 --- a/drivers/gpu/drm/panel/panel-samsung-s6d16d0.c +++ b/drivers/gpu/drm/panel/panel-samsung-s6d16d0.c @@ -48,14 +48,10 @@ static int s6d16d0_unprepare(struct drm_panel *panel) { struct s6d16d0 *s6 = panel_to_s6d16d0(panel); struct mipi_dsi_device *dsi = to_mipi_dsi_device(s6->dev); - int ret; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; - /* Enter sleep mode */ - ret = mipi_dsi_dcs_enter_sleep_mode(dsi); - if (ret) { - dev_err(s6->dev, "failed to enter sleep mode (%d)\n", ret); - return ret; - } + /* Enter sleep mode; error does not prevent power-off */ + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx); /* Assert RESET */ gpiod_set_value_cansleep(s6->reset_gpio, 1); @@ -68,6 +64,7 @@ static int s6d16d0_prepare(struct drm_panel *panel) { struct s6d16d0 *s6 = panel_to_s6d16d0(panel); struct mipi_dsi_device *dsi = to_mipi_dsi_device(s6->dev); + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; int ret; ret = regulator_enable(s6->supply); @@ -83,57 +80,39 @@ static int s6d16d0_prepare(struct drm_panel *panel) gpiod_set_value_cansleep(s6->reset_gpio, 0); msleep(120); - /* Enabe tearing mode: send TE (tearing effect) at VBLANK */ - ret = mipi_dsi_dcs_set_tear_on(dsi, + /* Enable tearing mode: send TE (tearing effect) at VBLANK */ + mipi_dsi_dcs_set_tear_on_multi(&dsi_ctx, MIPI_DSI_DCS_TEAR_MODE_VBLANK); - if (ret) { - dev_err(s6->dev, "failed to enable vblank TE (%d)\n", ret); - goto err_power_off; - } /* Exit sleep mode and power on */ - ret = mipi_dsi_dcs_exit_sleep_mode(dsi); - if (ret) { - dev_err(s6->dev, "failed to exit sleep mode (%d)\n", ret); - goto err_power_off; + mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx); + if (dsi_ctx.accum_err) { + gpiod_set_value_cansleep(s6->reset_gpio, 1); + regulator_disable(s6->supply); } - return 0; - -err_power_off: - gpiod_set_value_cansleep(s6->reset_gpio, 1); - regulator_disable(s6->supply); - - return ret; + return dsi_ctx.accum_err; } static int s6d16d0_enable(struct drm_panel *panel) { struct s6d16d0 *s6 = panel_to_s6d16d0(panel); struct mipi_dsi_device *dsi = to_mipi_dsi_device(s6->dev); - int ret; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; - ret = mipi_dsi_dcs_set_display_on(dsi); - if (ret) { - dev_err(s6->dev, "failed to turn display on (%d)\n", ret); - return ret; - } + mipi_dsi_dcs_set_display_on_multi(&dsi_ctx); - return 0; + return dsi_ctx.accum_err; } static int s6d16d0_disable(struct drm_panel *panel) { struct s6d16d0 *s6 = panel_to_s6d16d0(panel); struct mipi_dsi_device *dsi = to_mipi_dsi_device(s6->dev); - int ret; + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi }; - ret = mipi_dsi_dcs_set_display_off(dsi); - if (ret) { - dev_err(s6->dev, "failed to turn display off (%d)\n", ret); - return ret; - } + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx); - return 0; + return dsi_ctx.accum_err; } static int s6d16d0_get_modes(struct drm_panel *panel, From 0fb9e5faa8220db6b3268b3c8a39e31e0a963743 Mon Sep 17 00:00:00 2001 From: Youssef Samir Date: Mon, 27 Jul 2026 19:33:22 +0200 Subject: [PATCH 024/213] accel/qaic: Add missing to sahara and timesync qaic_timesync.c and sahara.c use struct mhi_device_id implicitly since commit 995832b2cebe ("Replace by more specific (c files)") removed . Explicitly include in qaic_timesync.c and sahara.c. Fixes: 995832b2cebe ("Replace by more specific (c files)") Signed-off-by: Youssef Samir Reviewed-by: Jeff Hugo Signed-off-by: Jeff Hugo Link: https://patch.msgid.link/20260727173326.2219191-2-youssef.abdulrahman@oss.qualcomm.com --- drivers/accel/qaic/qaic_timesync.c | 1 + drivers/accel/qaic/sahara.c | 1 + 2 files changed, 2 insertions(+) diff --git a/drivers/accel/qaic/qaic_timesync.c b/drivers/accel/qaic/qaic_timesync.c index 45e5f0728ebe..b5162677ccc4 100644 --- a/drivers/accel/qaic/qaic_timesync.c +++ b/drivers/accel/qaic/qaic_timesync.c @@ -2,6 +2,7 @@ /* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ +#include #include #include #include diff --git a/drivers/accel/qaic/sahara.c b/drivers/accel/qaic/sahara.c index c7c0b3eb4b65..8476be9b9148 100644 --- a/drivers/accel/qaic/sahara.c +++ b/drivers/accel/qaic/sahara.c @@ -3,6 +3,7 @@ /* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ #include +#include #include #include #include From c8e6229a81d0400a8347c70b89ff363ec08c816f Mon Sep 17 00:00:00 2001 From: Youssef Samir Date: Mon, 27 Jul 2026 19:33:23 +0200 Subject: [PATCH 025/213] accel/qaic: Add to qaic_drv.c qaic_drv.c uses mhi_device_id implicitly. Include . Fixes: c501ca23a6a3 ("accel/qaic: Add uapi and core driver file") Signed-off-by: Youssef Samir Reviewd-by: Jeff Hugo Signed-off-by: Jeff Hugo Link: https://patch.msgid.link/20260727173326.2219191-3-youssef.abdulrahman@oss.qualcomm.com --- drivers/accel/qaic/qaic_drv.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/accel/qaic/qaic_drv.c b/drivers/accel/qaic/qaic_drv.c index 0acf6cdd44f8..c1bb6db78d12 100644 --- a/drivers/accel/qaic/qaic_drv.c +++ b/drivers/accel/qaic/qaic_drv.c @@ -4,6 +4,7 @@ /* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ #include +#include #include #include #include From cef139aa5394844f61fdb81d9fb07e71ba4e2407 Mon Sep 17 00:00:00 2001 From: Youssef Samir Date: Mon, 27 Jul 2026 19:33:24 +0200 Subject: [PATCH 026/213] accel/qaic: Add to qaic_debugfs.c qaic_debugfs.c uses mhi_device_id implicitly. Include . Fixes: 5f8df5c6def6 ("accel/qaic: Add bootlog debugfs") Signed-off-by: Youssef Samir Reviewd-by: Jeff Hugo Signed-off-by: Jeff Hugo Link: https://patch.msgid.link/20260727173326.2219191-4-youssef.abdulrahman@oss.qualcomm.com --- drivers/accel/qaic/qaic_debugfs.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/accel/qaic/qaic_debugfs.c b/drivers/accel/qaic/qaic_debugfs.c index 95c78e12dd61..909a4d774d90 100644 --- a/drivers/accel/qaic/qaic_debugfs.c +++ b/drivers/accel/qaic/qaic_debugfs.c @@ -5,6 +5,7 @@ #include #include +#include #include #include #include From 7de023a5a251b6599ca767415e07a0ea79fc2dcb Mon Sep 17 00:00:00 2001 From: Youssef Samir Date: Mon, 27 Jul 2026 19:33:25 +0200 Subject: [PATCH 027/213] accel/qaic: Add to qaic_ras.c qaic_ras.c uses mhi_device_id implicitly. Include . Fixes: c11a50b170e7 ("accel/qaic: Add Reliability, Accessibility, Serviceability (RAS)") Signed-off-by: Youssef Samir Reviewd-by: Jeff Hugo Signed-off-by: Jeff Hugo Link: https://patch.msgid.link/20260727173326.2219191-5-youssef.abdulrahman@oss.qualcomm.com --- drivers/accel/qaic/qaic_ras.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/accel/qaic/qaic_ras.c b/drivers/accel/qaic/qaic_ras.c index e0205c2cda3d..236e520f708f 100644 --- a/drivers/accel/qaic/qaic_ras.c +++ b/drivers/accel/qaic/qaic_ras.c @@ -5,6 +5,7 @@ #include #include +#include #include #include From f29ab62f7ed55eae7b246bb535ede56d3b338674 Mon Sep 17 00:00:00 2001 From: Youssef Samir Date: Mon, 27 Jul 2026 19:33:26 +0200 Subject: [PATCH 028/213] accel/qaic: Add to qaic_ssr.c qaic_ssr.c uses mhi_device_id implicitly. Include . Fixes: 9675093acea0 ("accel/qaic: Implement basic SSR handling") Signed-off-by: Youssef Samir Reviewd-by: Jeff Hugo Signed-off-by: Jeff Hugo Link: https://patch.msgid.link/20260727173326.2219191-6-youssef.abdulrahman@oss.qualcomm.com --- drivers/accel/qaic/qaic_ssr.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/accel/qaic/qaic_ssr.c b/drivers/accel/qaic/qaic_ssr.c index a98928654959..418f14936f9a 100644 --- a/drivers/accel/qaic/qaic_ssr.c +++ b/drivers/accel/qaic/qaic_ssr.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include From 568d4cb5dcc5ec4e22975699b6ea67a30806884c Mon Sep 17 00:00:00 2001 From: Hao-Qun Huang Date: Wed, 8 Jul 2026 11:19:59 +0800 Subject: [PATCH 029/213] accel/qaic: reject BO sizes that overflow page count create_sgt() receives the BO size as a u64 but stores the number of pages in an int. qaic_create_bo_ioctl() page-aligns the user-supplied size without an upper bound, so DIV_ROUND_UP(size, PAGE_SIZE) can exceed INT_MAX and truncate when assigned to nr_pages. When the page count wraps to a small positive value (for example a BO larger than 16 TiB on a 4 KiB page kernel), create_sgt() succeeds after allocating only a handful of pages while the GEM object still records the full size, so the request never fails with -ENOMEM. The temporary pages array and the allocation loop both use the truncated count, so the backing SG table ends up smaller than obj->size. Later slice operations validate against obj->size and then clone ranges from this short sgt. Compute the page count as a u64 and reject sizes that do not fit in nr_pages before the narrowing conversion. Fixes: ff13be830333 ("accel/qaic: Add datapath") Assisted-by: Claude:claude-fable-5 Signed-off-by: Hao-Qun Huang Reviewed-by: Jeff Hugo Signed-off-by: Jeff Hugo Link: https://patch.msgid.link/20260708031959.966009-1-alvinhuang0603@gmail.com --- drivers/accel/qaic/qaic_data.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/accel/qaic/qaic_data.c b/drivers/accel/qaic/qaic_data.c index ef0351b6dc9b..4d55531bf1c9 100644 --- a/drivers/accel/qaic/qaic_data.c +++ b/drivers/accel/qaic/qaic_data.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -455,7 +456,12 @@ static int create_sgt(struct qaic_device *qdev, struct sg_table **sgt_out, u64 s int order; if (size) { - nr_pages = DIV_ROUND_UP(size, PAGE_SIZE); + u64 nr_pages_u64 = DIV_ROUND_UP_ULL(size, PAGE_SIZE); + + if (overflows_type(nr_pages_u64, int)) + return -E2BIG; + + nr_pages = nr_pages_u64; /* * calculate how much extra we are going to allocate, to remove * later From db684a65df75ebd5c592b331e5b1beac10950523 Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Thu, 13 Aug 2026 16:46:47 -0400 Subject: [PATCH 030/213] drm/nouveau: Fix cleanup bug in nouveau_drm_device_new() Sashiko caught this while reviewing the patches for enabling atomic by default - if we fail to allocate the DRM device pointer, we'll attempt to free the error pointer that it returns rather than the actual struct. Let's fix this while we're at it. Signed-off-by: Lyude Paul Reviewed-by: Dave Airlie Link: https://patch.msgid.link/20260813204803.2097176-2-lyude@redhat.com --- drivers/gpu/drm/nouveau/nouveau_drm.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nouveau_drm.c b/drivers/gpu/drm/nouveau/nouveau_drm.c index 4d1ad718e09b..a348115518ec 100644 --- a/drivers/gpu/drm/nouveau/nouveau_drm.c +++ b/drivers/gpu/drm/nouveau/nouveau_drm.c @@ -749,7 +749,8 @@ nouveau_drm_device_new(const struct drm_driver *drm_driver, struct device *paren drm->dev = drm_dev_alloc(drm_driver, parent); if (IS_ERR(drm->dev)) { ret = PTR_ERR(drm->dev); - goto done; + kfree(drm); + return ERR_PTR(ret); } drm->dev->dev_private = drm; From dfed5a2bb51e3498cca6e03487a4322aa6d58326 Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Thu, 13 Aug 2026 16:46:48 -0400 Subject: [PATCH 031/213] drm/nouveau: Print the nouveau.atomic parameter in nouveau_display_options() Seems like we never remembered to start printing the value for this, so let's start to aid in troubleshooting in case we run into any issues with atomic. Signed-off-by: Lyude Paul Reviewed-by: Dave Airlie Link: https://patch.msgid.link/20260813204803.2097176-3-lyude@redhat.com --- drivers/gpu/drm/nouveau/nouveau_drm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/nouveau/nouveau_drm.c b/drivers/gpu/drm/nouveau/nouveau_drm.c index a348115518ec..451009adee0d 100644 --- a/drivers/gpu/drm/nouveau/nouveau_drm.c +++ b/drivers/gpu/drm/nouveau/nouveau_drm.c @@ -1417,6 +1417,7 @@ static void nouveau_display_options(void) DRM_DEBUG_DRIVER("... debug : %s\n", nouveau_debug); DRM_DEBUG_DRIVER("... noaccel : %d\n", nouveau_noaccel); DRM_DEBUG_DRIVER("... modeset : %d\n", nouveau_modeset); + DRM_DEBUG_DRIVER("... atomic : %d\n", nouveau_atomic); DRM_DEBUG_DRIVER("... runpm : %d\n", nouveau_runtime_pm); DRM_DEBUG_DRIVER("... vram_pushbuf : %d\n", nouveau_vram_pushbuf); DRM_DEBUG_DRIVER("... hdmimhz : %d\n", nouveau_hdmimhz); From 830b2e55dfe0b120f0ebaf81e3b89d2dbee4e0e6 Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Thu, 13 Aug 2026 16:46:49 -0400 Subject: [PATCH 032/213] drm/nouveau: Fix drm_driver struct/nouveau.atomic parameter handling The way we handled the nouveau.atomic module parameter before was fairly broken, and had a number of issues: - It was only ever actually parsed in the case of PCI devices. - When nouveau.atomic was enabled, it would add the cap for atomic modesetting to the global driver_pci structure. This meant that if one GPU on a system supported atomic and another didn't, it would still get enabled for both. Looking into this exposed further silliness in the way that we actually handle the drm_driver struct. We have one global structure for platform devices, and another for PCI devices - both of which are literally identical. So before we start preparing to enable atomic modesetting by default, let's fix this. Instead of sharing driver_pci and driver_platform, we instead create driver_legacy_kms and driver_atomic_kms, each of which is identical except for the DRIVER_ATOMIC capabilities flag, and then assign either depending on the nouveau_atomic module parameter. Doing this is also preferable, as the next step for enabling atomic modesetting by default will be ensuring that we don't enable it for legacy devices that still don't support it. This requires only checking the atomic modesetting module parameter after the NVKM device is ready, as this allows us to check the GPU family that nouveau is running on. Signed-off-by: Lyude Paul Reviewed-by: Dave Airlie Link: https://patch.msgid.link/20260813204803.2097176-4-lyude@redhat.com --- drivers/gpu/drm/nouveau/nouveau_drm.c | 98 ++++++++++++++++----------- 1 file changed, 57 insertions(+), 41 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nouveau_drm.c b/drivers/gpu/drm/nouveau/nouveau_drm.c index 451009adee0d..d021a3049be1 100644 --- a/drivers/gpu/drm/nouveau/nouveau_drm.c +++ b/drivers/gpu/drm/nouveau/nouveau_drm.c @@ -111,9 +111,8 @@ MODULE_PARM_DESC(runpm, "disable (0), force enable (1), optimus only default (-1 static int nouveau_runtime_pm = -1; module_param_named(runpm, nouveau_runtime_pm, int, 0400); -static struct drm_driver driver_stub; -static struct drm_driver driver_pci; -static struct drm_driver driver_platform; +static const struct drm_driver driver_legacy_kms; +static const struct drm_driver driver_atomic_kms; #ifdef CONFIG_DEBUG_FS struct dentry *nouveau_debugfs_root; @@ -727,8 +726,7 @@ nouveau_drm_device_del(struct nouveau_drm *drm) } static struct nouveau_drm * -nouveau_drm_device_new(const struct drm_driver *drm_driver, struct device *parent, - struct nvkm_device *device) +nouveau_drm_device_new(struct device *parent, struct nvkm_device *device) { static const struct nvif_mclass mmus[] = { @@ -737,16 +735,21 @@ nouveau_drm_device_new(const struct drm_driver *drm_driver, struct device *paren { NVIF_CLASS_MMU_NV04 , -1 }, {} }; + const struct drm_driver *driver; struct nouveau_drm *drm; int ret; + if (nouveau_atomic) + driver = &driver_atomic_kms; + else + driver = &driver_legacy_kms; + drm = kzalloc_obj(*drm); if (!drm) return ERR_PTR(-ENOMEM); drm->nvkm = device; - - drm->dev = drm_dev_alloc(drm_driver, parent); + drm->dev = drm_dev_alloc(driver, parent); if (IS_ERR(drm->dev)) { ret = PTR_ERR(drm->dev); kfree(drm); @@ -874,16 +877,13 @@ static int nouveau_drm_probe(struct pci_dev *pdev, return ret; /* Remove conflicting drivers (vesafb, efifb etc). */ - ret = aperture_remove_conflicting_pci_devices(pdev, driver_pci.name); + ret = aperture_remove_conflicting_pci_devices(pdev, DRIVER_NAME); if (ret) goto fail_nvkm; pci_set_master(pdev); - if (nouveau_atomic) - driver_pci.driver_features |= DRIVER_ATOMIC; - - drm = nouveau_drm_device_new(&driver_pci, &pdev->dev, device); + drm = nouveau_drm_device_new(&pdev->dev, device); if (IS_ERR(drm)) { ret = PTR_ERR(drm); goto fail_nvkm; @@ -1360,35 +1360,54 @@ nouveau_driver_fops = { .fop_flags = FOP_UNSIGNED_OFFSET, }; -static struct drm_driver -driver_stub = { - .driver_features = DRIVER_GEM | - DRIVER_SYNCOBJ | DRIVER_SYNCOBJ_TIMELINE | - DRIVER_MODESET | - DRIVER_RENDER, - .open = nouveau_drm_open, - .postclose = nouveau_drm_postclose, - -#if defined(CONFIG_DEBUG_FS) - .debugfs_init = nouveau_drm_debugfs_init, +#ifdef CONFIG_DEBUG_FS +#define NOUVEAU_DEBUGFS_OPS .debugfs_init = nouveau_drm_debugfs_init, +#else +#define NOUVEAU_DEBUGFS_OPS #endif - .ioctls = nouveau_ioctls, - .num_ioctls = ARRAY_SIZE(nouveau_ioctls), - .fops = &nouveau_driver_fops, +#define NOUVEAU_DRIVER_OPS \ + .open = nouveau_drm_open, \ + .postclose = nouveau_drm_postclose, \ + \ + NOUVEAU_DEBUGFS_OPS \ + \ + .ioctls = nouveau_ioctls, \ + .num_ioctls = ARRAY_SIZE(nouveau_ioctls), \ + .fops = &nouveau_driver_fops, \ + \ + .gem_prime_import_sg_table = nouveau_gem_prime_import_sg_table, \ + \ + .dumb_create = nouveau_display_dumb_create, \ + .dumb_map_offset = drm_gem_ttm_dumb_map_offset, \ + \ + DRM_FBDEV_TTM_DRIVER_OPS, \ + \ + .name = DRIVER_NAME, \ + .desc = DRIVER_DESC, \ + .major = DRIVER_MAJOR, \ + .minor = DRIVER_MINOR, \ + .patchlevel = DRIVER_PATCHLEVEL - .gem_prime_import_sg_table = nouveau_gem_prime_import_sg_table, +static const struct drm_driver +driver_legacy_kms = { + .driver_features = DRIVER_GEM + | DRIVER_SYNCOBJ + | DRIVER_SYNCOBJ_TIMELINE + | DRIVER_MODESET + | DRIVER_RENDER, + NOUVEAU_DRIVER_OPS, +}; - .dumb_create = nouveau_display_dumb_create, - .dumb_map_offset = drm_gem_ttm_dumb_map_offset, - - DRM_FBDEV_TTM_DRIVER_OPS, - - .name = DRIVER_NAME, - .desc = DRIVER_DESC, - .major = DRIVER_MAJOR, - .minor = DRIVER_MINOR, - .patchlevel = DRIVER_PATCHLEVEL, +static const struct drm_driver +driver_atomic_kms = { + .driver_features = DRIVER_GEM + | DRIVER_SYNCOBJ + | DRIVER_SYNCOBJ_TIMELINE + | DRIVER_MODESET + | DRIVER_RENDER + | DRIVER_ATOMIC, + NOUVEAU_DRIVER_OPS, }; static struct pci_device_id @@ -1457,7 +1476,7 @@ nouveau_platform_device_create(const struct nvkm_device_tegra_func *func, if (err) goto err_free; - drm = nouveau_drm_device_new(&driver_platform, &pdev->dev, *pdevice); + drm = nouveau_drm_device_new(&pdev->dev, *pdevice); if (IS_ERR(drm)) { err = PTR_ERR(drm); goto err_free; @@ -1482,9 +1501,6 @@ nouveau_drm_init(void) { int ret; - driver_pci = driver_stub; - driver_platform = driver_stub; - nouveau_display_options(); if (nouveau_modeset == -1) { From 69facd615b98984e9d7ccede2fa9f3365b4c6cfa Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Thu, 13 Aug 2026 16:46:50 -0400 Subject: [PATCH 033/213] drm/nouveau/kms: Only allow enabling atomic modesetting on nv50+ Atomic modesetting support was never added for pre-nv50 chipsets, so make sure we don't allow it to be forced on. Additionally, print a small warning when it's not supported. Signed-off-by: Lyude Paul Reviewed-by: Dave Airlie Link: https://patch.msgid.link/20260813204803.2097176-5-lyude@redhat.com --- drivers/gpu/drm/nouveau/nouveau_drm.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nouveau_drm.c b/drivers/gpu/drm/nouveau/nouveau_drm.c index d021a3049be1..82a8061e6d3e 100644 --- a/drivers/gpu/drm/nouveau/nouveau_drm.c +++ b/drivers/gpu/drm/nouveau/nouveau_drm.c @@ -739,10 +739,17 @@ nouveau_drm_device_new(struct device *parent, struct nvkm_device *device) struct nouveau_drm *drm; int ret; - if (nouveau_atomic) - driver = &driver_atomic_kms; - else + if (device->card_type >= NV_50) { + if (nouveau_atomic) + driver = &driver_atomic_kms; + else + driver = &driver_legacy_kms; + } else { + if (nouveau_atomic) + dev_warn(parent, "Atomic modesetting not supported (needs nv50+)\n"); + driver = &driver_legacy_kms; + } drm = kzalloc_obj(*drm); if (!drm) From 2652aca460dcfdd0d169bbfa358aa15804dc736b Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Thu, 13 Aug 2026 16:46:51 -0400 Subject: [PATCH 034/213] drm/nouveau/kms/nv50-: Enable atomic modesetting by default Nouveau is one of the very few modern hardware drivers in the kernel that doesn't have atomic modesetting enabled by default, in part because when it was originally written by Ben there wasn't much in the way of good atomic modesetting clients to actually test things out with. Nowadays however, atomic modesetting is very much the norm - and support in userspace for non-atomic drivers is starting to bitrot a bit - leading to its own set of issues. At the same time, many of those issues are fixed by just turning on atomic in nouveau. Plus, I've been running nouveau with atomic modesetting on by default for most of the machines I work on, and I've already fixed quite a number of issues to the point where things seem quite stable (excluding a single screen flashing bug on my desktop, which I am not particularly convinced has anything to do with atomic modesetting). Now that we've protected against breaking things for chipsets where atomic isn't supported ( Reviewed-by: Dave Airlie Link: https://patch.msgid.link/20260813204803.2097176-6-lyude@redhat.com --- drivers/gpu/drm/nouveau/nouveau_drm.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nouveau_drm.c b/drivers/gpu/drm/nouveau/nouveau_drm.c index 82a8061e6d3e..b0f9fb10a74d 100644 --- a/drivers/gpu/drm/nouveau/nouveau_drm.c +++ b/drivers/gpu/drm/nouveau/nouveau_drm.c @@ -103,8 +103,9 @@ MODULE_PARM_DESC(modeset, "enable driver (default: auto, " int nouveau_modeset = -1; module_param_named(modeset, nouveau_modeset, int, 0400); -MODULE_PARM_DESC(atomic, "Expose atomic ioctl (default: disabled)"); -static int nouveau_atomic = 0; +MODULE_PARM_DESC(atomic, "Expose atomic ioctl (default: auto, " + "0 = disabled, 1 = enabled)"); +static int nouveau_atomic = -1; module_param_named(atomic, nouveau_atomic, int, 0400); MODULE_PARM_DESC(runpm, "disable (0), force enable (1), optimus only default (-1)"); @@ -745,7 +746,7 @@ nouveau_drm_device_new(struct device *parent, struct nvkm_device *device) else driver = &driver_legacy_kms; } else { - if (nouveau_atomic) + if (nouveau_atomic == 1) dev_warn(parent, "Atomic modesetting not supported (needs nv50+)\n"); driver = &driver_legacy_kms; From 94097122bfd701976bc1a62ccd434c13f3f67cde Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Fri, 14 Aug 2026 15:43:48 -0400 Subject: [PATCH 035/213] Revert "nouveau/gsp: fix suspend/resume regression on r570 firmware" This reverts commit 8302d0afeaec0bc57d951dd085e0cffe997d4d18. It turns out this looked like the right fix on some systems, but it's not - as this causes runtime PM to actually fail on many a laptop. Fixes: 8302d0afeaec ("nouveau/gsp: fix suspend/resume regression on r570 firmware") Cc: # v6.19+ Signed-off-by: Lyude Paul Reviewed-by: Dave Airlie Link: https://patch.msgid.link/20260814194542.781955-2-lyude@redhat.com --- drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/fbsr.c | 2 +- drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c | 2 +- drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c | 8 ++++---- drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/fbsr.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/fbsr.c index f128330f30d7..40bf83ea33ac 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/fbsr.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/fbsr.c @@ -208,7 +208,7 @@ r535_fbsr_resume(struct nvkm_gsp *gsp) } static int -r535_fbsr_suspend(struct nvkm_gsp *gsp, bool runtime) +r535_fbsr_suspend(struct nvkm_gsp *gsp) { struct nvkm_subdev *subdev = &gsp->subdev; struct nvkm_device *device = subdev->device; diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c index f544afa12b6b..4a3b771ded25 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c @@ -1749,7 +1749,7 @@ r535_gsp_fini(struct nvkm_gsp *gsp, enum nvkm_suspend_state suspend) sr->sysmemAddrOfSuspendResumeData = gsp->sr.radix3.lvl0.addr; sr->sizeOfSuspendResumeData = len; - ret = rm->api->fbsr->suspend(gsp, suspend == NVKM_RUNTIME_SUSPEND); + ret = rm->api->fbsr->suspend(gsp); if (ret) { nvkm_gsp_mem_dtor(&gsp->sr.meta); nvkm_gsp_radix3_dtor(gsp, &gsp->sr.radix3); diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c index 8ef8b4f65588..2945d5b4e570 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c @@ -62,7 +62,7 @@ r570_fbsr_resume(struct nvkm_gsp *gsp) } static int -r570_fbsr_init(struct nvkm_gsp *gsp, struct sg_table *sgt, u64 size, bool runtime) +r570_fbsr_init(struct nvkm_gsp *gsp, struct sg_table *sgt, u64 size) { NV2080_CTRL_INTERNAL_FBSR_INIT_PARAMS *ctrl; struct nvkm_gsp_object memlist; @@ -81,7 +81,7 @@ r570_fbsr_init(struct nvkm_gsp *gsp, struct sg_table *sgt, u64 size, bool runtim ctrl->hClient = gsp->internal.client.object.handle; ctrl->hSysMem = memlist.handle; ctrl->sysmemAddrOfSuspendResumeData = gsp->sr.meta.addr; - ctrl->bEnteringGcoffState = runtime ? 1 : 0; + ctrl->bEnteringGcoffState = 1; ret = nvkm_gsp_rm_ctrl_wr(&gsp->internal.device.subdevice, ctrl); if (ret) @@ -92,7 +92,7 @@ r570_fbsr_init(struct nvkm_gsp *gsp, struct sg_table *sgt, u64 size, bool runtim } static int -r570_fbsr_suspend(struct nvkm_gsp *gsp, bool runtime) +r570_fbsr_suspend(struct nvkm_gsp *gsp) { struct nvkm_subdev *subdev = &gsp->subdev; struct nvkm_device *device = subdev->device; @@ -133,7 +133,7 @@ r570_fbsr_suspend(struct nvkm_gsp *gsp, bool runtime) return ret; /* Initialise FBSR on RM. */ - ret = r570_fbsr_init(gsp, &gsp->sr.fbsr, size, runtime); + ret = r570_fbsr_init(gsp, &gsp->sr.fbsr, size); if (ret) { nvkm_gsp_sg_free(device, &gsp->sr.fbsr); return ret; diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h index a9af94adf9ef..0fb0e67406c6 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h @@ -78,7 +78,7 @@ struct nvkm_rm_api { } *device; const struct nvkm_rm_api_fbsr { - int (*suspend)(struct nvkm_gsp *, bool runtime); + int (*suspend)(struct nvkm_gsp *); void (*resume)(struct nvkm_gsp *); } *fbsr; From c7abe771e013848970421e5ca29c6b2f05c31965 Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Fri, 14 Aug 2026 15:43:49 -0400 Subject: [PATCH 036/213] drm/nouveau/gsp/r570: Set GcOff = 0 in fbsr Previously, it looked as if we were able to fix suspend/resume on some desktops by setting Gcoff based on whether or not we were entering runtime PM. This was a mistake though - the only time suspend/resume would end up actually working was if Gcoff = 0. It seems like it's likely the main reason for this is the FBSR GcOff argument actually controls GSP's behavior with regards to which buffers it decides to save across suspend/resume. When GcOff = 1, RM reserved regions are saved unless they are marked as LOST_ON_SUSPEND, and RM channel-context and kernel-client buffers are also saved -including- when they are LOST_ON_SUSPEND. This means with GcOff = 1, we end up having GSP save and restore buffers that actually need to be reinitialized on resume - causing the failures we're setting. Thanks to John Hubbard from Nvidia for providing some background on what these options do in the GSP firmware do! Signed-off-by: Lyude Paul Fixes: 53dac0623853 ("drm/nouveau/gsp: add support for 570.144") Cc: # v6.16+ Reviewed-by: Dave Airlie Link: https://patch.msgid.link/20260814194542.781955-3-lyude@redhat.com --- drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c index 2945d5b4e570..af5aa5065c3d 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c @@ -81,7 +81,7 @@ r570_fbsr_init(struct nvkm_gsp *gsp, struct sg_table *sgt, u64 size) ctrl->hClient = gsp->internal.client.object.handle; ctrl->hSysMem = memlist.handle; ctrl->sysmemAddrOfSuspendResumeData = gsp->sr.meta.addr; - ctrl->bEnteringGcoffState = 1; + ctrl->bEnteringGcoffState = 0; ret = nvkm_gsp_rm_ctrl_wr(&gsp->internal.device.subdevice, ctrl); if (ret) From ca57629b3eb912c77bc4357178a2130ea6c2d6df Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Fri, 14 Aug 2026 15:43:50 -0400 Subject: [PATCH 037/213] drm/nouveau/gsp/r570: Enable S/R Display workaround in GSP There's two flags that we've never been setting when asking GSP to suspend the GPU, which OpenRM does set: GPU_STATE_FLAGS_PRESERVING GPU_STATE_FLAGS_PM_TRANSITION These flags aren't -supposed- to do much in GSP, they're mostly used by OpenRM itself for state tracking. The only thing they do from GSP's side is control whether or not a single display related workaround is applied during suspend. But as it turns out, that single workaround is actually quite crucial for getting runtime PM working with nouveau - and without it set we end up seeing a lot more failures with runtime PM resume. So, let's start setting it. Signed-off-by: Lyude Paul Fixes: 53dac0623853 ("drm/nouveau/gsp: add support for 570.144") Cc: # v6.16+ Reviewed-by: Dave Airlie Link: https://patch.msgid.link/20260814194542.781955-4-lyude@redhat.com --- drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c | 3 ++- .../gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/gsp.h | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c index 996941c668ba..3e391646d8f7 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c @@ -198,7 +198,8 @@ r570_gsp_set_rmargs(struct nvkm_gsp *gsp, bool resume) args->srInitArguments.bInPMTransition = 0; } else { args->srInitArguments.oldLevel = NV2080_CTRL_GPU_SET_POWER_STATE_GPU_LEVEL_3; - args->srInitArguments.flags = 0; + args->srInitArguments.flags = + GPU_STATE_FLAGS_PRESERVING | GPU_STATE_FLAGS_PM_TRANSITION; args->srInitArguments.bInPMTransition = 1; } diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/gsp.h b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/gsp.h index b6075021e74f..c458569af9d7 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/gsp.h +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/gsp.h @@ -523,6 +523,14 @@ typedef struct #define NV2080_CTRL_GPU_SET_POWER_STATE_GPU_LEVEL_3 (0x00000003U) +#define GPU_STATE_FLAGS_PRESERVING BIT(0) // GPU state is preserved +#define GPU_STATE_FLAGS_VGA_TRANSITION BIT(1) // To be used with GPU_STATE_FLAGS_PRESERVING. +#define GPU_STATE_FLAGS_PM_TRANSITION BIT(2) // To be used with GPU_STATE_FLAGS_PRESERVING. +#define GPU_STATE_FLAGS_PM_SUSPEND BIT(3) +#define GPU_STATE_FLAGS_PM_HIBERNATE BIT(4) +#define GPU_STATE_FLAGS_GC6_TRANSITION BIT(5) // To be used with GPU_STATE_FLAGS_PRESERVING. +#define GPU_STATE_FLAGS_FAST_UNLOAD BIT(6) // Used during windows restart, skips stateDestroy steps + typedef struct { // Magic for verification by secure ucode From 09b47186a4164f3aaa3591313f80794443117342 Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Fri, 14 Aug 2026 15:43:51 -0400 Subject: [PATCH 038/213] drm/nouveau/gsp: Increase delay for magic sleep in r535_gsp_fini() As it turns out, Turing isn't the only architecture that needs this. On this Dell Precision 7780 with an AD103 GPU, along with pretty much every other laptop I tested, runtime PM is still somewhat unreliable. At first glance it seems as if it's fixed, but lowering the autosuspend delay to 500ms and then doing a stress test of suspend/resume cycles on the GPU ends up causing everything to start timing out. After quite a lot of digging, I eventually landed back on this magic timeout in r535_gsp_fini(). As it turns out, increasing the timeout ends up fixing the runtime PM issues as far as I can tell, even during intense stress testing. Unfortunately after spending quite a bit of time trying to dig through OpenRM to figure out what this magic sleep is actually doing, I've also come up short with any reasonable explanation. In lieu of that, I'm going to include the observations I did make while trying to figure this out in hopes someone eventually does figure this out: * The magic sleep has to occur after fbsr is initialized. Performing it at any time before that doesn't appear to work. * In situations where runtime PM starts getting flaky, some rather interesting visual effects end up happening on occasion before the GPU fully falls over. In particular, squares that look like the result of an incomplete blitting operation to a tiled buffer end up showing up on applications like vkcube. Interestingly enough, they remain in precisely the same place between runtime PM cycles until the GPU falls over - even when restarting vkcube multiple times, and even when vkcube is actively updating the screen. Even more interestingly, they're not limited to a specific framebuffer - you can see the squares changing as the cube rotates around. We cannot however, say that this is likely to be a incomplete fbsr operation. The magic sleep happens before fbsr is actually saved (which happens on the GSP unload), so it's something else. * During a short bit of testing with a desktop that I have, the magic sleep seemed to make no difference to whether or not suspend/resume works. It seems to generally work almost always. So we can assume this is likely exclusive to runtime PM, not S3. As well, here's a list of the things I tried before settling on the magic sleep: * Hooking up NV2080_CTRL_CMD_INTERNAL_GCX_ENTRY_PREREQUISITE and then blocking runtime PM until OpenRM signals that GC6/GCOFF is ready appears to make no difference. * Hooking up some (maybe not all, unsure about that part) bits of comptag saving including: * Fetching static memsys information from GSP * Adding the size of the comptag storage to the fbsr data * Adding a GA103+ workaround for disabling raw compression mode during fbsr (it doesn't seem like it applies for any systems I tried it on anyhow) * Setting bPreserveVideoMemoryAllocations=1 in GspSystemInfo So, until we can figure this out properly - just sleep for longer. Signed-off-by: Lyude Paul Fixes: 53dac0623853 ("drm/nouveau/gsp: add support for 570.144") Cc: # v6.16+ Reviewed-by: Dave Airlie Link: https://patch.msgid.link/20260814194542.781955-5-lyude@redhat.com --- drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c index 4a3b771ded25..94925f1590ea 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c @@ -1761,8 +1761,12 @@ r535_gsp_fini(struct nvkm_gsp *gsp, enum nvkm_suspend_state suspend) * TODO: Debug the GSP firmware / RPC handling to find out why * without this Turing (but none of the other architectures) * ends up resetting all channels after resume. + * Additionally, runtime suspend on other architectures quickly + * becomes unreliable without this sleep. If you're experiencing + * issues with runtime suspend, try bumping this delay up and + * sending a patch if it fixes your GPU. */ - msleep(50); + msleep(200); } ret = r535_gsp_rpc_unloading_guest_driver(gsp, suspend); From 5d2584e2ffaabf2d420edcb2d120209e151f8dce Mon Sep 17 00:00:00 2001 From: Osama Abdelkader Date: Thu, 21 May 2026 23:25:28 +0200 Subject: [PATCH 039/213] drm/bridge: chipone-icn6211: use devm_mipi_dsi_attach Follow up on the previous devm_drm_bridge_add() conversion by converting the remaining manual cleanup in chipone-icn6211 to the managed helper. Suggested-by: Luca Ceresoli Signed-off-by: Osama Abdelkader Reviewed-by: Luca Ceresoli Link: https://patch.msgid.link/20260521212529.151493-1-osama.abdelkader@gmail.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/chipone-icn6211.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/drivers/gpu/drm/bridge/chipone-icn6211.c b/drivers/gpu/drm/bridge/chipone-icn6211.c index 0f6173c61502..4ec50fb63aba 100644 --- a/drivers/gpu/drm/bridge/chipone-icn6211.c +++ b/drivers/gpu/drm/bridge/chipone-icn6211.c @@ -531,7 +531,7 @@ static int chipone_dsi_attach(struct chipone *icn) dsi->hs_rate = 500000000; dsi->lp_rate = 16000000; - ret = mipi_dsi_attach(dsi); + ret = devm_mipi_dsi_attach(dev, dsi); if (ret < 0) dev_err(icn->dev, "failed to attach dsi\n"); @@ -763,11 +763,6 @@ static int chipone_i2c_probe(struct i2c_client *client) return chipone_dsi_host_attach(icn); } -static void chipone_dsi_remove(struct mipi_dsi_device *dsi) -{ - mipi_dsi_detach(dsi); -} - static const struct of_device_id chipone_of_match[] = { { .compatible = "chipone,icn6211", }, { /* sentinel */ } @@ -776,7 +771,6 @@ MODULE_DEVICE_TABLE(of, chipone_of_match); static struct mipi_dsi_driver chipone_dsi_driver = { .probe = chipone_dsi_probe, - .remove = chipone_dsi_remove, .driver = { .name = "chipone-icn6211", .of_match_table = chipone_of_match, From 0e118b936dc5904cf0d9859882a40c96057b083d Mon Sep 17 00:00:00 2001 From: Philipp Stanner Date: Thu, 13 Aug 2026 11:45:40 +0200 Subject: [PATCH 040/213] drm/sched: Lock drm_sched_entity_is_idle() drm_sched_entity_is_idle() contains a badly documented memory barrier and an invalid lockless access to entity->stopped. Moreover, it also accesses entity->list which is at all other places protected by entity->lock. This function is in no way performance critical, so it is safer, more readable and more maintainable to take the spinlock. Add locking to drm_sched_entity_is_idle(). Reviewed-by: Tvrtko Ursulin Signed-off-by: Philipp Stanner Link: https://patch.msgid.link/20260813094539.2407911-2-phasta@kernel.org --- drivers/gpu/drm/scheduler/sched_entity.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/scheduler/sched_entity.c b/drivers/gpu/drm/scheduler/sched_entity.c index c51101ec70c1..2d941b2b2861 100644 --- a/drivers/gpu/drm/scheduler/sched_entity.c +++ b/drivers/gpu/drm/scheduler/sched_entity.c @@ -179,14 +179,18 @@ EXPORT_SYMBOL(drm_sched_entity_modify_sched); static bool drm_sched_entity_is_idle(struct drm_sched_entity *entity) { - rmb(); /* for list_empty to work without lock */ + bool idle = false; + + spin_lock(&entity->lock); if (list_empty(&entity->list) || spsc_queue_count(&entity->job_queue) == 0 || entity->stopped) - return true; + idle = true; - return false; + spin_unlock(&entity->lock); + + return idle; } /** From 38125d38d850bd2fdd5d7840c3bd52d9d208995b Mon Sep 17 00:00:00 2001 From: Zhenhao Wan Date: Thu, 13 Aug 2026 21:14:08 +0800 Subject: [PATCH 041/213] drm/nouveau: reject zero-size notifier object allocation nouveau_abi16_ioctl_notifierobj_alloc() passes the userspace-controlled info->size to nvkm_mm_head() as both size_max and size_min without a lower bound. A zero size satisfies the allocator's "e - s < size_min" gate (size_min == 0 makes the unsigned comparison inert) and yields a zero-length node, after which args.limit = ntfy->node->offset + ntfy->node->length - 1; underflows (offset 0, length 0 -> 0xffffffff) into an oversized ~4 GiB DMA window. The dma object constructor only rejects start > limit, so the validly ordered [base, base + 0xffffffff] range passes and is programmed into the GPU DMA context. The ioctl is DRM_RENDER_ALLOW, so any render node client can trigger this on pre-Fermi hardware. Reject a zero-size request before allocating anything. Fixes: ebb945a94bba ("drm/nouveau: port all engines to new engine module format") Reported-by: Yuhao Jiang Assisted-by: Claude:claude-opus-5 Cc: stable@vger.kernel.org Signed-off-by: Zhenhao Wan Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260813-nouveau-abi16-notifierobj-zero-size-v1-1-20dee38077cb@gmail.com --- drivers/gpu/drm/nouveau/nouveau_abi16.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/gpu/drm/nouveau/nouveau_abi16.c b/drivers/gpu/drm/nouveau/nouveau_abi16.c index 4542d5f4ded8..e907c1d07887 100644 --- a/drivers/gpu/drm/nouveau/nouveau_abi16.c +++ b/drivers/gpu/drm/nouveau/nouveau_abi16.c @@ -666,6 +666,10 @@ nouveau_abi16_ioctl_notifierobj_alloc(ABI16_IOCTL_ARGS) if (unlikely(device->info.family >= NV_DEVICE_INFO_V0_FERMI)) return nouveau_abi16_put(abi16, -EINVAL); + /* zero size yields a zero-length node, underflowing args.limit */ + if (unlikely(!info->size)) + return nouveau_abi16_put(abi16, -EINVAL); + chan = nouveau_abi16_chan(abi16, info->channel); if (!chan) return nouveau_abi16_put(abi16, -ENOENT); From b46ddaf0823405f81e681eb26ec98508f3eb9a07 Mon Sep 17 00:00:00 2001 From: Zhenhao Wan Date: Fri, 14 Aug 2026 18:00:05 +0800 Subject: [PATCH 042/213] drm/nouveau/svm: drain fault handler before freeing svmm The SVM fault handler nouveau_svm_fault() looks up each fault's nouveau_svmm under svm->mutex, caches it in the fault array, then drops svm->mutex and dereferences the svmm across blocking faults (mmu_interval_notifier_insert(), hmm_range_fault()). nouveau_svmm is not refcounted. On DRM file close nouveau_svmm_fini() -> mmu_notifier_put() frees it asynchronously via call_srcu() without waiting for the handler, and mmget_not_zero() pins the mm, not the svmm. flush_work() on the fault buffer otherwise runs only at device removal and suspend, never on close, so a fault handled concurrently with a close can dereference a freed svmm. The instance is already unlinked by nouveau_svmm_part() earlier in the same close, so no new fault can resolve to it. Drain the handler in nouveau_svmm_fini() before the free, guarding the flush with a NULL check on drm->svm: on device teardown nouveau_svm_fini() runs first and frees drm->svm (NULL) after blocking the notify and flushing the buffer, so the handler is already drained; on per-client close drm->svm is alive and the flush runs. Fixes: eeaf06ac1a55 ("drm/nouveau/svm: initial support for shared virtual memory") Reported-by: Yuhao Jiang Assisted-by: Claude:claude-opus-5 Cc: stable@vger.kernel.org Signed-off-by: Zhenhao Wan Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260814-nouveau-svm-svmm-uaf-v2-1-8e6590519f07@gmail.com --- drivers/gpu/drm/nouveau/nouveau_svm.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/gpu/drm/nouveau/nouveau_svm.c b/drivers/gpu/drm/nouveau/nouveau_svm.c index dcc92131488e..a87406d3da30 100644 --- a/drivers/gpu/drm/nouveau/nouveau_svm.c +++ b/drivers/gpu/drm/nouveau/nouveau_svm.c @@ -304,6 +304,19 @@ nouveau_svmm_fini(struct nouveau_svmm **psvmm) { struct nouveau_svmm *svmm = *psvmm; if (svmm) { + struct nouveau_svm *svm = svmm->vmm->cli->drm->svm; + + /* The fault handler caches svmm pointers under svm->mutex and + * dereferences them after dropping it, across blocking faults. + * The instance is already unlinked (nouveau_svmm_part), so drain + * the handler before the free to release any in-flight reference. + * + * On device teardown nouveau_svm_fini() ran first and freed + * drm->svm (NULL) after draining the buffer; nothing to flush. + */ + if (svm) + flush_work(&svm->buffer[0].work); + mutex_lock(&svmm->mutex); svmm->vmm = NULL; mutex_unlock(&svmm->mutex); From 354d47fb3f35461b7f942831c1b2a7105e92b57f Mon Sep 17 00:00:00 2001 From: Marek Czernohous Date: Sun, 16 Aug 2026 15:09:04 +0200 Subject: [PATCH 043/213] drm/nouveau/pci: use config-space MSI rearm on MCP79/MCP7A (NVAC) NVAC (MCP79/MCP7A) uses g94_pci_func, whose .msi_rearm is nv40_pci_msi_rearm(): a re-arm write through the MMIO mirror of PCI config space. On this IGP that path is unreliable; when a re-arm is missed the interrupt line stays dead, command submission times out and the GPU appears hung until reboot. On an Apple Mac mini (early 2009, MCP79, boot0 0x0ac080b1) this showed as sporadic fifo timeouts and GPU hangs under load unless MSI was disabled via config=NvMSI=0. Give NVAC its own pci func that re-arms through real PCI config space (nv46_pci_msi_rearm) instead. This follows existing precedent: nv46.c documents the MMIO-mirror re-arm as broken on several related parts, and commit 5112abc6a433 ("drm/nouveau/pci/g92: Fix rearm") fixed g92 the same way while moving the remaining chipsets, NVAC included, into the newly added shared g94 table, where NVAC stayed on the MMIO path. This change completes that fix for NVAC. The sibling IGP NVAA (MCP77/MCP78) has MSI disabled entirely as "reported broken" in nvkm_pci_new_(); NVAC works correctly once the re-arm goes through config space, so disabling MSI is not necessary. Only NVAC is switched: that is the hardware this has been validated on. The other users of g94_pci_func (G94/G96/G98/GT2xx and the MCP77/MCP89 IGPs) keep their current behavior; MCP77 and MCP89 plausibly want the same treatment but were not tested. Tested on the Mac mini as a daily driver for two months with MSI enabled and zero fifo timeouts. Independently confirmed stable on an iMac9,1 (MCP79) running 6.12.90 with the v1 form of this change (the same one-line functional switch, applied to that kernel's g94 implementation). Fixes: 5112abc6a433 ("drm/nouveau/pci/g92: Fix rearm") Cc: # v6.16+ Tested-by: Fab Stz Assisted-by: Claude:claude-opus-4-7 Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Marek Czernohous Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/178688574401.522643.12812428444459119913@gmail.com --- .../gpu/drm/nouveau/include/nvkm/subdev/pci.h | 1 + .../gpu/drm/nouveau/nvkm/engine/device/base.c | 2 +- .../gpu/drm/nouveau/nvkm/subdev/pci/Kbuild | 1 + .../gpu/drm/nouveau/nvkm/subdev/pci/mcp79.c | 35 +++++++++++++++++++ 4 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/nouveau/nvkm/subdev/pci/mcp79.c diff --git a/drivers/gpu/drm/nouveau/include/nvkm/subdev/pci.h b/drivers/gpu/drm/nouveau/include/nvkm/subdev/pci.h index 112b674ed9c8..0172e0d200f8 100644 --- a/drivers/gpu/drm/nouveau/include/nvkm/subdev/pci.h +++ b/drivers/gpu/drm/nouveau/include/nvkm/subdev/pci.h @@ -46,6 +46,7 @@ int nv4c_pci_new(struct nvkm_device *, enum nvkm_subdev_type, int inst, struct n int g84_pci_new(struct nvkm_device *, enum nvkm_subdev_type, int inst, struct nvkm_pci **); int g92_pci_new(struct nvkm_device *, enum nvkm_subdev_type, int inst, struct nvkm_pci **); int g94_pci_new(struct nvkm_device *, enum nvkm_subdev_type, int inst, struct nvkm_pci **); +int mcp79_pci_new(struct nvkm_device *, enum nvkm_subdev_type, int inst, struct nvkm_pci **); int gf100_pci_new(struct nvkm_device *, enum nvkm_subdev_type, int inst, struct nvkm_pci **); int gf106_pci_new(struct nvkm_device *, enum nvkm_subdev_type, int inst, struct nvkm_pci **); int gk104_pci_new(struct nvkm_device *, enum nvkm_subdev_type, int inst, struct nvkm_pci **); diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/device/base.c b/drivers/gpu/drm/nouveau/nvkm/engine/device/base.c index ea62dc97f118..f7e02a16e5bc 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/device/base.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/device/base.c @@ -1237,7 +1237,7 @@ nvac_chipset = { .mc = { 0x00000001, g98_mc_new }, .mmu = { 0x00000001, mcp77_mmu_new }, .mxm = { 0x00000001, nv50_mxm_new }, - .pci = { 0x00000001, g94_pci_new }, + .pci = { 0x00000001, mcp79_pci_new }, .therm = { 0x00000001, g84_therm_new }, .timer = { 0x00000001, nv41_timer_new }, .volt = { 0x00000001, nv40_volt_new }, diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/pci/Kbuild b/drivers/gpu/drm/nouveau/nvkm/subdev/pci/Kbuild index a14ea0f7b1c8..90f03baeb22a 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/pci/Kbuild +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/pci/Kbuild @@ -9,6 +9,7 @@ nvkm-y += nvkm/subdev/pci/nv4c.o nvkm-y += nvkm/subdev/pci/g84.o nvkm-y += nvkm/subdev/pci/g92.o nvkm-y += nvkm/subdev/pci/g94.o +nvkm-y += nvkm/subdev/pci/mcp79.o nvkm-y += nvkm/subdev/pci/gf100.o nvkm-y += nvkm/subdev/pci/gf106.o nvkm-y += nvkm/subdev/pci/gk104.o diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/pci/mcp79.c b/drivers/gpu/drm/nouveau/nvkm/subdev/pci/mcp79.c new file mode 100644 index 000000000000..e2ae242f1aa7 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/pci/mcp79.c @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright 2026 Marek Czernohous + * + * MCP79/MCP7A (NVAC): like g94, but MSI re-arm goes through real PCI + * config space. The MMIO-mirror re-arm is unreliable on this IGP and a + * missed re-arm kills the interrupt line (see the nv46 comment; g92 + * already re-arms through config space for the same reason). + */ +#include "priv.h" + +static const struct nvkm_pci_func +mcp79_pci_func = { + .cfg = { .addr = 0x088000, .size = 0x1000 }, + + .init = g84_pci_init, + .msi_rearm = nv46_pci_msi_rearm, + + .pcie.init = g84_pcie_init, + .pcie.set_link = g84_pcie_set_link, + + .pcie.max_speed = g84_pcie_max_speed, + .pcie.cur_speed = g84_pcie_cur_speed, + + .pcie.set_version = g84_pcie_set_version, + .pcie.version = g84_pcie_version, + .pcie.version_supported = g92_pcie_version_supported, +}; + +int +mcp79_pci_new(struct nvkm_device *device, enum nvkm_subdev_type type, int inst, + struct nvkm_pci **ppci) +{ + return nvkm_pci_new_(&mcp79_pci_func, device, type, inst, ppci); +} From b90caca5151c3b1fb0b291fd13e2bb1a784aa9f5 Mon Sep 17 00:00:00 2001 From: Suraj Kandpal Date: Tue, 4 Aug 2026 11:18:06 +0530 Subject: [PATCH 044/213] drm/dp: Add EDP_20 rev definition Add EDP_20 DPCD revision definition. Signed-off-by: Suraj Kandpal Reviewed-by: Ankit Nautiyal Link: https://patch.msgid.link/20260804054806.1110078-1-suraj.kandpal@intel.com --- include/drm/display/drm_dp.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/drm/display/drm_dp.h b/include/drm/display/drm_dp.h index 7154216e0aff..0e1ef59107b3 100644 --- a/include/drm/display/drm_dp.h +++ b/include/drm/display/drm_dp.h @@ -1035,6 +1035,7 @@ # define DP_EDP_14a 0x04 /* eDP 1.4a */ # define DP_EDP_14b 0x05 /* eDP 1.4b */ # define DP_EDP_15 0x06 /* eDP 1.5 */ +# define DP_EDP_20 0x07 /* eDP 2.0 */ #define DP_EDP_GENERAL_CAP_1 0x701 # define DP_EDP_TCON_BACKLIGHT_ADJUSTMENT_CAP (1 << 0) From 6575bb5d0d3c7db0d7d293428ecc07cb38e46ecc Mon Sep 17 00:00:00 2001 From: Icenowy Zheng Date: Wed, 12 Aug 2026 23:48:29 +0800 Subject: [PATCH 045/213] drm: verisilicon: fix cursor offset field update The 4th argument of regmap_update_bits() expects a pre-shifted register value instead of the field value when it's used to set a single field, however I forgot this when writing the code setting the cursor X/Y offset fields, thus the write operation doesn't happen at all (because of the non-shifted values being masked by regmap_update_bits()). Fix this problem so the cursor is correctly clipped. The framebuffer address is also clipped so the cursor offset problem was previously not discovered, and the framebuffer address deserves another fix for proper cursor behavior when it's clipped. Fixes: 8c4ae2189125 ("drm: verisilicon: add support for cursor planes") Signed-off-by: Icenowy Zheng Reviewed-by: Chen-Yu Tsai Link: https://patch.msgid.link/20260812154829.671777-1-zhengxingda@iscas.ac.cn Signed-off-by: Chen-Yu Tsai --- drivers/gpu/drm/verisilicon/vs_cursor_plane.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/verisilicon/vs_cursor_plane.c b/drivers/gpu/drm/verisilicon/vs_cursor_plane.c index fa4f601dd0c8..4ce311ae4882 100644 --- a/drivers/gpu/drm/verisilicon/vs_cursor_plane.c +++ b/drivers/gpu/drm/verisilicon/vs_cursor_plane.c @@ -199,7 +199,7 @@ static void vs_cursor_plane_atomic_update(struct drm_plane *plane, } else { regmap_update_bits(dc->regs, VSDC_CURSOR_CONFIG(output), VSDC_CURSOR_CONFIG_X_OFF_MASK, - -state->crtc_x); + VSDC_CURSOR_CONFIG_X_OFF(-state->crtc_x)); regmap_update_bits(dc->regs, VSDC_CURSOR_LOCATION(output), VSDC_CURSOR_LOCATION_X_MASK, 0); } @@ -213,7 +213,7 @@ static void vs_cursor_plane_atomic_update(struct drm_plane *plane, } else { regmap_update_bits(dc->regs, VSDC_CURSOR_CONFIG(output), VSDC_CURSOR_CONFIG_Y_OFF_MASK, - -state->crtc_y); + VSDC_CURSOR_CONFIG_Y_OFF(-state->crtc_y)); regmap_update_bits(dc->regs, VSDC_CURSOR_LOCATION(output), VSDC_CURSOR_LOCATION_Y_MASK, 0); } From 063019ac51afffda4ab3d658701b086caa0c8e9e Mon Sep 17 00:00:00 2001 From: Chen-Yu Tsai Date: Thu, 13 Aug 2026 14:51:26 +0800 Subject: [PATCH 046/213] drm/verisilicon: Switch to drm_fb_dma_get_addr() for framebuffer addresses The verisilicon driver has a custom framebuffer address calculating helper that the common drm_fb_dma_get_addr() can substitute. Differences from drm_fb_dma_get_addr(): - Uses drm_format_info_min_pitch() to calculate the horizontal offset; however the driver does not support any of the blocked formats, so this just ends up being the same as in drm_fb_dma_get_addr(): "cpp[plane] * y" - Uses clipped source coordinates instead of non-clipped coordinates as in drm_fb_dma_get_addr(); For the primary plane this doesn't matter, since the primary plane must match the output, i.e. it cannot be clipped. Also this driver doesn't support scaling. For the cursor plane this seems wrong, as the clipping seems to be done by the hardware, and thus the buffer address should be unclipped. As such, it should be fine to use the common helper and drop the custom code. Tested-by: Icenowy Zheng Reviewed-by: Icenowy Zheng Fixes: 8c4ae2189125 ("drm: verisilicon: add support for cursor planes") Link: https://patch.msgid.link/20260813065127.341349-1-wenst@chromium.org Signed-off-by: Chen-Yu Tsai --- drivers/gpu/drm/verisilicon/vs_cursor_plane.c | 4 +++- drivers/gpu/drm/verisilicon/vs_plane.c | 20 ------------------- drivers/gpu/drm/verisilicon/vs_plane.h | 2 -- .../gpu/drm/verisilicon/vs_primary_plane.c | 7 ++++++- 4 files changed, 9 insertions(+), 24 deletions(-) diff --git a/drivers/gpu/drm/verisilicon/vs_cursor_plane.c b/drivers/gpu/drm/verisilicon/vs_cursor_plane.c index 4ce311ae4882..05e7a04e614c 100644 --- a/drivers/gpu/drm/verisilicon/vs_cursor_plane.c +++ b/drivers/gpu/drm/verisilicon/vs_cursor_plane.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -176,7 +177,8 @@ static void vs_cursor_plane_atomic_update(struct drm_plane *plane, break; } - dma_addr = vs_fb_get_dma_addr(fb, &state->src); + /* hardware handles clipping as seen below */ + dma_addr = drm_fb_dma_get_gem_addr(fb, state, 0); regmap_write(dc->regs, VSDC_CURSOR_ADDRESS(output), lower_32_bits(dma_addr)); diff --git a/drivers/gpu/drm/verisilicon/vs_plane.c b/drivers/gpu/drm/verisilicon/vs_plane.c index d81f7b8f4c65..38b8b536eccb 100644 --- a/drivers/gpu/drm/verisilicon/vs_plane.c +++ b/drivers/gpu/drm/verisilicon/vs_plane.c @@ -107,26 +107,6 @@ int drm_format_to_vs_format(u32 drm_format, struct vs_format *vs_format) return 0; } -dma_addr_t vs_fb_get_dma_addr(struct drm_framebuffer *fb, - const struct drm_rect *src_rect) -{ - struct drm_gem_dma_object *gem; - dma_addr_t dma_addr; - - /* Get the physical address of the buffer in memory */ - gem = drm_fb_dma_get_gem_obj(fb, 0); - - /* Compute the start of the displayed memory */ - dma_addr = gem->dma_addr + fb->offsets[0]; - - /* Fixup framebuffer address for src coordinates */ - dma_addr += drm_format_info_min_pitch(fb->format, 0, - src_rect->x1 >> 16); - dma_addr += (src_rect->y1 >> 16) * fb->pitches[0]; - - return dma_addr; -} - struct drm_plane_state *vs_plane_duplicate_state(struct drm_plane *plane) { struct vs_plane_state *vs_state, *vs_state_old; diff --git a/drivers/gpu/drm/verisilicon/vs_plane.h b/drivers/gpu/drm/verisilicon/vs_plane.h index 32ed1d70f3b8..e4f82f1fc6e9 100644 --- a/drivers/gpu/drm/verisilicon/vs_plane.h +++ b/drivers/gpu/drm/verisilicon/vs_plane.h @@ -75,8 +75,6 @@ static inline struct vs_plane_state *to_vs_plane_state(struct drm_plane_state *s } int drm_format_to_vs_format(u32 drm_format, struct vs_format *vs_format); -dma_addr_t vs_fb_get_dma_addr(struct drm_framebuffer *fb, - const struct drm_rect *src_rect); struct drm_plane_state *vs_plane_duplicate_state(struct drm_plane *plane); void vs_plane_destroy_state(struct drm_plane *plane, diff --git a/drivers/gpu/drm/verisilicon/vs_primary_plane.c b/drivers/gpu/drm/verisilicon/vs_primary_plane.c index 1f2be41ae496..2750016a7f2c 100644 --- a/drivers/gpu/drm/verisilicon/vs_primary_plane.c +++ b/drivers/gpu/drm/verisilicon/vs_primary_plane.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -126,7 +127,11 @@ static void vs_primary_plane_atomic_update(struct drm_plane *plane, VSDC_FB_CONFIG_UV_SWIZZLE_EN, vs_state->format.uv_swizzle); - dma_addr = vs_fb_get_dma_addr(fb, &state->src); + /* + * Primary plane cannot be moved, no clipping is involved, + * so the non-clipped framebuffer address can be used. + */ + dma_addr = drm_fb_dma_get_gem_addr(fb, state, 0); regmap_write(dc->regs, VSDC_FB_ADDRESS(output), lower_32_bits(dma_addr)); From a34340574bad0e3280d8b43b3836147bf2149abc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Larumbe?= Date: Thu, 20 Aug 2026 15:04:47 +0100 Subject: [PATCH 047/213] drm/panfrost: Check another bo field for cache option query MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When querying the properties of a BO, whether it was created in Writeback caching mode for userspace mappings, is recorded in a Panfrost BO's wb_mmap field. Reported-by: Claude Closes: https://gitlab.freedesktop.org/panfrost/linux/-/work_items/88 Fixes: 62eedf1ccba5 ("drm/panfrost: Add flag to map GEM object Write-Back Cacheable") Reviewed-by: Steven Price Reviewed-by: Boris Brezillon Link: https://patch.msgid.link/20260820-b4-panfrost-fixes-v1-1-1b2c2376f4bc@collabora.com Signed-off-by: Adrián Larumbe --- drivers/gpu/drm/panfrost/panfrost_drv.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/panfrost/panfrost_drv.c b/drivers/gpu/drm/panfrost/panfrost_drv.c index 36cc2e67a308..96a66d673ba6 100644 --- a/drivers/gpu/drm/panfrost/panfrost_drv.c +++ b/drivers/gpu/drm/panfrost/panfrost_drv.c @@ -662,7 +662,7 @@ static int panfrost_ioctl_query_bo_info(struct drm_device *dev, void *data, if (bo->is_heap) args->create_flags |= PANFROST_BO_HEAP; - if (!bo->base.map_wc) + if (bo->wb_mmap) args->create_flags |= PANFROST_BO_WB_MMAP; } From fd4227f03546c323b5b21568f727233e9792d298 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Larumbe?= Date: Thu, 20 Aug 2026 15:04:48 +0100 Subject: [PATCH 048/213] drm/panfrost: Prevent division by 0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When updating and debug-printing devfreq stats, in the very unlikely off-chance that total device time is less than 100 ns, division by 0 would happen. Fix the divisor when relevant and rearrange operands. Reported-by: Claude Closes: https://gitlab.freedesktop.org/panfrost/linux/-/work_items/88 Fixes: f3ba91228e8e ("drm/panfrost: Add initial panfrost driver") Reviewed-by: Steven Price Reviewed-by: Boris Brezillon Link: https://patch.msgid.link/20260820-b4-panfrost-fixes-v1-2-1b2c2376f4bc@collabora.com Signed-off-by: Adrián Larumbe --- drivers/gpu/drm/panfrost/panfrost_devfreq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/panfrost/panfrost_devfreq.c b/drivers/gpu/drm/panfrost/panfrost_devfreq.c index b51c30778811..0fe81d259274 100644 --- a/drivers/gpu/drm/panfrost/panfrost_devfreq.c +++ b/drivers/gpu/drm/panfrost/panfrost_devfreq.c @@ -78,7 +78,7 @@ static int panfrost_devfreq_get_dev_status(struct device *dev, dev_dbg(pfdev->base.dev, "busy %lu total %lu %lu %% freq %lu MHz\n", status->busy_time, status->total_time, - status->busy_time / (status->total_time / 100), + status->busy_time * 100 / MAX(status->total_time, 1), status->current_frequency / 1000 / 1000); return 0; From 8aee06aa4c72e9bdfda81ce720b51fe0fe0f307e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Larumbe?= Date: Thu, 20 Aug 2026 15:04:49 +0100 Subject: [PATCH 049/213] drm/panfrost: Remove unnecessary header file include MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit None of panfrost_features.h definitions are used in panfrost_device.c Reviewed-by: Steven Price Reviewed-by: Boris Brezillon Link: https://patch.msgid.link/20260820-b4-panfrost-fixes-v1-3-1b2c2376f4bc@collabora.com Signed-off-by: Adrián Larumbe --- drivers/gpu/drm/panfrost/panfrost_device.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/gpu/drm/panfrost/panfrost_device.c b/drivers/gpu/drm/panfrost/panfrost_device.c index d0a23c51be39..485349faf251 100644 --- a/drivers/gpu/drm/panfrost/panfrost_device.c +++ b/drivers/gpu/drm/panfrost/panfrost_device.c @@ -11,7 +11,6 @@ #include "panfrost_device.h" #include "panfrost_devfreq.h" -#include "panfrost_features.h" #include "panfrost_gem.h" #include "panfrost_issues.h" #include "panfrost_gpu.h" From 827eb9328eaed831d7cc426ac2705038a40835b6 Mon Sep 17 00:00:00 2001 From: Marek Czernohous Date: Sun, 16 Aug 2026 14:58:52 +0200 Subject: [PATCH 050/213] drm/nouveau: disable the fence uevent work instead of just cancelling it nouveau_fence_context_del() drains the uevent work while the event that feeds it is still armed: cancel_work_sync(&fctx->uevent_work); nouveau_fence_context_kill(fctx, 0); nvif_event_dtor(&fctx->event); nouveau_fence_wait_uevent_handler() queues the work unconditionally: schedule_work(&fctx->uevent_work); return NVIF_EVENT_KEEP; so a non-stall interrupt arriving after cancel_work_sync() has returned re-arms the work that was just drained. The window closes in two steps, neither of which is the drain. The kill blocks the event when the last fence holding a notify_ref is signalled, which reaches atomic_xchg(&ntfy->allowed, 0) (nvkm/core/event.c:104), and nvkm_event_ntfy() skips a ntfy that is not allowed (:183). That stops further handlers from starting, but not one that is already inside nvkm_event_ntfy(): the event is created with wait = false (nouveau_fence.c:201), so nvkm_event_ntfy_block_() leaves it on the list and never takes event->list_lock. Only nvif_event_dtor() waits that one out: nvkm_event_ntfy_del() (:141) goes through nvkm_event_ntfy_remove(), which takes write_lock_irq() on that same list_lock (:84). Either way the re-arm happens after the drain, and the caller drops its reference immediately afterwards, for example nv84_fence_context_del(): nouveau_fence_context_del(&fctx->base); chan->fence = NULL; nouveau_fence_context_free(&fctx->base); That is a kref_put() on fctx->fence_ref, so the context outlives the teardown only while emitted fences still hold a reference of their own. That is no safety net: whenever none do, the count reaches zero right there and nouveau_fence_context_put() kfree()s fctx while the work is still queued. &fctx->uevent_work is embedded in that allocation, so the workqueue already dereferences freed memory when it picks the item up, and nouveau_fence_uevent_work() can then take fctx->lock on it. With CONFIG_DEBUG_OBJECTS_WORK and CONFIG_DEBUG_OBJECTS_FREE, kfree() of a still-queued work item is reported as the free of an active object. On live memory the re-armed work has nothing left to do: nouveau_fence_context_kill() empties fctx->pending and sets fctx->killed under fctx->lock, nouveau_fence_emit() then returns -ENODEV rather than queueing anything new, and nouveau_fence_update() only reaches nvif_event_block() if it signalled something off that list. The defect is the access to freed memory, not what the work would have found. Only chips from G84 on can reach this at all: nouveau_fence_context_new() returns before nvif_event_ctor() when priv->uevent is clear, and nv84_fence_create() is the only place that sets it. nv84_fence_context_del() is the context_del for all of those, because nvc0_fence_create() and gv100_fence_create() build on nv84_fence_create() and override only context_new. Use disable_work_sync() instead. It drains the work exactly like cancel_work_sync() does, and additionally increments the work item's disable count, after which "any attempt to queue @work will fail and return %false" (kernel/workqueue.c, disable_work()). The handler's schedule_work() then has nothing to re-arm, and the teardown order stays as it is. Draining a second time after nvif_event_dtor() would close the window as well, and without the newer API: once nvkm_event_ntfy_remove() has returned, no handler can start or still be running, so nothing re-arms the work past that point. disable_work_sync() is preferred here because it needs one synchronisation point instead of two, it keeps the work from being queued at all rather than cleaning up after it, and it is what drm has settled on for this (drm/xe, drm/panthor, drm_pagemap). Blocking the event rather than the work is not an option: fctx->event is created with wait = false, so a handler already inside nvkm_event_ntfy() can still queue the work. Note for backports: disable_work_sync() arrived in v6.10 with commit 86898fa6b8cd ("workqueue: Implement disable/enable for (delayed) work items"), while the fix being corrected here reached 6.6.18 and 6.7.6. linux-6.6.y therefore carries this bug without the API, and this patch would apply there and then fail to build. A 6.6.y backport wants the second drain described above instead, as its own patch. Reported-by: sashiko-bot Closes: https://lore.kernel.org/all/20260812233022.159301F000E9@smtp.kernel.org/ Fixes: 39126abc5e20 ("nouveau: offload fence uevents work to workqueue") Cc: # 6.10.x Assisted-by: Claude:claude-opus-5 Signed-off-by: Marek Czernohous Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/178688513268.513871.3468844663561639695@gmail.com --- drivers/gpu/drm/nouveau/nouveau_fence.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nouveau_fence.c b/drivers/gpu/drm/nouveau/nouveau_fence.c index edbe9e08ba0f..11e95c37ce50 100644 --- a/drivers/gpu/drm/nouveau/nouveau_fence.c +++ b/drivers/gpu/drm/nouveau/nouveau_fence.c @@ -96,7 +96,7 @@ nouveau_fence_context_kill(struct nouveau_fence_chan *fctx, int error) void nouveau_fence_context_del(struct nouveau_fence_chan *fctx) { - cancel_work_sync(&fctx->uevent_work); + disable_work_sync(&fctx->uevent_work); nouveau_fence_context_kill(fctx, 0); nvif_event_dtor(&fctx->event); fctx->dead = 1; From f60849975a886d70883bcb6b3658c0fa83c6971f Mon Sep 17 00:00:00 2001 From: Marek Czernohous Date: Sat, 15 Aug 2026 21:54:20 +0200 Subject: [PATCH 051/213] drm/nouveau: cancel the DP IRQ work before freeing the connector nouveau_connector_destroy() tears the two nvif events down and then frees the connector, but never cancels the work the IRQ event queues: nvif_event_dtor(&nv_connector->irq); nvif_event_dtor(&nv_connector->hpd); kfree(nv_connector->edid); ... kfree(connector); nouveau_connector_irq() queues that work unconditionally: schedule_work(&nv_connector->irq_work); return NVIF_EVENT_KEEP; A DP IRQ arriving just before nvif_event_dtor() therefore leaves nv_connector->irq_work on the system queue past the kfree(). When it runs, nouveau_dp_irq() derives both nv_connector and connector from the work_struct and dereferences them, and goes on to take outp->dp.hpd_irq_lock. There is no cancel_work_sync() for irq_work anywhere in the driver, so nothing else covers this. Add it after the event teardown, where no further work can be queued, and before anything is freed. Reported by the Sashiko review bot as a pre-existing issue, in its review of an earlier nv04 FIFO series of mine, and confirmed against the source. Reported-by: sashiko-bot Link: https://sashiko.dev/#/patchset/20260812231330.705425-1-mczernohous@gmail.com?part=1 Fixes: 773eb04d14a1 ("drm/nouveau/disp: expose conn event class") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5 Signed-off-by: Marek Czernohous Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/178682366002.3748010.4096452389040554615@gmail.com --- drivers/gpu/drm/nouveau/nouveau_connector.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.c b/drivers/gpu/drm/nouveau/nouveau_connector.c index b0b0ad9a0c24..e49dcaa6d210 100644 --- a/drivers/gpu/drm/nouveau/nouveau_connector.c +++ b/drivers/gpu/drm/nouveau/nouveau_connector.c @@ -397,6 +397,7 @@ nouveau_connector_destroy(struct drm_connector *connector) struct nouveau_connector *nv_connector = nouveau_connector(connector); nvif_event_dtor(&nv_connector->irq); nvif_event_dtor(&nv_connector->hpd); + cancel_work_sync(&nv_connector->irq_work); kfree(nv_connector->edid); drm_connector_unregister(connector); drm_connector_cleanup(connector); From 843bc34db94bff0612c8294861b156489958897b Mon Sep 17 00:00:00 2001 From: Marek Czernohous Date: Sat, 15 Aug 2026 21:54:20 +0200 Subject: [PATCH 052/213] drm/nouveau: don't dereference outp before checking it in nouveau_dp_irq nouveau_dp_irq() looks the encoder up and dereferences it in the same breath, five lines before testing it: struct nouveau_encoder *outp = find_encoder(connector, DCB_OUTPUT_DP); struct nouveau_drm *drm = nouveau_drm(outp->base.base.dev); ... if (!outp) return; find_encoder() walks the connector's possible encoders and returns NULL when none of them matches the requested type, so the NULL test is not decoration: it is the author saying this can happen. The initialiser above it dereferences the same pointer regardless. The NULL test predates the dereference. commit 773eb04d14a1 ("drm/nouveau/disp: expose conn event class") turned nouveau_dp_irq() into a work callback, and since the drm pointer was no longer passed in as an argument it was recovered from the encoder in the declaration block, which put the dereference above the existing test. Move the drm lookup below the test. No functional change when outp is non-NULL. Fixes: 773eb04d14a1 ("drm/nouveau/disp: expose conn event class") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5 Signed-off-by: Marek Czernohous Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/178682366004.3748010.14933649768375463967@gmail.com --- drivers/gpu/drm/nouveau/nouveau_dp.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nouveau_dp.c b/drivers/gpu/drm/nouveau/nouveau_dp.c index 55691ec44aba..738802358d85 100644 --- a/drivers/gpu/drm/nouveau/nouveau_dp.c +++ b/drivers/gpu/drm/nouveau/nouveau_dp.c @@ -486,7 +486,7 @@ nouveau_dp_irq(struct work_struct *work) container_of(work, typeof(*nv_connector), irq_work); struct drm_connector *connector = &nv_connector->base; struct nouveau_encoder *outp = find_encoder(connector, DCB_OUTPUT_DP); - struct nouveau_drm *drm = nouveau_drm(outp->base.base.dev); + struct nouveau_drm *drm; struct nv50_mstm *mstm; u64 hpd = 0; int ret; @@ -494,6 +494,8 @@ nouveau_dp_irq(struct work_struct *work) if (!outp) return; + drm = nouveau_drm(outp->base.base.dev); + mstm = outp->dp.mstm; NV_DEBUG(drm, "service %s\n", connector->name); From 20207f78382ff339f3a7f7c5db2c0219b157d32a Mon Sep 17 00:00:00 2001 From: Andreas Kemnade Date: Tue, 4 Aug 2026 10:37:00 +0200 Subject: [PATCH 053/213] drm/omap: dsi: avoid sending bta sync all the time in writes Some chips need configuration commands to be sent first, before they can send data. TC358762 for example needs PPI_LPTXTIMECNT configured and PPI_STARTPPI set to 1 to be able to transmit anything. To be able to configure such chips, do not send bta sync during writes if no acks are requested. Instead just wait for the packet to be sent to avoid FIFO overflows. There might be more to do about acks, but there seem to be virtually no users of that flag. This came to light when fiddling with the Epson Moverio BT-200 display which consists of 2 TC358762 bridges with SPI funneled through to the unknown display chip. With that patch the bridge can be accessed, Reading back registers works, when the above-mentioned registers are set. In Command-Mode update, there was a nop sent, apparently the most relevant part was the bta sync to actually force low power mode. Video mode panel at OMAP4 (BT-200) and video mode at OMAP5 was tested. Fixes: e70965386353e ("drm/omap: dsi: simplify write function") Signed-off-by: Andreas Kemnade Link: https://patch.msgid.link/20260629-vm-upstr-v4-1-9f0c62d8808e@kemnade.info Link: https://patch.msgid.link/20260628-vm-upstr-v3-1-9e9add93378b@kemnade.info Link: https://patch.msgid.link/20260529-vm-upstr-v2-1-24c30671719f@kernel.org Link: https://lore.gitlab.freedesktop.org/drm-ai-reviews/review-patch1-20260529-vm-upstr-v2-1-24c30671719f@kernel.org/ Link: https://patch.msgid.link/20260528-vm-upstr-v1-1-fb93ef8cbe47@kernel.org Link: https://patch.msgid.link/20260804-vm-upstr-v6-1-1bf3748b2fa3@kemnade.info Signed-off-by: Tomi Valkeinen --- drivers/gpu/drm/omapdrm/dss/dsi.c | 63 +++++++++++++++---------------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/drivers/gpu/drm/omapdrm/dss/dsi.c b/drivers/gpu/drm/omapdrm/dss/dsi.c index 6da720e8732d..a57f5df5c830 100644 --- a/drivers/gpu/drm/omapdrm/dss/dsi.c +++ b/drivers/gpu/drm/omapdrm/dss/dsi.c @@ -59,9 +59,6 @@ static void dsi_uninit_dispc(struct dsi_data *dsi); static int dsi_vc_send_null(struct dsi_data *dsi, int vc, int channel); -static ssize_t _omap_dsi_host_transfer(struct dsi_data *dsi, int vc, - const struct mipi_dsi_msg *msg); - #ifdef DSI_PERF_MEASURE static bool dsi_perf; module_param(dsi_perf, bool, 0644); @@ -2190,28 +2187,45 @@ static int dsi_vc_send_null(struct dsi_data *dsi, int vc, int channel) static int dsi_vc_write_common(struct omap_dss_device *dssdev, int vc, const struct mipi_dsi_msg *msg) { + DECLARE_COMPLETION_ONSTACK(completion); struct dsi_data *dsi = to_dsi_data(dssdev); + u32 err; int r; + /* wait for IRQ for packet transmission confirmation */ + r = dsi_register_isr_vc(dsi, vc, dsi_completion_handler, + &completion, DSI_VC_IRQ_PACKET_SENT); + if (r) + return r; + if (mipi_dsi_packet_format_is_short(msg->type)) r = dsi_vc_send_short(dsi, vc, msg); else r = dsi_vc_send_long(dsi, vc, msg); - if (r < 0) + if ((!r) && wait_for_completion_timeout(&completion, + msecs_to_jiffies(500)) == 0) + r = -EIO; + + dsi_unregister_isr_vc(dsi, vc, dsi_completion_handler, + &completion, DSI_VC_IRQ_PACKET_SENT); + if (r) return r; - /* - * TODO: we do not always have to do the BTA sync, for example - * we can improve performance by setting the update window - * information without sending BTA sync between the commands. - * In that case we can return early. - */ + /* TODO: find out if more needs to be done for MIPI_DSI_MSG_REQ_ACK */ - r = dsi_vc_send_bta_sync(dssdev, vc); - if (r) { - DSSERR("bta sync failed\n"); - return r; + if (msg->flags & MIPI_DSI_MSG_REQ_ACK) { + r = dsi_vc_send_bta_sync(dssdev, vc); + if (r) { + DSSERR("bta sync failed\n"); + return r; + } + } else { + err = dsi_get_errors(dsi); + if (err) { + DSSERR("Error while sending: %x\n", err); + return -EIO; + } } /* RX_FIFO_NOT_EMPTY */ @@ -3229,21 +3243,6 @@ static int _dsi_update(struct dsi_data *dsi) return 0; } -static int _dsi_send_nop(struct dsi_data *dsi, int vc, int channel) -{ - const u8 payload[] = { MIPI_DCS_NOP }; - const struct mipi_dsi_msg msg = { - .channel = channel, - .type = MIPI_DSI_DCS_SHORT_WRITE, - .tx_len = 1, - .tx_buf = payload, - }; - - WARN_ON(!dsi_bus_is_locked(dsi)); - - return _omap_dsi_host_transfer(dsi, vc, &msg); -} - static int dsi_update_channel(struct omap_dss_device *dssdev, int vc) { struct dsi_data *dsi = to_dsi_data(dssdev); @@ -3264,13 +3263,13 @@ static int dsi_update_channel(struct omap_dss_device *dssdev, int vc) DSSDBG("dsi_update_channel: %d", vc); /* - * Send NOP between the frames. If we don't send something here, the + * Transition to LP here. If we don't send something here, the * updates stop working. This is probably related to DSI spec stating * that the DSI host should transition to LP at least once per frame. */ - r = _dsi_send_nop(dsi, VC_CMD, dsi->dsidev->channel); + r = dsi_vc_send_bta_sync(dssdev, VC_CMD); if (r < 0) { - DSSWARN("failed to send nop between frames: %d\n", r); + DSSWARN("failed to send bta sync between frames: %d\n", r); goto err; } From 699e5975e2a53820d8248e4e69e3645d8fa3eff8 Mon Sep 17 00:00:00 2001 From: Ivaylo Dimitrov Date: Wed, 19 Aug 2026 20:19:35 +0300 Subject: [PATCH 054/213] drm/omap: Report HDMI hotplug events to the ASoC HDMI codec The OMAP HDMI audio driver currently has no way of reporting HDMI cable hotplug events to ASoC. As a result, user space cannot detect HDMI audio availability through the standard jack mechanism and audio policy managers cannot automatically route audio to or from the HDMI output. Add an optional HPD callback to `omap_hdmi_audio_pdata` and invoke it from the DRM HDMI bridge whenever the connector status changes. The HDMI audio driver registers an ASoC jack and reports `SND_JACK_AVOUT` state changes in response to these notifications. Also fix the disconnect path by taking a runtime PM reference before updating the CEC physical address. Signed-off-by: Ivaylo Dimitrov Acked-by: Mark Brown Acked-by: Jarkko Nikula Tested-by: Jarkko Nikula Link: https://patch.msgid.link/20260819171935.233946-1-ivo.g.dimitrov.75@gmail.com Signed-off-by: Tomi Valkeinen --- drivers/gpu/drm/omapdrm/dss/hdmi.h | 6 +++ drivers/gpu/drm/omapdrm/dss/hdmi4.c | 29 ++++++++++--- drivers/gpu/drm/omapdrm/dss/hdmi5.c | 37 +++++++++++++--- drivers/gpu/drm/omapdrm/dss/hdmi_common.c | 14 ++++++ include/sound/omap-hdmi-audio.h | 1 + sound/soc/ti/omap-hdmi.c | 53 +++++++++++++++++++++-- 6 files changed, 125 insertions(+), 15 deletions(-) diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi.h b/drivers/gpu/drm/omapdrm/dss/hdmi.h index c4a4e07f0b99..a79d4590611f 100644 --- a/drivers/gpu/drm/omapdrm/dss/hdmi.h +++ b/drivers/gpu/drm/omapdrm/dss/hdmi.h @@ -15,6 +15,7 @@ #include #include #include +#include #include "omapdss.h" #include "dss.h" @@ -368,6 +369,8 @@ struct omap_hdmi { struct drm_bridge bridge; struct platform_device *audio_pdev; + struct mutex audio_lock; + void (*audio_abort_cb)(struct device *dev); int wp_idlemode; @@ -380,6 +383,9 @@ struct omap_hdmi { bool display_enabled; }; +void hdmi_audio_hpd_notify(struct omap_hdmi *hdmi, + enum drm_connector_status status); + #define drm_bridge_to_hdmi(b) container_of(b, struct omap_hdmi, bridge) #endif diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi4.c b/drivers/gpu/drm/omapdrm/dss/hdmi4.c index 61cfd003569c..bd6c4d2dcdc8 100644 --- a/drivers/gpu/drm/omapdrm/dss/hdmi4.c +++ b/drivers/gpu/drm/omapdrm/dss/hdmi4.c @@ -433,8 +433,15 @@ static void hdmi4_bridge_hpd_notify(struct drm_bridge *bridge, { struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge); - if (status == connector_status_disconnected) + hdmi_audio_hpd_notify(hdmi, status); + + if (status == connector_status_disconnected) { + if (hdmi_runtime_get(hdmi)) + return; + hdmi4_cec_set_phys_addr(&hdmi->core, CEC_PHYS_ADDR_INVALID); + hdmi_runtime_put(hdmi); + } } static const struct drm_edid *hdmi4_bridge_edid_read(struct drm_bridge *bridge, @@ -626,12 +633,17 @@ static int hdmi_audio_register(struct omap_hdmi *hdmi) .ops = &hdmi_audio_ops, }; + guard(mutex)(&hdmi->audio_lock); hdmi->audio_pdev = platform_device_register_data( &hdmi->pdev->dev, "omap-hdmi-audio", PLATFORM_DEVID_AUTO, &pdata, sizeof(pdata)); - if (IS_ERR(hdmi->audio_pdev)) - return PTR_ERR(hdmi->audio_pdev); + if (IS_ERR(hdmi->audio_pdev)) { + int err = PTR_ERR(hdmi->audio_pdev); + + hdmi->audio_pdev = NULL; + return err; + } return 0; } @@ -688,8 +700,14 @@ static void hdmi4_unbind(struct device *dev, struct device *master, void *data) dss_debugfs_remove_file(hdmi->debugfs); - if (hdmi->audio_pdev) - platform_device_unregister(hdmi->audio_pdev); + scoped_guard(mutex, &hdmi->audio_lock) { + if (hdmi->audio_pdev) { + struct platform_device *pdev = hdmi->audio_pdev; + + hdmi->audio_pdev = NULL; + platform_device_unregister(pdev); + } + } hdmi4_cec_uninit(&hdmi->core); hdmi_pll_uninit(&hdmi->pll); @@ -770,6 +788,7 @@ static int hdmi4_probe(struct platform_device *pdev) dev_set_drvdata(&pdev->dev, hdmi); mutex_init(&hdmi->lock); + mutex_init(&hdmi->audio_lock); spin_lock_init(&hdmi->audio_playing_lock); r = hdmi4_probe_of(hdmi); diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi5.c b/drivers/gpu/drm/omapdrm/dss/hdmi5.c index 1ee3bbe20583..c542ba6ce7d3 100644 --- a/drivers/gpu/drm/omapdrm/dss/hdmi5.c +++ b/drivers/gpu/drm/omapdrm/dss/hdmi5.c @@ -425,6 +425,15 @@ static void hdmi5_bridge_disable(struct drm_bridge *bridge, mutex_unlock(&hdmi->lock); } +static void hdmi5_bridge_hpd_notify(struct drm_bridge *bridge, + struct drm_connector *connector, + enum drm_connector_status status) +{ + struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge); + + hdmi_audio_hpd_notify(hdmi, status); +} + static const struct drm_edid *hdmi5_bridge_edid_read(struct drm_bridge *bridge, struct drm_connector *connector) { @@ -475,6 +484,7 @@ static const struct drm_bridge_funcs hdmi5_bridge_funcs = { .atomic_create_state = drm_atomic_helper_bridge_create_state, .atomic_enable = hdmi5_bridge_enable, .atomic_disable = hdmi5_bridge_disable, + .hpd_notify = hdmi5_bridge_hpd_notify, .edid_read = hdmi5_bridge_edid_read, }; @@ -601,12 +611,18 @@ static int hdmi_audio_register(struct omap_hdmi *hdmi) .ops = &hdmi_audio_ops, }; - hdmi->audio_pdev = platform_device_register_data( - &hdmi->pdev->dev, "omap-hdmi-audio", PLATFORM_DEVID_AUTO, - &pdata, sizeof(pdata)); + scoped_guard(mutex, &hdmi->audio_lock) { + hdmi->audio_pdev = platform_device_register_data( + &hdmi->pdev->dev, "omap-hdmi-audio", + PLATFORM_DEVID_AUTO, &pdata, sizeof(pdata)); - if (IS_ERR(hdmi->audio_pdev)) - return PTR_ERR(hdmi->audio_pdev); + if (IS_ERR(hdmi->audio_pdev)) { + int err = PTR_ERR(hdmi->audio_pdev); + + hdmi->audio_pdev = NULL; + return err; + } + } hdmi_runtime_get(hdmi); hdmi->wp_idlemode = @@ -654,8 +670,14 @@ static void hdmi5_unbind(struct device *dev, struct device *master, void *data) dss_debugfs_remove_file(hdmi->debugfs); - if (hdmi->audio_pdev) - platform_device_unregister(hdmi->audio_pdev); + scoped_guard(mutex, &hdmi->audio_lock) { + if (hdmi->audio_pdev) { + struct platform_device *pdev = hdmi->audio_pdev; + + hdmi->audio_pdev = NULL; + platform_device_unregister(pdev); + } + } hdmi_pll_uninit(&hdmi->pll); } @@ -735,6 +757,7 @@ static int hdmi5_probe(struct platform_device *pdev) dev_set_drvdata(&pdev->dev, hdmi); mutex_init(&hdmi->lock); + mutex_init(&hdmi->audio_lock); spin_lock_init(&hdmi->audio_playing_lock); r = hdmi5_probe_of(hdmi); diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi_common.c b/drivers/gpu/drm/omapdrm/dss/hdmi_common.c index 3ecde23ac604..e9adc5775913 100644 --- a/drivers/gpu/drm/omapdrm/dss/hdmi_common.c +++ b/drivers/gpu/drm/omapdrm/dss/hdmi_common.c @@ -147,3 +147,17 @@ int hdmi_compute_acr(u32 pclk, u32 sample_freq, u32 *n, u32 *cts) return 0; } + +void hdmi_audio_hpd_notify(struct omap_hdmi *hdmi, + enum drm_connector_status status) +{ + guard(mutex)(&hdmi->audio_lock); + + if (hdmi->audio_pdev) { + struct device *dev = &hdmi->audio_pdev->dev; + struct omap_hdmi_audio_pdata *ha = dev_get_platdata(dev); + + if (ha->audio_hpd) + ha->audio_hpd(dev, status == connector_status_connected); + } +} diff --git a/include/sound/omap-hdmi-audio.h b/include/sound/omap-hdmi-audio.h index e5f82044a404..8dca92360ee0 100644 --- a/include/sound/omap-hdmi-audio.h +++ b/include/sound/omap-hdmi-audio.h @@ -34,6 +34,7 @@ struct omap_hdmi_audio_pdata { phys_addr_t audio_dma_addr; const struct omap_hdmi_audio_ops *ops; + void (*audio_hpd)(struct device *dev, bool connected); }; #endif /* __OMAP_HDMI_AUDIO_H__ */ diff --git a/sound/soc/ti/omap-hdmi.c b/sound/soc/ti/omap-hdmi.c index e60f5b483fc5..90ce12a81246 100644 --- a/sound/soc/ti/omap-hdmi.c +++ b/sound/soc/ti/omap-hdmi.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include "sdma-pcm.h" @@ -35,6 +36,8 @@ struct hdmi_audio_data { struct mutex current_stream_lock; struct snd_pcm_substream *current_stream; + struct snd_soc_jack jack; + atomic_t jack_state; }; static @@ -262,6 +265,34 @@ static void hdmi_dai_shutdown(struct snd_pcm_substream *substream, ad->current_stream = NULL; } +static void hdmi_audio_hpd(struct device *dev, bool connected) +{ + struct hdmi_audio_data *ad = dev_get_drvdata(dev); + + if (atomic_xchg(&ad->jack_state, connected) == connected) + return; + + snd_soc_jack_report(&ad->jack, + connected ? SND_JACK_AVOUT : 0, SND_JACK_AVOUT); + dev_dbg(dev, "HDMI %s\n", connected ? "CONNECTED" : "DISCONNECTED"); +} + +static int hdmi_audio_dai_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + struct hdmi_audio_data *ad = snd_soc_card_get_drvdata(rtd->card); + int ret; + + ret = snd_soc_card_jack_new( + card, "HDMI", SND_JACK_AVOUT, &ad->jack); + if (ret < 0) { + dev_err(card->dev, "Cannot create HDMI jack: %i\n", ret); + return ret; + } + + return 0; +} + static const struct snd_soc_dai_ops hdmi_dai_ops = { .startup = hdmi_dai_startup, .hw_params = hdmi_dai_hw_params, @@ -371,28 +402,44 @@ static int omap_hdmi_audio_probe(struct platform_device *pdev) card->dai_link->stream_name = card->name; card->dai_link->cpus->dai_name = dev_name(ad->dssdev); card->dai_link->platforms->name = dev_name(ad->dssdev); + card->dai_link->init = hdmi_audio_dai_init; card->num_links = 1; card->dev = dev; + atomic_set(&ad->jack_state, -1); + ad->card = card; + + snd_soc_card_set_drvdata(card, ad); ret = devm_snd_soc_register_card(dev, card); if (ret) { dev_err(dev, "snd_soc_register_card failed (%d)\n", ret); return ret; } - ad->card = card; - snd_soc_card_set_drvdata(card, ad); - dev_set_drvdata(dev, ad); + ha->audio_hpd = hdmi_audio_hpd; + return 0; } +static void omap_hdmi_audio_remove(struct platform_device *pdev) +{ + struct omap_hdmi_audio_pdata *ha = pdev->dev.platform_data; + + /* + * hdmi4_unbind() holds audio lock across platform_device_unregister(), + * so audio_hpd() cannot run concurrently with this callback. + */ + ha->audio_hpd = NULL; +} + static struct platform_driver hdmi_audio_driver = { .driver = { .name = DRV_NAME, }, .probe = omap_hdmi_audio_probe, + .remove = omap_hdmi_audio_remove, }; module_platform_driver(hdmi_audio_driver); From e3eca845ef7d6cd8c407f9b42beb45af94ff096b Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Thu, 5 Mar 2026 21:58:15 +0100 Subject: [PATCH 055/213] drm: of: Fix drm_of_get_data_lanes_count_ep() return value documentation Update drm_of_get_data_lanes_count_ep() return value documentation to match the drm_of_get_data_lanes_count() return value documentation. The drm_of_get_data_lanes_count_ep() is only a wrapper around the drm_of_get_data_lanes_count() and therefore returns the same error codes. Simplify the return code description of drm_of_get_data_lanes_count() and drm_of_get_data_lanes_count_ep() to -ENODATA and -EINVAL. Fixes: fc801750b197 ("drm: of: Add drm_of_get_data_lanes_count and drm_of_get_data_lanes_ep") Reported-by: Geert Uytterhoeven Signed-off-by: Marek Vasut Reviewed-by: Maxime Ripard Link: https://patch.msgid.link/20260305205840.95978-1-marek.vasut+renesas@mailbox.org Signed-off-by: Tomi Valkeinen --- drivers/gpu/drm/drm_of.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/drm_of.c b/drivers/gpu/drm/drm_of.c index e495117735e1..cf02dbca5458 100644 --- a/drivers/gpu/drm/drm_of.c +++ b/drivers/gpu/drm/drm_of.c @@ -550,8 +550,8 @@ EXPORT_SYMBOL_GPL(drm_of_lvds_get_data_mapping); * * Return: * * min..max - positive integer count of "data-lanes" elements - * * -ve - the "data-lanes" property is missing or invalid - * * -EINVAL - the "data-lanes" property is unsupported + * * -ENODATA - the property does not have a value. + * * -EINVAL - the "data-lanes" property is missing or invalid */ int drm_of_get_data_lanes_count(const struct device_node *endpoint, const unsigned int min, const unsigned int max) @@ -582,8 +582,8 @@ EXPORT_SYMBOL_GPL(drm_of_get_data_lanes_count); * * Return: * * min..max - positive integer count of "data-lanes" elements - * * -EINVAL - the "data-mapping" property is unsupported - * * -ENODEV - the "data-mapping" property is missing + * * -ENODATA - the property does not have a value. + * * -EINVAL - the "data-lanes" property is missing or invalid */ int drm_of_get_data_lanes_count_ep(const struct device_node *port, int port_reg, int reg, From a8a198cefe5cbb5fb5bec00d613233b0431d7ebf Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Fri, 14 Aug 2026 16:56:50 +0200 Subject: [PATCH 056/213] drm/simple-kms: Add create_plane_state hook The simple-kms helper provides a reset_plane hook to let drivers override the default plane reset. However, the reset hook is overloaded: it is used to create the initial software state at probe time, but also to reset both the hardware and software state during suspend/resume. These two roles have different expectations, and reset is not fallible which makes error handling difficult. The atomic_create_state pattern was introduced to untangle this by providing a hook that only allocates and initializes a pristine state without any side effect. Add a create_plane_state hook to struct drm_simple_display_pipe_funcs. When provided, drm_simple_kms_plane_reset() will use it to allocate a fresh state, falling back to reset_plane and then to the default drm_atomic_helper_plane_reset(). This will allow simple-kms drivers to be converted to the atomic_create_state pattern. Reviewed-by: Thomas Zimmermann Link: https://patch.msgid.link/20260814-drm-no-more-plane-reset-v2-1-82d2963dd134@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/drm_simple_kms_helper.c | 26 +++++++++++++++++++++++-- include/drm/drm_simple_kms_helper.h | 1 + 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/drm_simple_kms_helper.c b/drivers/gpu/drm/drm_simple_kms_helper.c index 8e1d07b9f1e3..528968ea5c4b 100644 --- a/drivers/gpu/drm/drm_simple_kms_helper.c +++ b/drivers/gpu/drm/drm_simple_kms_helper.c @@ -281,15 +281,37 @@ static const struct drm_plane_helper_funcs drm_simple_kms_plane_helper_funcs = { .atomic_update = drm_simple_kms_plane_atomic_update, }; +static void drm_simple_kms_plane_destroy_state(struct drm_plane *plane, + struct drm_plane_state *state); + static void drm_simple_kms_plane_reset(struct drm_plane *plane) { struct drm_simple_display_pipe *pipe; pipe = container_of(plane, struct drm_simple_display_pipe, plane); - if (!pipe->funcs || !pipe->funcs->reset_plane) + if (!pipe->funcs) return drm_atomic_helper_plane_reset(plane); - return pipe->funcs->reset_plane(pipe); + if (pipe->funcs->create_plane_state) { + struct drm_plane_state *state; + + if (plane->state) { + drm_simple_kms_plane_destroy_state(plane, plane->state); + plane->state = NULL; + } + + state = pipe->funcs->create_plane_state(pipe); + if (WARN_ON(IS_ERR(state))) + return; + + plane->state = state; + return; + } + + if (pipe->funcs->reset_plane) + return pipe->funcs->reset_plane(pipe); + + return drm_atomic_helper_plane_reset(plane); } static struct drm_plane_state *drm_simple_kms_plane_duplicate_state(struct drm_plane *plane) diff --git a/include/drm/drm_simple_kms_helper.h b/include/drm/drm_simple_kms_helper.h index cb672ce0e856..b58f6d532820 100644 --- a/include/drm/drm_simple_kms_helper.h +++ b/include/drm/drm_simple_kms_helper.h @@ -44,6 +44,7 @@ struct drm_simple_display_pipe_funcs { void (*destroy_crtc_state)(struct drm_simple_display_pipe *pipe, struct drm_crtc_state *crtc_state); void (*reset_plane)(struct drm_simple_display_pipe *pipe); + struct drm_plane_state * (*create_plane_state)(struct drm_simple_display_pipe *pipe); struct drm_plane_state * (*duplicate_plane_state)(struct drm_simple_display_pipe *pipe); void (*destroy_plane_state)(struct drm_simple_display_pipe *pipe, struct drm_plane_state *plane_state); From 3dcb0564c509c43787f265fad11a52f1f9c8c86a Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Fri, 14 Aug 2026 16:56:51 +0200 Subject: [PATCH 057/213] drm/gem-atomic-helper: Create drm_gem_create_shadow_plane_state() The GEM shadow-plane helpers only expose reset functions (drm_gem_reset_shadow_plane and __drm_gem_reset_shadow_plane) that handle both the initial state allocation and the reset path. The new atomic_create_state hook requires a function that only allocates and initializes a pristine state without any side effect. Create __drm_gem_shadow_plane_state_init() to initialize a pre-allocated shadow plane state, and drm_gem_create_shadow_plane_state() to allocate and initialize one from scratch. Reviewed-by: Thomas Zimmermann Link: https://patch.msgid.link/20260814-drm-no-more-plane-reset-v2-2-82d2963dd134@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/drm_gem_atomic_helper.c | 42 +++++++++++++++++++++++++ include/drm/drm_gem_atomic_helper.h | 3 ++ 2 files changed, 45 insertions(+) diff --git a/drivers/gpu/drm/drm_gem_atomic_helper.c b/drivers/gpu/drm/drm_gem_atomic_helper.c index abef865c5f2c..7ef67c2d77ce 100644 --- a/drivers/gpu/drm/drm_gem_atomic_helper.c +++ b/drivers/gpu/drm/drm_gem_atomic_helper.c @@ -180,6 +180,48 @@ EXPORT_SYMBOL_GPL(drm_gem_plane_helper_prepare_fb); * Shadow-buffered Planes */ +/** + * __drm_gem_shadow_plane_state_init - initializes shadow-buffered plane state + * @plane: the plane + * @shadow_plane_state: the shadow-buffered plane state + * + * This function initializes a pre-allocated shadow-buffered plane state. + * Helpful for drivers that subclass struct drm_shadow_plane_state. + */ +void __drm_gem_shadow_plane_state_init(struct drm_plane *plane, + struct drm_shadow_plane_state *shadow_plane_state) +{ + __drm_atomic_helper_plane_state_init(&shadow_plane_state->base, plane); + drm_format_conv_state_init(&shadow_plane_state->fmtcnv_state); +} +EXPORT_SYMBOL(__drm_gem_shadow_plane_state_init); + +/** + * drm_gem_create_shadow_plane_state - creates a shadow-buffered plane state + * @plane: the plane + * + * This function implements struct &drm_plane_funcs.atomic_create_state for + * shadow-buffered planes. It assumes the new state to be of type + * struct drm_shadow_plane_state. + * + * Returns: + * A pointer to a new plane state on success, or an ERR_PTR()-encoded + * error code otherwise. + */ +struct drm_plane_state *drm_gem_create_shadow_plane_state(struct drm_plane *plane) +{ + struct drm_shadow_plane_state *shadow_plane_state; + + shadow_plane_state = kzalloc_obj(*shadow_plane_state); + if (!shadow_plane_state) + return ERR_PTR(-ENOMEM); + + __drm_gem_shadow_plane_state_init(plane, shadow_plane_state); + + return &shadow_plane_state->base; +} +EXPORT_SYMBOL(drm_gem_create_shadow_plane_state); + /** * __drm_gem_duplicate_shadow_plane_state - duplicates shadow-buffered plane state * @plane: the plane diff --git a/include/drm/drm_gem_atomic_helper.h b/include/drm/drm_gem_atomic_helper.h index 3e01c619a25e..43657d8fb1c8 100644 --- a/include/drm/drm_gem_atomic_helper.h +++ b/include/drm/drm_gem_atomic_helper.h @@ -95,6 +95,9 @@ void __drm_gem_reset_shadow_plane(struct drm_plane *plane, struct drm_shadow_plane_state *shadow_plane_state); void drm_gem_reset_shadow_plane(struct drm_plane *plane); +void __drm_gem_shadow_plane_state_init(struct drm_plane *plane, + struct drm_shadow_plane_state *shadow_plane_state); +struct drm_plane_state *drm_gem_create_shadow_plane_state(struct drm_plane *plane); struct drm_plane_state *drm_gem_duplicate_shadow_plane_state(struct drm_plane *plane); void drm_gem_destroy_shadow_plane_state(struct drm_plane *plane, struct drm_plane_state *plane_state); From d5460f6a279b9fc7645b3b08aa45760ea324e56a Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Fri, 14 Aug 2026 16:56:52 +0200 Subject: [PATCH 058/213] drm/gem-atomic-helper: Convert simple-kms shadow helpers to create_plane_state drm_gem_simple_kms_reset_shadow_plane() implements the reset_plane hook for simple-kms shadow-buffered planes. Since the reset hook conflates initial state creation with hardware and software reset during suspend/resume, convert it to the create_plane_state pattern. Replace it with drm_gem_simple_kms_create_shadow_plane_state(), which implements the create_plane_state hook using drm_gem_create_shadow_plane_state(). Update DRM_GEM_SIMPLE_DISPLAY_PIPE_SHADOW_PLANE_FUNCS accordingly. Reviewed-by: Thomas Zimmermann Link: https://patch.msgid.link/20260814-drm-no-more-plane-reset-v2-3-82d2963dd134@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/drm_gem_atomic_helper.c | 15 ++++++++++----- include/drm/drm_gem_atomic_helper.h | 5 +++-- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/drm_gem_atomic_helper.c b/drivers/gpu/drm/drm_gem_atomic_helper.c index 7ef67c2d77ce..ae854555fd12 100644 --- a/drivers/gpu/drm/drm_gem_atomic_helper.c +++ b/drivers/gpu/drm/drm_gem_atomic_helper.c @@ -450,17 +450,22 @@ void drm_gem_simple_kms_end_shadow_fb_access(struct drm_simple_display_pipe *pip EXPORT_SYMBOL(drm_gem_simple_kms_end_shadow_fb_access); /** - * drm_gem_simple_kms_reset_shadow_plane - resets a shadow-buffered plane + * drm_gem_simple_kms_create_shadow_plane_state - creates shadow-buffered plane state * @pipe: the simple display pipe * - * This function implements struct drm_simple_display_funcs.reset_plane + * This function implements struct drm_simple_display_pipe_funcs.create_plane_state * for shadow-buffered planes. + * + * Returns: + * A pointer to a new plane state on success, or an ERR_PTR()-encoded + * error code otherwise. */ -void drm_gem_simple_kms_reset_shadow_plane(struct drm_simple_display_pipe *pipe) +struct drm_plane_state * +drm_gem_simple_kms_create_shadow_plane_state(struct drm_simple_display_pipe *pipe) { - drm_gem_reset_shadow_plane(&pipe->plane); + return drm_gem_create_shadow_plane_state(&pipe->plane); } -EXPORT_SYMBOL(drm_gem_simple_kms_reset_shadow_plane); +EXPORT_SYMBOL(drm_gem_simple_kms_create_shadow_plane_state); /** * drm_gem_simple_kms_duplicate_shadow_plane_state - duplicates shadow-buffered plane state diff --git a/include/drm/drm_gem_atomic_helper.h b/include/drm/drm_gem_atomic_helper.h index 43657d8fb1c8..8a3bb27233db 100644 --- a/include/drm/drm_gem_atomic_helper.h +++ b/include/drm/drm_gem_atomic_helper.h @@ -133,7 +133,8 @@ int drm_gem_simple_kms_begin_shadow_fb_access(struct drm_simple_display_pipe *pi struct drm_plane_state *plane_state); void drm_gem_simple_kms_end_shadow_fb_access(struct drm_simple_display_pipe *pipe, struct drm_plane_state *plane_state); -void drm_gem_simple_kms_reset_shadow_plane(struct drm_simple_display_pipe *pipe); +struct drm_plane_state * +drm_gem_simple_kms_create_shadow_plane_state(struct drm_simple_display_pipe *pipe); struct drm_plane_state * drm_gem_simple_kms_duplicate_shadow_plane_state(struct drm_simple_display_pipe *pipe); void drm_gem_simple_kms_destroy_shadow_plane_state(struct drm_simple_display_pipe *pipe, @@ -150,7 +151,7 @@ void drm_gem_simple_kms_destroy_shadow_plane_state(struct drm_simple_display_pip #define DRM_GEM_SIMPLE_DISPLAY_PIPE_SHADOW_PLANE_FUNCS \ .begin_fb_access = drm_gem_simple_kms_begin_shadow_fb_access, \ .end_fb_access = drm_gem_simple_kms_end_shadow_fb_access, \ - .reset_plane = drm_gem_simple_kms_reset_shadow_plane, \ + .create_plane_state = drm_gem_simple_kms_create_shadow_plane_state, \ .duplicate_plane_state = drm_gem_simple_kms_duplicate_shadow_plane_state, \ .destroy_plane_state = drm_gem_simple_kms_destroy_shadow_plane_state From 0316901a73d8d28c759a3fc15054a70f34bc2c52 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Fri, 14 Aug 2026 16:56:53 +0200 Subject: [PATCH 059/213] drm/gem-atomic-helper: Switch DRM_GEM_SHADOW_PLANE_FUNCS to atomic_create_state DRM_GEM_SHADOW_PLANE_FUNCS currently sets .reset to drm_gem_reset_shadow_plane. Since drm_gem_create_shadow_plane_state() now exists and only handles pristine state allocation without conflating it with hardware or suspend/resume reset, switch the macro to use .atomic_create_state instead. Reviewed-by: Thomas Zimmermann Link: https://patch.msgid.link/20260814-drm-no-more-plane-reset-v2-4-82d2963dd134@kernel.org Signed-off-by: Maxime Ripard --- include/drm/drm_gem_atomic_helper.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/drm/drm_gem_atomic_helper.h b/include/drm/drm_gem_atomic_helper.h index 8a3bb27233db..da8a4fb442b4 100644 --- a/include/drm/drm_gem_atomic_helper.h +++ b/include/drm/drm_gem_atomic_helper.h @@ -110,7 +110,7 @@ void drm_gem_destroy_shadow_plane_state(struct drm_plane *plane, * macro initializes struct drm_plane_funcs to use the rsp helper functions. */ #define DRM_GEM_SHADOW_PLANE_FUNCS \ - .reset = drm_gem_reset_shadow_plane, \ + .atomic_create_state = drm_gem_create_shadow_plane_state, \ .atomic_duplicate_state = drm_gem_duplicate_shadow_plane_state, \ .atomic_destroy_state = drm_gem_destroy_shadow_plane_state From 56a1eb3f594b5ee8b5b0dd31c3f3b52b649e9409 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Fri, 14 Aug 2026 16:56:54 +0200 Subject: [PATCH 060/213] drm/gem-atomic-helper: Remove drm_gem_reset_shadow_plane() drm_gem_reset_shadow_plane() is no longer used: all callers now go through drm_gem_create_shadow_plane_state() via the atomic_create_state hook. Remove it. Reviewed-by: Thomas Zimmermann Link: https://patch.msgid.link/20260814-drm-no-more-plane-reset-v2-5-82d2963dd134@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/drm_gem_atomic_helper.c | 23 ----------------------- include/drm/drm_gem_atomic_helper.h | 1 - 2 files changed, 24 deletions(-) diff --git a/drivers/gpu/drm/drm_gem_atomic_helper.c b/drivers/gpu/drm/drm_gem_atomic_helper.c index ae854555fd12..3bb6bfd560d3 100644 --- a/drivers/gpu/drm/drm_gem_atomic_helper.c +++ b/drivers/gpu/drm/drm_gem_atomic_helper.c @@ -339,29 +339,6 @@ void __drm_gem_reset_shadow_plane(struct drm_plane *plane, } EXPORT_SYMBOL(__drm_gem_reset_shadow_plane); -/** - * drm_gem_reset_shadow_plane - resets a shadow-buffered plane - * @plane: the plane - * - * This function implements struct &drm_plane_funcs.reset_plane for - * shadow-buffered planes. It assumes the current plane state to be - * of type struct drm_shadow_plane and it allocates the new state of - * this type. - */ -void drm_gem_reset_shadow_plane(struct drm_plane *plane) -{ - struct drm_shadow_plane_state *shadow_plane_state; - - if (plane->state) { - drm_gem_destroy_shadow_plane_state(plane, plane->state); - plane->state = NULL; /* must be set to NULL here */ - } - - shadow_plane_state = kzalloc_obj(*shadow_plane_state); - __drm_gem_reset_shadow_plane(plane, shadow_plane_state); -} -EXPORT_SYMBOL(drm_gem_reset_shadow_plane); - /** * drm_gem_begin_shadow_fb_access - prepares shadow framebuffers for CPU access * @plane: the plane diff --git a/include/drm/drm_gem_atomic_helper.h b/include/drm/drm_gem_atomic_helper.h index da8a4fb442b4..2deb0cd9ebbe 100644 --- a/include/drm/drm_gem_atomic_helper.h +++ b/include/drm/drm_gem_atomic_helper.h @@ -94,7 +94,6 @@ void __drm_gem_destroy_shadow_plane_state(struct drm_shadow_plane_state *shadow_ void __drm_gem_reset_shadow_plane(struct drm_plane *plane, struct drm_shadow_plane_state *shadow_plane_state); -void drm_gem_reset_shadow_plane(struct drm_plane *plane); void __drm_gem_shadow_plane_state_init(struct drm_plane *plane, struct drm_shadow_plane_state *shadow_plane_state); struct drm_plane_state *drm_gem_create_shadow_plane_state(struct drm_plane *plane); From 7e8bd554c367739c832371179da9f106f8ba1358 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Fri, 14 Aug 2026 16:56:55 +0200 Subject: [PATCH 061/213] drm/sysfb: Convert to atomic_create_state drm_sysfb_plane_reset() handles both state creation and teardown of the previous state through the reset hook, which conflates initial state allocation with hardware and software reset at resume. Convert it to drm_sysfb_plane_atomic_create_state(), which only allocates and initializes a pristine state using __drm_gem_shadow_plane_state_init(). Update DRM_SYSFB_PLANE_FUNCS to use .atomic_create_state instead of .reset. Reviewed-by: Thomas Zimmermann Link: https://patch.msgid.link/20260814-drm-no-more-plane-reset-v2-6-82d2963dd134@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/sysfb/drm_sysfb_helper.h | 4 +-- drivers/gpu/drm/sysfb/drm_sysfb_modeset.c | 30 ++++++++++++++++------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/sysfb/drm_sysfb_helper.h b/drivers/gpu/drm/sysfb/drm_sysfb_helper.h index 547f2327af5e..1c31267daa00 100644 --- a/drivers/gpu/drm/sysfb/drm_sysfb_helper.h +++ b/drivers/gpu/drm/sysfb/drm_sysfb_helper.h @@ -134,15 +134,15 @@ int drm_sysfb_plane_helper_get_scanout_buffer(struct drm_plane *plane, .atomic_disable = drm_sysfb_plane_helper_atomic_disable, \ .get_scanout_buffer = drm_sysfb_plane_helper_get_scanout_buffer -void drm_sysfb_plane_reset(struct drm_plane *plane); +struct drm_plane_state *drm_sysfb_plane_atomic_create_state(struct drm_plane *plane); struct drm_plane_state *drm_sysfb_plane_atomic_duplicate_state(struct drm_plane *plane); void drm_sysfb_plane_atomic_destroy_state(struct drm_plane *plane, struct drm_plane_state *plane_state); #define DRM_SYSFB_PLANE_FUNCS \ - .reset = drm_sysfb_plane_reset, \ .update_plane = drm_atomic_helper_update_plane, \ .disable_plane = drm_atomic_helper_disable_plane, \ + .atomic_create_state = drm_sysfb_plane_atomic_create_state, \ .atomic_duplicate_state = drm_sysfb_plane_atomic_duplicate_state, \ .atomic_destroy_state = drm_sysfb_plane_atomic_destroy_state diff --git a/drivers/gpu/drm/sysfb/drm_sysfb_modeset.c b/drivers/gpu/drm/sysfb/drm_sysfb_modeset.c index d2de29caf89e..9a4c210d6f96 100644 --- a/drivers/gpu/drm/sysfb/drm_sysfb_modeset.c +++ b/drivers/gpu/drm/sysfb/drm_sysfb_modeset.c @@ -420,20 +420,32 @@ int drm_sysfb_plane_helper_get_scanout_buffer(struct drm_plane *plane, } EXPORT_SYMBOL(drm_sysfb_plane_helper_get_scanout_buffer); -void drm_sysfb_plane_reset(struct drm_plane *plane) +/** + * drm_sysfb_plane_atomic_create_state - creates sysfb plane state + * @plane: the plane + * + * This function implements struct &drm_plane_funcs.atomic_create_state for + * sysfb planes. It allocates the plane state of type + * struct drm_sysfb_plane_state. + * + * Returns: + * A pointer to a new plane state on success, or an ERR_PTR()-encoded + * error code otherwise. + */ +struct drm_plane_state * +drm_sysfb_plane_atomic_create_state(struct drm_plane *plane) { struct drm_sysfb_plane_state *sysfb_plane_state; - if (plane->state) - drm_sysfb_plane_state_destroy(to_drm_sysfb_plane_state(plane->state)); - sysfb_plane_state = kzalloc_obj(*sysfb_plane_state); - if (sysfb_plane_state) - __drm_gem_reset_shadow_plane(plane, &sysfb_plane_state->base); - else - __drm_gem_reset_shadow_plane(plane, NULL); + if (!sysfb_plane_state) + return ERR_PTR(-ENOMEM); + + __drm_gem_shadow_plane_state_init(plane, &sysfb_plane_state->base); + + return &sysfb_plane_state->base.base; } -EXPORT_SYMBOL(drm_sysfb_plane_reset); +EXPORT_SYMBOL(drm_sysfb_plane_atomic_create_state); struct drm_plane_state *drm_sysfb_plane_atomic_duplicate_state(struct drm_plane *plane) { From 7f879284390ced83f0e81e981dec71adcfa8d838 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Fri, 14 Aug 2026 16:56:56 +0200 Subject: [PATCH 062/213] drm/simple-kms: Switch to atomic_create_state drm_simple_kms_plane_reset() handled three cases: delegating to reset_plane, using create_plane_state with manual teardown, or falling back to the default helper. This complexity existed to bridge the old reset hook with the new create_plane_state hook during the transition. Now that all simple-kms drivers implement create_plane_state instead of reset_plane, replace drm_simple_kms_plane_reset() with drm_simple_kms_plane_create_state() which only dispatches between create_plane_state and drm_atomic_helper_plane_create_state(). Wire it to .atomic_create_state instead of .reset. Reviewed-by: Thomas Zimmermann Link: https://patch.msgid.link/20260814-drm-no-more-plane-reset-v2-7-82d2963dd134@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/drm_simple_kms_helper.c | 33 +++++-------------------- 1 file changed, 6 insertions(+), 27 deletions(-) diff --git a/drivers/gpu/drm/drm_simple_kms_helper.c b/drivers/gpu/drm/drm_simple_kms_helper.c index 528968ea5c4b..c99ff74dca92 100644 --- a/drivers/gpu/drm/drm_simple_kms_helper.c +++ b/drivers/gpu/drm/drm_simple_kms_helper.c @@ -281,37 +281,16 @@ static const struct drm_plane_helper_funcs drm_simple_kms_plane_helper_funcs = { .atomic_update = drm_simple_kms_plane_atomic_update, }; -static void drm_simple_kms_plane_destroy_state(struct drm_plane *plane, - struct drm_plane_state *state); - -static void drm_simple_kms_plane_reset(struct drm_plane *plane) +static struct drm_plane_state * +drm_simple_kms_plane_create_state(struct drm_plane *plane) { struct drm_simple_display_pipe *pipe; pipe = container_of(plane, struct drm_simple_display_pipe, plane); - if (!pipe->funcs) - return drm_atomic_helper_plane_reset(plane); + if (!pipe->funcs || !pipe->funcs->create_plane_state) + return drm_atomic_helper_plane_create_state(plane); - if (pipe->funcs->create_plane_state) { - struct drm_plane_state *state; - - if (plane->state) { - drm_simple_kms_plane_destroy_state(plane, plane->state); - plane->state = NULL; - } - - state = pipe->funcs->create_plane_state(pipe); - if (WARN_ON(IS_ERR(state))) - return; - - plane->state = state; - return; - } - - if (pipe->funcs->reset_plane) - return pipe->funcs->reset_plane(pipe); - - return drm_atomic_helper_plane_reset(plane); + return pipe->funcs->create_plane_state(pipe); } static struct drm_plane_state *drm_simple_kms_plane_duplicate_state(struct drm_plane *plane) @@ -341,7 +320,7 @@ static const struct drm_plane_funcs drm_simple_kms_plane_funcs = { .update_plane = drm_atomic_helper_update_plane, .disable_plane = drm_atomic_helper_disable_plane, .destroy = drm_plane_cleanup, - .reset = drm_simple_kms_plane_reset, + .atomic_create_state = drm_simple_kms_plane_create_state, .atomic_duplicate_state = drm_simple_kms_plane_duplicate_state, .atomic_destroy_state = drm_simple_kms_plane_destroy_state, .format_mod_supported = drm_simple_kms_format_mod_supported, From 932d6e27e43fc2806654e518df730ae383543f6e Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Fri, 14 Aug 2026 16:56:57 +0200 Subject: [PATCH 063/213] drm/ssd130x: Convert to atomic_create_state The plane reset implementation creates a custom state subclass, but only initializes a pristine state without resetting any hardware. This is equivalent to what atomic_create_state expects. Convert to it. The conversion was done using the following Coccinelle semantic patch: @@ identifier funcs; symbol drm_atomic_helper_plane_reset; symbol drm_atomic_helper_plane_create_state; @@ struct drm_plane_funcs funcs = { ..., - .reset = drm_atomic_helper_plane_reset, + .atomic_create_state = drm_atomic_helper_plane_create_state, ..., }; @match_struct_reset@ identifier funcs, reset_func; @@ struct drm_plane_funcs funcs = { ..., .reset = reset_func, ..., }; @reset_uses_helpers depends on match_struct_reset@ identifier match_struct_reset.reset_func; @@ void reset_func(...) { <+... ( __drm_atomic_helper_plane_reset(...); | __drm_gem_reset_shadow_plane(...); ) ...+> } @match_struct_destroy@ identifier funcs, destroy_func; @@ struct drm_plane_funcs funcs = { ..., .atomic_destroy_state = destroy_func, ..., }; @script:python renamed_func@ old_name << match_struct_reset.reset_func; new_name; @@ if old_name.endswith("_reset"): coccinelle.new_name = old_name.replace("_reset", "_create_state") else: coccinelle.new_name = old_name @update_struct depends on match_struct_reset && reset_uses_helpers@ identifier match_struct_reset.funcs, match_struct_reset.reset_func; identifier renamed_func.new_name; @@ struct drm_plane_funcs funcs = { ..., - .reset = reset_func, + .atomic_create_state = new_name, ..., }; @drop_destroy depends on update_struct && match_struct_destroy@ identifier match_struct_reset.reset_func; identifier match_struct_destroy.destroy_func; identifier container_func; identifier P; symbol drm_atomic_helper_plane_destroy_state; symbol __drm_atomic_helper_plane_destroy_state; @@ void reset_func(struct drm_plane *P) { ... ( - if (P->state) { - <+... ( - drm_atomic_helper_plane_destroy_state(P, P->state); | - __drm_atomic_helper_plane_destroy_state(P->state); | - P->funcs->atomic_destroy_state(P, P->state); | - destroy_func(P, P->state); ) - ...+> - } | - drm_WARN_ON_ONCE(P->dev, P->state); | - WARN_ON(P->state); ) ... ( - kfree(P->state); | - kfree(container_func(P->state)); | // kfree is optional ) ( - P->state = NULL; | // plane->state clearing is optional ) ... } @drop_destroy_mtk depends on update_struct@ identifier P; symbol __drm_atomic_helper_plane_destroy_state; symbol to_mtk_plane_state; @@ void mtk_plane_reset(struct drm_plane *P) { ... - if (P->state) { - __drm_atomic_helper_plane_destroy_state(P->state); - ... - } else { ... - } ... } @transform_nv50_wndw depends on update_struct@ identifier S; @@ void nv50_wndw_reset(...) { ... - if (WARN_ON(!(S = kzalloc_obj(*S)))) + S = kzalloc_obj(*S); + if (WARN_ON(!S)) return; ... } @transform_kzalloc depends on update_struct@ identifier match_struct_reset.reset_func; identifier P, S; statement ST; statement list STL; @@ void reset_func(struct drm_plane *P) { <... S = kzalloc_obj(*S); ( - if (S) - { - STL - } + if (!S) return; + + STL | - if (S) ST + if (!S) return; + + ST ) ...> } @transform_body depends on update_struct@ identifier match_struct_reset.reset_func; identifier renamed_func.new_name; identifier S, P; expression PS; @@ - void reset_func(struct drm_plane *P) + struct drm_plane_state *new_name(struct drm_plane *P) { ... S = kzalloc_obj(*S); ... ( if (!S) { ... - return; + return ERR_PTR(-ENOMEM); } | if (WARN_ON(!S)) { ... - return; + return ERR_PTR(-ENOMEM); } | if (S == NULL) { ... - return; + return ERR_PTR(-ENOMEM); } ) ... ( - __drm_atomic_helper_plane_reset(P, PS); + __drm_atomic_helper_plane_state_init(PS, P); | - __drm_gem_reset_shadow_plane(P, PS); + __drm_gem_shadow_plane_state_init(P, PS); ) ... } @update_early_return depends on update_struct@ identifier match_struct_reset.reset_func; identifier renamed_func.new_name; identifier P; expression PS; @@ struct drm_plane_state *new_name(struct drm_plane *P) { <+... - return; + return ERR_PTR(-EINVAL); ...+> } @update_return_plane depends on update_struct@ identifier match_struct_reset.reset_func; identifier renamed_func.new_name; identifier P; expression PS; @@ struct drm_plane_state *new_name(struct drm_plane *P) { ... __drm_atomic_helper_plane_state_init(PS, P); ... + + return PS; } @update_return_shadow depends on update_struct@ identifier renamed_func.new_name; identifier P; expression PS; @@ struct drm_plane_state *new_name(struct drm_plane *P) { ... __drm_gem_shadow_plane_state_init(P, PS); ... + + return &PS->base; } Reviewed-by: Javier Martinez Canillas Link: https://patch.msgid.link/20260814-drm-no-more-plane-reset-v2-8-82d2963dd134@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/solomon/ssd130x.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/solomon/ssd130x.c b/drivers/gpu/drm/solomon/ssd130x.c index 0b0fc6fe3df2..012b399e7425 100644 --- a/drivers/gpu/drm/solomon/ssd130x.c +++ b/drivers/gpu/drm/solomon/ssd130x.c @@ -1310,17 +1310,17 @@ static void ssd133x_primary_plane_atomic_disable(struct drm_plane *plane, } /* Called during init to allocate the plane's atomic state. */ -static void ssd130x_primary_plane_reset(struct drm_plane *plane) +static struct drm_plane_state *ssd130x_primary_plane_create_state(struct drm_plane *plane) { struct ssd130x_plane_state *ssd130x_state; - drm_WARN_ON_ONCE(plane->dev, plane->state); - ssd130x_state = kzalloc_obj(*ssd130x_state); if (!ssd130x_state) - return; + return ERR_PTR(-ENOMEM); - __drm_gem_reset_shadow_plane(plane, &ssd130x_state->base); + __drm_gem_shadow_plane_state_init(plane, &ssd130x_state->base); + + return &(&ssd130x_state->base)->base; } static struct drm_plane_state *ssd130x_primary_plane_duplicate_state(struct drm_plane *plane) @@ -1383,7 +1383,7 @@ static const struct drm_plane_helper_funcs ssd130x_primary_plane_helper_funcs[] static const struct drm_plane_funcs ssd130x_primary_plane_funcs = { .update_plane = drm_atomic_helper_update_plane, .disable_plane = drm_atomic_helper_disable_plane, - .reset = ssd130x_primary_plane_reset, + .atomic_create_state = ssd130x_primary_plane_create_state, .atomic_duplicate_state = ssd130x_primary_plane_duplicate_state, .atomic_destroy_state = ssd130x_primary_plane_destroy_state, .destroy = drm_plane_cleanup, From c159654270fb5e6b826dd222a35d943c8c10cb2c Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Fri, 14 Aug 2026 16:56:58 +0200 Subject: [PATCH 064/213] drm/st7920: Convert to atomic_create_state The plane reset implementation creates a custom state subclass, but only initializes a pristine state without resetting any hardware. This is equivalent to what atomic_create_state expects. Convert to it. The conversion was done using the following Coccinelle semantic patch: @@ identifier funcs; symbol drm_atomic_helper_plane_reset; symbol drm_atomic_helper_plane_create_state; @@ struct drm_plane_funcs funcs = { ..., - .reset = drm_atomic_helper_plane_reset, + .atomic_create_state = drm_atomic_helper_plane_create_state, ..., }; @match_struct_reset@ identifier funcs, reset_func; @@ struct drm_plane_funcs funcs = { ..., .reset = reset_func, ..., }; @reset_uses_helpers depends on match_struct_reset@ identifier match_struct_reset.reset_func; @@ void reset_func(...) { <+... ( __drm_atomic_helper_plane_reset(...); | __drm_gem_reset_shadow_plane(...); ) ...+> } @match_struct_destroy@ identifier funcs, destroy_func; @@ struct drm_plane_funcs funcs = { ..., .atomic_destroy_state = destroy_func, ..., }; @script:python renamed_func@ old_name << match_struct_reset.reset_func; new_name; @@ if old_name.endswith("_reset"): coccinelle.new_name = old_name.replace("_reset", "_create_state") else: coccinelle.new_name = old_name @update_struct depends on match_struct_reset && reset_uses_helpers@ identifier match_struct_reset.funcs, match_struct_reset.reset_func; identifier renamed_func.new_name; @@ struct drm_plane_funcs funcs = { ..., - .reset = reset_func, + .atomic_create_state = new_name, ..., }; @drop_destroy depends on update_struct && match_struct_destroy@ identifier match_struct_reset.reset_func; identifier match_struct_destroy.destroy_func; identifier container_func; identifier P; symbol drm_atomic_helper_plane_destroy_state; symbol __drm_atomic_helper_plane_destroy_state; @@ void reset_func(struct drm_plane *P) { ... ( - if (P->state) { - <+... ( - drm_atomic_helper_plane_destroy_state(P, P->state); | - __drm_atomic_helper_plane_destroy_state(P->state); | - P->funcs->atomic_destroy_state(P, P->state); | - destroy_func(P, P->state); ) - ...+> - } | - drm_WARN_ON_ONCE(P->dev, P->state); | - WARN_ON(P->state); ) ... ( - kfree(P->state); | - kfree(container_func(P->state)); | // kfree is optional ) ( - P->state = NULL; | // plane->state clearing is optional ) ... } @drop_destroy_mtk depends on update_struct@ identifier P; symbol __drm_atomic_helper_plane_destroy_state; symbol to_mtk_plane_state; @@ void mtk_plane_reset(struct drm_plane *P) { ... - if (P->state) { - __drm_atomic_helper_plane_destroy_state(P->state); - ... - } else { ... - } ... } @transform_nv50_wndw depends on update_struct@ identifier S; @@ void nv50_wndw_reset(...) { ... - if (WARN_ON(!(S = kzalloc_obj(*S)))) + S = kzalloc_obj(*S); + if (WARN_ON(!S)) return; ... } @transform_kzalloc depends on update_struct@ identifier match_struct_reset.reset_func; identifier P, S; statement ST; statement list STL; @@ void reset_func(struct drm_plane *P) { <... S = kzalloc_obj(*S); ( - if (S) - { - STL - } + if (!S) return; + + STL | - if (S) ST + if (!S) return; + + ST ) ...> } @transform_body depends on update_struct@ identifier match_struct_reset.reset_func; identifier renamed_func.new_name; identifier S, P; expression PS; @@ - void reset_func(struct drm_plane *P) + struct drm_plane_state *new_name(struct drm_plane *P) { ... S = kzalloc_obj(*S); ... ( if (!S) { ... - return; + return ERR_PTR(-ENOMEM); } | if (WARN_ON(!S)) { ... - return; + return ERR_PTR(-ENOMEM); } | if (S == NULL) { ... - return; + return ERR_PTR(-ENOMEM); } ) ... ( - __drm_atomic_helper_plane_reset(P, PS); + __drm_atomic_helper_plane_state_init(PS, P); | - __drm_gem_reset_shadow_plane(P, PS); + __drm_gem_shadow_plane_state_init(P, PS); ) ... } @update_early_return depends on update_struct@ identifier match_struct_reset.reset_func; identifier renamed_func.new_name; identifier P; expression PS; @@ struct drm_plane_state *new_name(struct drm_plane *P) { <+... - return; + return ERR_PTR(-EINVAL); ...+> } @update_return_plane depends on update_struct@ identifier match_struct_reset.reset_func; identifier renamed_func.new_name; identifier P; expression PS; @@ struct drm_plane_state *new_name(struct drm_plane *P) { ... __drm_atomic_helper_plane_state_init(PS, P); ... + + return PS; } @update_return_shadow depends on update_struct@ identifier renamed_func.new_name; identifier P; expression PS; @@ struct drm_plane_state *new_name(struct drm_plane *P) { ... __drm_gem_shadow_plane_state_init(P, PS); ... + + return &PS->base; } Reviewed-by: Iker Pedrosa Link: https://patch.msgid.link/20260814-drm-no-more-plane-reset-v2-9-82d2963dd134@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 d320391801f3..0a75e9319080 100644 --- a/drivers/gpu/drm/sitronix/st7920.c +++ b/drivers/gpu/drm/sitronix/st7920.c @@ -453,17 +453,17 @@ static void st7920_primary_plane_atomic_disable(struct drm_plane *plane, } /* Called during init to allocate the plane's atomic state. */ -static void st7920_primary_plane_reset(struct drm_plane *plane) +static struct drm_plane_state *st7920_primary_plane_create_state(struct drm_plane *plane) { struct st7920_plane_state *st7920_state; - drm_WARN_ON_ONCE(plane->dev, plane->state); - st7920_state = kzalloc_obj(*st7920_state); if (!st7920_state) - return; + return ERR_PTR(-ENOMEM); - __drm_gem_reset_shadow_plane(plane, &st7920_state->base); + __drm_gem_shadow_plane_state_init(plane, &st7920_state->base); + + return &(&st7920_state->base)->base; } static struct drm_plane_state *st7920_primary_plane_duplicate_state(struct drm_plane *plane) @@ -507,7 +507,7 @@ static const struct drm_plane_helper_funcs st7920_primary_plane_helper_funcs = { static const struct drm_plane_funcs st7920_primary_plane_funcs = { .update_plane = drm_atomic_helper_update_plane, .disable_plane = drm_atomic_helper_disable_plane, - .reset = st7920_primary_plane_reset, + .atomic_create_state = st7920_primary_plane_create_state, .atomic_duplicate_state = st7920_primary_plane_duplicate_state, .atomic_destroy_state = st7920_primary_plane_destroy_state, .destroy = drm_plane_cleanup, From 3d95f93b30b811d65b70c552a06fcc592f87eb3a Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Fri, 14 Aug 2026 16:56:59 +0200 Subject: [PATCH 065/213] drm/appletbdrm: Convert to atomic_create_state The plane reset implementation creates a custom state subclass, but only initializes a pristine state without resetting any hardware. This is equivalent to what atomic_create_state expects. Convert to it. The conversion was done using the following Coccinelle semantic patch: @@ identifier funcs; symbol drm_atomic_helper_plane_reset; symbol drm_atomic_helper_plane_create_state; @@ struct drm_plane_funcs funcs = { ..., - .reset = drm_atomic_helper_plane_reset, + .atomic_create_state = drm_atomic_helper_plane_create_state, ..., }; @match_struct_reset@ identifier funcs, reset_func; @@ struct drm_plane_funcs funcs = { ..., .reset = reset_func, ..., }; @reset_uses_helpers depends on match_struct_reset@ identifier match_struct_reset.reset_func; @@ void reset_func(...) { <+... ( __drm_atomic_helper_plane_reset(...); | __drm_gem_reset_shadow_plane(...); ) ...+> } @match_struct_destroy@ identifier funcs, destroy_func; @@ struct drm_plane_funcs funcs = { ..., .atomic_destroy_state = destroy_func, ..., }; @script:python renamed_func@ old_name << match_struct_reset.reset_func; new_name; @@ if old_name.endswith("_reset"): coccinelle.new_name = old_name.replace("_reset", "_create_state") else: coccinelle.new_name = old_name @update_struct depends on match_struct_reset && reset_uses_helpers@ identifier match_struct_reset.funcs, match_struct_reset.reset_func; identifier renamed_func.new_name; @@ struct drm_plane_funcs funcs = { ..., - .reset = reset_func, + .atomic_create_state = new_name, ..., }; @drop_destroy depends on update_struct && match_struct_destroy@ identifier match_struct_reset.reset_func; identifier match_struct_destroy.destroy_func; identifier container_func; identifier P; symbol drm_atomic_helper_plane_destroy_state; symbol __drm_atomic_helper_plane_destroy_state; @@ void reset_func(struct drm_plane *P) { ... ( - if (P->state) { - <+... ( - drm_atomic_helper_plane_destroy_state(P, P->state); | - __drm_atomic_helper_plane_destroy_state(P->state); | - P->funcs->atomic_destroy_state(P, P->state); | - destroy_func(P, P->state); ) - ...+> - } | - drm_WARN_ON_ONCE(P->dev, P->state); | - WARN_ON(P->state); ) ... ( - kfree(P->state); | - kfree(container_func(P->state)); | // kfree is optional ) ( - P->state = NULL; | // plane->state clearing is optional ) ... } @drop_destroy_mtk depends on update_struct@ identifier P; symbol __drm_atomic_helper_plane_destroy_state; symbol to_mtk_plane_state; @@ void mtk_plane_reset(struct drm_plane *P) { ... - if (P->state) { - __drm_atomic_helper_plane_destroy_state(P->state); - ... - } else { ... - } ... } @transform_nv50_wndw depends on update_struct@ identifier S; @@ void nv50_wndw_reset(...) { ... - if (WARN_ON(!(S = kzalloc_obj(*S)))) + S = kzalloc_obj(*S); + if (WARN_ON(!S)) return; ... } @transform_kzalloc depends on update_struct@ identifier match_struct_reset.reset_func; identifier P, S; statement ST; statement list STL; @@ void reset_func(struct drm_plane *P) { <... S = kzalloc_obj(*S); ( - if (S) - { - STL - } + if (!S) return; + + STL | - if (S) ST + if (!S) return; + + ST ) ...> } @transform_body depends on update_struct@ identifier match_struct_reset.reset_func; identifier renamed_func.new_name; identifier S, P; expression PS; @@ - void reset_func(struct drm_plane *P) + struct drm_plane_state *new_name(struct drm_plane *P) { ... S = kzalloc_obj(*S); ... ( if (!S) { ... - return; + return ERR_PTR(-ENOMEM); } | if (WARN_ON(!S)) { ... - return; + return ERR_PTR(-ENOMEM); } | if (S == NULL) { ... - return; + return ERR_PTR(-ENOMEM); } ) ... ( - __drm_atomic_helper_plane_reset(P, PS); + __drm_atomic_helper_plane_state_init(PS, P); | - __drm_gem_reset_shadow_plane(P, PS); + __drm_gem_shadow_plane_state_init(P, PS); ) ... } @update_early_return depends on update_struct@ identifier match_struct_reset.reset_func; identifier renamed_func.new_name; identifier P; expression PS; @@ struct drm_plane_state *new_name(struct drm_plane *P) { <+... - return; + return ERR_PTR(-EINVAL); ...+> } @update_return_plane depends on update_struct@ identifier match_struct_reset.reset_func; identifier renamed_func.new_name; identifier P; expression PS; @@ struct drm_plane_state *new_name(struct drm_plane *P) { ... __drm_atomic_helper_plane_state_init(PS, P); ... + + return PS; } @update_return_shadow depends on update_struct@ identifier renamed_func.new_name; identifier P; expression PS; @@ struct drm_plane_state *new_name(struct drm_plane *P) { ... __drm_gem_shadow_plane_state_init(P, PS); ... + + return &PS->base; } Acked-by: Aditya Garg Link: https://patch.msgid.link/20260814-drm-no-more-plane-reset-v2-10-82d2963dd134@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/tiny/appletbdrm.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/tiny/appletbdrm.c b/drivers/gpu/drm/tiny/appletbdrm.c index cdd35af49892..742478198111 100644 --- a/drivers/gpu/drm/tiny/appletbdrm.c +++ b/drivers/gpu/drm/tiny/appletbdrm.c @@ -499,17 +499,17 @@ static void appletbdrm_primary_plane_helper_atomic_disable(struct drm_plane *pla drm_dev_exit(idx); } -static void appletbdrm_primary_plane_reset(struct drm_plane *plane) +static struct drm_plane_state *appletbdrm_primary_plane_create_state(struct drm_plane *plane) { struct appletbdrm_plane_state *appletbdrm_state; - WARN_ON(plane->state); - appletbdrm_state = kzalloc_obj(*appletbdrm_state); if (!appletbdrm_state) - return; + return ERR_PTR(-ENOMEM); - __drm_gem_reset_shadow_plane(plane, &appletbdrm_state->base); + __drm_gem_shadow_plane_state_init(plane, &appletbdrm_state->base); + + return &(&appletbdrm_state->base)->base; } static struct drm_plane_state *appletbdrm_primary_plane_duplicate_state(struct drm_plane *plane) @@ -561,7 +561,7 @@ static const struct drm_plane_helper_funcs appletbdrm_primary_plane_helper_funcs static const struct drm_plane_funcs appletbdrm_primary_plane_funcs = { .update_plane = drm_atomic_helper_update_plane, .disable_plane = drm_atomic_helper_disable_plane, - .reset = appletbdrm_primary_plane_reset, + .atomic_create_state = appletbdrm_primary_plane_create_state, .atomic_duplicate_state = appletbdrm_primary_plane_duplicate_state, .atomic_destroy_state = appletbdrm_primary_plane_destroy_state, .destroy = drm_plane_cleanup, From fc16c87942220651ab48bcadb777337457cb363e Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Fri, 14 Aug 2026 16:57:03 +0200 Subject: [PATCH 066/213] drm/hdlcd: Convert to atomic_create_state The plane only initializes a pristine state in its reset hook using drm_atomic_helper_plane_reset(), which is equivalent to what atomic_create_state expects. Convert to it. The conversion was done using the following Coccinelle semantic patch: @@ identifier funcs; symbol drm_atomic_helper_plane_reset; symbol drm_atomic_helper_plane_create_state; @@ struct drm_plane_funcs funcs = { ..., - .reset = drm_atomic_helper_plane_reset, + .atomic_create_state = drm_atomic_helper_plane_create_state, ..., }; @match_struct_reset@ identifier funcs, reset_func; @@ struct drm_plane_funcs funcs = { ..., .reset = reset_func, ..., }; @reset_uses_helpers depends on match_struct_reset@ identifier match_struct_reset.reset_func; @@ void reset_func(...) { <+... ( __drm_atomic_helper_plane_reset(...); | __drm_gem_reset_shadow_plane(...); ) ...+> } @match_struct_destroy@ identifier funcs, destroy_func; @@ struct drm_plane_funcs funcs = { ..., .atomic_destroy_state = destroy_func, ..., }; @script:python renamed_func@ old_name << match_struct_reset.reset_func; new_name; @@ if old_name.endswith("_reset"): coccinelle.new_name = old_name.replace("_reset", "_create_state") else: coccinelle.new_name = old_name @update_struct depends on match_struct_reset && reset_uses_helpers@ identifier match_struct_reset.funcs, match_struct_reset.reset_func; identifier renamed_func.new_name; @@ struct drm_plane_funcs funcs = { ..., - .reset = reset_func, + .atomic_create_state = new_name, ..., }; @drop_destroy depends on update_struct && match_struct_destroy@ identifier match_struct_reset.reset_func; identifier match_struct_destroy.destroy_func; identifier container_func; identifier P; symbol drm_atomic_helper_plane_destroy_state; symbol __drm_atomic_helper_plane_destroy_state; @@ void reset_func(struct drm_plane *P) { ... ( - if (P->state) { - <+... ( - drm_atomic_helper_plane_destroy_state(P, P->state); | - __drm_atomic_helper_plane_destroy_state(P->state); | - P->funcs->atomic_destroy_state(P, P->state); | - destroy_func(P, P->state); ) - ...+> - } | - drm_WARN_ON_ONCE(P->dev, P->state); | - WARN_ON(P->state); ) ... ( - kfree(P->state); | - kfree(container_func(P->state)); | // kfree is optional ) ( - P->state = NULL; | // plane->state clearing is optional ) ... } @drop_destroy_mtk depends on update_struct@ identifier P; symbol __drm_atomic_helper_plane_destroy_state; symbol to_mtk_plane_state; @@ void mtk_plane_reset(struct drm_plane *P) { ... - if (P->state) { - __drm_atomic_helper_plane_destroy_state(P->state); - ... - } else { ... - } ... } @transform_nv50_wndw depends on update_struct@ identifier S; @@ void nv50_wndw_reset(...) { ... - if (WARN_ON(!(S = kzalloc_obj(*S)))) + S = kzalloc_obj(*S); + if (WARN_ON(!S)) return; ... } @transform_kzalloc depends on update_struct@ identifier match_struct_reset.reset_func; identifier P, S; statement ST; statement list STL; @@ void reset_func(struct drm_plane *P) { <... S = kzalloc_obj(*S); ( - if (S) - { - STL - } + if (!S) return; + + STL | - if (S) ST + if (!S) return; + + ST ) ...> } @transform_body depends on update_struct@ identifier match_struct_reset.reset_func; identifier renamed_func.new_name; identifier S, P; expression PS; @@ - void reset_func(struct drm_plane *P) + struct drm_plane_state *new_name(struct drm_plane *P) { ... S = kzalloc_obj(*S); ... ( if (!S) { ... - return; + return ERR_PTR(-ENOMEM); } | if (WARN_ON(!S)) { ... - return; + return ERR_PTR(-ENOMEM); } | if (S == NULL) { ... - return; + return ERR_PTR(-ENOMEM); } ) ... ( - __drm_atomic_helper_plane_reset(P, PS); + __drm_atomic_helper_plane_state_init(PS, P); | - __drm_gem_reset_shadow_plane(P, PS); + __drm_gem_shadow_plane_state_init(P, PS); ) ... } @update_early_return depends on update_struct@ identifier match_struct_reset.reset_func; identifier renamed_func.new_name; identifier P; expression PS; @@ struct drm_plane_state *new_name(struct drm_plane *P) { <+... - return; + return ERR_PTR(-EINVAL); ...+> } @update_return_plane depends on update_struct@ identifier match_struct_reset.reset_func; identifier renamed_func.new_name; identifier P; expression PS; @@ struct drm_plane_state *new_name(struct drm_plane *P) { ... __drm_atomic_helper_plane_state_init(PS, P); ... + + return PS; } @update_return_shadow depends on update_struct@ identifier renamed_func.new_name; identifier P; expression PS; @@ struct drm_plane_state *new_name(struct drm_plane *P) { ... __drm_gem_shadow_plane_state_init(P, PS); ... + + return &PS->base; } Reviewed-by: Liviu Dudau Link: https://patch.msgid.link/20260814-drm-no-more-plane-reset-v2-14-82d2963dd134@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/arm/hdlcd_crtc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/arm/hdlcd_crtc.c b/drivers/gpu/drm/arm/hdlcd_crtc.c index 8565749a3e48..e026b9834547 100644 --- a/drivers/gpu/drm/arm/hdlcd_crtc.c +++ b/drivers/gpu/drm/arm/hdlcd_crtc.c @@ -297,7 +297,7 @@ static const struct drm_plane_helper_funcs hdlcd_plane_helper_funcs = { static const struct drm_plane_funcs hdlcd_plane_funcs = { .update_plane = drm_atomic_helper_update_plane, .disable_plane = drm_atomic_helper_disable_plane, - .reset = drm_atomic_helper_plane_reset, + .atomic_create_state = drm_atomic_helper_plane_create_state, .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, }; From a8e88a7e848e545f8a63b7220640dcc2fd6b67f8 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Fri, 14 Aug 2026 16:57:07 +0200 Subject: [PATCH 067/213] drm/imx/dcss: Convert to atomic_create_state The plane only initializes a pristine state in its reset hook using drm_atomic_helper_plane_reset(), which is equivalent to what atomic_create_state expects. Convert to it. The conversion was done using the following Coccinelle semantic patch: @@ identifier funcs; symbol drm_atomic_helper_plane_reset; symbol drm_atomic_helper_plane_create_state; @@ struct drm_plane_funcs funcs = { ..., - .reset = drm_atomic_helper_plane_reset, + .atomic_create_state = drm_atomic_helper_plane_create_state, ..., }; @match_struct_reset@ identifier funcs, reset_func; @@ struct drm_plane_funcs funcs = { ..., .reset = reset_func, ..., }; @reset_uses_helpers depends on match_struct_reset@ identifier match_struct_reset.reset_func; @@ void reset_func(...) { <+... ( __drm_atomic_helper_plane_reset(...); | __drm_gem_reset_shadow_plane(...); ) ...+> } @match_struct_destroy@ identifier funcs, destroy_func; @@ struct drm_plane_funcs funcs = { ..., .atomic_destroy_state = destroy_func, ..., }; @script:python renamed_func@ old_name << match_struct_reset.reset_func; new_name; @@ if old_name.endswith("_reset"): coccinelle.new_name = old_name.replace("_reset", "_create_state") else: coccinelle.new_name = old_name @update_struct depends on match_struct_reset && reset_uses_helpers@ identifier match_struct_reset.funcs, match_struct_reset.reset_func; identifier renamed_func.new_name; @@ struct drm_plane_funcs funcs = { ..., - .reset = reset_func, + .atomic_create_state = new_name, ..., }; @drop_destroy depends on update_struct && match_struct_destroy@ identifier match_struct_reset.reset_func; identifier match_struct_destroy.destroy_func; identifier container_func; identifier P; symbol drm_atomic_helper_plane_destroy_state; symbol __drm_atomic_helper_plane_destroy_state; @@ void reset_func(struct drm_plane *P) { ... ( - if (P->state) { - <+... ( - drm_atomic_helper_plane_destroy_state(P, P->state); | - __drm_atomic_helper_plane_destroy_state(P->state); | - P->funcs->atomic_destroy_state(P, P->state); | - destroy_func(P, P->state); ) - ...+> - } | - drm_WARN_ON_ONCE(P->dev, P->state); | - WARN_ON(P->state); ) ... ( - kfree(P->state); | - kfree(container_func(P->state)); | // kfree is optional ) ( - P->state = NULL; | // plane->state clearing is optional ) ... } @drop_destroy_mtk depends on update_struct@ identifier P; symbol __drm_atomic_helper_plane_destroy_state; symbol to_mtk_plane_state; @@ void mtk_plane_reset(struct drm_plane *P) { ... - if (P->state) { - __drm_atomic_helper_plane_destroy_state(P->state); - ... - } else { ... - } ... } @transform_nv50_wndw depends on update_struct@ identifier S; @@ void nv50_wndw_reset(...) { ... - if (WARN_ON(!(S = kzalloc_obj(*S)))) + S = kzalloc_obj(*S); + if (WARN_ON(!S)) return; ... } @transform_kzalloc depends on update_struct@ identifier match_struct_reset.reset_func; identifier P, S; statement ST; statement list STL; @@ void reset_func(struct drm_plane *P) { <... S = kzalloc_obj(*S); ( - if (S) - { - STL - } + if (!S) return; + + STL | - if (S) ST + if (!S) return; + + ST ) ...> } @transform_body depends on update_struct@ identifier match_struct_reset.reset_func; identifier renamed_func.new_name; identifier S, P; expression PS; @@ - void reset_func(struct drm_plane *P) + struct drm_plane_state *new_name(struct drm_plane *P) { ... S = kzalloc_obj(*S); ... ( if (!S) { ... - return; + return ERR_PTR(-ENOMEM); } | if (WARN_ON(!S)) { ... - return; + return ERR_PTR(-ENOMEM); } | if (S == NULL) { ... - return; + return ERR_PTR(-ENOMEM); } ) ... ( - __drm_atomic_helper_plane_reset(P, PS); + __drm_atomic_helper_plane_state_init(PS, P); | - __drm_gem_reset_shadow_plane(P, PS); + __drm_gem_shadow_plane_state_init(P, PS); ) ... } @update_early_return depends on update_struct@ identifier match_struct_reset.reset_func; identifier renamed_func.new_name; identifier P; expression PS; @@ struct drm_plane_state *new_name(struct drm_plane *P) { <+... - return; + return ERR_PTR(-EINVAL); ...+> } @update_return_plane depends on update_struct@ identifier match_struct_reset.reset_func; identifier renamed_func.new_name; identifier P; expression PS; @@ struct drm_plane_state *new_name(struct drm_plane *P) { ... __drm_atomic_helper_plane_state_init(PS, P); ... + + return PS; } @update_return_shadow depends on update_struct@ identifier renamed_func.new_name; identifier P; expression PS; @@ struct drm_plane_state *new_name(struct drm_plane *P) { ... __drm_gem_shadow_plane_state_init(P, PS); ... + + return &PS->base; } Reviewed-by: Laurentiu Palcu Link: https://patch.msgid.link/20260814-drm-no-more-plane-reset-v2-18-82d2963dd134@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/imx/dcss/dcss-plane.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/imx/dcss/dcss-plane.c b/drivers/gpu/drm/imx/dcss/dcss-plane.c index 1746dc5d4b64..303e93fd036c 100644 --- a/drivers/gpu/drm/imx/dcss/dcss-plane.c +++ b/drivers/gpu/drm/imx/dcss/dcss-plane.c @@ -96,7 +96,7 @@ static const struct drm_plane_funcs dcss_plane_funcs = { .update_plane = drm_atomic_helper_update_plane, .disable_plane = drm_atomic_helper_disable_plane, .destroy = dcss_plane_destroy, - .reset = drm_atomic_helper_plane_reset, + .atomic_create_state = drm_atomic_helper_plane_create_state, .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, .format_mod_supported = dcss_plane_format_mod_supported, From f4ff2730de74f6038fbd3750d01114e6a5b8df4c Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Fri, 14 Aug 2026 16:57:08 +0200 Subject: [PATCH 068/213] drm/ingenic: Convert to atomic_create_state The plane only initializes a pristine state in its reset hook using drm_atomic_helper_plane_reset(), which is equivalent to what atomic_create_state expects. Convert to it. The conversion was done using the following Coccinelle semantic patch: @@ identifier funcs; symbol drm_atomic_helper_plane_reset; symbol drm_atomic_helper_plane_create_state; @@ struct drm_plane_funcs funcs = { ..., - .reset = drm_atomic_helper_plane_reset, + .atomic_create_state = drm_atomic_helper_plane_create_state, ..., }; @match_struct_reset@ identifier funcs, reset_func; @@ struct drm_plane_funcs funcs = { ..., .reset = reset_func, ..., }; @reset_uses_helpers depends on match_struct_reset@ identifier match_struct_reset.reset_func; @@ void reset_func(...) { <+... ( __drm_atomic_helper_plane_reset(...); | __drm_gem_reset_shadow_plane(...); ) ...+> } @match_struct_destroy@ identifier funcs, destroy_func; @@ struct drm_plane_funcs funcs = { ..., .atomic_destroy_state = destroy_func, ..., }; @script:python renamed_func@ old_name << match_struct_reset.reset_func; new_name; @@ if old_name.endswith("_reset"): coccinelle.new_name = old_name.replace("_reset", "_create_state") else: coccinelle.new_name = old_name @update_struct depends on match_struct_reset && reset_uses_helpers@ identifier match_struct_reset.funcs, match_struct_reset.reset_func; identifier renamed_func.new_name; @@ struct drm_plane_funcs funcs = { ..., - .reset = reset_func, + .atomic_create_state = new_name, ..., }; @drop_destroy depends on update_struct && match_struct_destroy@ identifier match_struct_reset.reset_func; identifier match_struct_destroy.destroy_func; identifier container_func; identifier P; symbol drm_atomic_helper_plane_destroy_state; symbol __drm_atomic_helper_plane_destroy_state; @@ void reset_func(struct drm_plane *P) { ... ( - if (P->state) { - <+... ( - drm_atomic_helper_plane_destroy_state(P, P->state); | - __drm_atomic_helper_plane_destroy_state(P->state); | - P->funcs->atomic_destroy_state(P, P->state); | - destroy_func(P, P->state); ) - ...+> - } | - drm_WARN_ON_ONCE(P->dev, P->state); | - WARN_ON(P->state); ) ... ( - kfree(P->state); | - kfree(container_func(P->state)); | // kfree is optional ) ( - P->state = NULL; | // plane->state clearing is optional ) ... } @drop_destroy_mtk depends on update_struct@ identifier P; symbol __drm_atomic_helper_plane_destroy_state; symbol to_mtk_plane_state; @@ void mtk_plane_reset(struct drm_plane *P) { ... - if (P->state) { - __drm_atomic_helper_plane_destroy_state(P->state); - ... - } else { ... - } ... } @transform_nv50_wndw depends on update_struct@ identifier S; @@ void nv50_wndw_reset(...) { ... - if (WARN_ON(!(S = kzalloc_obj(*S)))) + S = kzalloc_obj(*S); + if (WARN_ON(!S)) return; ... } @transform_kzalloc depends on update_struct@ identifier match_struct_reset.reset_func; identifier P, S; statement ST; statement list STL; @@ void reset_func(struct drm_plane *P) { <... S = kzalloc_obj(*S); ( - if (S) - { - STL - } + if (!S) return; + + STL | - if (S) ST + if (!S) return; + + ST ) ...> } @transform_body depends on update_struct@ identifier match_struct_reset.reset_func; identifier renamed_func.new_name; identifier S, P; expression PS; @@ - void reset_func(struct drm_plane *P) + struct drm_plane_state *new_name(struct drm_plane *P) { ... S = kzalloc_obj(*S); ... ( if (!S) { ... - return; + return ERR_PTR(-ENOMEM); } | if (WARN_ON(!S)) { ... - return; + return ERR_PTR(-ENOMEM); } | if (S == NULL) { ... - return; + return ERR_PTR(-ENOMEM); } ) ... ( - __drm_atomic_helper_plane_reset(P, PS); + __drm_atomic_helper_plane_state_init(PS, P); | - __drm_gem_reset_shadow_plane(P, PS); + __drm_gem_shadow_plane_state_init(P, PS); ) ... } @update_early_return depends on update_struct@ identifier match_struct_reset.reset_func; identifier renamed_func.new_name; identifier P; expression PS; @@ struct drm_plane_state *new_name(struct drm_plane *P) { <+... - return; + return ERR_PTR(-EINVAL); ...+> } @update_return_plane depends on update_struct@ identifier match_struct_reset.reset_func; identifier renamed_func.new_name; identifier P; expression PS; @@ struct drm_plane_state *new_name(struct drm_plane *P) { ... __drm_atomic_helper_plane_state_init(PS, P); ... + + return PS; } @update_return_shadow depends on update_struct@ identifier renamed_func.new_name; identifier P; expression PS; @@ struct drm_plane_state *new_name(struct drm_plane *P) { ... __drm_gem_shadow_plane_state_init(P, PS); ... + + return &PS->base; } Acked-by: Paul Cercueil Link: https://patch.msgid.link/20260814-drm-no-more-plane-reset-v2-19-82d2963dd134@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/ingenic/ingenic-drm-drv.c | 2 +- drivers/gpu/drm/ingenic/ingenic-ipu.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/ingenic/ingenic-drm-drv.c b/drivers/gpu/drm/ingenic/ingenic-drm-drv.c index 738a80b2550f..3a58dedaaa98 100644 --- a/drivers/gpu/drm/ingenic/ingenic-drm-drv.c +++ b/drivers/gpu/drm/ingenic/ingenic-drm-drv.c @@ -987,7 +987,7 @@ static const struct drm_driver ingenic_drm_driver_data = { static const struct drm_plane_funcs ingenic_drm_primary_plane_funcs = { .update_plane = drm_atomic_helper_update_plane, .disable_plane = drm_atomic_helper_disable_plane, - .reset = drm_atomic_helper_plane_reset, + .atomic_create_state = drm_atomic_helper_plane_create_state, .destroy = drm_plane_cleanup, .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, diff --git a/drivers/gpu/drm/ingenic/ingenic-ipu.c b/drivers/gpu/drm/ingenic/ingenic-ipu.c index 56143a191f36..bf24882e8d9f 100644 --- a/drivers/gpu/drm/ingenic/ingenic-ipu.c +++ b/drivers/gpu/drm/ingenic/ingenic-ipu.c @@ -718,7 +718,7 @@ ingenic_ipu_plane_atomic_set_property(struct drm_plane *plane, static const struct drm_plane_funcs ingenic_ipu_plane_funcs = { .update_plane = drm_atomic_helper_update_plane, .disable_plane = drm_atomic_helper_disable_plane, - .reset = drm_atomic_helper_plane_reset, + .atomic_create_state = drm_atomic_helper_plane_create_state, .destroy = drm_plane_cleanup, .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, From 7075ff51eb511ee1dcf7f27a10b8de653235ef01 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Fri, 14 Aug 2026 16:57:12 +0200 Subject: [PATCH 069/213] drm/meson: Convert to atomic_create_state The plane only initializes a pristine state in its reset hook using drm_atomic_helper_plane_reset(), which is equivalent to what atomic_create_state expects. Convert to it. The conversion was done using the following Coccinelle semantic patch: @@ identifier funcs; symbol drm_atomic_helper_plane_reset; symbol drm_atomic_helper_plane_create_state; @@ struct drm_plane_funcs funcs = { ..., - .reset = drm_atomic_helper_plane_reset, + .atomic_create_state = drm_atomic_helper_plane_create_state, ..., }; @match_struct_reset@ identifier funcs, reset_func; @@ struct drm_plane_funcs funcs = { ..., .reset = reset_func, ..., }; @reset_uses_helpers depends on match_struct_reset@ identifier match_struct_reset.reset_func; @@ void reset_func(...) { <+... ( __drm_atomic_helper_plane_reset(...); | __drm_gem_reset_shadow_plane(...); ) ...+> } @match_struct_destroy@ identifier funcs, destroy_func; @@ struct drm_plane_funcs funcs = { ..., .atomic_destroy_state = destroy_func, ..., }; @script:python renamed_func@ old_name << match_struct_reset.reset_func; new_name; @@ if old_name.endswith("_reset"): coccinelle.new_name = old_name.replace("_reset", "_create_state") else: coccinelle.new_name = old_name @update_struct depends on match_struct_reset && reset_uses_helpers@ identifier match_struct_reset.funcs, match_struct_reset.reset_func; identifier renamed_func.new_name; @@ struct drm_plane_funcs funcs = { ..., - .reset = reset_func, + .atomic_create_state = new_name, ..., }; @drop_destroy depends on update_struct && match_struct_destroy@ identifier match_struct_reset.reset_func; identifier match_struct_destroy.destroy_func; identifier container_func; identifier P; symbol drm_atomic_helper_plane_destroy_state; symbol __drm_atomic_helper_plane_destroy_state; @@ void reset_func(struct drm_plane *P) { ... ( - if (P->state) { - <+... ( - drm_atomic_helper_plane_destroy_state(P, P->state); | - __drm_atomic_helper_plane_destroy_state(P->state); | - P->funcs->atomic_destroy_state(P, P->state); | - destroy_func(P, P->state); ) - ...+> - } | - drm_WARN_ON_ONCE(P->dev, P->state); | - WARN_ON(P->state); ) ... ( - kfree(P->state); | - kfree(container_func(P->state)); | // kfree is optional ) ( - P->state = NULL; | // plane->state clearing is optional ) ... } @drop_destroy_mtk depends on update_struct@ identifier P; symbol __drm_atomic_helper_plane_destroy_state; symbol to_mtk_plane_state; @@ void mtk_plane_reset(struct drm_plane *P) { ... - if (P->state) { - __drm_atomic_helper_plane_destroy_state(P->state); - ... - } else { ... - } ... } @transform_nv50_wndw depends on update_struct@ identifier S; @@ void nv50_wndw_reset(...) { ... - if (WARN_ON(!(S = kzalloc_obj(*S)))) + S = kzalloc_obj(*S); + if (WARN_ON(!S)) return; ... } @transform_kzalloc depends on update_struct@ identifier match_struct_reset.reset_func; identifier P, S; statement ST; statement list STL; @@ void reset_func(struct drm_plane *P) { <... S = kzalloc_obj(*S); ( - if (S) - { - STL - } + if (!S) return; + + STL | - if (S) ST + if (!S) return; + + ST ) ...> } @transform_body depends on update_struct@ identifier match_struct_reset.reset_func; identifier renamed_func.new_name; identifier S, P; expression PS; @@ - void reset_func(struct drm_plane *P) + struct drm_plane_state *new_name(struct drm_plane *P) { ... S = kzalloc_obj(*S); ... ( if (!S) { ... - return; + return ERR_PTR(-ENOMEM); } | if (WARN_ON(!S)) { ... - return; + return ERR_PTR(-ENOMEM); } | if (S == NULL) { ... - return; + return ERR_PTR(-ENOMEM); } ) ... ( - __drm_atomic_helper_plane_reset(P, PS); + __drm_atomic_helper_plane_state_init(PS, P); | - __drm_gem_reset_shadow_plane(P, PS); + __drm_gem_shadow_plane_state_init(P, PS); ) ... } @update_early_return depends on update_struct@ identifier match_struct_reset.reset_func; identifier renamed_func.new_name; identifier P; expression PS; @@ struct drm_plane_state *new_name(struct drm_plane *P) { <+... - return; + return ERR_PTR(-EINVAL); ...+> } @update_return_plane depends on update_struct@ identifier match_struct_reset.reset_func; identifier renamed_func.new_name; identifier P; expression PS; @@ struct drm_plane_state *new_name(struct drm_plane *P) { ... __drm_atomic_helper_plane_state_init(PS, P); ... + + return PS; } @update_return_shadow depends on update_struct@ identifier renamed_func.new_name; identifier P; expression PS; @@ struct drm_plane_state *new_name(struct drm_plane *P) { ... __drm_gem_shadow_plane_state_init(P, PS); ... + + return &PS->base; } Reviewed-by: Martin Blumenstingl Link: https://patch.msgid.link/20260814-drm-no-more-plane-reset-v2-23-82d2963dd134@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/meson/meson_overlay.c | 2 +- drivers/gpu/drm/meson/meson_plane.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/meson/meson_overlay.c b/drivers/gpu/drm/meson/meson_overlay.c index 7047807f1a3e..930119e18a4f 100644 --- a/drivers/gpu/drm/meson/meson_overlay.c +++ b/drivers/gpu/drm/meson/meson_overlay.c @@ -802,7 +802,7 @@ static const struct drm_plane_funcs meson_overlay_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_create_state = drm_atomic_helper_plane_create_state, .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, .format_mod_supported = meson_overlay_format_mod_supported, diff --git a/drivers/gpu/drm/meson/meson_plane.c b/drivers/gpu/drm/meson/meson_plane.c index 2d88fe87a11a..cf8c90158fcf 100644 --- a/drivers/gpu/drm/meson/meson_plane.c +++ b/drivers/gpu/drm/meson/meson_plane.c @@ -466,7 +466,7 @@ static const struct drm_plane_funcs meson_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_create_state = drm_atomic_helper_plane_create_state, .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, .format_mod_supported = meson_plane_format_mod_supported, From 8307577645cafef37fb4acb03063288fa0794f2f Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Fri, 14 Aug 2026 16:57:13 +0200 Subject: [PATCH 070/213] drm/msm/mdp4: Convert to atomic_create_state The plane only initializes a pristine state in its reset hook using drm_atomic_helper_plane_reset(), which is equivalent to what atomic_create_state expects. Convert to it. The conversion was done using the following Coccinelle semantic patch: @@ identifier funcs; symbol drm_atomic_helper_plane_reset; symbol drm_atomic_helper_plane_create_state; @@ struct drm_plane_funcs funcs = { ..., - .reset = drm_atomic_helper_plane_reset, + .atomic_create_state = drm_atomic_helper_plane_create_state, ..., }; @match_struct_reset@ identifier funcs, reset_func; @@ struct drm_plane_funcs funcs = { ..., .reset = reset_func, ..., }; @reset_uses_helpers depends on match_struct_reset@ identifier match_struct_reset.reset_func; @@ void reset_func(...) { <+... ( __drm_atomic_helper_plane_reset(...); | __drm_gem_reset_shadow_plane(...); ) ...+> } @match_struct_destroy@ identifier funcs, destroy_func; @@ struct drm_plane_funcs funcs = { ..., .atomic_destroy_state = destroy_func, ..., }; @script:python renamed_func@ old_name << match_struct_reset.reset_func; new_name; @@ if old_name.endswith("_reset"): coccinelle.new_name = old_name.replace("_reset", "_create_state") else: coccinelle.new_name = old_name @update_struct depends on match_struct_reset && reset_uses_helpers@ identifier match_struct_reset.funcs, match_struct_reset.reset_func; identifier renamed_func.new_name; @@ struct drm_plane_funcs funcs = { ..., - .reset = reset_func, + .atomic_create_state = new_name, ..., }; @drop_destroy depends on update_struct && match_struct_destroy@ identifier match_struct_reset.reset_func; identifier match_struct_destroy.destroy_func; identifier container_func; identifier P; symbol drm_atomic_helper_plane_destroy_state; symbol __drm_atomic_helper_plane_destroy_state; @@ void reset_func(struct drm_plane *P) { ... ( - if (P->state) { - <+... ( - drm_atomic_helper_plane_destroy_state(P, P->state); | - __drm_atomic_helper_plane_destroy_state(P->state); | - P->funcs->atomic_destroy_state(P, P->state); | - destroy_func(P, P->state); ) - ...+> - } | - drm_WARN_ON_ONCE(P->dev, P->state); | - WARN_ON(P->state); ) ... ( - kfree(P->state); | - kfree(container_func(P->state)); | // kfree is optional ) ( - P->state = NULL; | // plane->state clearing is optional ) ... } @drop_destroy_mtk depends on update_struct@ identifier P; symbol __drm_atomic_helper_plane_destroy_state; symbol to_mtk_plane_state; @@ void mtk_plane_reset(struct drm_plane *P) { ... - if (P->state) { - __drm_atomic_helper_plane_destroy_state(P->state); - ... - } else { ... - } ... } @transform_nv50_wndw depends on update_struct@ identifier S; @@ void nv50_wndw_reset(...) { ... - if (WARN_ON(!(S = kzalloc_obj(*S)))) + S = kzalloc_obj(*S); + if (WARN_ON(!S)) return; ... } @transform_kzalloc depends on update_struct@ identifier match_struct_reset.reset_func; identifier P, S; statement ST; statement list STL; @@ void reset_func(struct drm_plane *P) { <... S = kzalloc_obj(*S); ( - if (S) - { - STL - } + if (!S) return; + + STL | - if (S) ST + if (!S) return; + + ST ) ...> } @transform_body depends on update_struct@ identifier match_struct_reset.reset_func; identifier renamed_func.new_name; identifier S, P; expression PS; @@ - void reset_func(struct drm_plane *P) + struct drm_plane_state *new_name(struct drm_plane *P) { ... S = kzalloc_obj(*S); ... ( if (!S) { ... - return; + return ERR_PTR(-ENOMEM); } | if (WARN_ON(!S)) { ... - return; + return ERR_PTR(-ENOMEM); } | if (S == NULL) { ... - return; + return ERR_PTR(-ENOMEM); } ) ... ( - __drm_atomic_helper_plane_reset(P, PS); + __drm_atomic_helper_plane_state_init(PS, P); | - __drm_gem_reset_shadow_plane(P, PS); + __drm_gem_shadow_plane_state_init(P, PS); ) ... } @update_early_return depends on update_struct@ identifier match_struct_reset.reset_func; identifier renamed_func.new_name; identifier P; expression PS; @@ struct drm_plane_state *new_name(struct drm_plane *P) { <+... - return; + return ERR_PTR(-EINVAL); ...+> } @update_return_plane depends on update_struct@ identifier match_struct_reset.reset_func; identifier renamed_func.new_name; identifier P; expression PS; @@ struct drm_plane_state *new_name(struct drm_plane *P) { ... __drm_atomic_helper_plane_state_init(PS, P); ... + + return PS; } @update_return_shadow depends on update_struct@ identifier renamed_func.new_name; identifier P; expression PS; @@ struct drm_plane_state *new_name(struct drm_plane *P) { ... __drm_gem_shadow_plane_state_init(P, PS); ... + + return &PS->base; } Reviewed-by: Dmitry Baryshkov Link: https://patch.msgid.link/20260814-drm-no-more-plane-reset-v2-24-82d2963dd134@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/msm/disp/mdp4/mdp4_plane.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/msm/disp/mdp4/mdp4_plane.c b/drivers/gpu/drm/msm/disp/mdp4/mdp4_plane.c index 9459f70ce0ba..f9067e508f4c 100644 --- a/drivers/gpu/drm/msm/disp/mdp4/mdp4_plane.c +++ b/drivers/gpu/drm/msm/disp/mdp4/mdp4_plane.c @@ -71,7 +71,7 @@ static const struct drm_plane_funcs mdp4_plane_funcs = { .update_plane = drm_atomic_helper_update_plane, .disable_plane = drm_atomic_helper_disable_plane, .set_property = mdp4_plane_set_property, - .reset = drm_atomic_helper_plane_reset, + .atomic_create_state = drm_atomic_helper_plane_create_state, .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, }; From 288c9f832e966b7d1eaa4d13019d56ebfa1eeff3 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Fri, 14 Aug 2026 16:57:21 +0200 Subject: [PATCH 071/213] drm/sun4i: sun8i: Convert to atomic_create_state The plane only initializes a pristine state in its reset hook using drm_atomic_helper_plane_reset(), which is equivalent to what atomic_create_state expects. Convert to it. The conversion was done using the following Coccinelle semantic patch: @@ identifier funcs; symbol drm_atomic_helper_plane_reset; symbol drm_atomic_helper_plane_create_state; @@ struct drm_plane_funcs funcs = { ..., - .reset = drm_atomic_helper_plane_reset, + .atomic_create_state = drm_atomic_helper_plane_create_state, ..., }; @match_struct_reset@ identifier funcs, reset_func; @@ struct drm_plane_funcs funcs = { ..., .reset = reset_func, ..., }; @reset_uses_helpers depends on match_struct_reset@ identifier match_struct_reset.reset_func; @@ void reset_func(...) { <+... ( __drm_atomic_helper_plane_reset(...); | __drm_gem_reset_shadow_plane(...); ) ...+> } @match_struct_destroy@ identifier funcs, destroy_func; @@ struct drm_plane_funcs funcs = { ..., .atomic_destroy_state = destroy_func, ..., }; @script:python renamed_func@ old_name << match_struct_reset.reset_func; new_name; @@ if old_name.endswith("_reset"): coccinelle.new_name = old_name.replace("_reset", "_create_state") else: coccinelle.new_name = old_name @update_struct depends on match_struct_reset && reset_uses_helpers@ identifier match_struct_reset.funcs, match_struct_reset.reset_func; identifier renamed_func.new_name; @@ struct drm_plane_funcs funcs = { ..., - .reset = reset_func, + .atomic_create_state = new_name, ..., }; @drop_destroy depends on update_struct && match_struct_destroy@ identifier match_struct_reset.reset_func; identifier match_struct_destroy.destroy_func; identifier container_func; identifier P; symbol drm_atomic_helper_plane_destroy_state; symbol __drm_atomic_helper_plane_destroy_state; @@ void reset_func(struct drm_plane *P) { ... ( - if (P->state) { - <+... ( - drm_atomic_helper_plane_destroy_state(P, P->state); | - __drm_atomic_helper_plane_destroy_state(P->state); | - P->funcs->atomic_destroy_state(P, P->state); | - destroy_func(P, P->state); ) - ...+> - } | - drm_WARN_ON_ONCE(P->dev, P->state); | - WARN_ON(P->state); ) ... ( - kfree(P->state); | - kfree(container_func(P->state)); | // kfree is optional ) ( - P->state = NULL; | // plane->state clearing is optional ) ... } @drop_destroy_mtk depends on update_struct@ identifier P; symbol __drm_atomic_helper_plane_destroy_state; symbol to_mtk_plane_state; @@ void mtk_plane_reset(struct drm_plane *P) { ... - if (P->state) { - __drm_atomic_helper_plane_destroy_state(P->state); - ... - } else { ... - } ... } @transform_nv50_wndw depends on update_struct@ identifier S; @@ void nv50_wndw_reset(...) { ... - if (WARN_ON(!(S = kzalloc_obj(*S)))) + S = kzalloc_obj(*S); + if (WARN_ON(!S)) return; ... } @transform_kzalloc depends on update_struct@ identifier match_struct_reset.reset_func; identifier P, S; statement ST; statement list STL; @@ void reset_func(struct drm_plane *P) { <... S = kzalloc_obj(*S); ( - if (S) - { - STL - } + if (!S) return; + + STL | - if (S) ST + if (!S) return; + + ST ) ...> } @transform_body depends on update_struct@ identifier match_struct_reset.reset_func; identifier renamed_func.new_name; identifier S, P; expression PS; @@ - void reset_func(struct drm_plane *P) + struct drm_plane_state *new_name(struct drm_plane *P) { ... S = kzalloc_obj(*S); ... ( if (!S) { ... - return; + return ERR_PTR(-ENOMEM); } | if (WARN_ON(!S)) { ... - return; + return ERR_PTR(-ENOMEM); } | if (S == NULL) { ... - return; + return ERR_PTR(-ENOMEM); } ) ... ( - __drm_atomic_helper_plane_reset(P, PS); + __drm_atomic_helper_plane_state_init(PS, P); | - __drm_gem_reset_shadow_plane(P, PS); + __drm_gem_shadow_plane_state_init(P, PS); ) ... } @update_early_return depends on update_struct@ identifier match_struct_reset.reset_func; identifier renamed_func.new_name; identifier P; expression PS; @@ struct drm_plane_state *new_name(struct drm_plane *P) { <+... - return; + return ERR_PTR(-EINVAL); ...+> } @update_return_plane depends on update_struct@ identifier match_struct_reset.reset_func; identifier renamed_func.new_name; identifier P; expression PS; @@ struct drm_plane_state *new_name(struct drm_plane *P) { ... __drm_atomic_helper_plane_state_init(PS, P); ... + + return PS; } @update_return_shadow depends on update_struct@ identifier renamed_func.new_name; identifier P; expression PS; @@ struct drm_plane_state *new_name(struct drm_plane *P) { ... __drm_gem_shadow_plane_state_init(P, PS); ... + + return &PS->base; } Acked-by: Chen-Yu Tsai Link: https://patch.msgid.link/20260814-drm-no-more-plane-reset-v2-32-82d2963dd134@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/sun4i/sun8i_ui_layer.c | 2 +- drivers/gpu/drm/sun4i/sun8i_vi_layer.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/sun4i/sun8i_ui_layer.c b/drivers/gpu/drm/sun4i/sun8i_ui_layer.c index 00756ab78b4e..bad102134726 100644 --- a/drivers/gpu/drm/sun4i/sun8i_ui_layer.c +++ b/drivers/gpu/drm/sun4i/sun8i_ui_layer.c @@ -213,7 +213,7 @@ static const struct drm_plane_funcs sun8i_ui_layer_funcs = { .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, .destroy = drm_plane_cleanup, .disable_plane = drm_atomic_helper_disable_plane, - .reset = drm_atomic_helper_plane_reset, + .atomic_create_state = drm_atomic_helper_plane_create_state, .update_plane = drm_atomic_helper_update_plane, }; diff --git a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c index 09f668c8af24..2e9cda45c04e 100644 --- a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c +++ b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c @@ -293,7 +293,7 @@ static const struct drm_plane_funcs sun8i_vi_layer_funcs = { .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, .destroy = drm_plane_cleanup, .disable_plane = drm_atomic_helper_disable_plane, - .reset = drm_atomic_helper_plane_reset, + .atomic_create_state = drm_atomic_helper_plane_create_state, .update_plane = drm_atomic_helper_update_plane, }; From 0f54497a8cb6d148991efdde1f125bf4e213386c Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Fri, 14 Aug 2026 16:57:30 +0200 Subject: [PATCH 072/213] drm/komeda: Convert to atomic_create_state The plane reset implementation creates a custom state subclass, but only initializes a pristine state without resetting any hardware. This is equivalent to what atomic_create_state expects. Convert to it. The conversion was done using the following Coccinelle semantic patch: @@ identifier funcs; symbol drm_atomic_helper_plane_reset; symbol drm_atomic_helper_plane_create_state; @@ struct drm_plane_funcs funcs = { ..., - .reset = drm_atomic_helper_plane_reset, + .atomic_create_state = drm_atomic_helper_plane_create_state, ..., }; @match_struct_reset@ identifier funcs, reset_func; @@ struct drm_plane_funcs funcs = { ..., .reset = reset_func, ..., }; @reset_uses_helpers depends on match_struct_reset@ identifier match_struct_reset.reset_func; @@ void reset_func(...) { <+... ( __drm_atomic_helper_plane_reset(...); | __drm_gem_reset_shadow_plane(...); ) ...+> } @match_struct_destroy@ identifier funcs, destroy_func; @@ struct drm_plane_funcs funcs = { ..., .atomic_destroy_state = destroy_func, ..., }; @script:python renamed_func@ old_name << match_struct_reset.reset_func; new_name; @@ if old_name.endswith("_reset"): coccinelle.new_name = old_name.replace("_reset", "_create_state") else: coccinelle.new_name = old_name @update_struct depends on match_struct_reset && reset_uses_helpers@ identifier match_struct_reset.funcs, match_struct_reset.reset_func; identifier renamed_func.new_name; @@ struct drm_plane_funcs funcs = { ..., - .reset = reset_func, + .atomic_create_state = new_name, ..., }; @drop_destroy depends on update_struct && match_struct_destroy@ identifier match_struct_reset.reset_func; identifier match_struct_destroy.destroy_func; identifier container_func; identifier P; symbol drm_atomic_helper_plane_destroy_state; symbol __drm_atomic_helper_plane_destroy_state; @@ void reset_func(struct drm_plane *P) { ... ( - if (P->state) { - <+... ( - drm_atomic_helper_plane_destroy_state(P, P->state); | - __drm_atomic_helper_plane_destroy_state(P->state); | - P->funcs->atomic_destroy_state(P, P->state); | - destroy_func(P, P->state); ) - ...+> - } | - drm_WARN_ON_ONCE(P->dev, P->state); | - WARN_ON(P->state); ) ... ( - kfree(P->state); | - kfree(container_func(P->state)); | // kfree is optional ) ( - P->state = NULL; | // plane->state clearing is optional ) ... } @drop_destroy_mtk depends on update_struct@ identifier P; symbol __drm_atomic_helper_plane_destroy_state; symbol to_mtk_plane_state; @@ void mtk_plane_reset(struct drm_plane *P) { ... - if (P->state) { - __drm_atomic_helper_plane_destroy_state(P->state); - ... - } else { ... - } ... } @transform_nv50_wndw depends on update_struct@ identifier S; @@ void nv50_wndw_reset(...) { ... - if (WARN_ON(!(S = kzalloc_obj(*S)))) + S = kzalloc_obj(*S); + if (WARN_ON(!S)) return; ... } @transform_kzalloc depends on update_struct@ identifier match_struct_reset.reset_func; identifier P, S; statement ST; statement list STL; @@ void reset_func(struct drm_plane *P) { <... S = kzalloc_obj(*S); ( - if (S) - { - STL - } + if (!S) return; + + STL | - if (S) ST + if (!S) return; + + ST ) ...> } @transform_body depends on update_struct@ identifier match_struct_reset.reset_func; identifier renamed_func.new_name; identifier S, P; expression PS; @@ - void reset_func(struct drm_plane *P) + struct drm_plane_state *new_name(struct drm_plane *P) { ... S = kzalloc_obj(*S); ... ( if (!S) { ... - return; + return ERR_PTR(-ENOMEM); } | if (WARN_ON(!S)) { ... - return; + return ERR_PTR(-ENOMEM); } | if (S == NULL) { ... - return; + return ERR_PTR(-ENOMEM); } ) ... ( - __drm_atomic_helper_plane_reset(P, PS); + __drm_atomic_helper_plane_state_init(PS, P); | - __drm_gem_reset_shadow_plane(P, PS); + __drm_gem_shadow_plane_state_init(P, PS); ) ... } @update_early_return depends on update_struct@ identifier match_struct_reset.reset_func; identifier renamed_func.new_name; identifier P; expression PS; @@ struct drm_plane_state *new_name(struct drm_plane *P) { <+... - return; + return ERR_PTR(-EINVAL); ...+> } @update_return_plane depends on update_struct@ identifier match_struct_reset.reset_func; identifier renamed_func.new_name; identifier P; expression PS; @@ struct drm_plane_state *new_name(struct drm_plane *P) { ... __drm_atomic_helper_plane_state_init(PS, P); ... + + return PS; } @update_return_shadow depends on update_struct@ identifier renamed_func.new_name; identifier P; expression PS; @@ struct drm_plane_state *new_name(struct drm_plane *P) { ... __drm_gem_shadow_plane_state_init(P, PS); ... + + return &PS->base; } Reviewed-by: Liviu Dudau Link: https://patch.msgid.link/20260814-drm-no-more-plane-reset-v2-41-82d2963dd134@kernel.org Signed-off-by: Maxime Ripard --- .../gpu/drm/arm/display/komeda/komeda_plane.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_plane.c b/drivers/gpu/drm/arm/display/komeda/komeda_plane.c index 57a4a0c7d404..955dbef5d34d 100644 --- a/drivers/gpu/drm/arm/display/komeda/komeda_plane.c +++ b/drivers/gpu/drm/arm/display/komeda/komeda_plane.c @@ -132,19 +132,17 @@ static void komeda_plane_destroy(struct drm_plane *plane) kfree(to_kplane(plane)); } -static void komeda_plane_reset(struct drm_plane *plane) +static struct drm_plane_state *komeda_plane_create_state(struct drm_plane *plane) { struct komeda_plane_state *state; - if (plane->state) - __drm_atomic_helper_plane_destroy_state(plane->state); - - kfree(plane->state); - plane->state = NULL; - state = kzalloc_obj(*state); - if (state) - __drm_atomic_helper_plane_reset(plane, &state->base); + if (!state) + return ERR_PTR(-ENOMEM); + + __drm_atomic_helper_plane_state_init(&state->base, plane); + + return &state->base; } static struct drm_plane_state * @@ -188,7 +186,7 @@ static const struct drm_plane_funcs komeda_plane_funcs = { .update_plane = drm_atomic_helper_update_plane, .disable_plane = drm_atomic_helper_disable_plane, .destroy = komeda_plane_destroy, - .reset = komeda_plane_reset, + .atomic_create_state = komeda_plane_create_state, .atomic_duplicate_state = komeda_plane_atomic_duplicate_state, .atomic_destroy_state = komeda_plane_atomic_destroy_state, .format_mod_supported = komeda_plane_format_mod_supported, From f24a2a07984c1c98d126998e46244b8774960c3e Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Fri, 14 Aug 2026 16:57:31 +0200 Subject: [PATCH 073/213] drm/malidp: Convert to atomic_create_state The plane reset implementation creates a custom state subclass, but only initializes a pristine state without resetting any hardware. This is equivalent to what atomic_create_state expects. Convert to it. The conversion was done using the following Coccinelle semantic patch: @@ identifier funcs; symbol drm_atomic_helper_plane_reset; symbol drm_atomic_helper_plane_create_state; @@ struct drm_plane_funcs funcs = { ..., - .reset = drm_atomic_helper_plane_reset, + .atomic_create_state = drm_atomic_helper_plane_create_state, ..., }; @match_struct_reset@ identifier funcs, reset_func; @@ struct drm_plane_funcs funcs = { ..., .reset = reset_func, ..., }; @reset_uses_helpers depends on match_struct_reset@ identifier match_struct_reset.reset_func; @@ void reset_func(...) { <+... ( __drm_atomic_helper_plane_reset(...); | __drm_gem_reset_shadow_plane(...); ) ...+> } @match_struct_destroy@ identifier funcs, destroy_func; @@ struct drm_plane_funcs funcs = { ..., .atomic_destroy_state = destroy_func, ..., }; @script:python renamed_func@ old_name << match_struct_reset.reset_func; new_name; @@ if old_name.endswith("_reset"): coccinelle.new_name = old_name.replace("_reset", "_create_state") else: coccinelle.new_name = old_name @update_struct depends on match_struct_reset && reset_uses_helpers@ identifier match_struct_reset.funcs, match_struct_reset.reset_func; identifier renamed_func.new_name; @@ struct drm_plane_funcs funcs = { ..., - .reset = reset_func, + .atomic_create_state = new_name, ..., }; @drop_destroy depends on update_struct && match_struct_destroy@ identifier match_struct_reset.reset_func; identifier match_struct_destroy.destroy_func; identifier container_func; identifier P; symbol drm_atomic_helper_plane_destroy_state; symbol __drm_atomic_helper_plane_destroy_state; @@ void reset_func(struct drm_plane *P) { ... ( - if (P->state) { - <+... ( - drm_atomic_helper_plane_destroy_state(P, P->state); | - __drm_atomic_helper_plane_destroy_state(P->state); | - P->funcs->atomic_destroy_state(P, P->state); | - destroy_func(P, P->state); ) - ...+> - } | - drm_WARN_ON_ONCE(P->dev, P->state); | - WARN_ON(P->state); ) ... ( - kfree(P->state); | - kfree(container_func(P->state)); | // kfree is optional ) ( - P->state = NULL; | // plane->state clearing is optional ) ... } @drop_destroy_mtk depends on update_struct@ identifier P; symbol __drm_atomic_helper_plane_destroy_state; symbol to_mtk_plane_state; @@ void mtk_plane_reset(struct drm_plane *P) { ... - if (P->state) { - __drm_atomic_helper_plane_destroy_state(P->state); - ... - } else { ... - } ... } @transform_nv50_wndw depends on update_struct@ identifier S; @@ void nv50_wndw_reset(...) { ... - if (WARN_ON(!(S = kzalloc_obj(*S)))) + S = kzalloc_obj(*S); + if (WARN_ON(!S)) return; ... } @transform_kzalloc depends on update_struct@ identifier match_struct_reset.reset_func; identifier P, S; statement ST; statement list STL; @@ void reset_func(struct drm_plane *P) { <... S = kzalloc_obj(*S); ( - if (S) - { - STL - } + if (!S) return; + + STL | - if (S) ST + if (!S) return; + + ST ) ...> } @transform_body depends on update_struct@ identifier match_struct_reset.reset_func; identifier renamed_func.new_name; identifier S, P; expression PS; @@ - void reset_func(struct drm_plane *P) + struct drm_plane_state *new_name(struct drm_plane *P) { ... S = kzalloc_obj(*S); ... ( if (!S) { ... - return; + return ERR_PTR(-ENOMEM); } | if (WARN_ON(!S)) { ... - return; + return ERR_PTR(-ENOMEM); } | if (S == NULL) { ... - return; + return ERR_PTR(-ENOMEM); } ) ... ( - __drm_atomic_helper_plane_reset(P, PS); + __drm_atomic_helper_plane_state_init(PS, P); | - __drm_gem_reset_shadow_plane(P, PS); + __drm_gem_shadow_plane_state_init(P, PS); ) ... } @update_early_return depends on update_struct@ identifier match_struct_reset.reset_func; identifier renamed_func.new_name; identifier P; expression PS; @@ struct drm_plane_state *new_name(struct drm_plane *P) { <+... - return; + return ERR_PTR(-EINVAL); ...+> } @update_return_plane depends on update_struct@ identifier match_struct_reset.reset_func; identifier renamed_func.new_name; identifier P; expression PS; @@ struct drm_plane_state *new_name(struct drm_plane *P) { ... __drm_atomic_helper_plane_state_init(PS, P); ... + + return PS; } @update_return_shadow depends on update_struct@ identifier renamed_func.new_name; identifier P; expression PS; @@ struct drm_plane_state *new_name(struct drm_plane *P) { ... __drm_gem_shadow_plane_state_init(P, PS); ... + + return &PS->base; } Reviewed-by: Liviu Dudau Link: https://patch.msgid.link/20260814-drm-no-more-plane-reset-v2-42-82d2963dd134@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/arm/malidp_planes.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/arm/malidp_planes.c b/drivers/gpu/drm/arm/malidp_planes.c index 60b3f2bb153b..d971ed6eb6eb 100644 --- a/drivers/gpu/drm/arm/malidp_planes.c +++ b/drivers/gpu/drm/arm/malidp_planes.c @@ -73,17 +73,17 @@ * allocate a new empty object. We just need enough space to store * a malidp_plane_state instead of a drm_plane_state. */ -static void malidp_plane_reset(struct drm_plane *plane) +static struct drm_plane_state *malidp_plane_create_state(struct drm_plane *plane) { - struct malidp_plane_state *state = to_malidp_plane_state(plane->state); + struct malidp_plane_state *state; - if (state) - __drm_atomic_helper_plane_destroy_state(&state->base); - kfree(state); - plane->state = NULL; state = kzalloc_obj(*state); - if (state) - __drm_atomic_helper_plane_reset(plane, &state->base); + if (!state) + return ERR_PTR(-ENOMEM); + + __drm_atomic_helper_plane_state_init(&state->base, plane); + + return &state->base; } static struct @@ -252,7 +252,7 @@ static bool malidp_format_mod_supported_per_plane(struct drm_plane *plane, static const struct drm_plane_funcs malidp_de_plane_funcs = { .update_plane = drm_atomic_helper_update_plane, .disable_plane = drm_atomic_helper_disable_plane, - .reset = malidp_plane_reset, + .atomic_create_state = malidp_plane_create_state, .atomic_duplicate_state = malidp_duplicate_plane_state, .atomic_destroy_state = malidp_destroy_plane_state, .atomic_print_state = malidp_plane_atomic_print_state, From b599e05cd507e9c28221121046c06577396e2ce1 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Fri, 14 Aug 2026 16:57:38 +0200 Subject: [PATCH 074/213] drm/msm/dpu1: Convert to atomic_create_state The plane reset implementation creates a custom state subclass, but only initializes a pristine state without resetting any hardware. This is equivalent to what atomic_create_state expects. Convert to it. The conversion was done using the following Coccinelle semantic patch: @@ identifier funcs; symbol drm_atomic_helper_plane_reset; symbol drm_atomic_helper_plane_create_state; @@ struct drm_plane_funcs funcs = { ..., - .reset = drm_atomic_helper_plane_reset, + .atomic_create_state = drm_atomic_helper_plane_create_state, ..., }; @match_struct_reset@ identifier funcs, reset_func; @@ struct drm_plane_funcs funcs = { ..., .reset = reset_func, ..., }; @reset_uses_helpers depends on match_struct_reset@ identifier match_struct_reset.reset_func; @@ void reset_func(...) { <+... ( __drm_atomic_helper_plane_reset(...); | __drm_gem_reset_shadow_plane(...); ) ...+> } @match_struct_destroy@ identifier funcs, destroy_func; @@ struct drm_plane_funcs funcs = { ..., .atomic_destroy_state = destroy_func, ..., }; @script:python renamed_func@ old_name << match_struct_reset.reset_func; new_name; @@ if old_name.endswith("_reset"): coccinelle.new_name = old_name.replace("_reset", "_create_state") else: coccinelle.new_name = old_name @update_struct depends on match_struct_reset && reset_uses_helpers@ identifier match_struct_reset.funcs, match_struct_reset.reset_func; identifier renamed_func.new_name; @@ struct drm_plane_funcs funcs = { ..., - .reset = reset_func, + .atomic_create_state = new_name, ..., }; @drop_destroy depends on update_struct && match_struct_destroy@ identifier match_struct_reset.reset_func; identifier match_struct_destroy.destroy_func; identifier container_func; identifier P; symbol drm_atomic_helper_plane_destroy_state; symbol __drm_atomic_helper_plane_destroy_state; @@ void reset_func(struct drm_plane *P) { ... ( - if (P->state) { - <+... ( - drm_atomic_helper_plane_destroy_state(P, P->state); | - __drm_atomic_helper_plane_destroy_state(P->state); | - P->funcs->atomic_destroy_state(P, P->state); | - destroy_func(P, P->state); ) - ...+> - } | - drm_WARN_ON_ONCE(P->dev, P->state); | - WARN_ON(P->state); ) ... ( - kfree(P->state); | - kfree(container_func(P->state)); | // kfree is optional ) ( - P->state = NULL; | // plane->state clearing is optional ) ... } @drop_destroy_mtk depends on update_struct@ identifier P; symbol __drm_atomic_helper_plane_destroy_state; symbol to_mtk_plane_state; @@ void mtk_plane_reset(struct drm_plane *P) { ... - if (P->state) { - __drm_atomic_helper_plane_destroy_state(P->state); - ... - } else { ... - } ... } @transform_nv50_wndw depends on update_struct@ identifier S; @@ void nv50_wndw_reset(...) { ... - if (WARN_ON(!(S = kzalloc_obj(*S)))) + S = kzalloc_obj(*S); + if (WARN_ON(!S)) return; ... } @transform_kzalloc depends on update_struct@ identifier match_struct_reset.reset_func; identifier P, S; statement ST; statement list STL; @@ void reset_func(struct drm_plane *P) { <... S = kzalloc_obj(*S); ( - if (S) - { - STL - } + if (!S) return; + + STL | - if (S) ST + if (!S) return; + + ST ) ...> } @transform_body depends on update_struct@ identifier match_struct_reset.reset_func; identifier renamed_func.new_name; identifier S, P; expression PS; @@ - void reset_func(struct drm_plane *P) + struct drm_plane_state *new_name(struct drm_plane *P) { ... S = kzalloc_obj(*S); ... ( if (!S) { ... - return; + return ERR_PTR(-ENOMEM); } | if (WARN_ON(!S)) { ... - return; + return ERR_PTR(-ENOMEM); } | if (S == NULL) { ... - return; + return ERR_PTR(-ENOMEM); } ) ... ( - __drm_atomic_helper_plane_reset(P, PS); + __drm_atomic_helper_plane_state_init(PS, P); | - __drm_gem_reset_shadow_plane(P, PS); + __drm_gem_shadow_plane_state_init(P, PS); ) ... } @update_early_return depends on update_struct@ identifier match_struct_reset.reset_func; identifier renamed_func.new_name; identifier P; expression PS; @@ struct drm_plane_state *new_name(struct drm_plane *P) { <+... - return; + return ERR_PTR(-EINVAL); ...+> } @update_return_plane depends on update_struct@ identifier match_struct_reset.reset_func; identifier renamed_func.new_name; identifier P; expression PS; @@ struct drm_plane_state *new_name(struct drm_plane *P) { ... __drm_atomic_helper_plane_state_init(PS, P); ... + + return PS; } @update_return_shadow depends on update_struct@ identifier renamed_func.new_name; identifier P; expression PS; @@ struct drm_plane_state *new_name(struct drm_plane *P) { ... __drm_gem_shadow_plane_state_init(P, PS); ... + + return &PS->base; } Reviewed-by: Dmitry Baryshkov Link: https://patch.msgid.link/20260814-drm-no-more-plane-reset-v2-49-82d2963dd134@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/msm/disp/dpu1/dpu_plane.c | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_plane.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_plane.c index 7b92082d35a6..1a471cdf767b 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_plane.c +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_plane.c @@ -1728,32 +1728,28 @@ static void dpu_plane_atomic_print_state(struct drm_printer *p, } } -static void dpu_plane_reset(struct drm_plane *plane) +static struct drm_plane_state *dpu_plane_create_state(struct drm_plane *plane) { struct dpu_plane *pdpu; struct dpu_plane_state *pstate; if (!plane) { DPU_ERROR("invalid plane\n"); - return; + return ERR_PTR(-EINVAL); } pdpu = to_dpu_plane(plane); DPU_DEBUG_PLANE(pdpu, "\n"); - /* remove previous state, if present */ - if (plane->state) { - dpu_plane_destroy_state(plane, plane->state); - plane->state = NULL; - } - pstate = kzalloc_obj(*pstate); if (!pstate) { DPU_ERROR_PLANE(pdpu, "failed to allocate state\n"); - return; + return ERR_PTR(-ENOMEM); } - __drm_atomic_helper_plane_reset(plane, &pstate->base); + __drm_atomic_helper_plane_state_init(&pstate->base, plane); + + return &pstate->base; } #ifdef CONFIG_DEBUG_FS @@ -1796,7 +1792,7 @@ static bool dpu_plane_format_mod_supported(struct drm_plane *plane, static const struct drm_plane_funcs dpu_plane_funcs = { .update_plane = drm_atomic_helper_update_plane, .disable_plane = drm_atomic_helper_disable_plane, - .reset = dpu_plane_reset, + .atomic_create_state = dpu_plane_create_state, .atomic_duplicate_state = dpu_plane_duplicate_state, .atomic_destroy_state = dpu_plane_destroy_state, .atomic_print_state = dpu_plane_atomic_print_state, From eec66e23e819fc35b9fd2d5b01edb16e97e39625 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Fri, 14 Aug 2026 16:57:39 +0200 Subject: [PATCH 075/213] drm/msm/mdp5: Convert to atomic_create_state The plane reset implementation creates a custom state subclass, but only initializes a pristine state without resetting any hardware. This is equivalent to what atomic_create_state expects. Convert to it. The conversion was done using the following Coccinelle semantic patch: @@ identifier funcs; symbol drm_atomic_helper_plane_reset; symbol drm_atomic_helper_plane_create_state; @@ struct drm_plane_funcs funcs = { ..., - .reset = drm_atomic_helper_plane_reset, + .atomic_create_state = drm_atomic_helper_plane_create_state, ..., }; @match_struct_reset@ identifier funcs, reset_func; @@ struct drm_plane_funcs funcs = { ..., .reset = reset_func, ..., }; @reset_uses_helpers depends on match_struct_reset@ identifier match_struct_reset.reset_func; @@ void reset_func(...) { <+... ( __drm_atomic_helper_plane_reset(...); | __drm_gem_reset_shadow_plane(...); ) ...+> } @match_struct_destroy@ identifier funcs, destroy_func; @@ struct drm_plane_funcs funcs = { ..., .atomic_destroy_state = destroy_func, ..., }; @script:python renamed_func@ old_name << match_struct_reset.reset_func; new_name; @@ if old_name.endswith("_reset"): coccinelle.new_name = old_name.replace("_reset", "_create_state") else: coccinelle.new_name = old_name @update_struct depends on match_struct_reset && reset_uses_helpers@ identifier match_struct_reset.funcs, match_struct_reset.reset_func; identifier renamed_func.new_name; @@ struct drm_plane_funcs funcs = { ..., - .reset = reset_func, + .atomic_create_state = new_name, ..., }; @drop_destroy depends on update_struct && match_struct_destroy@ identifier match_struct_reset.reset_func; identifier match_struct_destroy.destroy_func; identifier container_func; identifier P; symbol drm_atomic_helper_plane_destroy_state; symbol __drm_atomic_helper_plane_destroy_state; @@ void reset_func(struct drm_plane *P) { ... ( - if (P->state) { - <+... ( - drm_atomic_helper_plane_destroy_state(P, P->state); | - __drm_atomic_helper_plane_destroy_state(P->state); | - P->funcs->atomic_destroy_state(P, P->state); | - destroy_func(P, P->state); ) - ...+> - } | - drm_WARN_ON_ONCE(P->dev, P->state); | - WARN_ON(P->state); ) ... ( - kfree(P->state); | - kfree(container_func(P->state)); | // kfree is optional ) ( - P->state = NULL; | // plane->state clearing is optional ) ... } @drop_destroy_mtk depends on update_struct@ identifier P; symbol __drm_atomic_helper_plane_destroy_state; symbol to_mtk_plane_state; @@ void mtk_plane_reset(struct drm_plane *P) { ... - if (P->state) { - __drm_atomic_helper_plane_destroy_state(P->state); - ... - } else { ... - } ... } @transform_nv50_wndw depends on update_struct@ identifier S; @@ void nv50_wndw_reset(...) { ... - if (WARN_ON(!(S = kzalloc_obj(*S)))) + S = kzalloc_obj(*S); + if (WARN_ON(!S)) return; ... } @transform_kzalloc depends on update_struct@ identifier match_struct_reset.reset_func; identifier P, S; statement ST; statement list STL; @@ void reset_func(struct drm_plane *P) { <... S = kzalloc_obj(*S); ( - if (S) - { - STL - } + if (!S) return; + + STL | - if (S) ST + if (!S) return; + + ST ) ...> } @transform_body depends on update_struct@ identifier match_struct_reset.reset_func; identifier renamed_func.new_name; identifier S, P; expression PS; @@ - void reset_func(struct drm_plane *P) + struct drm_plane_state *new_name(struct drm_plane *P) { ... S = kzalloc_obj(*S); ... ( if (!S) { ... - return; + return ERR_PTR(-ENOMEM); } | if (WARN_ON(!S)) { ... - return; + return ERR_PTR(-ENOMEM); } | if (S == NULL) { ... - return; + return ERR_PTR(-ENOMEM); } ) ... ( - __drm_atomic_helper_plane_reset(P, PS); + __drm_atomic_helper_plane_state_init(PS, P); | - __drm_gem_reset_shadow_plane(P, PS); + __drm_gem_shadow_plane_state_init(P, PS); ) ... } @update_early_return depends on update_struct@ identifier match_struct_reset.reset_func; identifier renamed_func.new_name; identifier P; expression PS; @@ struct drm_plane_state *new_name(struct drm_plane *P) { <+... - return; + return ERR_PTR(-EINVAL); ...+> } @update_return_plane depends on update_struct@ identifier match_struct_reset.reset_func; identifier renamed_func.new_name; identifier P; expression PS; @@ struct drm_plane_state *new_name(struct drm_plane *P) { ... __drm_atomic_helper_plane_state_init(PS, P); ... + + return PS; } @update_return_shadow depends on update_struct@ identifier renamed_func.new_name; identifier P; expression PS; @@ struct drm_plane_state *new_name(struct drm_plane *P) { ... __drm_gem_shadow_plane_state_init(P, PS); ... + + return &PS->base; } Reviewed-by: Dmitry Baryshkov Link: https://patch.msgid.link/20260814-drm-no-more-plane-reset-v2-50-82d2963dd134@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/msm/disp/mdp5/mdp5_plane.c | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_plane.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_plane.c index 841f444a8d68..7e878c698247 100644 --- a/drivers/gpu/drm/msm/disp/mdp5/mdp5_plane.c +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_plane.c @@ -80,19 +80,15 @@ mdp5_plane_atomic_print_state(struct drm_printer *p, drm_printf(p, "\tstage=%s\n", stage2name(pstate->stage)); } -static void mdp5_plane_reset(struct drm_plane *plane) +static struct drm_plane_state *mdp5_plane_create_state(struct drm_plane *plane) { struct mdp5_plane_state *mdp5_state; - - if (plane->state) - __drm_atomic_helper_plane_destroy_state(plane->state); - - kfree(to_mdp5_plane_state(plane->state)); - plane->state = NULL; mdp5_state = kzalloc_obj(*mdp5_state); if (!mdp5_state) - return; - __drm_atomic_helper_plane_reset(plane, &mdp5_state->base); + return ERR_PTR(-ENOMEM); + __drm_atomic_helper_plane_state_init(&mdp5_state->base, plane); + + return &mdp5_state->base; } static struct drm_plane_state * @@ -126,7 +122,7 @@ static void mdp5_plane_destroy_state(struct drm_plane *plane, static const struct drm_plane_funcs mdp5_plane_funcs = { .update_plane = drm_atomic_helper_update_plane, .disable_plane = drm_atomic_helper_disable_plane, - .reset = mdp5_plane_reset, + .atomic_create_state = mdp5_plane_create_state, .atomic_duplicate_state = mdp5_plane_duplicate_state, .atomic_destroy_state = mdp5_plane_destroy_state, .atomic_print_state = mdp5_plane_atomic_print_state, From 5a7530adceaee703ac63fe33e87888733e9aacec Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Fri, 14 Aug 2026 16:57:46 +0200 Subject: [PATCH 076/213] drm/vc4: Convert to atomic_create_state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The plane reset implementation creates a custom state subclass, but only initializes a pristine state without resetting any hardware. This is equivalent to what atomic_create_state expects. Convert to it. The conversion was done using the following Coccinelle semantic patch: @@ identifier funcs; symbol drm_atomic_helper_plane_reset; symbol drm_atomic_helper_plane_create_state; @@ struct drm_plane_funcs funcs = { ..., - .reset = drm_atomic_helper_plane_reset, + .atomic_create_state = drm_atomic_helper_plane_create_state, ..., }; @match_struct_reset@ identifier funcs, reset_func; @@ struct drm_plane_funcs funcs = { ..., .reset = reset_func, ..., }; @reset_uses_helpers depends on match_struct_reset@ identifier match_struct_reset.reset_func; @@ void reset_func(...) { <+... ( __drm_atomic_helper_plane_reset(...); | __drm_gem_reset_shadow_plane(...); ) ...+> } @match_struct_destroy@ identifier funcs, destroy_func; @@ struct drm_plane_funcs funcs = { ..., .atomic_destroy_state = destroy_func, ..., }; @script:python renamed_func@ old_name << match_struct_reset.reset_func; new_name; @@ if old_name.endswith("_reset"): coccinelle.new_name = old_name.replace("_reset", "_create_state") else: coccinelle.new_name = old_name @update_struct depends on match_struct_reset && reset_uses_helpers@ identifier match_struct_reset.funcs, match_struct_reset.reset_func; identifier renamed_func.new_name; @@ struct drm_plane_funcs funcs = { ..., - .reset = reset_func, + .atomic_create_state = new_name, ..., }; @drop_destroy depends on update_struct && match_struct_destroy@ identifier match_struct_reset.reset_func; identifier match_struct_destroy.destroy_func; identifier container_func; identifier P; symbol drm_atomic_helper_plane_destroy_state; symbol __drm_atomic_helper_plane_destroy_state; @@ void reset_func(struct drm_plane *P) { ... ( - if (P->state) { - <+... ( - drm_atomic_helper_plane_destroy_state(P, P->state); | - __drm_atomic_helper_plane_destroy_state(P->state); | - P->funcs->atomic_destroy_state(P, P->state); | - destroy_func(P, P->state); ) - ...+> - } | - drm_WARN_ON_ONCE(P->dev, P->state); | - WARN_ON(P->state); ) ... ( - kfree(P->state); | - kfree(container_func(P->state)); | // kfree is optional ) ( - P->state = NULL; | // plane->state clearing is optional ) ... } @drop_destroy_mtk depends on update_struct@ identifier P; symbol __drm_atomic_helper_plane_destroy_state; symbol to_mtk_plane_state; @@ void mtk_plane_reset(struct drm_plane *P) { ... - if (P->state) { - __drm_atomic_helper_plane_destroy_state(P->state); - ... - } else { ... - } ... } @transform_nv50_wndw depends on update_struct@ identifier S; @@ void nv50_wndw_reset(...) { ... - if (WARN_ON(!(S = kzalloc_obj(*S)))) + S = kzalloc_obj(*S); + if (WARN_ON(!S)) return; ... } @transform_kzalloc depends on update_struct@ identifier match_struct_reset.reset_func; identifier P, S; statement ST; statement list STL; @@ void reset_func(struct drm_plane *P) { <... S = kzalloc_obj(*S); ( - if (S) - { - STL - } + if (!S) return; + + STL | - if (S) ST + if (!S) return; + + ST ) ...> } @transform_body depends on update_struct@ identifier match_struct_reset.reset_func; identifier renamed_func.new_name; identifier S, P; expression PS; @@ - void reset_func(struct drm_plane *P) + struct drm_plane_state *new_name(struct drm_plane *P) { ... S = kzalloc_obj(*S); ... ( if (!S) { ... - return; + return ERR_PTR(-ENOMEM); } | if (WARN_ON(!S)) { ... - return; + return ERR_PTR(-ENOMEM); } | if (S == NULL) { ... - return; + return ERR_PTR(-ENOMEM); } ) ... ( - __drm_atomic_helper_plane_reset(P, PS); + __drm_atomic_helper_plane_state_init(PS, P); | - __drm_gem_reset_shadow_plane(P, PS); + __drm_gem_shadow_plane_state_init(P, PS); ) ... } @update_early_return depends on update_struct@ identifier match_struct_reset.reset_func; identifier renamed_func.new_name; identifier P; expression PS; @@ struct drm_plane_state *new_name(struct drm_plane *P) { <+... - return; + return ERR_PTR(-EINVAL); ...+> } @update_return_plane depends on update_struct@ identifier match_struct_reset.reset_func; identifier renamed_func.new_name; identifier P; expression PS; @@ struct drm_plane_state *new_name(struct drm_plane *P) { ... __drm_atomic_helper_plane_state_init(PS, P); ... + + return PS; } @update_return_shadow depends on update_struct@ identifier renamed_func.new_name; identifier P; expression PS; @@ struct drm_plane_state *new_name(struct drm_plane *P) { ... __drm_gem_shadow_plane_state_init(P, PS); ... + + return &PS->base; } Reviewed-by: Maíra Canal Link: https://patch.msgid.link/20260814-drm-no-more-plane-reset-v2-57-82d2963dd134@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/vc4/vc4_plane.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/vc4/vc4_plane.c b/drivers/gpu/drm/vc4/vc4_plane.c index 6c433270214d..20c9fc9283a8 100644 --- a/drivers/gpu/drm/vc4/vc4_plane.c +++ b/drivers/gpu/drm/vc4/vc4_plane.c @@ -363,20 +363,17 @@ static void vc4_plane_destroy_state(struct drm_plane *plane, } /* Called during init to allocate the plane's atomic state. */ -static void vc4_plane_reset(struct drm_plane *plane) +static struct drm_plane_state *vc4_plane_create_state(struct drm_plane *plane) { struct vc4_plane_state *vc4_state; - if (plane->state) - __drm_atomic_helper_plane_destroy_state(plane->state); - - kfree(plane->state); - vc4_state = kzalloc_obj(*vc4_state); if (!vc4_state) - return; + return ERR_PTR(-ENOMEM); - __drm_atomic_helper_plane_reset(plane, &vc4_state->base); + __drm_atomic_helper_plane_state_init(&vc4_state->base, plane); + + return &vc4_state->base; } static void vc4_dlist_counter_increment(struct vc4_plane_state *vc4_state) @@ -2495,7 +2492,7 @@ static bool vc4_format_mod_supported(struct drm_plane *plane, static const struct drm_plane_funcs vc4_plane_funcs = { .update_plane = drm_atomic_helper_update_plane, .disable_plane = drm_atomic_helper_disable_plane, - .reset = vc4_plane_reset, + .atomic_create_state = vc4_plane_create_state, .atomic_duplicate_state = vc4_plane_duplicate_state, .atomic_destroy_state = vc4_plane_destroy_state, .format_mod_supported = vc4_format_mod_supported, From d3d1e0c4343385fb343a552e4f3b6b97d5762fc2 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Fri, 14 Aug 2026 16:57:49 +0200 Subject: [PATCH 077/213] drm/tegra: Convert to atomic_create_state The plane reset implementation creates a custom state subclass, but only initializes a pristine state without resetting any hardware. This is equivalent to what atomic_create_state expects. Convert to it. Reviewed-by: Mikko Perttunen Link: https://patch.msgid.link/20260814-drm-no-more-plane-reset-v2-60-82d2963dd134@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/tegra/plane.c | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/drivers/gpu/drm/tegra/plane.c b/drivers/gpu/drm/tegra/plane.c index 0cb30910773f..2148b0c59815 100644 --- a/drivers/gpu/drm/tegra/plane.c +++ b/drivers/gpu/drm/tegra/plane.c @@ -24,28 +24,24 @@ static void tegra_plane_destroy(struct drm_plane *plane) kfree(p); } -static void tegra_plane_reset(struct drm_plane *plane) +static struct drm_plane_state *tegra_plane_atomic_create_state(struct drm_plane *plane) { struct tegra_plane *p = to_tegra_plane(plane); struct tegra_plane_state *state; unsigned int i; - if (plane->state) - __drm_atomic_helper_plane_destroy_state(plane->state); - - kfree(plane->state); - plane->state = NULL; - state = kzalloc_obj(*state); - if (state) { - plane->state = &state->base; - plane->state->plane = plane; - plane->state->zpos = p->index; - plane->state->normalized_zpos = p->index; + if (!state) + return ERR_PTR(-ENOMEM); - for (i = 0; i < 3; i++) - state->iova[i] = DMA_MAPPING_ERROR; - } + state->base.plane = plane; + state->base.zpos = p->index; + state->base.normalized_zpos = p->index; + + for (i = 0; i < 3; i++) + state->iova[i] = DMA_MAPPING_ERROR; + + return &state->base; } static struct drm_plane_state * @@ -131,7 +127,7 @@ const struct drm_plane_funcs tegra_plane_funcs = { .update_plane = drm_atomic_helper_update_plane, .disable_plane = drm_atomic_helper_disable_plane, .destroy = tegra_plane_destroy, - .reset = tegra_plane_reset, + .atomic_create_state = tegra_plane_atomic_create_state, .atomic_duplicate_state = tegra_plane_atomic_duplicate_state, .atomic_destroy_state = tegra_plane_atomic_destroy_state, .format_mod_supported = tegra_plane_format_mod_supported, From ae3592a2b58a9acc6e6211440f0f638b79b96e9b Mon Sep 17 00:00:00 2001 From: Haikun Zhou Date: Thu, 20 Aug 2026 09:25:54 +0800 Subject: [PATCH 078/213] drm/panel-edp: Support MNE007QS3-F and TM140VDXP15 edp panel MNE007QS3-F raw edid: 00 ff ff ff ff ff ff 00 0e 77 87 14 00 00 00 00 0f 24 01 04 a5 1e 13 78 03 2c c5 94 5c 59 95 29 1e 50 54 00 00 00 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 35 30 80 a0 70 b0 24 40 30 20 66 00 2d bc 10 00 00 1a 00 00 00 fd 00 28 3c 4b 4b 10 01 0a 20 20 20 20 20 20 00 00 00 fe 00 3d 4c 20 20 20 20 20 20 20 20 20 20 ff 00 00 00 fc 00 4d 4e 45 30 30 37 51 53 33 2d 46 0a 20 01 41 70 20 79 02 00 21 00 1d c8 0b 5d 07 80 07 b0 04 80 3f 79 5c 9e 05 95 9c f2 1d 02 45 54 b0 5c b0 5c 00 42 12 78 22 00 14 8c 5a 02 85 7f 07 9f 00 2f 80 1f 00 af 04 23 00 05 00 05 00 2b 00 0c 27 00 28 3b 00 00 27 00 28 3b 00 00 2e 00 06 00 42 b0 5c b0 5c 81 00 1e 72 1a 00 00 03 01 28 3c 00 00 53 ff 53 ff 3c 00 00 00 00 e3 05 04 00 e6 06 01 01 53 53 ff 00 00 00 00 00 00 00 00 00 7e 90 TM140VDXP15 raw edid: 00 ff ff ff ff ff ff 00 51 a1 37 21 00 00 00 00 0b 24 01 04 a5 1e 13 78 03 2b a5 97 5b 57 8c 28 20 50 54 00 00 00 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 24 31 80 a0 70 b0 3c 40 30 20 a6 00 2d bc 10 00 00 1a 00 00 00 fd 00 28 3c 4c 4c 10 01 0a 20 20 20 20 20 20 00 00 00 fe 00 3d 4c 20 20 20 20 20 20 20 20 20 20 ff 00 00 00 fc 00 54 4d 31 34 30 56 44 58 50 31 35 0a 20 01 dd 70 20 79 02 00 21 01 1d c2 0b 58 07 80 07 b0 04 80 71 69 5b 79 d5 8c 87 82 20 02 45 54 b0 5c b0 5c 00 42 12 78 22 00 14 3f 66 02 85 7f 07 9f 00 2f 80 1f 00 af 04 3b 00 05 00 05 00 2b 00 0c 27 00 28 3b 00 00 27 00 28 3b 00 00 2e 00 06 00 42 b0 5c b0 5c 81 00 17 72 1a 00 00 03 01 28 3c 00 00 53 ff 53 ff 3c 00 00 00 00 e3 05 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0a 90 Signed-off-by: Haikun Zhou Reviewed-by: Douglas Anderson Signed-off-by: Douglas Anderson Link: https://patch.msgid.link/20260820012554.4038893-1-zhouhaikun5@huaqin.corp-partner.google.com --- drivers/gpu/drm/panel/panel-edp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/panel/panel-edp.c b/drivers/gpu/drm/panel/panel-edp.c index 2b455369ed70..96783d5e27fc 100644 --- a/drivers/gpu/drm/panel/panel-edp.c +++ b/drivers/gpu/drm/panel/panel-edp.c @@ -2143,6 +2143,7 @@ static const struct edp_panel_entry edp_panels[] = { EDP_PANEL_ENTRY('C', 'S', 'W', 0x146e, &delay_80_500_e50_d50, "MNE007QB3-1"), EDP_PANEL_ENTRY('C', 'S', 'W', 0x147c, &delay_200_500_e50_d100, "MNE007QB3-1"), EDP_PANEL_ENTRY('C', 'S', 'W', 0x1486, &delay_200_500_e50_d100, "MNE001BS6-2"), + EDP_PANEL_ENTRY('C', 'S', 'W', 0x1487, &delay_200_500_e50, "MNE007QS3-F"), EDP_PANEL_ENTRY('C', 'S', 'W', 0x1519, &delay_200_500_e80_d50, "MNF601BS1-3"), EDP_PANEL_ENTRY('C', 'S', 'W', 0x1529, &delay_200_500_e80_d50, "MNF307QS3-2"), EDP_PANEL_ENTRY('C', 'S', 'W', 0x153f, &delay_200_500_e200_d50, "MNF601BS4-1"), @@ -2203,6 +2204,7 @@ static const struct edp_panel_entry edp_panels[] = { EDP_PANEL_ENTRY('T', 'M', 'A', 0x0811, &delay_200_500_e80_d50, "TM140VDXP01-04"), EDP_PANEL_ENTRY('T', 'M', 'A', 0x2094, &delay_200_500_e50_d100, "TL140VDMS03-01"), + EDP_PANEL_ENTRY('T', 'M', 'A', 0x2137, &delay_200_500_e50, "TM140VDXP15"), EDP_PANEL_ENTRY('T', 'M', 'A', 0x2139, &delay_200_500_e80_pu100, "TM156VDXP25"), { /* sentinal */ } From 2d697fd246338ef7fe748c8bcf5798b9ee76e3f0 Mon Sep 17 00:00:00 2001 From: Dawid Osuchowski Date: Thu, 20 Aug 2026 12:48:10 +0200 Subject: [PATCH 079/213] drm/print: add drm_warn_ratelimited logging macro Add a drm_warn_ratelimited() macro to complement the existing drm_err_ratelimited(), providing rate-limited logging at warning level for drivers that need it. Signed-off-by: Dawid Osuchowski Reviewed-by: Karol Wachowski Signed-off-by: Karol Wachowski Link: https://patch.msgid.link/20260820104811.803146-2-dawid.osuchowski@linux.intel.com --- include/drm/drm_print.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/drm/drm_print.h b/include/drm/drm_print.h index 2adc5ac688e1..0805903c093b 100644 --- a/include/drm/drm_print.h +++ b/include/drm/drm_print.h @@ -644,6 +644,9 @@ static inline struct device *__drm_to_dev(const struct drm_device *drm) #define drm_err_ratelimited(drm, fmt, ...) \ __drm_printk((drm), err, _ratelimited, "*ERROR* " fmt, ##__VA_ARGS__) +#define drm_warn_ratelimited(drm, fmt, ...) \ + __drm_printk((drm), warn, _ratelimited, fmt, ##__VA_ARGS__) + #define drm_dbg_core(drm, fmt, ...) \ drm_dev_dbg(__drm_to_dev(drm), DRM_UT_CORE, fmt, ##__VA_ARGS__) From 66159dccf14dbaa786337a06d8a2c8f71b675c03 Mon Sep 17 00:00:00 2001 From: Dawid Osuchowski Date: Thu, 20 Aug 2026 12:48:11 +0200 Subject: [PATCH 080/213] accel/ivpu: Use drm_warn_ratelimited macro Fix ivpu_warn_ratelimited() to properly use WARN logging level instead of an ERROR. Signed-off-by: Dawid Osuchowski Reviewed-by: Karol Wachowski Signed-off-by: Karol Wachowski Link: https://patch.msgid.link/20260820104811.803146-3-dawid.osuchowski@linux.intel.com --- drivers/accel/ivpu/ivpu_drv.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/accel/ivpu/ivpu_drv.h b/drivers/accel/ivpu/ivpu_drv.h index 86d7c9966cac..8f29f1dc52b9 100644 --- a/drivers/accel/ivpu/ivpu_drv.h +++ b/drivers/accel/ivpu/ivpu_drv.h @@ -91,7 +91,7 @@ drm_warn(&(vdev)->drm, "%s(): " fmt, __func__, ##__VA_ARGS__) #define ivpu_warn_ratelimited(vdev, fmt, ...) \ - drm_err_ratelimited(&(vdev)->drm, "%s(): " fmt, __func__, ##__VA_ARGS__) + drm_warn_ratelimited(&(vdev)->drm, "%s(): " fmt, __func__, ##__VA_ARGS__) #define ivpu_info(vdev, fmt, ...) drm_info(&(vdev)->drm, fmt, ##__VA_ARGS__) From 315caca109d678de5ac8aa187beb349c6152ea87 Mon Sep 17 00:00:00 2001 From: Michael Kelley Date: Wed, 5 Aug 2026 13:37:49 -0700 Subject: [PATCH 081/213] drm_hyperv: Remove support for synth video protocol of old Hyper-V hosts With the overall removal of Linux support for running on Hyper-V hosts earlier than WS2016 and Windows 10, it's no longer necessary to support older synthetic video protocols. Remove the support. Signed-off-by: Michael Kelley Signed-off-by: Hamza Mahfooz Link: https://patch.msgid.link/20260805203751.2678-5-mhklinux@outlook.com --- drivers/gpu/drm/hyperv/hyperv_drm.h | 1 - drivers/gpu/drm/hyperv/hyperv_drm_proto.c | 44 ++++++----------------- 2 files changed, 10 insertions(+), 35 deletions(-) diff --git a/drivers/gpu/drm/hyperv/hyperv_drm.h b/drivers/gpu/drm/hyperv/hyperv_drm.h index 78136ec2c2f4..29e6f54af15f 100644 --- a/drivers/gpu/drm/hyperv/hyperv_drm.h +++ b/drivers/gpu/drm/hyperv/hyperv_drm.h @@ -29,7 +29,6 @@ struct hv_drm_device { unsigned long fb_base; unsigned long fb_size; struct completion wait; - u32 synthvid_version; u32 mmio_megabytes; bool dirt_needed; diff --git a/drivers/gpu/drm/hyperv/hyperv_drm_proto.c b/drivers/gpu/drm/hyperv/hyperv_drm_proto.c index f0ef627b4898..1d09bba1efdd 100644 --- a/drivers/gpu/drm/hyperv/hyperv_drm_proto.c +++ b/drivers/gpu/drm/hyperv/hyperv_drm_proto.c @@ -17,7 +17,7 @@ #define SYNTHVID_VER_GET_MAJOR(ver) (ver & 0x0000ffff) #define SYNTHVID_VER_GET_MINOR(ver) ((ver & 0xffff0000) >> 16) -/* Support for VERSION_WIN7 is removed. #define is retained for reference. */ +/* Support for WIN7 and WIN8 is removed. #define's retained for reference. */ #define SYNTHVID_VERSION_WIN7 SYNTHVID_VERSION(3, 0) #define SYNTHVID_VERSION_WIN8 SYNTHVID_VERSION(3, 2) #define SYNTHVID_VERSION_WIN10 SYNTHVID_VERSION(3, 5) @@ -181,16 +181,6 @@ struct synthvid_msg { }; } __packed; -static inline bool hv_drm_version_ge(u32 ver1, u32 ver2) -{ - if (SYNTHVID_VER_GET_MAJOR(ver1) > SYNTHVID_VER_GET_MAJOR(ver2) || - (SYNTHVID_VER_GET_MAJOR(ver1) == SYNTHVID_VER_GET_MAJOR(ver2) && - SYNTHVID_VER_GET_MINOR(ver1) >= SYNTHVID_VER_GET_MINOR(ver2))) - return true; - - return false; -} - static inline int hv_drm_sendpacket(struct hv_device *hdev, struct synthvid_msg *msg) { static atomic64_t request_id = ATOMIC64_INIT(0); @@ -236,7 +226,6 @@ static int hv_drm_negotiate_version(struct hv_device *hdev, u32 ver) return -ENODEV; } - hv->synthvid_version = ver; drm_info(dev, "Synthvid Version major %d, minor %d\n", SYNTHVID_VER_GET_MAJOR(ver), SYNTHVID_VER_GET_MINOR(ver)); @@ -557,23 +546,12 @@ int hv_drm_connect_vsp(struct hv_device *hdev) return ret; } - /* Negotiate the protocol version with host */ - switch (vmbus_proto_version) { - case VERSION_WIN10: - case VERSION_WIN10_V5: - ret = hv_drm_negotiate_version(hdev, SYNTHVID_VERSION_WIN10); - if (!ret) - break; - fallthrough; - case VERSION_WIN8: - case VERSION_WIN8_1: - ret = hv_drm_negotiate_version(hdev, SYNTHVID_VERSION_WIN8); - break; - default: - ret = hv_drm_negotiate_version(hdev, SYNTHVID_VERSION_WIN10); - break; - } - + /* + * Negotiate the protocol version with host. Since support for hosts + * older than WIN10 has been removed from Linux, only negotiate the + * WIN10 version. + */ + ret = hv_drm_negotiate_version(hdev, SYNTHVID_VERSION_WIN10); if (ret) { drm_err(dev, "Synthetic video device version not accepted %d\n", ret); goto error; @@ -581,11 +559,9 @@ int hv_drm_connect_vsp(struct hv_device *hdev) hv->screen_depth = SYNTHVID_DEPTH_WIN8; - if (hv_drm_version_ge(hv->synthvid_version, SYNTHVID_VERSION_WIN10)) { - ret = hv_drm_get_supported_resolution(hdev); - if (ret) - drm_err(dev, "Failed to get supported resolution from host, use default\n"); - } + ret = hv_drm_get_supported_resolution(hdev); + if (ret) + drm_err(dev, "Failed to get supported resolution from host, use default\n"); if (!hv->screen_width_max) { hv->screen_width_max = SYNTHVID_WIDTH_WIN8; From 59b68932fea75846a36b248014eec9f6daa93d91 Mon Sep 17 00:00:00 2001 From: Sean Chang Date: Wed, 19 Aug 2026 00:09:13 +0800 Subject: [PATCH 082/213] drm/ttm: drop stale @ctx kernel-doc entry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ttm_bo_alloc_state no longer has a ctx member, but its kernel-doc comment still documents @ctx. Remove the stale entry so the documentation matches the struct. Signed-off-by: Sean Chang Link: https://lore.kernel.org/r/20260818160914.108313-2-seanwascoding@gmail.com Reviewed-by: Christian König Signed-off-by: Christian König --- drivers/gpu/drm/ttm/ttm_bo.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/gpu/drm/ttm/ttm_bo.c b/drivers/gpu/drm/ttm/ttm_bo.c index ef56c18ded1b..943297b49dc5 100644 --- a/drivers/gpu/drm/ttm/ttm_bo.c +++ b/drivers/gpu/drm/ttm/ttm_bo.c @@ -506,7 +506,6 @@ struct ttm_bo_alloc_state { * * @bo: The buffer to allocate the backing store of * @place: The place to attempt allocation in - * @ctx: ttm_operation_ctx associated with this allocation * @force_space: If we should evict buffers to force space * @res: On allocation success, the resulting struct ttm_resource. * @alloc_state: Object holding allocation state such as charged cgroups. From ec99b095aebecf20b9c8ace885d582cd01aae161 Mon Sep 17 00:00:00 2001 From: Sean Chang Date: Wed, 19 Aug 2026 00:09:14 +0800 Subject: [PATCH 083/213] drm/ttm: make ttm_swap_ops static MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ttm_swap_ops is not used outside ttm_bo.c and is not exported via EXPORT_SYMBOL(), so give it static linkage to keep it file-local and avoid polluting the global symbol namespace. Signed-off-by: Sean Chang Link: https://lore.kernel.org/r/20260818160914.108313-3-seanwascoding@gmail.com Reviewed-by: Christian König Signed-off-by: Christian König --- drivers/gpu/drm/ttm/ttm_bo.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/ttm/ttm_bo.c b/drivers/gpu/drm/ttm/ttm_bo.c index 943297b49dc5..1b9477381a1d 100644 --- a/drivers/gpu/drm/ttm/ttm_bo.c +++ b/drivers/gpu/drm/ttm/ttm_bo.c @@ -1449,7 +1449,7 @@ ttm_bo_swapout_cb(struct ttm_lru_walk *walk, struct ttm_buffer_object *bo) return ret; } -const struct ttm_lru_walk_ops ttm_swap_ops = { +static const struct ttm_lru_walk_ops ttm_swap_ops = { .process_bo = ttm_bo_swapout_cb, }; From 8a04262e0aad0905cfa46ccaf52d6771dae335d1 Mon Sep 17 00:00:00 2001 From: Mallesh Koujalagi Date: Sun, 23 Aug 2026 06:03:50 +0530 Subject: [PATCH 084/213] drm/drv: Convert wedged event string building to seq_buf event_string[] has a fixed size of WEDGE_STR_LEN (32) bytes. The original scnprintf()-based loop required a manual pre-flight bounds check. Replace the manual bookkeeping with seq_buf, which tracks overflow internally. seq_buf_printf() writes each "method," token into the buffer. On overflow, a warning is issued, len retains the position of the last successful write, so the trailing comma is stripped cleanly without including any partial method name in the uevent payload. Assisted-by: Claude:claude-Sonnet-4.6 Signed-off-by: Mallesh Koujalagi Reviewed-by: Raag Jadav Link: https://patch.msgid.link/20260823003349.587495-2-mallesh.koujalagi@intel.com Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/drm_drv.c | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c index 3c570f9393b9..71a93131746e 100644 --- a/drivers/gpu/drm/drm_drv.c +++ b/drivers/gpu/drm/drm_drv.c @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -566,27 +567,31 @@ static const char *drm_get_wedge_recovery(unsigned int opt) int drm_dev_wedged_event(struct drm_device *dev, unsigned long method, struct drm_wedge_task_info *info) { - char event_string[WEDGE_STR_LEN], pid_string[PID_STR_LEN], comm_string[COMM_STR_LEN]; - char *envp[] = { event_string, NULL, NULL, NULL }; - const char *recovery = NULL; - unsigned int len, opt; + DECLARE_SEQ_BUF(event_string, WEDGE_STR_LEN); + char pid_string[PID_STR_LEN], comm_string[COMM_STR_LEN]; + char *envp[4] = { }; + unsigned int len = 0, opt; - len = scnprintf(event_string, sizeof(event_string), "%s", "WEDGED="); + seq_buf_puts(&event_string, "WEDGED="); + envp[0] = event_string.buffer; for_each_set_bit(opt, &method, BITS_PER_TYPE(method)) { - recovery = drm_get_wedge_recovery(opt); + const char *recovery = drm_get_wedge_recovery(opt); if (drm_WARN_ONCE(dev, !recovery, "invalid recovery method %u\n", opt)) break; - len += scnprintf(event_string + len, sizeof(event_string) - len, "%s,", recovery); + if (drm_WARN_ON_ONCE(dev, seq_buf_printf(&event_string, "%s,", recovery))) + break; + + len = seq_buf_used(&event_string); } - if (recovery) - /* Get rid of trailing comma */ - event_string[len - 1] = '\0'; + if (len) + /* Strip trailing comma; also discards any partial overflow entry */ + event_string.buffer[len - 1] = '\0'; else - /* Caller is unsure about recovery, do the best we can at this point. */ - snprintf(event_string, sizeof(event_string), "%s", "WEDGED=unknown"); + /* No complete entry written, do the best we can at this point. */ + snprintf(event_string.buffer, event_string.size, "%s", "WEDGED=unknown"); drm_info(dev, "device wedged, %s\n", method == DRM_WEDGE_RECOVERY_NONE ? "but no recovery needed" : "needs recovery"); From 014da1db5e6449a3578fba8a22866ea98cdbbc99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Paku=C5=82a?= Date: Thu, 30 Jul 2026 13:17:52 -0400 Subject: [PATCH 085/213] drm/edid: parse HDMI 2.1 gaming (ALLM/VRR) capabilities from HF-VSDB MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Parse the HDMI 2.1 gaming-related capabilities advertised in the HDMI Forum VSDB (HF-VSDB) and expose them through struct drm_hdmi_info so drivers can consume them. Add struct drm_hdmi_vrr_cap describing the sink's VRR capabilities: Fast VActive (Quick Frame Transport), Negative M VRR, Cinema VRR, MDelta, and the VRRmin/VRRmax range, together with a "supported" flag derived from that range. Add the fapa_start_location and allm (Auto Low Latency Mode) flags to struct drm_hdmi_info. drm_parse_hdmi_gaming_info() reads byte 8 of the HF-VSDB for the FAPA/ALLM/FVA/CNMVRR/CinemaVRR/MDelta flags and bytes 9-10 for VRRmin/VRRmax. Per HDMI 2.1, VRR is considered supported when VRRmin is within 1-48 and VRRmax is either 0 (maximum based on the video mode) or >= 100. It is invoked from drm_parse_hdmi_forum_scds(), and the parsed values are logged for debugging. Signed-off-by: Fangzhi Zuo Signed-off-by: Tomasz Pakuła Tested-by: Bernhard Berger Reviewed-by: Harry Wentland Signed-off-by: Harry Wentland Link: https://patch.msgid.link/20260730171754.704049-2-jerry.zuo@amd.com --- drivers/gpu/drm/drm_edid.c | 42 +++++++++++++++++++++++++++++++++ include/drm/drm_connector.h | 47 +++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+) diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index 98dd72d986d5..9990a836d0b6 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -6210,6 +6210,33 @@ static void drm_parse_ycbcr420_deep_color_info(struct drm_connector *connector, hdmi->y420_dc_modes = dc_mask; } +static void drm_parse_hdmi_gaming_info(struct drm_hdmi_info *hdmi, const u8 *db) +{ + struct drm_hdmi_vrr_cap *vrr = &hdmi->vrr_cap; + + if (cea_db_payload_len(db) < 8) + return; + + hdmi->fapa_start_location = db[8] & DRM_EDID_FAPA_START_LOCATION; + hdmi->allm = db[8] & DRM_EDID_ALLM; + vrr->fva = db[8] & DRM_EDID_FVA; + vrr->cnmvrr = db[8] & DRM_EDID_CNMVRR; + vrr->cinema_vrr = db[8] & DRM_EDID_CINEMA_VRR; + vrr->mdelta = db[8] & DRM_EDID_MDELTA; + + if (cea_db_payload_len(db) < 9) + return; + + vrr->vrr_min = db[9] & DRM_EDID_VRR_MIN_MASK; + vrr->supported = (vrr->vrr_min > 0 && vrr->vrr_min <= 48); + + if (cea_db_payload_len(db) < 10) + return; + + vrr->vrr_max = (db[9] & DRM_EDID_VRR_MAX_UPPER_MASK) << 2 | db[10]; + vrr->supported &= (vrr->vrr_max == 0 || vrr->vrr_max >= 100); +} + static void drm_parse_dsc_info(struct drm_hdmi_dsc_cap *hdmi_dsc, const u8 *hf_scds) { @@ -6336,6 +6363,8 @@ static void drm_parse_hdmi_forum_scds(struct drm_connector *connector, drm_parse_ycbcr420_deep_color_info(connector, hf_scds); + drm_parse_hdmi_gaming_info(&connector->display_info.hdmi, hf_scds); + if (cea_db_payload_len(hf_scds) >= 11 && hf_scds[11]) { drm_parse_dsc_info(hdmi_dsc, hf_scds); dsc_support = true; @@ -6345,6 +6374,19 @@ static void drm_parse_hdmi_forum_scds(struct drm_connector *connector, "[CONNECTOR:%d:%s] HF-VSDB: max TMDS clock: %d KHz, HDMI 2.1 support: %s, DSC 1.2 support: %s\n", connector->base.id, connector->name, max_tmds_clock, str_yes_no(max_frl_rate), str_yes_no(dsc_support)); + drm_dbg_kms(connector->dev, + "[CONNECTOR:%d:%s] FAPA in blanking: %s, ALLM support: %s, Fast Vactive support: %s\n", + connector->base.id, connector->name, str_yes_no(hdmi->fapa_start_location), + str_yes_no(hdmi->allm), str_yes_no(hdmi->vrr_cap.fva)); + drm_dbg_kms(connector->dev, + "[CONNECTOR:%d:%s] Negative M VRR support: %s, CinemaVRR support: %s, Mdelta: %d\n", + connector->base.id, connector->name, str_yes_no(hdmi->vrr_cap.cnmvrr), + str_yes_no(hdmi->vrr_cap.cinema_vrr), hdmi->vrr_cap.mdelta); + drm_dbg_kms(connector->dev, + "[CONNECTOR:%d:%s] VRRmin: %u, VRRmax: %u, VRR supported: %s\n", + connector->base.id, connector->name, hdmi->vrr_cap.vrr_min, + hdmi->vrr_cap.vrr_max, str_yes_no(hdmi->vrr_cap.supported)); + } static void drm_parse_hdmi_deep_color_info(struct drm_connector *connector, diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h index 48059e7073e5..c61345e6fb08 100644 --- a/include/drm/drm_connector.h +++ b/include/drm/drm_connector.h @@ -254,6 +254,44 @@ struct drm_scdc { struct drm_scrambling scrambling; }; +/** + * struct drm_hdmi_vrr_cap - Information about VRR capabilities of a HDMI sink + * + * Describes the VRR support provided by HDMI 2.1 sink. The information is + * fetched fom additional HFVSDB blocks defined for HDMI 2.1. + */ +struct drm_hdmi_vrr_cap { + /** @fva: flag for Fast VActive (Quick Frame Transport) support */ + bool fva; + + /** @mcnmvrr: flag for Negative M VRR support */ + bool cnmvrr; + + /** @mcinema_vrr: flag for Cinema VRR support */ + bool cinema_vrr; + + /** @mdelta: flag for limited frame-to-frame compensation support */ + bool mdelta; + + /** + * @vrr_min : minimum supported variable refresh rate in Hz. + * Valid values only inide 1 - 48 range + */ + u16 vrr_min; + + /** + * @vrr_max : maximum supported variable refresh rate in Hz (optional). + * Valid values are either 0 (max based on video mode) or >= 100 + */ + u16 vrr_max; + + /** + * @supported: flag for vrr support based on checking for VRRmin and + * VRRmax values having correct values. + */ + bool supported; +}; + /** * struct drm_hdmi_dsc_cap - DSC capabilities of HDMI sink * @@ -330,6 +368,15 @@ struct drm_hdmi_info { /** @max_lanes: supported by sink */ u8 max_lanes; + /** @fapa_start_location: flag for the FAPA in blanking support */ + bool fapa_start_location; + + /** @allm: flag for Auto Low Latency Mode support by sink */ + bool allm; + + /** @vrr_cap: VRR capabilities of the sink */ + struct drm_hdmi_vrr_cap vrr_cap; + /** @dsc_cap: DSC capabilities of the sink */ struct drm_hdmi_dsc_cap dsc_cap; }; From 35b9103349b463e63b1616d1c90037feb3a1ecd2 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Fri, 10 Jul 2026 18:59:35 +0800 Subject: [PATCH 086/213] accel: ethosu: Remove redundant dev_err() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_threaded_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err() calls. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260710105937.409992-2-panchuang@vivo.com Signed-off-by: Rob Herring (Arm) --- drivers/accel/ethosu/ethosu_job.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/accel/ethosu/ethosu_job.c b/drivers/accel/ethosu/ethosu_job.c index 74f3493fb11a..cdc0d61ee53d 100644 --- a/drivers/accel/ethosu/ethosu_job.c +++ b/drivers/accel/ethosu/ethosu_job.c @@ -338,10 +338,8 @@ int ethosu_job_init(struct ethosu_device *edev) ethosu_job_irq_handler_thread, IRQF_SHARED, KBUILD_MODNAME, edev); - if (ret) { - dev_err(dev, "failed to request irq\n"); + if (ret) return ret; - } edev->fence_context = dma_fence_context_alloc(1); From 8778fc64962306ea0486d26aa877494ff882f1a1 Mon Sep 17 00:00:00 2001 From: Shixiong Ou Date: Fri, 21 Aug 2026 16:47:33 +0800 Subject: [PATCH 087/213] drm/sched: Fix incorrect test description in drm_sched_basic_cases The second test case in drm_sched_basic_cases[] has num_entities = 1 but its description claims "across multiple entities". Fix it to accurately reflect that the dependency chain operates within a single entity. Signed-off-by: Shixiong Ou Signed-off-by: Philipp Stanner Link: https://patch.msgid.link/20260821084733.541572-1-oushixiong1025@163.com --- drivers/gpu/drm/scheduler/tests/tests_basic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/scheduler/tests/tests_basic.c b/drivers/gpu/drm/scheduler/tests/tests_basic.c index da5c4952780f..34bbb2c27913 100644 --- a/drivers/gpu/drm/scheduler/tests/tests_basic.c +++ b/drivers/gpu/drm/scheduler/tests/tests_basic.c @@ -90,7 +90,7 @@ static const struct drm_sched_basic_params drm_sched_basic_cases[] = { .num_entities = 1, }, { - .description = "A chain of dependent jobs across multiple entities", + .description = "A chain of dependent jobs in a single entity", .queue_depth = 100, .job_us = 1000, .num_entities = 1, From fe2aecf33a013b8f780ae23d7188126917267e12 Mon Sep 17 00:00:00 2001 From: Haojie Li Date: Tue, 25 Aug 2026 14:48:15 +0800 Subject: [PATCH 088/213] dma-buf: fix coding style in dma_resv_get_singleton MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The conditional "if (r)" following the dma_resv_get_fences() call was indented with spaces instead of a tab, violating the kernel coding style. Fix it to use a single tab for consistency with the rest of the function. Signed-off-by: Haojie Li Link: https://lore.kernel.org/r/20260825064815.331653-1-lihaojie@kylinos.cn Reviewed-by: Christian König Signed-off-by: Christian König --- drivers/dma-buf/dma-resv.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/dma-buf/dma-resv.c b/drivers/dma-buf/dma-resv.c index 39a92d9f2413..2365a6d8ca15 100644 --- a/drivers/dma-buf/dma-resv.c +++ b/drivers/dma-buf/dma-resv.c @@ -633,7 +633,7 @@ int dma_resv_get_singleton(struct dma_resv *obj, enum dma_resv_usage usage, int r; r = dma_resv_get_fences(obj, usage, &count, &fences); - if (r) + if (r) return r; if (count == 0) { From 3fc4721629c40bb5f7bc7696875a9df633e6a35a Mon Sep 17 00:00:00 2001 From: Nicolas Frattaroli Date: Wed, 12 Aug 2026 16:07:18 +0200 Subject: [PATCH 089/213] drm/panthor: Add tracepoint for cache flushing Add a new event tracepoint: gpu_cache_flush to be emitted after a GPU cache flush completes, with duration and return status arguments. This allows debugging the duration a flush takes irrespective of initial function entry lock contention, and communicates information about whether the flush timed out or errored out in other ways, and which caches were flushed. Signed-off-by: Nicolas Frattaroli Reviewed-by: Liviu Dudau Reviewed-by: Steven Price Reviewed-by: Boris Brezillon Signed-off-by: Liviu Dudau Link: https://patch.msgid.link/20260812-panthor-cache-flush-fix-v4-1-751e32901898@collabora.com --- drivers/gpu/drm/panthor/panthor_gpu.c | 26 ++++++++++++++++- drivers/gpu/drm/panthor/panthor_trace.h | 38 +++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/panthor/panthor_gpu.c b/drivers/gpu/drm/panthor/panthor_gpu.c index c013d6bf9a59..7088371c6d64 100644 --- a/drivers/gpu/drm/panthor/panthor_gpu.c +++ b/drivers/gpu/drm/panthor/panthor_gpu.c @@ -317,6 +317,21 @@ int panthor_gpu_l2_power_on(struct panthor_device *ptdev) return panthor_gpu_power_on(ptdev, L2, 1, 20000); } +static inline void panthor_gpu_emit_flush_caches_tp(struct panthor_device *ptdev, + u64 start, u32 l2, u32 lsc, + u32 other, int ret) +{ + u32 duration; + + if (!tracepoint_enabled(gpu_cache_flush) || !start) + return; + + if (check_sub_overflow(ktime_get_ns(), start, &duration)) + duration = U32_MAX; + + trace_gpu_cache_flush(ptdev->base.dev, l2, lsc, other, duration, ret); +} + /** * panthor_gpu_flush_caches() - Flush caches * @ptdev: Device. @@ -331,12 +346,17 @@ int panthor_gpu_flush_caches(struct panthor_device *ptdev, { struct panthor_gpu *gpu = ptdev->gpu; unsigned long flags; + u64 start = 0; int ret = 0; /* Serialize cache flush operations. */ guard(mutex)(&ptdev->gpu->cache_flush_lock); spin_lock_irqsave(&ptdev->gpu->reqs_lock, flags); + + if (tracepoint_enabled(gpu_cache_flush)) + start = ktime_get_ns(); + if (!(ptdev->gpu->pending_reqs & GPU_IRQ_CLEAN_CACHES_COMPLETED)) { ptdev->gpu->pending_reqs |= GPU_IRQ_CLEAN_CACHES_COMPLETED; gpu_write(gpu->iomem, GPU_CMD, GPU_FLUSH_CACHES(l2, lsc, other)); @@ -345,8 +365,10 @@ int panthor_gpu_flush_caches(struct panthor_device *ptdev, } spin_unlock_irqrestore(&ptdev->gpu->reqs_lock, flags); - if (ret) + if (ret) { + panthor_gpu_emit_flush_caches_tp(ptdev, start, l2, lsc, other, ret); return ret; + } if (!wait_event_timeout(ptdev->gpu->reqs_acked, !(ptdev->gpu->pending_reqs & GPU_IRQ_CLEAN_CACHES_COMPLETED), @@ -360,6 +382,8 @@ int panthor_gpu_flush_caches(struct panthor_device *ptdev, spin_unlock_irqrestore(&ptdev->gpu->reqs_lock, flags); } + panthor_gpu_emit_flush_caches_tp(ptdev, start, l2, lsc, other, ret); + if (ret) { panthor_device_schedule_reset(ptdev); drm_err(&ptdev->base, "Flush caches timeout"); diff --git a/drivers/gpu/drm/panthor/panthor_trace.h b/drivers/gpu/drm/panthor/panthor_trace.h index 6ffeb4fe6599..bd8652549ab4 100644 --- a/drivers/gpu/drm/panthor/panthor_trace.h +++ b/drivers/gpu/drm/panthor/panthor_trace.h @@ -76,6 +76,44 @@ TRACE_EVENT(gpu_job_irq, __entry->events, __entry->duration_ns) ); +/** + * gpu_cache_flush - emitted after cache flush completes + * @dev: pointer to the &struct device, for printing the device name + * @l2: "l2" flush flags + * @lsc: "lsc" flush flags + * @other: "other" flush flags + * @duration_ns: how long the cache flush operation took, in nanoseconds + * @ret: return status, 0 == success, negative errno on error + * + * Begins measuring after any initial lock contention around the locks needed + * for flushing caches, but before the actual cache flush is requested. Stops + * measuring and is emitted after flush operation is over. + */ +TRACE_EVENT(gpu_cache_flush, + TP_PROTO(const struct device *dev, u32 l2, u32 lsc, u32 other, + u32 duration_ns, int ret), + TP_ARGS(dev, l2, lsc, other, duration_ns, ret), + TP_STRUCT__entry( + __string(dev_name, dev_name(dev)) + __field(u32, l2) + __field(u32, lsc) + __field(u32, other) + __field(u32, duration_ns) + __field(int, ret) + ), + TP_fast_assign( + __assign_str(dev_name); + __entry->l2 = l2; + __entry->lsc = lsc; + __entry->other = other; + __entry->duration_ns = duration_ns; + __entry->ret = ret; + ), + TP_printk("%s: l2=0x%x lsc=0x%x other=0x%x duration_ns=%u ret=%d", + __get_str(dev_name), __entry->l2, __entry->lsc, + __entry->other, __entry->duration_ns, __entry->ret) +); + #endif /* __PANTHOR_TRACE_H__ */ #undef TRACE_INCLUDE_PATH From 4fd08b3d1da78e33cb5939f0086e61f9d660cd6c Mon Sep 17 00:00:00 2001 From: Nicolas Frattaroli Date: Wed, 12 Aug 2026 16:07:19 +0200 Subject: [PATCH 090/213] drm/panthor: Revisit reqs_lock handling in flush/reset paths panthor_gpu_flush_caches() and panthor_gpu_soft_reset() acquire their reqs_lock spinlock with the IRQ-disabling variants of the spinlocking functions. This isn't necessary, as the lock is never taken from an atomic context, as Panthor uses threaded interrupt handlers. The result of this overly strict locking is that IRQs may be disabled more frequently and for longer than they should be, resulting in increased system latency. Switch the locking to use non-IRQ-disabling scoped_guard statements for locking. The wait_event_timeout read of pending_reqs outside of the spinlock is fine as wait_event_timeout is a memory barrier according to the Linux Memory Model. Fixes: 5cd894e258c4 ("drm/panthor: Add the GPU logical block") Reviewed-by: Boris Brezillon Signed-off-by: Nicolas Frattaroli Reviewed-by: Liviu Dudau Reviewed-by: Steven Price Signed-off-by: Liviu Dudau Link: https://patch.msgid.link/20260812-panthor-cache-flush-fix-v4-2-751e32901898@collabora.com --- drivers/gpu/drm/panthor/panthor_gpu.c | 70 ++++++++++++--------------- 1 file changed, 32 insertions(+), 38 deletions(-) diff --git a/drivers/gpu/drm/panthor/panthor_gpu.c b/drivers/gpu/drm/panthor/panthor_gpu.c index 7088371c6d64..110ca6cd3d09 100644 --- a/drivers/gpu/drm/panthor/panthor_gpu.c +++ b/drivers/gpu/drm/panthor/panthor_gpu.c @@ -345,41 +345,36 @@ int panthor_gpu_flush_caches(struct panthor_device *ptdev, u32 l2, u32 lsc, u32 other) { struct panthor_gpu *gpu = ptdev->gpu; - unsigned long flags; u64 start = 0; int ret = 0; /* Serialize cache flush operations. */ guard(mutex)(&ptdev->gpu->cache_flush_lock); - spin_lock_irqsave(&ptdev->gpu->reqs_lock, flags); + scoped_guard(spinlock, &ptdev->gpu->reqs_lock) { + if (tracepoint_enabled(gpu_cache_flush)) + start = ktime_get_ns(); - if (tracepoint_enabled(gpu_cache_flush)) - start = ktime_get_ns(); - - if (!(ptdev->gpu->pending_reqs & GPU_IRQ_CLEAN_CACHES_COMPLETED)) { - ptdev->gpu->pending_reqs |= GPU_IRQ_CLEAN_CACHES_COMPLETED; - gpu_write(gpu->iomem, GPU_CMD, GPU_FLUSH_CACHES(l2, lsc, other)); - } else { - ret = -EIO; - } - spin_unlock_irqrestore(&ptdev->gpu->reqs_lock, flags); - - if (ret) { - panthor_gpu_emit_flush_caches_tp(ptdev, start, l2, lsc, other, ret); - return ret; + if (!(ptdev->gpu->pending_reqs & GPU_IRQ_CLEAN_CACHES_COMPLETED)) { + ptdev->gpu->pending_reqs |= GPU_IRQ_CLEAN_CACHES_COMPLETED; + gpu_write(gpu->iomem, GPU_CMD, GPU_FLUSH_CACHES(l2, lsc, other)); + } else { + panthor_gpu_emit_flush_caches_tp(ptdev, start, l2, lsc, + other, -EIO); + return -EIO; + } } if (!wait_event_timeout(ptdev->gpu->reqs_acked, !(ptdev->gpu->pending_reqs & GPU_IRQ_CLEAN_CACHES_COMPLETED), msecs_to_jiffies(100))) { - spin_lock_irqsave(&ptdev->gpu->reqs_lock, flags); - if ((ptdev->gpu->pending_reqs & GPU_IRQ_CLEAN_CACHES_COMPLETED) != 0 && - !(gpu_read(gpu->irq.iomem, INT_RAWSTAT) & GPU_IRQ_CLEAN_CACHES_COMPLETED)) - ret = -ETIMEDOUT; - else - ptdev->gpu->pending_reqs &= ~GPU_IRQ_CLEAN_CACHES_COMPLETED; - spin_unlock_irqrestore(&ptdev->gpu->reqs_lock, flags); + scoped_guard(spinlock, &ptdev->gpu->reqs_lock) { + if ((ptdev->gpu->pending_reqs & GPU_IRQ_CLEAN_CACHES_COMPLETED) != 0 && + !(gpu_read(gpu->irq.iomem, INT_RAWSTAT) & GPU_IRQ_CLEAN_CACHES_COMPLETED)) + ret = -ETIMEDOUT; + else + ptdev->gpu->pending_reqs &= ~GPU_IRQ_CLEAN_CACHES_COMPLETED; + } } panthor_gpu_emit_flush_caches_tp(ptdev, start, l2, lsc, other, ret); @@ -402,27 +397,26 @@ int panthor_gpu_soft_reset(struct panthor_device *ptdev) { struct panthor_gpu *gpu = ptdev->gpu; bool timedout = false; - unsigned long flags; - spin_lock_irqsave(&ptdev->gpu->reqs_lock, flags); - if (!drm_WARN_ON(&ptdev->base, - ptdev->gpu->pending_reqs & GPU_IRQ_RESET_COMPLETED)) { - ptdev->gpu->pending_reqs |= GPU_IRQ_RESET_COMPLETED; - gpu_write(gpu->irq.iomem, INT_CLEAR, GPU_IRQ_RESET_COMPLETED); - gpu_write(gpu->iomem, GPU_CMD, GPU_SOFT_RESET); + scoped_guard(spinlock, &ptdev->gpu->reqs_lock) { + if (!drm_WARN_ON(&ptdev->base, + ptdev->gpu->pending_reqs & GPU_IRQ_RESET_COMPLETED)) { + ptdev->gpu->pending_reqs |= GPU_IRQ_RESET_COMPLETED; + gpu_write(gpu->irq.iomem, INT_CLEAR, GPU_IRQ_RESET_COMPLETED); + gpu_write(gpu->iomem, GPU_CMD, GPU_SOFT_RESET); + } } - spin_unlock_irqrestore(&ptdev->gpu->reqs_lock, flags); if (!wait_event_timeout(ptdev->gpu->reqs_acked, !(ptdev->gpu->pending_reqs & GPU_IRQ_RESET_COMPLETED), msecs_to_jiffies(100))) { - spin_lock_irqsave(&ptdev->gpu->reqs_lock, flags); - if ((ptdev->gpu->pending_reqs & GPU_IRQ_RESET_COMPLETED) != 0 && - !(gpu_read(gpu->irq.iomem, INT_RAWSTAT) & GPU_IRQ_RESET_COMPLETED)) - timedout = true; - else - ptdev->gpu->pending_reqs &= ~GPU_IRQ_RESET_COMPLETED; - spin_unlock_irqrestore(&ptdev->gpu->reqs_lock, flags); + scoped_guard(spinlock, &ptdev->gpu->reqs_lock) { + if ((ptdev->gpu->pending_reqs & GPU_IRQ_RESET_COMPLETED) != 0 && + !(gpu_read(gpu->irq.iomem, INT_RAWSTAT) & GPU_IRQ_RESET_COMPLETED)) + timedout = true; + else + ptdev->gpu->pending_reqs &= ~GPU_IRQ_RESET_COMPLETED; + } } if (timedout) { From 2d354adc5bbabe2e818be09bbe13f6bfde4a30c7 Mon Sep 17 00:00:00 2001 From: Nicolas Frattaroli Date: Wed, 12 Aug 2026 16:07:20 +0200 Subject: [PATCH 091/213] drm/panthor: Take reqs_lock in soft_reset for clearing pending_reqs panthor_gpu_soft_reset() sets pending_reqs to 0 without taking the requisite reqs_lock. Fix this by taking the lock for the duration of the modification. Fixes: 5cd894e258c4 ("drm/panthor: Add the GPU logical block") Reviewed-by: Boris Brezillon Signed-off-by: Nicolas Frattaroli Reviewed-by: Liviu Dudau Reviewed-by: Steven Price Signed-off-by: Liviu Dudau Link: https://patch.msgid.link/20260812-panthor-cache-flush-fix-v4-3-751e32901898@collabora.com --- drivers/gpu/drm/panthor/panthor_gpu.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/panthor/panthor_gpu.c b/drivers/gpu/drm/panthor/panthor_gpu.c index 110ca6cd3d09..aa903fda20a5 100644 --- a/drivers/gpu/drm/panthor/panthor_gpu.c +++ b/drivers/gpu/drm/panthor/panthor_gpu.c @@ -424,7 +424,10 @@ int panthor_gpu_soft_reset(struct panthor_device *ptdev) return -ETIMEDOUT; } - ptdev->gpu->pending_reqs = 0; + scoped_guard(spinlock, &ptdev->gpu->reqs_lock) { + ptdev->gpu->pending_reqs = 0; + } + return 0; } From 1ec57bb049bab41ded0ad2922e921ef4e7a7ae39 Mon Sep 17 00:00:00 2001 From: Mohamed Ahmed Date: Tue, 25 Aug 2026 04:14:07 +0400 Subject: [PATCH 092/213] drm/nouveau/dispnv50: program pixel clocks above 2.147GHz on GB20x The HEAD_SET_PIXEL_CLOCK_FREQUENCY(_MAX) methods carry only 31 HERTZ bits. Starting with C97D the upper bits live in separate HEAD_SET_PIXEL_CLOCK_FREQUENCY_HI(_MAX) methods, which nouveau never programmed and headca7d_mode() computed m->clock * 1000 into the 31-bit field. NVVAL's mask then silently truncates anything past 2^31 Hz, which means that every mode scanned out at pclk modulo 2^31. No mode nouveau can currently commit crosses the boundary (an uncompressed HDMI FRL mode tops out around 1.78GHz at 8bpc), but this is a prerequisite for the upcoming DSC work, which makes 2.147GHz+ modes reachable. Program the full value split across the low and HI methods, exactly as OpenRM's EvoSetRasterParams9() does (nvkms-evo4.c, 31-bit low word plus the 4 HI HERTZ bits, giving 35 bits of range). Signed-off-by: Mohamed Ahmed Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260825001408.14219-10-mohamedahmedegypt2001@gmail.com --- drivers/gpu/drm/nouveau/dispnv50/headca7d.c | 21 ++++++++++++++++--- .../drm/nouveau/include/nvhw/class/clca7d.h | 4 ++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/nouveau/dispnv50/headca7d.c b/drivers/gpu/drm/nouveau/dispnv50/headca7d.c index eeaeb15aa664..678cbc78ca9f 100644 --- a/drivers/gpu/drm/nouveau/dispnv50/headca7d.c +++ b/drivers/gpu/drm/nouveau/dispnv50/headca7d.c @@ -219,10 +219,11 @@ headca7d_mode(struct nv50_head *head, struct nv50_head_atom *asyh) { struct nvif_push *push = &head->disp->core->chan.push; struct nv50_head_mode *m = &asyh->mode; + const u64 hz = (u64)m->clock * 1000; const int i = head->base.index; int ret; - ret = PUSH_WAIT(push, 11); + ret = PUSH_WAIT(push, 14); if (ret) return ret; @@ -245,11 +246,25 @@ headca7d_mode(struct nv50_head *head, struct nv50_head_atom *asyh) PUSH_MTHD(push, NVCA7D, HEAD_SET_CONTROL(i), NVDEF(NVCA7D, HEAD_SET_CONTROL, STRUCTURE, PROGRESSIVE)); + /* The FREQUENCY methods carry only 31 HERTZ bits; the upper bits + * of anything past 2.147GHz live in the HI methods + * (EvoSetRasterParams9()). Truncating would scan out at pclk modulo 2^31. + */ PUSH_MTHD(push, NVCA7D, HEAD_SET_PIXEL_CLOCK_FREQUENCY(i), - NVVAL(NVCA7D, HEAD_SET_PIXEL_CLOCK_FREQUENCY, HERTZ, m->clock * 1000)); + NVVAL(NVCA7D, HEAD_SET_PIXEL_CLOCK_FREQUENCY, HERTZ, + (u32)(hz & 0x7fffffff))); PUSH_MTHD(push, NVCA7D, HEAD_SET_PIXEL_CLOCK_FREQUENCY_MAX(i), - NVVAL(NVCA7D, HEAD_SET_PIXEL_CLOCK_FREQUENCY_MAX, HERTZ, m->clock * 1000)); + NVVAL(NVCA7D, HEAD_SET_PIXEL_CLOCK_FREQUENCY_MAX, HERTZ, + (u32)(hz & 0x7fffffff))); + + PUSH_MTHD(push, NVCA7D, HEAD_SET_PIXEL_CLOCK_FREQUENCY_HI(i), + NVVAL(NVCA7D, HEAD_SET_PIXEL_CLOCK_FREQUENCY_HI, HERTZ, + (u32)(hz >> 31)), + + HEAD_SET_PIXEL_CLOCK_FREQUENCY_HI_MAX(i), + NVVAL(NVCA7D, HEAD_SET_PIXEL_CLOCK_FREQUENCY_HI_MAX, HERTZ, + (u32)(hz >> 31))); return 0; } diff --git a/drivers/gpu/drm/nouveau/include/nvhw/class/clca7d.h b/drivers/gpu/drm/nouveau/include/nvhw/class/clca7d.h index 0fec6fc21d44..5754878ddc69 100644 --- a/drivers/gpu/drm/nouveau/include/nvhw/class/clca7d.h +++ b/drivers/gpu/drm/nouveau/include/nvhw/class/clca7d.h @@ -734,6 +734,10 @@ #define NVCA7D_HEAD_SET_CONTROL_CURSOR_COMPOSITION_BYPASS 20:20 #define NVCA7D_HEAD_SET_CONTROL_CURSOR_COMPOSITION_BYPASS_DISABLE (0x00000000) #define NVCA7D_HEAD_SET_CONTROL_CURSOR_COMPOSITION_BYPASS_ENABLE (0x00000001) +#define NVCA7D_HEAD_SET_PIXEL_CLOCK_FREQUENCY_HI(a) (0x000020C0 + (a)*0x00000800) +#define NVCA7D_HEAD_SET_PIXEL_CLOCK_FREQUENCY_HI_HERTZ 3:0 +#define NVCA7D_HEAD_SET_PIXEL_CLOCK_FREQUENCY_HI_MAX(a) (0x000020C4 + (a)*0x00000800) +#define NVCA7D_HEAD_SET_PIXEL_CLOCK_FREQUENCY_HI_MAX_HERTZ 3:0 #define NVCA7D_HEAD_SET_SURFACE_ADDRESS_HI_CRC(a) (0x00002150 + (a)*0x00000800) #define NVCA7D_HEAD_SET_SURFACE_ADDRESS_HI_CRC_ADDRESS_HI 31:0 #define NVCA7D_HEAD_SET_SURFACE_ADDRESS_LO_CRC(a) (0x00002154 + (a)*0x00000800) From 3cc8eee9f3460b5b1366155ef31b3193a381fe30 Mon Sep 17 00:00:00 2001 From: Diogo Silva Date: Mon, 20 Jul 2026 17:40:33 +0200 Subject: [PATCH 093/213] drm/exynos: remove dependency on DRM simple helpers The simple KMS helpers are deprecated because they only add an intermediate layer between drivers and atomic modesetting. Open-code drm_simple_encoder_init() by calling drm_encoder_init() directly and providing driver-local drm_encoder_funcs. Also check the return value from drm_encoder_init() to avoid silent failures. Signed-off-by: Diogo Silva Signed-off-by: Thomas Zimmermann Reviewed-by: Thomas Zimmermann Link: https://patch.msgid.link/20260720-drm_simple_encoder_init-v2-1-5020b630668a@gmail.com --- drivers/gpu/drm/exynos/exynos_dp.c | 13 +++++++++++-- drivers/gpu/drm/exynos/exynos_drm_dpi.c | 13 +++++++++++-- drivers/gpu/drm/exynos/exynos_drm_dsi.c | 11 +++++++++-- drivers/gpu/drm/exynos/exynos_drm_vidi.c | 13 +++++++++++-- drivers/gpu/drm/exynos/exynos_hdmi.c | 14 ++++++++++++-- 5 files changed, 54 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/exynos/exynos_dp.c b/drivers/gpu/drm/exynos/exynos_dp.c index a418011f7d4d..e57d4da43300 100644 --- a/drivers/gpu/drm/exynos/exynos_dp.c +++ b/drivers/gpu/drm/exynos/exynos_dp.c @@ -24,11 +24,11 @@ #include #include #include +#include #include #include #include #include -#include #include #include "exynos_drm_crtc.h" @@ -79,6 +79,10 @@ static void exynos_dp_nop(struct drm_encoder *encoder) /* do nothing */ } +static const struct drm_encoder_funcs exynos_dp_encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + static const struct drm_encoder_helper_funcs exynos_dp_encoder_helper_funcs = { .mode_set = exynos_dp_mode_set, .enable = exynos_dp_nop, @@ -95,7 +99,12 @@ static int exynos_dp_bind(struct device *dev, struct device *master, void *data) dp->drm_dev = drm_dev; - drm_simple_encoder_init(drm_dev, encoder, DRM_MODE_ENCODER_TMDS); + ret = drm_encoder_init(drm_dev, encoder, &exynos_dp_encoder_funcs, + DRM_MODE_ENCODER_TMDS, NULL); + if (ret) { + drm_err(drm_dev, "Failed to initialize encoder\n"); + return ret; + } drm_encoder_helper_add(encoder, &exynos_dp_encoder_helper_funcs); diff --git a/drivers/gpu/drm/exynos/exynos_drm_dpi.c b/drivers/gpu/drm/exynos/exynos_drm_dpi.c index 9d15a0035ea9..74d3a2bebb74 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_dpi.c +++ b/drivers/gpu/drm/exynos/exynos_drm_dpi.c @@ -12,10 +12,10 @@ #include #include +#include #include #include #include -#include #include