From 144169e7be0831e09958a906d08d1856751aa6c6 Mon Sep 17 00:00:00 2001 From: Roman Li Date: Wed, 20 May 2026 16:50:34 -0400 Subject: drm/amd/display: Skip PHY SSC reduction on some 8K panels [Why] Some 8K displays cannot tolerate the reduced phy ssc value at high link utilization and show corruption or black screen. [How] Add an EDID panel-id quirk to utilize existing skip_phy_ssc_reduction flag. To pass the link into the quirk handler, change the signature of apply_edid_quirks() to take link as an argument. The dev local in dm_helpers_parse_edid_caps() becomes unused and is removed. Fixes: 5fa62c87cffd ("drm/amd/display: Add option to disable PHY SSC reduction on transmitter enable") Reviewed-by: Alex Hung Signed-off-by: Roman Li Signed-off-by: Aurabindo Pillai Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c index f257ea91a34d..c6f94eb71ffa 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c @@ -95,8 +95,11 @@ static u32 edid_extract_panel_id(struct edid *edid) (u32)EDID_PRODUCT_ID(edid); } -static void apply_edid_quirks(struct drm_device *dev, struct edid *edid, struct dc_edid_caps *edid_caps) +static void apply_edid_quirks(struct dc_link *link, struct edid *edid, + struct dc_edid_caps *edid_caps) { + struct amdgpu_dm_connector *aconnector = link->priv; + struct drm_device *dev = aconnector->base.dev; uint32_t panel_id = edid_extract_panel_id(edid); switch (panel_id) { @@ -126,6 +129,11 @@ static void apply_edid_quirks(struct drm_device *dev, struct edid *edid, struct drm_dbg_driver(dev, "Disabling VSC on monitor with panel id %X\n", panel_id); edid_caps->panel_patch.disable_colorimetry = true; break; + /* Workaround for monitors that get corrupted by the PHY SSC reduction */ + case drm_edid_encode_panel_id('D', 'E', 'L', 0x4147): + drm_dbg_driver(dev, "Skip PHY SSC reduction on panel id %X\n", panel_id); + link->wa_flags.skip_phy_ssc_reduction = true; + break; default: return; } @@ -147,7 +155,6 @@ enum dc_edid_status dm_helpers_parse_edid_caps( { struct amdgpu_dm_connector *aconnector = link->priv; struct drm_connector *connector = &aconnector->base; - struct drm_device *dev = connector->dev; struct edid *edid_buf = edid ? (struct edid *) edid->raw_edid : NULL; struct cea_sad *sads; int sad_count = -1; @@ -188,7 +195,7 @@ enum dc_edid_status dm_helpers_parse_edid_caps( edid_caps->frl_dsc_max_frl_rate, edid_caps->frl_dsc_total_chunk_kbytes); } - apply_edid_quirks(dev, edid_buf, edid_caps); + apply_edid_quirks(link, edid_buf, edid_caps); sad_count = drm_edid_to_sad((struct edid *) edid->raw_edid, &sads); if (sad_count <= 0) -- cgit v1.2.3 From 0fde96e06f1cd66d9850488095cc65f5dca5b6b2 Mon Sep 17 00:00:00 2001 From: Austin Zheng Date: Tue, 12 May 2026 16:20:54 -0400 Subject: drm/amd/display: Deprecate DMUB register offload functionality [Why] The DMUB register offload feature should no longer be used. This was originally a debug feature for DCN21. No longer applicable to the DMUB programming model. [How] Remove DMUB register offload infrastructure including helper functions, structures, debug options, and register sequence macros. Reviewed-by: Nicholas Kazlauskas Signed-off-by: Austin Zheng Signed-off-by: Aurabindo Pillai Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 7 - drivers/gpu/drm/amd/display/dc/dc.h | 3 - drivers/gpu/drm/amd/display/dc/dc_dmub_srv.c | 12 -- drivers/gpu/drm/amd/display/dc/dc_dmub_srv.h | 9 - drivers/gpu/drm/amd/display/dc/dc_helper.c | 226 --------------------- drivers/gpu/drm/amd/display/dc/dm_services.h | 4 - .../drm/amd/display/dc/dpp/dcn10/dcn10_dpp_cm.c | 5 - .../drm/amd/display/dc/hwss/dcn35/dcn35_hwseq.c | 3 - drivers/gpu/drm/amd/display/dc/inc/reg_helper.h | 19 -- .../gpu/drm/amd/display/dc/mpc/dcn20/dcn20_mpc.c | 4 - .../gpu/drm/amd/display/dc/optc/dcn10/dcn10_optc.c | 5 - .../gpu/drm/amd/display/dc/optc/dcn20/dcn20_optc.c | 5 - .../gpu/drm/amd/display/dc/optc/dcn31/dcn31_optc.c | 5 - .../drm/amd/display/dc/optc/dcn314/dcn314_optc.c | 5 - .../gpu/drm/amd/display/dc/optc/dcn32/dcn32_optc.c | 5 - .../gpu/drm/amd/display/dc/optc/dcn35/dcn35_optc.c | 5 - .../drm/amd/display/dc/optc/dcn401/dcn401_optc.c | 5 - .../amd/display/dc/resource/dcn35/dcn35_resource.c | 1 - .../display/dc/resource/dcn351/dcn351_resource.c | 1 - .../amd/display/dc/resource/dcn36/dcn36_resource.c | 1 - 20 files changed, 330 deletions(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') 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 1ed697a3a453..40f32c8024a0 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -13915,13 +13915,6 @@ uint32_t dm_read_reg_func(const struct dc_context *ctx, uint32_t address, } #endif - if (ctx->dmub_srv && - ctx->dmub_srv->reg_helper_offload.gather_in_progress && - !ctx->dmub_srv->reg_helper_offload.should_burst_write) { - ASSERT(false); - return 0; - } - amdgpu_dm_exit_ips_for_hw_access(ctx->dc); value = cgs_read_register(ctx->cgs_device, address); diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h index 82d02ebbd829..d5d9d56fbcb8 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -1129,8 +1129,6 @@ struct dc_debug_options { unsigned int force_fclk_khz; bool enable_tri_buf; bool ips_disallow_entry; - bool dmub_offload_enabled; - bool dmcub_emulation; bool disable_idle_power_optimizations; unsigned int mall_size_override; unsigned int mall_additional_timer_percent; @@ -1332,7 +1330,6 @@ struct dc_init_data { enum dce_environment dce_environment; struct dmub_offload_funcs *dmub_if; - struct dc_reg_helper_state *dmub_offload; struct dc_config flags; uint64_t log_mask; diff --git a/drivers/gpu/drm/amd/display/dc/dc_dmub_srv.c b/drivers/gpu/drm/amd/display/dc/dc_dmub_srv.c index 0ee5c0c5545c..4c81989898e2 100644 --- a/drivers/gpu/drm/amd/display/dc/dc_dmub_srv.c +++ b/drivers/gpu/drm/amd/display/dc/dc_dmub_srv.c @@ -518,9 +518,6 @@ void dc_dmub_srv_query_caps_cmd(struct dc_dmub_srv *dc_dmub_srv) { union dmub_rb_cmd cmd = { 0 }; - if (dc_dmub_srv->ctx->dc->debug.dmcub_emulation) - return; - memset(&cmd, 0, sizeof(cmd)); /* Prepare fw command */ @@ -1302,9 +1299,6 @@ bool dc_dmub_srv_is_hw_pwr_up(struct dc_dmub_srv *dc_dmub_srv, bool wait) if (!dc_dmub_srv || !dc_dmub_srv->dmub) return true; - if (dc_dmub_srv->ctx->dc->debug.dmcub_emulation) - return true; - dc_ctx = dc_dmub_srv->ctx; if (wait) { @@ -1345,9 +1339,6 @@ static void dc_dmub_srv_notify_idle(const struct dc *dc, bool allow_idle) struct dc_dmub_srv *dc_dmub_srv; union dmub_rb_cmd cmd = {0}; - if (dc->debug.dmcub_emulation) - return; - if (!dc->ctx->dmub_srv || !dc->ctx->dmub_srv->dmub) return; @@ -1466,9 +1457,6 @@ static void dc_dmub_srv_exit_low_power_state(const struct dc *dc) struct dc_dmub_srv *dc_dmub_srv; uint32_t rcg_exit_count = 0, ips1_exit_count = 0, ips2_exit_count = 0, ips1z8_exit_count = 0; - if (dc->debug.dmcub_emulation) - return; - if (!dc->ctx->dmub_srv || !dc->ctx->dmub_srv->dmub) return; diff --git a/drivers/gpu/drm/amd/display/dc/dc_dmub_srv.h b/drivers/gpu/drm/amd/display/dc/dc_dmub_srv.h index ebcaf49e5961..5d399e6a8345 100644 --- a/drivers/gpu/drm/amd/display/dc/dc_dmub_srv.h +++ b/drivers/gpu/drm/amd/display/dc/dc_dmub_srv.h @@ -37,17 +37,8 @@ struct dc_crtc_timing; struct dc_state; struct dc_surface_update; -struct dc_reg_helper_state { - bool gather_in_progress; - uint32_t same_addr_count; - bool should_burst_write; - union dmub_rb_cmd cmd_data; - unsigned int reg_seq_count; -}; - struct dc_dmub_srv { struct dmub_srv *dmub; - struct dc_reg_helper_state reg_helper_offload; struct dc_context *ctx; void *dm; diff --git a/drivers/gpu/drm/amd/display/dc/dc_helper.c b/drivers/gpu/drm/amd/display/dc/dc_helper.c index 0e0165764a57..cc7fea613d9e 100644 --- a/drivers/gpu/drm/amd/display/dc/dc_helper.c +++ b/drivers/gpu/drm/amd/display/dc/dc_helper.c @@ -39,53 +39,6 @@ #define DC_LOGGER \ ctx->logger -static inline void submit_dmub_read_modify_write( - struct dc_reg_helper_state *offload, - const struct dc_context *ctx) -{ - struct dmub_rb_cmd_read_modify_write *cmd_buf = &offload->cmd_data.read_modify_write; - - offload->should_burst_write = - (offload->same_addr_count == (DMUB_READ_MODIFY_WRITE_SEQ__MAX - 1)); - cmd_buf->header.payload_bytes = - sizeof(struct dmub_cmd_read_modify_write_sequence) * offload->reg_seq_count; - - dc_wake_and_execute_dmub_cmd(ctx, &offload->cmd_data, DM_DMUB_WAIT_TYPE_NO_WAIT); - - memset(cmd_buf, 0, sizeof(*cmd_buf)); - - offload->reg_seq_count = 0; - offload->same_addr_count = 0; -} - -static inline void submit_dmub_burst_write( - struct dc_reg_helper_state *offload, - const struct dc_context *ctx) -{ - struct dmub_rb_cmd_burst_write *cmd_buf = &offload->cmd_data.burst_write; - - cmd_buf->header.payload_bytes = - sizeof(uint32_t) * offload->reg_seq_count; - - dc_wake_and_execute_dmub_cmd(ctx, &offload->cmd_data, DM_DMUB_WAIT_TYPE_NO_WAIT); - - memset(cmd_buf, 0, sizeof(*cmd_buf)); - - offload->reg_seq_count = 0; -} - -static inline void submit_dmub_reg_wait( - struct dc_reg_helper_state *offload, - const struct dc_context *ctx) -{ - struct dmub_rb_cmd_reg_wait *cmd_buf = &offload->cmd_data.reg_wait; - - dc_wake_and_execute_dmub_cmd(ctx, &offload->cmd_data, DM_DMUB_WAIT_TYPE_NO_WAIT); - - memset(cmd_buf, 0, sizeof(*cmd_buf)); - offload->reg_seq_count = 0; -} - struct dc_reg_value_masks { uint32_t value; uint32_t mask; @@ -127,98 +80,6 @@ static void set_reg_field_values(struct dc_reg_value_masks *field_value_mask, } } -static void dmub_flush_buffer_execute( - struct dc_reg_helper_state *offload, - const struct dc_context *ctx) -{ - submit_dmub_read_modify_write(offload, ctx); -} - -static void dmub_flush_burst_write_buffer_execute( - struct dc_reg_helper_state *offload, - const struct dc_context *ctx) -{ - submit_dmub_burst_write(offload, ctx); -} - -static bool dmub_reg_value_burst_set_pack(const struct dc_context *ctx, uint32_t addr, - uint32_t reg_val) -{ - struct dc_reg_helper_state *offload = &ctx->dmub_srv->reg_helper_offload; - struct dmub_rb_cmd_burst_write *cmd_buf = &offload->cmd_data.burst_write; - - /* flush command if buffer is full */ - if (offload->reg_seq_count == DMUB_BURST_WRITE_VALUES__MAX) - dmub_flush_burst_write_buffer_execute(offload, ctx); - - if (offload->cmd_data.cmd_common.header.type == DMUB_CMD__REG_SEQ_BURST_WRITE && - addr != cmd_buf->addr) { - dmub_flush_burst_write_buffer_execute(offload, ctx); - return false; - } - - cmd_buf->header.type = DMUB_CMD__REG_SEQ_BURST_WRITE; - cmd_buf->header.sub_type = 0; - cmd_buf->addr = addr; - cmd_buf->write_values[offload->reg_seq_count] = reg_val; - offload->reg_seq_count++; - - return true; -} - -static uint32_t dmub_reg_value_pack(const struct dc_context *ctx, uint32_t addr, - struct dc_reg_value_masks *field_value_mask) -{ - struct dc_reg_helper_state *offload = &ctx->dmub_srv->reg_helper_offload; - struct dmub_rb_cmd_read_modify_write *cmd_buf = &offload->cmd_data.read_modify_write; - struct dmub_cmd_read_modify_write_sequence *seq; - - /* flush command if buffer is full */ - if (offload->cmd_data.cmd_common.header.type != DMUB_CMD__REG_SEQ_BURST_WRITE && - offload->reg_seq_count == DMUB_READ_MODIFY_WRITE_SEQ__MAX) - dmub_flush_buffer_execute(offload, ctx); - - if (offload->should_burst_write) { - if (dmub_reg_value_burst_set_pack(ctx, addr, field_value_mask->value)) - return field_value_mask->value; - else - offload->should_burst_write = false; - } - - /* pack commands */ - cmd_buf->header.type = DMUB_CMD__REG_SEQ_READ_MODIFY_WRITE; - cmd_buf->header.sub_type = 0; - seq = &cmd_buf->seq[offload->reg_seq_count]; - - if (offload->reg_seq_count) { - if (cmd_buf->seq[offload->reg_seq_count - 1].addr == addr) - offload->same_addr_count++; - else - offload->same_addr_count = 0; - } - - seq->addr = addr; - seq->modify_mask = field_value_mask->mask; - seq->modify_value = field_value_mask->value; - offload->reg_seq_count++; - - return field_value_mask->value; -} - -static void dmub_reg_wait_done_pack(const struct dc_context *ctx, uint32_t addr, - uint32_t mask, uint32_t shift, uint32_t condition_value, uint32_t time_out_us) -{ - struct dc_reg_helper_state *offload = &ctx->dmub_srv->reg_helper_offload; - struct dmub_rb_cmd_reg_wait *cmd_buf = &offload->cmd_data.reg_wait; - - cmd_buf->header.type = DMUB_CMD__REG_REG_WAIT; - cmd_buf->header.sub_type = 0; - cmd_buf->reg_wait.addr = addr; - cmd_buf->reg_wait.condition_field_value = mask & (condition_value << shift); - cmd_buf->reg_wait.mask = mask; - cmd_buf->reg_wait.time_out_us = time_out_us; -} - uint32_t generic_reg_update_ex(const struct dc_context *ctx, uint32_t addr, int n, uint8_t shift1, uint32_t mask1, uint32_t field_value1, @@ -235,11 +96,6 @@ uint32_t generic_reg_update_ex(const struct dc_context *ctx, va_end(ap); - if (ctx->dmub_srv && - ctx->dmub_srv->reg_helper_offload.gather_in_progress) - return dmub_reg_value_pack(ctx, addr, &field_value_mask); - /* todo: return void so we can decouple code running in driver from register states */ - /* mmio write directly */ reg_val = dm_read_reg(ctx, addr); reg_val = (reg_val & ~field_value_mask.mask) | field_value_mask.value; @@ -265,12 +121,6 @@ uint32_t generic_reg_set_ex(const struct dc_context *ctx, /* mmio write directly */ reg_val = (reg_val & ~field_value_mask.mask) | field_value_mask.value; - if (ctx->dmub_srv && - ctx->dmub_srv->reg_helper_offload.gather_in_progress) { - return dmub_reg_value_burst_set_pack(ctx, addr, reg_val); - /* todo: return void so we can decouple code running in driver from register states */ - } - dm_write_reg(ctx, addr, reg_val); return reg_val; } @@ -434,13 +284,6 @@ void generic_reg_wait(const struct dc_context *ctx, uint32_t reg_val; unsigned int i; - if (ctx->dmub_srv && - ctx->dmub_srv->reg_helper_offload.gather_in_progress) { - dmub_reg_wait_done_pack(ctx, addr, mask, shift, condition_value, - delay_between_poll_us * time_out_num_tries); - return; - } - /* * Something is terribly wrong if time out is > 3000ms. * 3000ms is the maximum time needed for SMU to pass values back. @@ -491,12 +334,6 @@ uint32_t generic_read_indirect_reg(const struct dc_context *ctx, { uint32_t value = 0; - // when reg read, there should not be any offload. - if (ctx->dmub_srv && - ctx->dmub_srv->reg_helper_offload.gather_in_progress) { - ASSERT(false); - } - dm_write_reg(ctx, addr_index, index); value = dm_read_reg(ctx, addr_data); @@ -624,69 +461,6 @@ uint32_t generic_indirect_reg_get_sync(const struct dc_context *ctx, return value; } -void reg_sequence_start_gather(const struct dc_context *ctx) -{ - /* if reg sequence is supported and enabled, set flag to - * indicate we want to have REG_SET, REG_UPDATE macro build - * reg sequence command buffer rather than MMIO directly. - */ - - if (ctx->dmub_srv && ctx->dc->debug.dmub_offload_enabled) { - struct dc_reg_helper_state *offload = - &ctx->dmub_srv->reg_helper_offload; - - /* caller sequence mismatch. need to debug caller. offload will not work!!! */ - ASSERT(!offload->gather_in_progress); - - offload->gather_in_progress = true; - } -} - -void reg_sequence_start_execute(const struct dc_context *ctx) -{ - struct dc_reg_helper_state *offload; - - if (!ctx->dmub_srv) - return; - - offload = &ctx->dmub_srv->reg_helper_offload; - - if (offload && offload->gather_in_progress) { - offload->gather_in_progress = false; - offload->should_burst_write = false; - switch (offload->cmd_data.cmd_common.header.type) { - case DMUB_CMD__REG_SEQ_READ_MODIFY_WRITE: - submit_dmub_read_modify_write(offload, ctx); - break; - case DMUB_CMD__REG_REG_WAIT: - submit_dmub_reg_wait(offload, ctx); - break; - case DMUB_CMD__REG_SEQ_BURST_WRITE: - submit_dmub_burst_write(offload, ctx); - break; - default: - return; - } - } -} - -void reg_sequence_wait_done(const struct dc_context *ctx) -{ - /* callback to DM to poll for last submission done*/ - struct dc_reg_helper_state *offload; - - if (!ctx->dmub_srv) - return; - - offload = &ctx->dmub_srv->reg_helper_offload; - - if (offload && - ctx->dc->debug.dmub_offload_enabled && - !ctx->dc->debug.dmcub_emulation) { - dc_dmub_srv_wait_for_idle(ctx->dmub_srv, DM_DMUB_WAIT_TYPE_WAIT, NULL); - } -} - char *dce_version_to_string(const int version) { switch (version) { diff --git a/drivers/gpu/drm/amd/display/dc/dm_services.h b/drivers/gpu/drm/amd/display/dc/dm_services.h index 8b062b011fc6..2cf4bcb03cb0 100644 --- a/drivers/gpu/drm/amd/display/dc/dm_services.h +++ b/drivers/gpu/drm/amd/display/dc/dm_services.h @@ -127,10 +127,6 @@ uint32_t generic_reg_update_ex(const struct dc_context *ctx, struct dc_dmub_srv *dc_dmub_srv_create(struct dc *dc, struct dmub_srv *dmub); void dc_dmub_srv_destroy(struct dc_dmub_srv **dmub_srv); -void reg_sequence_start_gather(const struct dc_context *ctx); -void reg_sequence_start_execute(const struct dc_context *ctx); -void reg_sequence_wait_done(const struct dc_context *ctx); - #define FD(reg_field) reg_field ## __SHIFT, \ reg_field ## _MASK diff --git a/drivers/gpu/drm/amd/display/dc/dpp/dcn10/dcn10_dpp_cm.c b/drivers/gpu/drm/amd/display/dc/dpp/dcn10/dcn10_dpp_cm.c index 53b21adc6267..9788628cf0ad 100644 --- a/drivers/gpu/drm/amd/display/dc/dpp/dcn10/dcn10_dpp_cm.c +++ b/drivers/gpu/drm/amd/display/dc/dpp/dcn10/dcn10_dpp_cm.c @@ -397,8 +397,6 @@ void dpp1_cm_program_regamma_lut(struct dpp *dpp_base, uint32_t i; struct dcn10_dpp *dpp = TO_DCN10_DPP(dpp_base); - REG_SEQ_START(); - for (i = 0 ; i < num; i++) { REG_SET(CM_RGAM_LUT_DATA, 0, CM_RGAM_LUT_DATA, rgb[i].red_reg); REG_SET(CM_RGAM_LUT_DATA, 0, CM_RGAM_LUT_DATA, rgb[i].green_reg); @@ -408,9 +406,6 @@ void dpp1_cm_program_regamma_lut(struct dpp *dpp_base, REG_SET(CM_RGAM_LUT_DATA, 0, CM_RGAM_LUT_DATA, rgb[i].delta_green_reg); REG_SET(CM_RGAM_LUT_DATA, 0, CM_RGAM_LUT_DATA, rgb[i].delta_blue_reg); } - - REG_SEQ_SUBMIT(); - REG_SEQ_WAIT_DONE(); } void dpp1_cm_configure_regamma_lut( diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn35/dcn35_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn35/dcn35_hwseq.c index 8f9038fec0f7..01027d120cb0 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn35/dcn35_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn35/dcn35_hwseq.c @@ -581,9 +581,6 @@ void dcn35_power_down_on_boot(struct dc *dc) bool dcn35_apply_idle_power_optimizations(struct dc *dc, bool enable) { - if (dc->debug.dmcub_emulation) - return true; - if (enable) { uint32_t num_active_edp = 0; int i; diff --git a/drivers/gpu/drm/amd/display/dc/inc/reg_helper.h b/drivers/gpu/drm/amd/display/dc/inc/reg_helper.h index 7a1ecb8d986f..6d15ccdc7f87 100644 --- a/drivers/gpu/drm/amd/display/dc/inc/reg_helper.h +++ b/drivers/gpu/drm/amd/display/dc/inc/reg_helper.h @@ -536,23 +536,4 @@ uint32_t generic_indirect_reg_update_ex_sync(const struct dc_context *ctx, uint8_t shift1, uint32_t mask1, uint32_t field_value1, ...); -/* register offload macros - * - * instead of MMIO to register directly, in some cases we want - * to gather register sequence and execute the register sequence - * from another thread so we optimize time required for lengthy ops - */ - -/* start gathering register sequence */ -#define REG_SEQ_START() \ - reg_sequence_start_gather(CTX) - -/* start execution of register sequence gathered since REG_SEQ_START */ -#define REG_SEQ_SUBMIT() \ - reg_sequence_start_execute(CTX) - -/* wait for the last REG_SEQ_SUBMIT to finish */ -#define REG_SEQ_WAIT_DONE() \ - reg_sequence_wait_done(CTX) - #endif /* DRIVERS_GPU_DRM_AMD_DC_DEV_DC_INC_REG_HELPER_H_ */ diff --git a/drivers/gpu/drm/amd/display/dc/mpc/dcn20/dcn20_mpc.c b/drivers/gpu/drm/amd/display/dc/mpc/dcn20/dcn20_mpc.c index fa600593f4c1..0e09d073ab29 100644 --- a/drivers/gpu/drm/amd/display/dc/mpc/dcn20/dcn20_mpc.c +++ b/drivers/gpu/drm/amd/display/dc/mpc/dcn20/dcn20_mpc.c @@ -380,7 +380,6 @@ static void mpc20_program_ogam_pwl( struct dcn20_mpc *mpc20 = TO_DCN20_MPC(mpc); PERF_TRACE(); - REG_SEQ_START(); for (i = 0 ; i < num; i++) { REG_SET(MPCC_OGAM_LUT_DATA[mpcc_id], 0, MPCC_OGAM_LUT_DATA, rgb[i].red_reg); @@ -395,9 +394,6 @@ static void mpc20_program_ogam_pwl( MPCC_OGAM_LUT_DATA, rgb[i].delta_blue_reg); } - REG_SEQ_SUBMIT(); - PERF_TRACE(); - REG_SEQ_WAIT_DONE(); PERF_TRACE(); } diff --git a/drivers/gpu/drm/amd/display/dc/optc/dcn10/dcn10_optc.c b/drivers/gpu/drm/amd/display/dc/optc/dcn10/dcn10_optc.c index e6426ccee2d8..cf8e22289d6a 100644 --- a/drivers/gpu/drm/amd/display/dc/optc/dcn10/dcn10_optc.c +++ b/drivers/gpu/drm/amd/display/dc/optc/dcn10/dcn10_optc.c @@ -539,16 +539,11 @@ static bool optc1_enable_crtc(struct timing_generator *optc) REG_UPDATE(CONTROL, VTG0_ENABLE, 1); - REG_SEQ_START(); - /* Enable CRTC */ REG_UPDATE_2(OTG_CONTROL, OTG_DISABLE_POINT_CNTL, 3, OTG_MASTER_EN, 1); - REG_SEQ_SUBMIT(); - REG_SEQ_WAIT_DONE(); - return true; } diff --git a/drivers/gpu/drm/amd/display/dc/optc/dcn20/dcn20_optc.c b/drivers/gpu/drm/amd/display/dc/optc/dcn20/dcn20_optc.c index c558b1d633f3..73cc8a713556 100644 --- a/drivers/gpu/drm/amd/display/dc/optc/dcn20/dcn20_optc.c +++ b/drivers/gpu/drm/amd/display/dc/optc/dcn20/dcn20_optc.c @@ -63,16 +63,11 @@ bool optc2_enable_crtc(struct timing_generator *optc) REG_UPDATE(CONTROL, VTG0_ENABLE, 1); - REG_SEQ_START(); - /* Enable CRTC */ REG_UPDATE_2(OTG_CONTROL, OTG_DISABLE_POINT_CNTL, 3, OTG_MASTER_EN, 1); - REG_SEQ_SUBMIT(); - REG_SEQ_WAIT_DONE(); - return true; } diff --git a/drivers/gpu/drm/amd/display/dc/optc/dcn31/dcn31_optc.c b/drivers/gpu/drm/amd/display/dc/optc/dcn31/dcn31_optc.c index 98aaa22ce81c..3ace83e1b50f 100644 --- a/drivers/gpu/drm/amd/display/dc/optc/dcn31/dcn31_optc.c +++ b/drivers/gpu/drm/amd/display/dc/optc/dcn31/dcn31_optc.c @@ -105,16 +105,11 @@ static bool optc31_enable_crtc(struct timing_generator *optc) REG_UPDATE(CONTROL, VTG0_ENABLE, 1); - REG_SEQ_START(); - /* Enable CRTC */ REG_UPDATE_2(OTG_CONTROL, OTG_DISABLE_POINT_CNTL, 2, OTG_MASTER_EN, 1); - REG_SEQ_SUBMIT(); - REG_SEQ_WAIT_DONE(); - return true; } diff --git a/drivers/gpu/drm/amd/display/dc/optc/dcn314/dcn314_optc.c b/drivers/gpu/drm/amd/display/dc/optc/dcn314/dcn314_optc.c index a7cf34937b2f..7250478a5092 100644 --- a/drivers/gpu/drm/amd/display/dc/optc/dcn314/dcn314_optc.c +++ b/drivers/gpu/drm/amd/display/dc/optc/dcn314/dcn314_optc.c @@ -115,16 +115,11 @@ static bool optc314_enable_crtc(struct timing_generator *optc) REG_UPDATE(CONTROL, VTG0_ENABLE, 1); - REG_SEQ_START(); - /* Enable CRTC */ REG_UPDATE_2(OTG_CONTROL, OTG_DISABLE_POINT_CNTL, 2, OTG_MASTER_EN, 1); - REG_SEQ_SUBMIT(); - REG_SEQ_WAIT_DONE(); - return true; } diff --git a/drivers/gpu/drm/amd/display/dc/optc/dcn32/dcn32_optc.c b/drivers/gpu/drm/amd/display/dc/optc/dcn32/dcn32_optc.c index 07895d5f4dfa..f9e05efcad98 100644 --- a/drivers/gpu/drm/amd/display/dc/optc/dcn32/dcn32_optc.c +++ b/drivers/gpu/drm/amd/display/dc/optc/dcn32/dcn32_optc.c @@ -155,16 +155,11 @@ static bool optc32_enable_crtc(struct timing_generator *optc) REG_UPDATE(CONTROL, VTG0_ENABLE, 1); - REG_SEQ_START(); - /* Enable CRTC */ REG_UPDATE_2(OTG_CONTROL, OTG_DISABLE_POINT_CNTL, 2, OTG_MASTER_EN, 1); - REG_SEQ_SUBMIT(); - REG_SEQ_WAIT_DONE(); - return true; } diff --git a/drivers/gpu/drm/amd/display/dc/optc/dcn35/dcn35_optc.c b/drivers/gpu/drm/amd/display/dc/optc/dcn35/dcn35_optc.c index 62f45c156c32..9b7f9d5bbfb3 100644 --- a/drivers/gpu/drm/amd/display/dc/optc/dcn35/dcn35_optc.c +++ b/drivers/gpu/drm/amd/display/dc/optc/dcn35/dcn35_optc.c @@ -122,16 +122,11 @@ static bool optc35_enable_crtc(struct timing_generator *optc) REG_UPDATE(CONTROL, VTG0_ENABLE, 1); - REG_SEQ_START(); - /* Enable CRTC */ REG_UPDATE_2(OTG_CONTROL, OTG_DISABLE_POINT_CNTL, 2, OTG_MASTER_EN, 1); - REG_SEQ_SUBMIT(); - REG_SEQ_WAIT_DONE(); - return true; } diff --git a/drivers/gpu/drm/amd/display/dc/optc/dcn401/dcn401_optc.c b/drivers/gpu/drm/amd/display/dc/optc/dcn401/dcn401_optc.c index a6d76f451cf8..5fcdd74eb4a0 100644 --- a/drivers/gpu/drm/amd/display/dc/optc/dcn401/dcn401_optc.c +++ b/drivers/gpu/drm/amd/display/dc/optc/dcn401/dcn401_optc.c @@ -189,16 +189,11 @@ bool optc401_enable_crtc(struct timing_generator *optc) REG_UPDATE(CONTROL, VTG0_ENABLE, 1); - REG_SEQ_START(); - /* Enable CRTC */ REG_UPDATE_2(OTG_CONTROL, OTG_DISABLE_POINT_CNTL, 2, OTG_MASTER_EN, 1); - REG_SEQ_SUBMIT(); - REG_SEQ_WAIT_DONE(); - return true; } diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn35/dcn35_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn35/dcn35_resource.c index 53596e790eb4..a5ed62db1de8 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn35/dcn35_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn35/dcn35_resource.c @@ -819,7 +819,6 @@ static const struct dc_debug_options debug_defaults_drv = { .enable_hpo_pg_support = false, .enable_single_display_2to1_odm_policy = true, .disable_idle_power_optimizations = false, - .dmcub_emulation = false, .disable_boot_optimizations = false, .disable_unbounded_requesting = false, .disable_mem_low_power = false, diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn351/dcn351_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn351/dcn351_resource.c index 3e2c9cfd555d..9c1d65c2d4ab 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn351/dcn351_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn351/dcn351_resource.c @@ -799,7 +799,6 @@ static const struct dc_debug_options debug_defaults_drv = { .enable_hpo_pg_support = false, .enable_single_display_2to1_odm_policy = true, .disable_idle_power_optimizations = false, - .dmcub_emulation = false, .disable_boot_optimizations = false, .disable_unbounded_requesting = false, .disable_mem_low_power = false, diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn36/dcn36_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn36/dcn36_resource.c index 9e795130eb89..8041e035f226 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn36/dcn36_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn36/dcn36_resource.c @@ -806,7 +806,6 @@ static const struct dc_debug_options debug_defaults_drv = { .enable_hpo_pg_support = false, .enable_single_display_2to1_odm_policy = true, .disable_idle_power_optimizations = false, - .dmcub_emulation = false, .disable_boot_optimizations = false, .disable_unbounded_requesting = false, .disable_mem_low_power = false, -- cgit v1.2.3 From 042b0a39806cf6019cc47047424868c2d130567d Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 25 May 2026 12:08:20 -0600 Subject: drm/amd/display: Fix writeback format loop and variable init [WHAT] 1. Use ARRAY_SIZE() instead of manual sizeof division for the format array iteration. Add a break statement to exit the loop early once a matching format is found. 2. Remove redundant zero initialization of res since all paths assign before use. Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Aurabindo Pillai Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.c index 110f0173eee6..ead3d0bb052f 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.c @@ -59,9 +59,11 @@ static int amdgpu_dm_wb_encoder_atomic_check(struct drm_encoder *encoder, return -EINVAL; } - for (i = 0; i < sizeof(amdgpu_dm_wb_formats) / sizeof(u32); i++) { - if (fb->format->format == amdgpu_dm_wb_formats[i]) + for (i = 0; i < ARRAY_SIZE(amdgpu_dm_wb_formats); i++) { + if (fb->format->format == amdgpu_dm_wb_formats[i]) { found = true; + break; + } } if (!found) { @@ -187,7 +189,7 @@ int amdgpu_dm_wb_connector_init(struct amdgpu_display_manager *dm, { struct dc *dc = dm->dc; struct dc_link *link = dc_get_link_at_index(dc, link_index); - int res = 0; + int res; wbcon->link = link; -- cgit v1.2.3 From 948739e14f3cc33679601e444c97f6704f1d327d Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 25 May 2026 12:08:49 -0600 Subject: drm/amd/display: Add KUnit tests for writeback connector [WHAT] Add KUnit tests for amdgpu_dm_wb_encoder_atomic_check() and amdgpu_dm_wb_connector_get_modes(). Tests cover null job, null fb, size mismatch, format validation, and mode count bounds using DRM KUnit mock devices. Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Aurabindo Pillai Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.c | 7 +- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.h | 13 + .../gpu/drm/amd/display/amdgpu_dm/tests/Makefile | 1 + .../display/amdgpu_dm/tests/amdgpu_dm_wb_test.c | 336 +++++++++++++++++++++ 4 files changed, 355 insertions(+), 2 deletions(-) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_wb_test.c (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.c index ead3d0bb052f..058d478a073d 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.c @@ -29,6 +29,7 @@ #include "amdgpu.h" #include "amdgpu_dm.h" #include "amdgpu_dm_wb.h" +#include "amdgpu_dm_kunit_helpers.h" #include "amdgpu_display.h" #include "dc.h" @@ -40,7 +41,7 @@ static const u32 amdgpu_dm_wb_formats[] = { DRM_FORMAT_XRGB2101010, }; -static int amdgpu_dm_wb_encoder_atomic_check(struct drm_encoder *encoder, +STATIC_IFN_KUNIT int amdgpu_dm_wb_encoder_atomic_check(struct drm_encoder *encoder, struct drm_crtc_state *crtc_state, struct drm_connector_state *conn_state) { @@ -74,13 +75,15 @@ static int amdgpu_dm_wb_encoder_atomic_check(struct drm_encoder *encoder, return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_wb_encoder_atomic_check); -static int amdgpu_dm_wb_connector_get_modes(struct drm_connector *connector) +STATIC_IFN_KUNIT int amdgpu_dm_wb_connector_get_modes(struct drm_connector *connector) { /* Maximum resolution supported by DWB */ return drm_add_modes_noedid(connector, 3840, 2160); } +EXPORT_IF_KUNIT(amdgpu_dm_wb_connector_get_modes); static int amdgpu_dm_wb_prepare_job(struct drm_writeback_connector *wb_connector, struct drm_writeback_job *job) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.h index 13d31c857dee..7e9fd7a036fa 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.h @@ -29,8 +29,21 @@ #include +struct amdgpu_display_manager; +struct amdgpu_dm_wb_connector; + int amdgpu_dm_wb_connector_init(struct amdgpu_display_manager *dm, struct amdgpu_dm_wb_connector *dm_wbcon, uint32_t link_index); +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +#include +#include + +int amdgpu_dm_wb_encoder_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state); +int amdgpu_dm_wb_connector_get_modes(struct drm_connector *connector); +#endif + #endif diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile index 768f9bbc50e1..ce1e46acb7af 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile @@ -16,3 +16,4 @@ obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_colorop_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_psr_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_replay_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_ism_test.o +obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_wb_test.o diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_wb_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_wb_test.c new file mode 100644 index 000000000000..b8ad4b87163a --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_wb_test.c @@ -0,0 +1,336 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit tests for amdgpu_dm_wb.c + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "amdgpu_dm_wb.h" + + +/* Helper functions */ + +static struct drm_crtc_state *alloc_test_crtc_state(struct kunit *test, + int hdisplay, int vdisplay) +{ + struct drm_crtc_state *crtc_state; + + crtc_state = kunit_kzalloc(test, sizeof(*crtc_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, crtc_state); + + crtc_state->mode.hdisplay = hdisplay; + crtc_state->mode.vdisplay = vdisplay; + + return crtc_state; +} + +static struct drm_connector_state *alloc_test_conn_state(struct kunit *test, + int fb_width, + int fb_height, + u32 format) +{ + struct drm_connector_state *conn_state; + struct drm_writeback_job *job; + struct drm_framebuffer *fb; + struct drm_format_info *fmt_info; + + conn_state = kunit_kzalloc(test, sizeof(*conn_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, conn_state); + + job = kunit_kzalloc(test, sizeof(*job), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, job); + + fb = kunit_kzalloc(test, sizeof(*fb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, fb); + + fmt_info = kunit_kzalloc(test, sizeof(*fmt_info), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, fmt_info); + + fb->width = fb_width; + fb->height = fb_height; + fmt_info->format = format; + fb->format = fmt_info; + + job->fb = fb; + conn_state->writeback_job = job; + + return conn_state; +} + +/* Tests for amdgpu_dm_wb_encoder_atomic_check */ + +/** + * dm_test_wb_atomic_check_no_job - Verify early return when no writeback job + * @test: KUnit test context + * + * When conn_state->writeback_job is NULL, no writeback is requested and the + * function should return 0 without further validation. + */ +static void dm_test_wb_atomic_check_no_job(struct kunit *test) +{ + struct drm_crtc_state *crtc_state; + struct drm_connector_state *conn_state; + int ret; + + crtc_state = alloc_test_crtc_state(test, 1920, 1080); + conn_state = kunit_kzalloc(test, sizeof(*conn_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, conn_state); + + /* No writeback_job — should return 0 */ + conn_state->writeback_job = NULL; + ret = amdgpu_dm_wb_encoder_atomic_check(NULL, crtc_state, conn_state); + KUNIT_EXPECT_EQ(test, ret, 0); +} + +/** + * dm_test_wb_atomic_check_no_fb - Verify early return when job has no framebuffer + * @test: KUnit test context + * + * When a writeback job exists but job->fb is NULL, the function should return 0 + * without validating dimensions or pixel format. + */ +static void dm_test_wb_atomic_check_no_fb(struct kunit *test) +{ + struct drm_crtc_state *crtc_state; + struct drm_connector_state *conn_state; + struct drm_writeback_job *job; + int ret; + + crtc_state = alloc_test_crtc_state(test, 1920, 1080); + conn_state = kunit_kzalloc(test, sizeof(*conn_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, conn_state); + + job = kunit_kzalloc(test, sizeof(*job), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, job); + + /* writeback_job exists but no fb — should return 0 */ + job->fb = NULL; + conn_state->writeback_job = job; + ret = amdgpu_dm_wb_encoder_atomic_check(NULL, crtc_state, conn_state); + KUNIT_EXPECT_EQ(test, ret, 0); +} + +/** + * dm_test_wb_atomic_check_valid - Verify success with matching size and supported format + * @test: KUnit test context + * + * When the framebuffer dimensions match the CRTC mode and the pixel format is + * in the supported formats list, the function should return 0. + */ +static void dm_test_wb_atomic_check_valid(struct kunit *test) +{ + struct drm_crtc_state *crtc_state; + struct drm_connector_state *conn_state; + int ret; + + crtc_state = alloc_test_crtc_state(test, 1920, 1080); + conn_state = alloc_test_conn_state(test, 1920, 1080, + DRM_FORMAT_XRGB2101010); + + ret = amdgpu_dm_wb_encoder_atomic_check(NULL, crtc_state, conn_state); + KUNIT_EXPECT_EQ(test, ret, 0); +} + +/** + * dm_test_wb_atomic_check_size_mismatch - Verify rejection when both dimensions differ + * @test: KUnit test context + * + * When both framebuffer width and height differ from the CRTC mode, the + * function should return -EINVAL. + */ +static void dm_test_wb_atomic_check_size_mismatch(struct kunit *test) +{ + struct drm_crtc_state *crtc_state; + struct drm_connector_state *conn_state; + int ret; + + /* FB is 3840x2160 but mode is 1920x1080 */ + crtc_state = alloc_test_crtc_state(test, 1920, 1080); + conn_state = alloc_test_conn_state(test, 3840, 2160, + DRM_FORMAT_XRGB2101010); + + ret = amdgpu_dm_wb_encoder_atomic_check(NULL, crtc_state, conn_state); + KUNIT_EXPECT_EQ(test, ret, -EINVAL); +} + +/** + * dm_test_wb_atomic_check_width_mismatch - Verify rejection when width alone differs + * @test: KUnit test context + * + * When only the framebuffer width differs from the CRTC mode hdisplay, the + * function should return -EINVAL. + */ +static void dm_test_wb_atomic_check_width_mismatch(struct kunit *test) +{ + struct drm_crtc_state *crtc_state; + struct drm_connector_state *conn_state; + int ret; + + /* Width doesn't match */ + crtc_state = alloc_test_crtc_state(test, 1920, 1080); + conn_state = alloc_test_conn_state(test, 1280, 1080, + DRM_FORMAT_XRGB2101010); + + ret = amdgpu_dm_wb_encoder_atomic_check(NULL, crtc_state, conn_state); + KUNIT_EXPECT_EQ(test, ret, -EINVAL); +} + +/** + * dm_test_wb_atomic_check_height_mismatch - Verify rejection when height alone differs + * @test: KUnit test context + * + * When only the framebuffer height differs from the CRTC mode vdisplay, the + * function should return -EINVAL. + */ +static void dm_test_wb_atomic_check_height_mismatch(struct kunit *test) +{ + struct drm_crtc_state *crtc_state; + struct drm_connector_state *conn_state; + int ret; + + /* Height doesn't match */ + crtc_state = alloc_test_crtc_state(test, 1920, 1080); + conn_state = alloc_test_conn_state(test, 1920, 720, + DRM_FORMAT_XRGB2101010); + + ret = amdgpu_dm_wb_encoder_atomic_check(NULL, crtc_state, conn_state); + KUNIT_EXPECT_EQ(test, ret, -EINVAL); +} + +/** + * dm_test_wb_atomic_check_invalid_format - Verify rejection of unsupported pixel format + * @test: KUnit test context + * + * When the framebuffer dimensions match but the pixel format is not in + * amdgpu_dm_wb_formats[], the function should return -EINVAL. + */ +static void dm_test_wb_atomic_check_invalid_format(struct kunit *test) +{ + struct drm_crtc_state *crtc_state; + struct drm_connector_state *conn_state; + int ret; + + /* Correct size but unsupported format */ + crtc_state = alloc_test_crtc_state(test, 1920, 1080); + conn_state = alloc_test_conn_state(test, 1920, 1080, + DRM_FORMAT_XRGB8888); + + ret = amdgpu_dm_wb_encoder_atomic_check(NULL, crtc_state, conn_state); + KUNIT_EXPECT_EQ(test, ret, -EINVAL); +} + +/* Tests for amdgpu_dm_wb_connector_get_modes using DRM mock */ + +static const struct drm_connector_funcs dm_wb_test_connector_funcs = { + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .reset = drm_atomic_helper_connector_reset, +}; + +/** + * dm_test_wb_get_modes_returns_modes - Verify at least one mode is returned + * @test: KUnit test context + * + * Uses a DRM mock connector to verify that amdgpu_dm_wb_connector_get_modes() + * populates the connector with at least one display mode. + */ +static void dm_test_wb_get_modes_returns_modes(struct kunit *test) +{ + struct device *dev; + struct drm_device *drm; + struct drm_connector *connector; + int count; + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + drm = __drm_kunit_helper_alloc_drm_device(test, dev, + sizeof(*drm), 0, + DRIVER_MODESET | DRIVER_ATOMIC); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, connector); + + drmm_connector_init(drm, connector, &dm_wb_test_connector_funcs, + DRM_MODE_CONNECTOR_VIRTUAL, NULL); + + count = amdgpu_dm_wb_connector_get_modes(connector); + + /* drm_add_modes_noedid should return at least one mode */ + KUNIT_EXPECT_GT(test, count, 0); +} + +/** + * dm_test_wb_get_modes_bounded_by_max - Verify all modes are within max resolution + * @test: KUnit test context + * + * Uses a DRM mock connector to verify that all modes returned by + * amdgpu_dm_wb_connector_get_modes() have hdisplay <= 3840 and + * vdisplay <= 2160, matching the DWB hardware maximum. + */ +static void dm_test_wb_get_modes_bounded_by_max(struct kunit *test) +{ + struct device *dev; + struct drm_device *drm; + struct drm_connector *connector; + struct drm_display_mode *mode; + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + drm = __drm_kunit_helper_alloc_drm_device(test, dev, + sizeof(*drm), 0, + DRIVER_MODESET | DRIVER_ATOMIC); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, connector); + + drmm_connector_init(drm, connector, &dm_wb_test_connector_funcs, + DRM_MODE_CONNECTOR_VIRTUAL, NULL); + + amdgpu_dm_wb_connector_get_modes(connector); + + /* All modes must fit within 3840x2160 */ + list_for_each_entry(mode, &connector->probed_modes, head) { + KUNIT_EXPECT_LE(test, mode->hdisplay, 3840); + KUNIT_EXPECT_LE(test, mode->vdisplay, 2160); + } +} + +static struct kunit_case dm_wb_test_cases[] = { + /* amdgpu_dm_wb_encoder_atomic_check */ + KUNIT_CASE(dm_test_wb_atomic_check_no_job), + KUNIT_CASE(dm_test_wb_atomic_check_no_fb), + KUNIT_CASE(dm_test_wb_atomic_check_valid), + KUNIT_CASE(dm_test_wb_atomic_check_size_mismatch), + KUNIT_CASE(dm_test_wb_atomic_check_width_mismatch), + KUNIT_CASE(dm_test_wb_atomic_check_height_mismatch), + KUNIT_CASE(dm_test_wb_atomic_check_invalid_format), + /* amdgpu_dm_wb_connector_get_modes */ + KUNIT_CASE(dm_test_wb_get_modes_returns_modes), + KUNIT_CASE(dm_test_wb_get_modes_bounded_by_max), + {} +}; + +static struct kunit_suite dm_wb_test_suite = { + .name = "amdgpu_dm_wb", + .test_cases = dm_wb_test_cases, +}; + +kunit_test_suite(dm_wb_test_suite); + +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_wb"); -- cgit v1.2.3 From 828a1a67e15e234f0ae59dc735350e525aa7dd66 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Wed, 27 May 2026 16:20:29 -0600 Subject: drm/amd/display: remove redundant code in amdgpu_dm_replay [WHAT] In amdgpu_dm_link_setup_replay(), nom_coasting_vtotal was used only once immediately after in set_replay_coasting_vtotal(). Inline the value directly to remove the no-op alias. In amdgpu_dm_set_replay_caps(), replace link->ctx->dc->debug with dc->debug since dc is already assigned as link->ctx->dc, eliminating a redundant pointer round-trip. Assisted-by: Copilot:Claude-Sonnet-4.6 Reviewed-by: Ray Wu Signed-off-by: Alex Hung Signed-off-by: Aurabindo Pillai Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_replay.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_replay.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_replay.c index 22aa4305d2af..f3cea2aba901 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_replay.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_replay.c @@ -121,8 +121,7 @@ bool amdgpu_dm_set_replay_caps(struct dc_link *link, struct amdgpu_dm_connector debug_flags = (union replay_debug_flags *)&pr_config.debug_flags; debug_flags->u32All = 0; - debug_flags->bitfields.visual_confirm = - link->ctx->dc->debug.visual_confirm == VISUAL_CONFIRM_REPLAY; + debug_flags->bitfields.visual_confirm = dc->debug.visual_confirm == VISUAL_CONFIRM_REPLAY; debug_flags->bitfields.skip_crtc_disabled = dc->debug.replay_skip_crtc_disabled; init_replay_config(link, &pr_config); @@ -144,7 +143,6 @@ bool amdgpu_dm_link_setup_replay(struct dc_stream_state *stream, { struct dc_link *link; unsigned int static_coasting_vtotal; - unsigned int nom_coasting_vtotal; if (!stream || !stream->link || !vrr_params) return false; @@ -159,12 +157,11 @@ bool amdgpu_dm_link_setup_replay(struct dc_stream_state *stream, calculate_replay_link_off_frame_count(link, stream->timing.v_total, stream->timing.h_total); - nom_coasting_vtotal = stream->timing.v_total; static_coasting_vtotal = mod_freesync_calc_v_total_from_refresh(stream, vrr_params->min_refresh_in_uhz); set_replay_coasting_vtotal(link, PR_COASTING_TYPE_NOM, - nom_coasting_vtotal); + stream->timing.v_total); set_replay_coasting_vtotal(link, PR_COASTING_TYPE_STATIC, static_coasting_vtotal); return true; -- cgit v1.2.3 From 2985b49ed6f51cc3982beb9ab2171d2c8a33296e Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Wed, 27 May 2026 17:35:47 -0600 Subject: drm/amd/display: Enable warnings as errors for KUnit tests [WHAT] Add CONFIG_WERROR=y to .kunitconfig to treat compiler warnings as errors during KUnit builds, ensuring warnings are caught early. Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Ray Wu Signed-off-by: Alex Hung Signed-off-by: Aurabindo Pillai Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/tests/.kunitconfig | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/.kunitconfig b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/.kunitconfig index bd1bf8d959f9..1e93bd8b44ce 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/.kunitconfig +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/.kunitconfig @@ -15,6 +15,9 @@ CONFIG_I2C=y CONFIG_POWER_SUPPLY=y CONFIG_CRC16=y +# Treat warnings as errors +CONFIG_WERROR=y + # GCOV Coverage - see tools/testing/kunit/configs/coverage_uml.config CONFIG_DEBUG_KERNEL=y CONFIG_DEBUG_INFO=y -- cgit v1.2.3 From 7c030f8df237740607c4d92d201516125a71f2f8 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 25 May 2026 13:12:16 -0600 Subject: drm/amd/display: Remove dead code in dm_dp_mst_get_modes [WHAT] Remove unreachable null check on aconnector after container_of, and redundant dc_sink checks where dc_sink is guaranteed non-NULL after earlier null-check with early return. Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Aurabindo Pillai Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_mst_types.c | 23 +++++++++------------- 1 file changed, 9 insertions(+), 14 deletions(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c index b3af7445b457..99b78dd50caf 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c @@ -363,9 +363,6 @@ static int dm_dp_mst_get_modes(struct drm_connector *connector) struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector); int ret = 0; - if (!aconnector) - return drm_add_edid_modes(connector, NULL); - if (!aconnector->drm_edid) { const struct drm_edid *drm_edid; @@ -456,7 +453,7 @@ static int dm_dp_mst_get_modes(struct drm_connector *connector) * plugged back with same display index, its hdcp properties * will be retrieved from hdcp_work within dm_dp_mst_get_modes */ - if (aconnector->dc_sink && connector->state) { + if (connector->state) { struct drm_device *dev = connector->dev; struct amdgpu_device *adev = drm_to_adev(dev); @@ -472,20 +469,18 @@ static int dm_dp_mst_get_modes(struct drm_connector *connector) } } - if (aconnector->dc_sink) { - amdgpu_dm_update_freesync_caps( - connector, aconnector->drm_edid, true); + amdgpu_dm_update_freesync_caps( + connector, aconnector->drm_edid, true); #if defined(CONFIG_DRM_AMD_DC_FP) - if (!validate_dsc_caps_on_connector(aconnector)) - memset(&aconnector->dc_sink->dsc_caps, - 0, sizeof(aconnector->dc_sink->dsc_caps)); + if (!validate_dsc_caps_on_connector(aconnector)) + memset(&aconnector->dc_sink->dsc_caps, + 0, sizeof(aconnector->dc_sink->dsc_caps)); #endif - if (!retrieve_downstream_port_device(aconnector)) - memset(&aconnector->mst_downstream_port_present, - 0, sizeof(aconnector->mst_downstream_port_present)); - } + if (!retrieve_downstream_port_device(aconnector)) + memset(&aconnector->mst_downstream_port_present, + 0, sizeof(aconnector->mst_downstream_port_present)); } drm_edid_connector_update(&aconnector->base, aconnector->drm_edid); -- cgit v1.2.3 From 1c37d1b6c74116f2e6adcb675426eb36e60ae4d1 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 25 May 2026 14:48:34 -0600 Subject: drm/amd/display: Add KUnit tests for amdgpu_dm_mst_types [WHAT] Add KUnit test coverage for needs_dsc_aux_workaround() in amdgpu_dm_mst_types.c. Tests verify the function correctly identifies links requiring the DSC AUX workaround based on branch device ID, DPCD revision, and sink count. Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Aurabindo Pillai Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_mst_types.c | 2 + .../amd/display/amdgpu_dm/amdgpu_dm_mst_types.h | 6 + .../gpu/drm/amd/display/amdgpu_dm/tests/Makefile | 1 + .../amdgpu_dm/tests/amdgpu_dm_mst_types_test.c | 124 +++++++++++++++++++++ 4 files changed, 133 insertions(+) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c index 99b78dd50caf..ff3afeb0ec07 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c @@ -39,6 +39,7 @@ #include "dc.h" #include "dm_helpers.h" +#include "amdgpu_dm_kunit_helpers.h" #include "ddc_service_types.h" #include "dpcd_defs.h" @@ -248,6 +249,7 @@ bool needs_dsc_aux_workaround(struct dc_link *link) return false; } +EXPORT_IF_KUNIT(needs_dsc_aux_workaround); #if defined(CONFIG_DRM_AMD_DC_FP) static bool is_synaptics_cascaded_panamera(struct dc_link *link, struct drm_dp_mst_port *port) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.h index 0e8eef5bdb74..208629ca3721 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.h @@ -57,8 +57,14 @@ enum mst_msg_ready_type { DOWN_OR_UP_MSG_RDY_EVENT = 3 }; +struct amdgpu_device; struct amdgpu_display_manager; struct amdgpu_dm_connector; +struct dc_state; +struct dc_stream_state; +struct dm_atomic_state; +struct drm_atomic_state; +struct drm_dp_mst_topology_mgr; uint32_t dm_mst_get_pbn_divider(struct dc_link *link); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile index ce1e46acb7af..fe9f32c9bdde 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile @@ -17,3 +17,4 @@ obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_psr_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_replay_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_ism_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_wb_test.o +obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_mst_types_test.o diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c new file mode 100644 index 000000000000..e21386819ea1 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit tests for amdgpu_dm_mst_types.c + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include + +#include "dc.h" +#include "dpcd_defs.h" +#include "amdgpu_dm_mst_types.h" + +/* Tests for needs_dsc_aux_workaround */ + +/** + * dm_mst_test_needs_dsc_aux_workaround_match - Test workaround triggers for matching device + * @test: KUnit test context + * + * Verify that needs_dsc_aux_workaround() returns true when the link has + * the specific branch device ID, DPCD rev 1.4, and sink count >= 2. + */ +static void dm_mst_test_needs_dsc_aux_workaround_match(struct kunit *test) +{ + struct dc_link link = {0}; + + link.dpcd_caps.branch_dev_id = DP_BRANCH_DEVICE_ID_90CC24; + link.dpcd_caps.dpcd_rev.raw = DPCD_REV_14; + link.dpcd_caps.sink_count.bits.SINK_COUNT = 2; + + KUNIT_EXPECT_TRUE(test, needs_dsc_aux_workaround(&link)); +} + +/** + * dm_mst_test_needs_dsc_aux_workaround_rev12 - Test workaround triggers for DPCD rev 1.2 + * @test: KUnit test context + * + * Verify that needs_dsc_aux_workaround() returns true when the link has + * the specific branch device ID, DPCD rev 1.2, and sink count >= 2. + */ +static void dm_mst_test_needs_dsc_aux_workaround_rev12(struct kunit *test) +{ + struct dc_link link = {0}; + + link.dpcd_caps.branch_dev_id = DP_BRANCH_DEVICE_ID_90CC24; + link.dpcd_caps.dpcd_rev.raw = DPCD_REV_12; + link.dpcd_caps.sink_count.bits.SINK_COUNT = 3; + + KUNIT_EXPECT_TRUE(test, needs_dsc_aux_workaround(&link)); +} + +/** + * dm_mst_test_needs_dsc_aux_workaround_wrong_dev_id - Test workaround skipped for wrong device + * @test: KUnit test context + * + * Verify that needs_dsc_aux_workaround() returns false when the branch + * device ID does not match DP_BRANCH_DEVICE_ID_90CC24. + */ +static void dm_mst_test_needs_dsc_aux_workaround_wrong_dev_id(struct kunit *test) +{ + struct dc_link link = {0}; + + link.dpcd_caps.branch_dev_id = 0x123456; + link.dpcd_caps.dpcd_rev.raw = DPCD_REV_14; + link.dpcd_caps.sink_count.bits.SINK_COUNT = 2; + + KUNIT_EXPECT_FALSE(test, needs_dsc_aux_workaround(&link)); +} + +/** + * dm_mst_test_needs_dsc_aux_workaround_wrong_rev - Test workaround skipped for unsupported rev + * @test: KUnit test context + * + * Verify that needs_dsc_aux_workaround() returns false when the DPCD + * revision is neither 1.2 nor 1.4. + */ +static void dm_mst_test_needs_dsc_aux_workaround_wrong_rev(struct kunit *test) +{ + struct dc_link link = {0}; + + link.dpcd_caps.branch_dev_id = DP_BRANCH_DEVICE_ID_90CC24; + link.dpcd_caps.dpcd_rev.raw = 0x11; /* DPCD 1.1 */ + link.dpcd_caps.sink_count.bits.SINK_COUNT = 2; + + KUNIT_EXPECT_FALSE(test, needs_dsc_aux_workaround(&link)); +} + +/** + * dm_mst_test_needs_dsc_aux_workaround_low_sink_count - Test workaround skipped for single sink + * @test: KUnit test context + * + * Verify that needs_dsc_aux_workaround() returns false when the sink + * count is less than 2, even if device ID and DPCD rev match. + */ +static void dm_mst_test_needs_dsc_aux_workaround_low_sink_count(struct kunit *test) +{ + struct dc_link link = {0}; + + link.dpcd_caps.branch_dev_id = DP_BRANCH_DEVICE_ID_90CC24; + link.dpcd_caps.dpcd_rev.raw = DPCD_REV_14; + link.dpcd_caps.sink_count.bits.SINK_COUNT = 1; + + KUNIT_EXPECT_FALSE(test, needs_dsc_aux_workaround(&link)); +} + +static struct kunit_case dm_mst_types_test_cases[] = { + /* needs_dsc_aux_workaround tests */ + KUNIT_CASE(dm_mst_test_needs_dsc_aux_workaround_match), + KUNIT_CASE(dm_mst_test_needs_dsc_aux_workaround_rev12), + KUNIT_CASE(dm_mst_test_needs_dsc_aux_workaround_wrong_dev_id), + KUNIT_CASE(dm_mst_test_needs_dsc_aux_workaround_wrong_rev), + KUNIT_CASE(dm_mst_test_needs_dsc_aux_workaround_low_sink_count), + {} +}; + +static struct kunit_suite dm_mst_types_test_suite = { + .name = "amdgpu_dm_mst_types", + .test_cases = dm_mst_types_test_cases, +}; + +kunit_test_suite(dm_mst_types_test_suite); + +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_mst_types"); -- cgit v1.2.3 From e561531f2fca3ff4346b791dcbf7801aa1e172e8 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Thu, 28 May 2026 11:48:11 -0600 Subject: drm/amd/display: Fix incorrect logic in CRC source handling [WHAT] Fix three issues amdgpu_dm_crc.c: - Use cur_crc_src instead of source when deciding whether to call drm_dp_stop_crc() in the disable path of set_crc_source(). When disabling CRC, source is always NONE so dm_is_crc_source_dprx(source) was always false, meaning drm_dp_stop_crc() was never called when stopping a DPRX CRC source. Use cur_crc_src to check what was previously active instead. - Replace fragile 'source < 0' comparisons in verify_crc_source() and set_crc_source() with AMDGPU_DM_PIPE_CRC_SOURCE_INVALID. and avoiding signed/unsigned enum comparison concerns. - Remove redundant NULL initializations for drm_dev and acrtc in handle_crc_irq(). Both variables are unconditionally assigned right after. Assisted-by: Copilot:Claude-Sonnet-4.6 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Aurabindo Pillai Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c index 88f7cfea5624..daf50ec6bc80 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c @@ -496,7 +496,7 @@ amdgpu_dm_crtc_verify_crc_source(struct drm_crtc *crtc, const char *src_name, { enum amdgpu_dm_pipe_crc_source source = dm_parse_crc_source(src_name); - if (source < 0) { + if (source == AMDGPU_DM_PIPE_CRC_SOURCE_INVALID) { DRM_DEBUG_DRIVER("Unknown CRC source %s for CRTC%d\n", src_name, crtc->index); return -EINVAL; @@ -595,7 +595,7 @@ int amdgpu_dm_crtc_set_crc_source(struct drm_crtc *crtc, const char *src_name) bool enabled = false; int ret = 0; - if (source < 0) { + if (source == AMDGPU_DM_PIPE_CRC_SOURCE_INVALID) { DRM_DEBUG_DRIVER("Unknown CRC source %s for CRTC%d\n", src_name, crtc->index); return -EINVAL; @@ -724,7 +724,7 @@ int amdgpu_dm_crtc_set_crc_source(struct drm_crtc *crtc, const char *src_name) } } else if (enabled && !enable) { drm_crtc_vblank_put(crtc); - if (dm_is_crc_source_dprx(source)) { + if (dm_is_crc_source_dprx(cur_crc_src)) { if (drm_dp_stop_crc(aux)) { DRM_DEBUG_DRIVER("dp stop crc failed\n"); ret = -EINVAL; @@ -767,9 +767,9 @@ void amdgpu_dm_crtc_handle_crc_irq(struct drm_crtc *crtc) { struct dm_crtc_state *crtc_state; struct dc_stream_state *stream_state; - struct drm_device *drm_dev = NULL; + struct drm_device *drm_dev; enum amdgpu_dm_pipe_crc_source cur_crc_src; - struct amdgpu_crtc *acrtc = NULL; + struct amdgpu_crtc *acrtc; uint32_t crcs[3]; unsigned long flags; -- cgit v1.2.3 From 29757b93d796558bc8eababb1bd2f77b0c32e349 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Thu, 28 May 2026 14:01:08 -0600 Subject: drm/amd/display: Extract DPRX CRC transition helpers for KUnit testing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extract three pure predicate functions from amdgpu_dm_crtc_set_crc_source(): - dm_need_dp_aux - dm_crc_source_should_start_dprx - dm_crc_source_should_stop_dprx Refactor set_crc_source() to use these helpers, replacing the nested if/else if structure with flat, mutually-exclusive branches driven by the new predicates. Add KUnit test cases covering all relevant source combinations for each helper, including the regression case where DPRX→NONE must trigger drm_dp_stop_crc(). Assisted-by: Copilot:Claude-Sonnet-4.6 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Aurabindo Pillai Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c | 92 +++++++++++++--- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.h | 6 + .../display/amdgpu_dm/tests/amdgpu_dm_crc_test.c | 122 +++++++++++++++++++++ 3 files changed, 203 insertions(+), 17 deletions(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c index daf50ec6bc80..54d3c5c9e652 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c @@ -87,6 +87,65 @@ bool dm_need_crc_dither(enum amdgpu_dm_pipe_crc_source src) } EXPORT_IF_KUNIT(dm_need_crc_dither); +/** + * dm_need_dp_aux() - Does this source transition require the DP AUX handle? + * @source: Requested CRC source. + * @cur_crc_src: Current CRC source. + * + * Returns true when either the new source is DPRX-based (starting DPRX CRC), + * or the current source is DPRX-based and the new source is NONE (stopping it). + * + * Return: true if the DP AUX handle is needed, false otherwise. + */ +STATIC_IFN_KUNIT +bool dm_need_dp_aux(enum amdgpu_dm_pipe_crc_source source, + enum amdgpu_dm_pipe_crc_source cur_crc_src) +{ + return dm_is_crc_source_dprx(source) || + (source == AMDGPU_DM_PIPE_CRC_SOURCE_NONE && dm_is_crc_source_dprx(cur_crc_src)); +} +EXPORT_IF_KUNIT(dm_need_dp_aux); + +/** + * dm_crc_source_should_start_dprx() - Should drm_dp_start_crc() be called? + * @source: Requested CRC source. + * @cur_crc_src: Current CRC source. + * + * True when CRC is transitioning from off to a DPRX source + * (!enabled && enable && is_dprx(@source)). + * + * Return: true if drm_dp_start_crc() should be called, false otherwise. + */ +STATIC_IFN_KUNIT +bool dm_crc_source_should_start_dprx(enum amdgpu_dm_pipe_crc_source source, + enum amdgpu_dm_pipe_crc_source cur_crc_src) +{ + return !amdgpu_dm_is_valid_crc_source(cur_crc_src) && + amdgpu_dm_is_valid_crc_source(source) && + dm_is_crc_source_dprx(source); +} +EXPORT_IF_KUNIT(dm_crc_source_should_start_dprx); + +/** + * dm_crc_source_should_stop_dprx() - Should drm_dp_stop_crc() be called? + * @source: Requested CRC source. + * @cur_crc_src: Current CRC source. + * + * True when CRC is transitioning from a DPRX source to off + * (enabled && !enable && is_dprx(@cur_crc_src)). + * + * Return: true if drm_dp_stop_crc() should be called, false otherwise. + */ +STATIC_IFN_KUNIT +bool dm_crc_source_should_stop_dprx(enum amdgpu_dm_pipe_crc_source source, + enum amdgpu_dm_pipe_crc_source cur_crc_src) +{ + return amdgpu_dm_is_valid_crc_source(cur_crc_src) && + !amdgpu_dm_is_valid_crc_source(source) && + dm_is_crc_source_dprx(cur_crc_src); +} +EXPORT_IF_KUNIT(dm_crc_source_should_stop_dprx); + const char *const *amdgpu_dm_crtc_get_crc_sources(struct drm_crtc *crtc, size_t *count) { @@ -650,9 +709,7 @@ int amdgpu_dm_crtc_set_crc_source(struct drm_crtc *crtc, const char *src_name) * CRTC DITHER | XXXX | Enable CRTC CRC, set dither * DPRX DITHER | XXXX | Enable DPRX CRC, need 'aux', set dither */ - if (dm_is_crc_source_dprx(source) || - (source == AMDGPU_DM_PIPE_CRC_SOURCE_NONE && - dm_is_crc_source_dprx(cur_crc_src))) { + if (dm_need_dp_aux(source, cur_crc_src)) { struct amdgpu_dm_connector *aconn = NULL; struct drm_connector *connector; struct drm_connector_list_iter conn_iter; @@ -714,23 +771,24 @@ int amdgpu_dm_crtc_set_crc_source(struct drm_crtc *crtc, const char *src_name) goto cleanup; } - if (!enabled && enable) { - if (dm_is_crc_source_dprx(source)) { - if (drm_dp_start_crc(aux, crtc)) { - DRM_DEBUG_DRIVER("dp start crc failed\n"); - ret = -EINVAL; - goto cleanup; - } + if (dm_crc_source_should_start_dprx(source, cur_crc_src)) { + /* !enabled && enable && is_dprx(source): CRC off → DPRX on */ + if (drm_dp_start_crc(aux, crtc)) { + DRM_DEBUG_DRIVER("dp start crc failed\n"); + ret = -EINVAL; + goto cleanup; } - } else if (enabled && !enable) { + } else if (dm_crc_source_should_stop_dprx(source, cur_crc_src)) { + /* enabled && !enable && is_dprx(cur_crc_src): DPRX on → CRC off */ drm_crtc_vblank_put(crtc); - if (dm_is_crc_source_dprx(cur_crc_src)) { - if (drm_dp_stop_crc(aux)) { - DRM_DEBUG_DRIVER("dp stop crc failed\n"); - ret = -EINVAL; - goto cleanup; - } + if (drm_dp_stop_crc(aux)) { + DRM_DEBUG_DRIVER("dp stop crc failed\n"); + ret = -EINVAL; + goto cleanup; } + } else if (enabled && !enable) { + /* Non-DPRX source (e.g. CRTC) turning off: release vblank ref */ + drm_crtc_vblank_put(crtc); } spin_lock_irq(&drm_dev->event_lock); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.h index c9aa0c82038f..8bb8a6f6c148 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.h @@ -156,6 +156,12 @@ enum amdgpu_dm_pipe_crc_source dm_parse_crc_source(const char *source); bool dm_is_crc_source_crtc(enum amdgpu_dm_pipe_crc_source src); bool dm_is_crc_source_dprx(enum amdgpu_dm_pipe_crc_source src); bool dm_need_crc_dither(enum amdgpu_dm_pipe_crc_source src); +bool dm_need_dp_aux(enum amdgpu_dm_pipe_crc_source source, + enum amdgpu_dm_pipe_crc_source cur_crc_src); +bool dm_crc_source_should_start_dprx(enum amdgpu_dm_pipe_crc_source source, + enum amdgpu_dm_pipe_crc_source cur_crc_src); +bool dm_crc_source_should_stop_dprx(enum amdgpu_dm_pipe_crc_source source, + enum amdgpu_dm_pipe_crc_source cur_crc_src); #endif #endif /* AMD_DAL_DEV_AMDGPU_DM_AMDGPU_DM_CRC_H_ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crc_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crc_test.c index bba8b1a8fa1c..a6fd3a6fd803 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crc_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crc_test.c @@ -95,17 +95,139 @@ static void dm_test_is_valid_crc_source(struct kunit *test) KUNIT_EXPECT_FALSE(test, amdgpu_dm_is_valid_crc_source(AMDGPU_DM_PIPE_CRC_SOURCE_INVALID)); } +/** + * dm_test_need_dp_aux() - Test dm_need_dp_aux(). + * @test: KUnit test context. + * + * Verifies that dm_need_dp_aux() returns true when the transition starts or + * stops a DPRX CRC source (requiring the DP AUX handle), and false for + * non-DPRX transitions such as CRTC or NONE→NONE. + */ +static void dm_test_need_dp_aux(struct kunit *test) +{ + /* Starting a DPRX source always needs AUX, regardless of current source */ + KUNIT_EXPECT_TRUE(test, dm_need_dp_aux(AMDGPU_DM_PIPE_CRC_SOURCE_DPRX, + AMDGPU_DM_PIPE_CRC_SOURCE_NONE)); + KUNIT_EXPECT_TRUE(test, dm_need_dp_aux(AMDGPU_DM_PIPE_CRC_SOURCE_DPRX, + AMDGPU_DM_PIPE_CRC_SOURCE_CRTC)); + KUNIT_EXPECT_TRUE(test, dm_need_dp_aux(AMDGPU_DM_PIPE_CRC_SOURCE_DPRX_DITHER, + AMDGPU_DM_PIPE_CRC_SOURCE_NONE)); + + /* Stopping a DPRX source (NONE requested, DPRX was active) needs AUX */ + KUNIT_EXPECT_TRUE(test, dm_need_dp_aux(AMDGPU_DM_PIPE_CRC_SOURCE_NONE, + AMDGPU_DM_PIPE_CRC_SOURCE_DPRX)); + KUNIT_EXPECT_TRUE(test, dm_need_dp_aux(AMDGPU_DM_PIPE_CRC_SOURCE_NONE, + AMDGPU_DM_PIPE_CRC_SOURCE_DPRX_DITHER)); + + /* CRTC transitions do not need AUX */ + KUNIT_EXPECT_FALSE(test, dm_need_dp_aux(AMDGPU_DM_PIPE_CRC_SOURCE_CRTC, + AMDGPU_DM_PIPE_CRC_SOURCE_NONE)); + KUNIT_EXPECT_FALSE(test, dm_need_dp_aux(AMDGPU_DM_PIPE_CRC_SOURCE_NONE, + AMDGPU_DM_PIPE_CRC_SOURCE_CRTC)); + KUNIT_EXPECT_FALSE(test, dm_need_dp_aux(AMDGPU_DM_PIPE_CRC_SOURCE_NONE, + AMDGPU_DM_PIPE_CRC_SOURCE_NONE)); +} + +/** + * dm_test_crc_source_should_start_dprx() - Test dm_crc_source_should_start_dprx(). + * @test: KUnit test context. + * + * Verifies that dm_crc_source_should_start_dprx() returns true only when CRC + * is transitioning from off (!enabled) to a DPRX source (enable && + * is_dprx(source)), and false for all other combinations including + * already-enabled or non-DPRX targets. + */ +static void dm_test_crc_source_should_start_dprx(struct kunit *test) +{ + /* CRC off → DPRX: should start */ + KUNIT_EXPECT_TRUE(test, + dm_crc_source_should_start_dprx(AMDGPU_DM_PIPE_CRC_SOURCE_DPRX, + AMDGPU_DM_PIPE_CRC_SOURCE_NONE)); + KUNIT_EXPECT_TRUE(test, + dm_crc_source_should_start_dprx(AMDGPU_DM_PIPE_CRC_SOURCE_DPRX_DITHER, + AMDGPU_DM_PIPE_CRC_SOURCE_NONE)); + + /* CRC already on (any source) → DPRX: should NOT start (already enabled) */ + KUNIT_EXPECT_FALSE(test, + dm_crc_source_should_start_dprx(AMDGPU_DM_PIPE_CRC_SOURCE_DPRX, + AMDGPU_DM_PIPE_CRC_SOURCE_CRTC)); + KUNIT_EXPECT_FALSE(test, + dm_crc_source_should_start_dprx(AMDGPU_DM_PIPE_CRC_SOURCE_DPRX, + AMDGPU_DM_PIPE_CRC_SOURCE_DPRX)); + + /* CRC off → CRTC: not a DPRX start */ + KUNIT_EXPECT_FALSE(test, + dm_crc_source_should_start_dprx(AMDGPU_DM_PIPE_CRC_SOURCE_CRTC, + AMDGPU_DM_PIPE_CRC_SOURCE_NONE)); + + /* Disabling: should not start */ + KUNIT_EXPECT_FALSE(test, + dm_crc_source_should_start_dprx(AMDGPU_DM_PIPE_CRC_SOURCE_NONE, + AMDGPU_DM_PIPE_CRC_SOURCE_DPRX)); +} + +/** + * dm_test_crc_source_should_stop_dprx() - Test dm_crc_source_should_stop_dprx(). + * @test: KUnit test context. + * + * Verifies that dm_crc_source_should_stop_dprx() returns true only when CRC + * is transitioning from a DPRX source (enabled && is_dprx(cur_crc_src)) to + * off (!enable), and false for non-DPRX disables, DPRX starts, and no-op + * transitions. + */ +static void dm_test_crc_source_should_stop_dprx(struct kunit *test) +{ + /* DPRX → off: should stop */ + KUNIT_EXPECT_TRUE(test, + dm_crc_source_should_stop_dprx(AMDGPU_DM_PIPE_CRC_SOURCE_NONE, + AMDGPU_DM_PIPE_CRC_SOURCE_DPRX)); + KUNIT_EXPECT_TRUE(test, + dm_crc_source_should_stop_dprx(AMDGPU_DM_PIPE_CRC_SOURCE_NONE, + AMDGPU_DM_PIPE_CRC_SOURCE_DPRX_DITHER)); + + /* CRTC → off: not a DPRX stop */ + KUNIT_EXPECT_FALSE(test, + dm_crc_source_should_stop_dprx(AMDGPU_DM_PIPE_CRC_SOURCE_NONE, + AMDGPU_DM_PIPE_CRC_SOURCE_CRTC)); + + /* off → DPRX: not a stop */ + KUNIT_EXPECT_FALSE(test, + dm_crc_source_should_stop_dprx(AMDGPU_DM_PIPE_CRC_SOURCE_DPRX, + AMDGPU_DM_PIPE_CRC_SOURCE_NONE)); + + /* DPRX → DPRX: no transition, not a stop */ + KUNIT_EXPECT_FALSE(test, + dm_crc_source_should_stop_dprx(AMDGPU_DM_PIPE_CRC_SOURCE_DPRX, + AMDGPU_DM_PIPE_CRC_SOURCE_DPRX)); + + /* off → off: not a stop */ + KUNIT_EXPECT_FALSE(test, + dm_crc_source_should_stop_dprx(AMDGPU_DM_PIPE_CRC_SOURCE_NONE, + AMDGPU_DM_PIPE_CRC_SOURCE_NONE)); +} + static struct kunit_case dm_crc_test_cases[] = { + /* dm_parse_crc_source() */ KUNIT_CASE(dm_test_parse_crc_source_none), KUNIT_CASE(dm_test_parse_crc_source_crtc), KUNIT_CASE(dm_test_parse_crc_source_dprx), KUNIT_CASE(dm_test_parse_crc_source_crtc_dither), KUNIT_CASE(dm_test_parse_crc_source_dprx_dither), KUNIT_CASE(dm_test_parse_crc_source_invalid), + /* dm_is_crc_source_crtc() */ KUNIT_CASE(dm_test_is_crc_source_crtc), + /* dm_is_crc_source_dprx() */ KUNIT_CASE(dm_test_is_crc_source_dprx), + /* dm_need_crc_dither() */ KUNIT_CASE(dm_test_need_crc_dither), + /* amdgpu_dm_is_valid_crc_source() */ KUNIT_CASE(dm_test_is_valid_crc_source), + /* dm_need_dp_aux() */ + KUNIT_CASE(dm_test_need_dp_aux), + /* dm_crc_source_should_start_dprx() */ + KUNIT_CASE(dm_test_crc_source_should_start_dprx), + /* dm_crc_source_should_stop_dprx() */ + KUNIT_CASE(dm_test_crc_source_should_stop_dprx), {} }; -- cgit v1.2.3 From 569cc68d6a7b82fa971153f08347712844db469c Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Wed, 27 May 2026 17:01:13 -0600 Subject: drm/amd/display: Extract HDCP testable helpers for KUnit [WHAT] Extract hdcp_get_content_protection_from_status() and hdcp_get_link_display_adjustments() from event_property_update() and hdcp_update_display() so the pure decision logic can be KUnit-tested. Also update function comments to kernel-doc formats. Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Aurabindo Pillai Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c | 115 +++++--- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h | 12 + .../display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c | 297 +++++++++++++++++++-- 3 files changed, 370 insertions(+), 54 deletions(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c index 4c164ae4a4f9..5dbeb1e017d4 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c @@ -182,6 +182,70 @@ void process_output(struct hdcp_workqueue *hdcp_work) } EXPORT_IF_KUNIT(process_output); +STATIC_IFN_KUNIT +bool hdcp_get_content_protection_from_status( + unsigned int hdcp_content_type, + enum mod_hdcp_encryption_status encryption_status, + unsigned int *content_protection) +{ + if (encryption_status == MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF) { + *content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; + return true; + } + + if (hdcp_content_type == DRM_MODE_HDCP_CONTENT_TYPE0 && + encryption_status <= MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE0_ON) { + *content_protection = DRM_MODE_CONTENT_PROTECTION_ENABLED; + return true; + } + + if (hdcp_content_type == DRM_MODE_HDCP_CONTENT_TYPE1 && + encryption_status == MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON) { + *content_protection = DRM_MODE_CONTENT_PROTECTION_ENABLED; + return true; + } + + return false; +} +EXPORT_IF_KUNIT(hdcp_get_content_protection_from_status); + +STATIC_IFN_KUNIT +void hdcp_get_link_display_adjustments( + bool enable_encryption, + u8 content_type, + bool fused_io_supported, + bool hdcp_lc_force_fw_enable, + bool hdcp_lc_enable_sw_fallback, + struct mod_hdcp_link_adjustment *link_adjust, + struct mod_hdcp_display_adjustment *display_adjust) +{ + memset(link_adjust, 0, sizeof(*link_adjust)); + memset(display_adjust, 0, sizeof(*display_adjust)); + + if (!enable_encryption) { + display_adjust->disable = + MOD_HDCP_DISPLAY_DISABLE_AUTHENTICATION; + return; + } + + display_adjust->disable = MOD_HDCP_DISPLAY_NOT_DISABLE; + link_adjust->auth_delay = 2; + link_adjust->retry_limit = MAX_NUM_OF_ATTEMPTS; + + if (content_type == DRM_MODE_HDCP_CONTENT_TYPE0) { + link_adjust->hdcp2.force_type = MOD_HDCP_FORCE_TYPE_0; + } else if (content_type == DRM_MODE_HDCP_CONTENT_TYPE1) { + link_adjust->hdcp1.disable = 1; + link_adjust->hdcp2.force_type = MOD_HDCP_FORCE_TYPE_1; + } + + link_adjust->hdcp2.use_fw_locality_check = + fused_io_supported || hdcp_lc_force_fw_enable; + link_adjust->hdcp2.use_sw_locality_fallback = + hdcp_lc_enable_sw_fallback; +} +EXPORT_IF_KUNIT(hdcp_get_link_display_adjustments); + static void link_lock(struct hdcp_workqueue *work, bool lock) { int i = 0; @@ -212,8 +276,11 @@ void hdcp_update_display(struct hdcp_workqueue *hdcp_work, drm_connector_put(&hdcp_w->aconnector[conn_index]->base); hdcp_w->aconnector[conn_index] = aconnector; - memset(&link_adjust, 0, sizeof(link_adjust)); - memset(&display_adjust, 0, sizeof(display_adjust)); + hdcp_get_link_display_adjustments(enable_encryption, content_type, + dc->caps.fused_io_supported, + dc->debug.hdcp_lc_force_fw_enable, + dc->debug.hdcp_lc_enable_sw_fallback, + &link_adjust, &display_adjust); if (enable_encryption) { /* Explicitly set the saved SRM as sysfs call will be after we already enabled hdcp @@ -224,25 +291,9 @@ void hdcp_update_display(struct hdcp_workqueue *hdcp_work, hdcp_work->srm_size, &hdcp_work->srm_version); - display_adjust.disable = MOD_HDCP_DISPLAY_NOT_DISABLE; - - link_adjust.auth_delay = 2; - link_adjust.retry_limit = MAX_NUM_OF_ATTEMPTS; - - if (content_type == DRM_MODE_HDCP_CONTENT_TYPE0) { - link_adjust.hdcp2.force_type = MOD_HDCP_FORCE_TYPE_0; - } else if (content_type == DRM_MODE_HDCP_CONTENT_TYPE1) { - link_adjust.hdcp1.disable = 1; - link_adjust.hdcp2.force_type = MOD_HDCP_FORCE_TYPE_1; - } - link_adjust.hdcp2.use_fw_locality_check = - (dc->caps.fused_io_supported || dc->debug.hdcp_lc_force_fw_enable); - link_adjust.hdcp2.use_sw_locality_fallback = dc->debug.hdcp_lc_enable_sw_fallback; - schedule_delayed_work(&hdcp_w->property_validate_dwork, msecs_to_jiffies(DRM_HDCP_CHECK_PERIOD_MS)); } else { - display_adjust.disable = MOD_HDCP_DISPLAY_DISABLE_AUTHENTICATION; hdcp_w->encryption_status[conn_index] = MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF; cancel_delayed_work(&hdcp_w->property_validate_dwork); } @@ -336,6 +387,7 @@ static void event_property_update(struct work_struct *work) property_update_work); struct amdgpu_dm_connector *aconnector = NULL; struct drm_device *dev; + unsigned int content_protection; long ret; unsigned int conn_index; struct drm_connector *connector; @@ -375,26 +427,15 @@ static void event_property_update(struct work_struct *work) MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF; } } - if (hdcp_work->encryption_status[conn_index] != - MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF) { - if (conn_state->hdcp_content_type == - DRM_MODE_HDCP_CONTENT_TYPE0 && - hdcp_work->encryption_status[conn_index] <= - MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE0_ON) { + if (hdcp_get_content_protection_from_status(conn_state->hdcp_content_type, + hdcp_work->encryption_status[conn_index], + &content_protection)) { + if (content_protection == DRM_MODE_CONTENT_PROTECTION_ENABLED) DRM_DEBUG_DRIVER("[HDCP_DM] DRM_MODE_CONTENT_PROTECTION_ENABLED\n"); - drm_hdcp_update_content_protection(connector, - DRM_MODE_CONTENT_PROTECTION_ENABLED); - } else if (conn_state->hdcp_content_type == - DRM_MODE_HDCP_CONTENT_TYPE1 && - hdcp_work->encryption_status[conn_index] == - MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON) { - drm_hdcp_update_content_protection(connector, - DRM_MODE_CONTENT_PROTECTION_ENABLED); - } - } else { - DRM_DEBUG_DRIVER("[HDCP_DM] DRM_MODE_CONTENT_PROTECTION_DESIRED\n"); - drm_hdcp_update_content_protection(connector, - DRM_MODE_CONTENT_PROTECTION_DESIRED); + else + DRM_DEBUG_DRIVER("[HDCP_DM] DRM_MODE_CONTENT_PROTECTION_DESIRED\n"); + + drm_hdcp_update_content_protection(connector, content_protection); } drm_modeset_unlock(&dev->mode_config.connection_mutex); } diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h index 90b18c450ca6..3ba5823aed9f 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h @@ -96,6 +96,18 @@ struct hdcp_workqueue *hdcp_create_workqueue(struct amdgpu_device *adev, struct #if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) void process_output(struct hdcp_workqueue *hdcp_work); +bool hdcp_get_content_protection_from_status( + unsigned int hdcp_content_type, + enum mod_hdcp_encryption_status encryption_status, + unsigned int *content_protection); +void hdcp_get_link_display_adjustments( + bool enable_encryption, + u8 content_type, + bool fused_io_supported, + bool hdcp_lc_force_fw_enable, + bool hdcp_lc_enable_sw_fallback, + struct mod_hdcp_link_adjustment *link_adjust, + struct mod_hdcp_display_adjustment *display_adjust); #endif #endif /* AMDGPU_DM_AMDGPU_DM_HDCP_H_ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c index d03b606d27bc..619b4a80c82b 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_hdcp_test.c @@ -12,11 +12,241 @@ static void dummy_work_fn(struct work_struct *work) {} +/* Tests for hdcp_get_content_protection_from_status() */ + +/** + * dm_test_hdcp_get_cp_disabled_returns_desired - HDCP off maps to DESIRED + * @test: KUnit test context + * + * When encryption status is HDCP_OFF, content_protection should be set + * to DESIRED and the function should return true to indicate an update. + */ +static void dm_test_hdcp_get_cp_disabled_returns_desired(struct kunit *test) +{ + unsigned int content_protection = DRM_MODE_CONTENT_PROTECTION_UNDESIRED; + bool update; + + update = hdcp_get_content_protection_from_status( + DRM_MODE_HDCP_CONTENT_TYPE0, + MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF, + &content_protection); + + KUNIT_EXPECT_TRUE(test, update); + KUNIT_EXPECT_EQ(test, content_protection, + DRM_MODE_CONTENT_PROTECTION_DESIRED); +} + +/** + * dm_test_hdcp_get_cp_type0_returns_enabled - TYPE0 with TYPE0_ON maps to ENABLED + * @test: KUnit test context + * + * When content type is TYPE0 and encryption status is at or below + * HDCP2_TYPE0_ON, content_protection should be set to ENABLED. + */ +static void dm_test_hdcp_get_cp_type0_returns_enabled(struct kunit *test) +{ + unsigned int content_protection = DRM_MODE_CONTENT_PROTECTION_UNDESIRED; + bool update; + + update = hdcp_get_content_protection_from_status( + DRM_MODE_HDCP_CONTENT_TYPE0, + MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE0_ON, + &content_protection); + + KUNIT_EXPECT_TRUE(test, update); + KUNIT_EXPECT_EQ(test, content_protection, + DRM_MODE_CONTENT_PROTECTION_ENABLED); +} + +/** + * dm_test_hdcp_get_cp_type1_returns_enabled - TYPE1 with TYPE1_ON maps to ENABLED + * @test: KUnit test context + * + * When content type is TYPE1 and encryption status is exactly + * HDCP2_TYPE1_ON, content_protection should be set to ENABLED. + */ +static void dm_test_hdcp_get_cp_type1_returns_enabled(struct kunit *test) +{ + unsigned int content_protection = DRM_MODE_CONTENT_PROTECTION_UNDESIRED; + bool update; + + update = hdcp_get_content_protection_from_status( + DRM_MODE_HDCP_CONTENT_TYPE1, + MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON, + &content_protection); + + KUNIT_EXPECT_TRUE(test, update); + KUNIT_EXPECT_EQ(test, content_protection, + DRM_MODE_CONTENT_PROTECTION_ENABLED); +} + +/** + * dm_test_hdcp_get_cp_type1_rejects_type0_status - TYPE1 rejects TYPE0_ON + * @test: KUnit test context + * + * When content type is TYPE1 but encryption status is only TYPE0_ON, + * the function should return false and leave content_protection unchanged. + */ +static void dm_test_hdcp_get_cp_type1_rejects_type0_status(struct kunit *test) +{ + unsigned int content_protection = DRM_MODE_CONTENT_PROTECTION_UNDESIRED; + bool update; + + update = hdcp_get_content_protection_from_status( + DRM_MODE_HDCP_CONTENT_TYPE1, + MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE0_ON, + &content_protection); + + KUNIT_EXPECT_FALSE(test, update); + KUNIT_EXPECT_EQ(test, content_protection, + DRM_MODE_CONTENT_PROTECTION_UNDESIRED); +} + +/** + * dm_test_hdcp_get_cp_type0_rejects_type1_status - TYPE0 rejects TYPE1_ON + * @test: KUnit test context + * + * When content type is TYPE0 but encryption status exceeds the TYPE0_ON + * boundary (TYPE1_ON), the function should return false. + */ +static void dm_test_hdcp_get_cp_type0_rejects_type1_status(struct kunit *test) +{ + unsigned int content_protection = DRM_MODE_CONTENT_PROTECTION_UNDESIRED; + bool update; + + update = hdcp_get_content_protection_from_status( + DRM_MODE_HDCP_CONTENT_TYPE0, + MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON, + &content_protection); + + KUNIT_EXPECT_FALSE(test, update); + KUNIT_EXPECT_EQ(test, content_protection, + DRM_MODE_CONTENT_PROTECTION_UNDESIRED); +} + +/* Tests for hdcp_get_link_display_adjustments() */ + +/** + * dm_test_hdcp_get_adjustments_disable_authentication - disable path zeroes adjustments + * @test: KUnit test context + * + * When enable_encryption is false, display_adjust should disable + * authentication and all link_adjust fields should remain zeroed. + */ +static void dm_test_hdcp_get_adjustments_disable_authentication(struct kunit *test) +{ + struct mod_hdcp_link_adjustment link_adjust; + struct mod_hdcp_display_adjustment display_adjust; + unsigned int disable; + unsigned int hdcp1_disable; + unsigned int force_type; + + hdcp_get_link_display_adjustments(false, DRM_MODE_HDCP_CONTENT_TYPE0, + false, false, false, &link_adjust, &display_adjust); + disable = display_adjust.disable; + hdcp1_disable = link_adjust.hdcp1.disable; + force_type = link_adjust.hdcp2.force_type; + + KUNIT_EXPECT_EQ(test, disable, + MOD_HDCP_DISPLAY_DISABLE_AUTHENTICATION); + KUNIT_EXPECT_EQ(test, link_adjust.auth_delay, 0); + KUNIT_EXPECT_EQ(test, link_adjust.retry_limit, 0); + KUNIT_EXPECT_EQ(test, hdcp1_disable, 0); + KUNIT_EXPECT_EQ(test, force_type, 0); +} + +/** + * dm_test_hdcp_get_adjustments_type0_policy - TYPE0 enables HDCP1 and forces TYPE0 + * @test: KUnit test context + * + * When encryption is enabled with content TYPE0, hdcp1 should remain + * enabled, force_type should be TYPE_0, and sw_locality_fallback should + * be propagated from the input parameter. + */ +static void dm_test_hdcp_get_adjustments_type0_policy(struct kunit *test) +{ + struct mod_hdcp_link_adjustment link_adjust; + struct mod_hdcp_display_adjustment display_adjust; + unsigned int disable; + unsigned int hdcp1_disable; + unsigned int force_type; + + hdcp_get_link_display_adjustments(true, DRM_MODE_HDCP_CONTENT_TYPE0, + false, false, true, &link_adjust, &display_adjust); + disable = display_adjust.disable; + hdcp1_disable = link_adjust.hdcp1.disable; + force_type = link_adjust.hdcp2.force_type; + + KUNIT_EXPECT_EQ(test, disable, + MOD_HDCP_DISPLAY_NOT_DISABLE); + KUNIT_EXPECT_EQ(test, link_adjust.auth_delay, 2); + KUNIT_EXPECT_EQ(test, link_adjust.retry_limit, MAX_NUM_OF_ATTEMPTS); + KUNIT_EXPECT_EQ(test, hdcp1_disable, 0); + KUNIT_EXPECT_EQ(test, force_type, + MOD_HDCP_FORCE_TYPE_0); + KUNIT_EXPECT_FALSE(test, link_adjust.hdcp2.use_fw_locality_check); + KUNIT_EXPECT_TRUE(test, link_adjust.hdcp2.use_sw_locality_fallback); +} + +/** + * dm_test_hdcp_get_adjustments_type1_policy - TYPE1 disables HDCP1 and forces TYPE1 + * @test: KUnit test context + * + * When encryption is enabled with content TYPE1, hdcp1 should be + * disabled, force_type should be TYPE_1, and fw_locality_check should + * be enabled when hdcp_lc_force_fw_enable is set. + */ +static void dm_test_hdcp_get_adjustments_type1_policy(struct kunit *test) +{ + struct mod_hdcp_link_adjustment link_adjust; + struct mod_hdcp_display_adjustment display_adjust; + unsigned int disable; + unsigned int hdcp1_disable; + unsigned int force_type; + + hdcp_get_link_display_adjustments(true, DRM_MODE_HDCP_CONTENT_TYPE1, + false, true, false, &link_adjust, &display_adjust); + disable = display_adjust.disable; + hdcp1_disable = link_adjust.hdcp1.disable; + force_type = link_adjust.hdcp2.force_type; + + KUNIT_EXPECT_EQ(test, disable, + MOD_HDCP_DISPLAY_NOT_DISABLE); + KUNIT_EXPECT_EQ(test, link_adjust.auth_delay, 2); + KUNIT_EXPECT_EQ(test, link_adjust.retry_limit, MAX_NUM_OF_ATTEMPTS); + KUNIT_EXPECT_EQ(test, hdcp1_disable, 1); + KUNIT_EXPECT_EQ(test, force_type, + MOD_HDCP_FORCE_TYPE_1); + KUNIT_EXPECT_TRUE(test, link_adjust.hdcp2.use_fw_locality_check); + KUNIT_EXPECT_FALSE(test, link_adjust.hdcp2.use_sw_locality_fallback); +} + +/** + * dm_test_hdcp_get_adjustments_fused_io_enables_fw_check - fused_io enables FW locality check + * @test: KUnit test context + * + * When fused_io_supported is true, use_fw_locality_check should be + * enabled regardless of hdcp_lc_force_fw_enable. + */ +static void dm_test_hdcp_get_adjustments_fused_io_enables_fw_check(struct kunit *test) +{ + struct mod_hdcp_link_adjustment link_adjust; + struct mod_hdcp_display_adjustment display_adjust; + + hdcp_get_link_display_adjustments(true, DRM_MODE_HDCP_CONTENT_TYPE0, + true, false, false, &link_adjust, &display_adjust); + + KUNIT_EXPECT_TRUE(test, link_adjust.hdcp2.use_fw_locality_check); +} + /* Tests for process_output() */ -/* - * Helper: allocate and initialise a minimal hdcp_workqueue sufficient for - * process_output() testing. Only the three delayed works accessed by +/** + * alloc_test_workqueue - allocate a minimal hdcp_workqueue for testing + * @test: KUnit test context for managed allocation + * + * Allocates and initialises a minimal hdcp_workqueue sufficient for + * process_output() testing. Only the three delayed works accessed by * process_output() are initialised; everything else is zeroed. */ static struct hdcp_workqueue *alloc_test_workqueue(struct kunit *test) @@ -33,9 +263,12 @@ static struct hdcp_workqueue *alloc_test_workqueue(struct kunit *test) return work; } -/* +/** + * dm_test_process_output_property_validate_always_scheduled - validate_dwork always queued + * @test: KUnit test context + * * process_output() always schedules property_validate_dwork with delay=0, - * which queues the work item directly (bypassing the timer). Use + * which queues the work item directly (bypassing the timer). Uses * work_pending() rather than delayed_work_pending() to detect this. */ static void dm_test_process_output_property_validate_always_scheduled(struct kunit *test) @@ -52,8 +285,12 @@ static void dm_test_process_output_property_validate_always_scheduled(struct kun cancel_delayed_work_sync(&work->property_validate_dwork); } -/* - * output.callback_needed=true must schedule callback_dwork. +/** + * dm_test_process_output_callback_needed - callback_needed schedules callback_dwork + * @test: KUnit test context + * + * When output.callback_needed is true, process_output() must schedule + * callback_dwork with the specified delay. */ static void dm_test_process_output_callback_needed(struct kunit *test) { @@ -70,8 +307,12 @@ static void dm_test_process_output_callback_needed(struct kunit *test) cancel_delayed_work_sync(&work->property_validate_dwork); } -/* - * output.callback_stop=true must cancel a previously scheduled callback_dwork. +/** + * dm_test_process_output_callback_stop - callback_stop cancels callback_dwork + * @test: KUnit test context + * + * When output.callback_stop is true, process_output() must cancel a + * previously scheduled callback_dwork. */ static void dm_test_process_output_callback_stop(struct kunit *test) { @@ -90,8 +331,12 @@ static void dm_test_process_output_callback_stop(struct kunit *test) cancel_delayed_work_sync(&work->property_validate_dwork); } -/* - * output.watchdog_timer_needed=true must schedule watchdog_timer_dwork. +/** + * dm_test_process_output_watchdog_needed - watchdog_needed schedules watchdog_dwork + * @test: KUnit test context + * + * When output.watchdog_timer_needed is true, process_output() must + * schedule watchdog_timer_dwork with the specified delay. */ static void dm_test_process_output_watchdog_needed(struct kunit *test) { @@ -108,9 +353,12 @@ static void dm_test_process_output_watchdog_needed(struct kunit *test) cancel_delayed_work_sync(&work->property_validate_dwork); } -/* - * output.watchdog_timer_stop=true must cancel a previously scheduled - * watchdog_timer_dwork. +/** + * dm_test_process_output_watchdog_stop - watchdog_stop cancels watchdog_dwork + * @test: KUnit test context + * + * When output.watchdog_timer_stop is true, process_output() must cancel + * a previously scheduled watchdog_timer_dwork. */ static void dm_test_process_output_watchdog_stop(struct kunit *test) { @@ -129,9 +377,12 @@ static void dm_test_process_output_watchdog_stop(struct kunit *test) cancel_delayed_work_sync(&work->property_validate_dwork); } -/* - * Both callback_needed and watchdog_timer_needed set: both dworks are - * scheduled independently. +/** + * dm_test_process_output_callback_and_watchdog_needed - both dworks scheduled independently + * @test: KUnit test context + * + * When both callback_needed and watchdog_timer_needed are set, + * process_output() must schedule both dworks independently. */ static void dm_test_process_output_callback_and_watchdog_needed(struct kunit *test) { @@ -154,6 +405,18 @@ static void dm_test_process_output_callback_and_watchdog_needed(struct kunit *te /* End of tests for process_output() */ static struct kunit_case dm_hdcp_test_cases[] = { + /* hdcp_get_content_protection_from_status() */ + KUNIT_CASE(dm_test_hdcp_get_cp_disabled_returns_desired), + KUNIT_CASE(dm_test_hdcp_get_cp_type0_returns_enabled), + KUNIT_CASE(dm_test_hdcp_get_cp_type1_returns_enabled), + KUNIT_CASE(dm_test_hdcp_get_cp_type1_rejects_type0_status), + KUNIT_CASE(dm_test_hdcp_get_cp_type0_rejects_type1_status), + /* hdcp_get_link_display_adjustments() */ + KUNIT_CASE(dm_test_hdcp_get_adjustments_disable_authentication), + KUNIT_CASE(dm_test_hdcp_get_adjustments_type0_policy), + KUNIT_CASE(dm_test_hdcp_get_adjustments_type1_policy), + KUNIT_CASE(dm_test_hdcp_get_adjustments_fused_io_enables_fw_check), + /* process_output() */ KUNIT_CASE(dm_test_process_output_property_validate_always_scheduled), KUNIT_CASE(dm_test_process_output_callback_needed), KUNIT_CASE(dm_test_process_output_callback_stop), -- cgit v1.2.3 From dfbb757c4ce6048b67a95c46f73db3ced6fa8541 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 25 May 2026 15:27:00 -0600 Subject: drm/amd/display: Remove duplicate pp_rn_set_wm_ranges [WHAT] Remove pp_rn_set_wm_ranges and reuse the identical pp_nv_set_wm_ranges for the DCN_VERSION_2_1 case instead. Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Aurabindo Pillai Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c index 2cdb8fea504a..2fda6fbed88f 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c @@ -686,17 +686,6 @@ static enum pp_smu_status pp_rn_get_dpm_clock_table( return PP_SMU_RESULT_OK; } -static enum pp_smu_status pp_rn_set_wm_ranges(struct pp_smu *pp, - struct pp_smu_wm_range_sets *ranges) -{ - const struct dc_context *ctx = pp->dm; - struct amdgpu_device *adev = ctx->driver_context; - - amdgpu_dpm_set_watermarks_for_clocks_ranges(adev, ranges); - - return PP_SMU_RESULT_OK; -} - void dm_pp_get_funcs( struct dc_context *ctx, struct pp_smu_funcs *funcs) @@ -743,7 +732,7 @@ void dm_pp_get_funcs( case DCN_VERSION_2_1: funcs->ctx.ver = PP_SMU_VER_RN; funcs->rn_funcs.pp_smu.dm = ctx; - funcs->rn_funcs.set_wm_ranges = pp_rn_set_wm_ranges; + funcs->rn_funcs.set_wm_ranges = pp_nv_set_wm_ranges; funcs->rn_funcs.get_dpm_clock_table = pp_rn_get_dpm_clock_table; break; default: -- cgit v1.2.3 From bb2baf1dc9e68a68dc76740dcf857106380abd24 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Fri, 29 May 2026 10:06:22 -0600 Subject: drm/amd/display: Add KUnit tests for amdgpu_dm_pp_smu [WHAT] Add KUnit tests for two functions in amdgpu_dm_pp_smu.c: get_default_clock_levels and dc_to_pp_clock_type. Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Aurabindo Pillai Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c | 8 +- .../drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.h | 16 ++ .../gpu/drm/amd/display/amdgpu_dm/tests/Makefile | 2 + .../amdgpu_dm/tests/amdgpu_dm_pp_smu_test.c | 241 +++++++++++++++++++++ 4 files changed, 265 insertions(+), 2 deletions(-) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.h create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_pp_smu_test.c (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c index 2fda6fbed88f..ca7141dbdf6a 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c @@ -33,6 +33,8 @@ #include "amdgpu_dm_irq.h" #include "amdgpu_pm.h" #include "dm_pp_smu.h" +#include "amdgpu_dm_kunit_helpers.h" +#include "amdgpu_dm_pp_smu.h" bool dm_pp_apply_display_requirements( const struct dc_context *ctx, @@ -109,7 +111,7 @@ bool dm_pp_apply_display_requirements( return true; } -static void get_default_clock_levels( +STATIC_IFN_KUNIT void get_default_clock_levels( enum dm_pp_clock_type clk_type, struct dm_pp_clock_levels *clks) { @@ -140,8 +142,9 @@ static void get_default_clock_levels( break; } } +EXPORT_IF_KUNIT(get_default_clock_levels); -static enum amd_pp_clock_type dc_to_pp_clock_type( +STATIC_IFN_KUNIT enum amd_pp_clock_type dc_to_pp_clock_type( enum dm_pp_clock_type dm_pp_clk_type) { enum amd_pp_clock_type amd_pp_clk_type = 0; @@ -182,6 +185,7 @@ static enum amd_pp_clock_type dc_to_pp_clock_type( return amd_pp_clk_type; } +EXPORT_IF_KUNIT(dc_to_pp_clock_type); static void pp_to_dc_clock_levels( const struct amd_pp_clocks *pp_clks, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.h new file mode 100644 index 000000000000..827b60d5affe --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0 OR MIT */ +/* + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#ifndef __AMDGPU_DM_PP_SMU_H__ +#define __AMDGPU_DM_PP_SMU_H__ + +#include "dm_pp_interface.h" + +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +void get_default_clock_levels(enum dm_pp_clock_type clk_type, struct dm_pp_clock_levels *clks); +enum amd_pp_clock_type dc_to_pp_clock_type(enum dm_pp_clock_type dm_pp_clk_type); +#endif + +#endif /* __AMDGPU_DM_PP_SMU_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile index fe9f32c9bdde..4d2eb301c2af 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile @@ -8,6 +8,7 @@ ccflags-y += -I$(src)/../../include ccflags-y += -I$(src)/../../modules/inc ccflags-y += -I$(src)/../../dc ccflags-y += -I$(src)/../../../amdgpu +ccflags-y += -I$(src)/../../../include obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_crc_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_hdcp_test.o @@ -18,3 +19,4 @@ obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_replay_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_ism_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_wb_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_mst_types_test.o +obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_pp_smu_test.o diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_pp_smu_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_pp_smu_test.c new file mode 100644 index 000000000000..556473f55ebe --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_pp_smu_test.c @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit tests for amdgpu_dm_pp_smu.c + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include +#include + +#include "dc.h" +#include "amdgpu_mode.h" +#include "amdgpu_dm.h" +#include "amdgpu_dm_pp_smu.h" + +/* ---- Tests for get_default_clock_levels ---- */ + +/** + * dm_test_default_clock_levels_display - Test display clock default levels + * @test: KUnit test context + * + * Verify that get_default_clock_levels populates 6 display clock levels + * with the expected frequencies in kHz. + */ +static void dm_test_default_clock_levels_display(struct kunit *test) +{ + struct dm_pp_clock_levels clks = { 0 }; + uint32_t expected[] = { 300000, 400000, 496560, 626090, 685720, 757900 }; + int i; + + get_default_clock_levels(DM_PP_CLOCK_TYPE_DISPLAY_CLK, &clks); + + KUNIT_EXPECT_EQ(test, clks.num_levels, 6U); + for (i = 0; i < 6; i++) + KUNIT_EXPECT_EQ(test, clks.clocks_in_khz[i], expected[i]); +} + +/** + * dm_test_default_clock_levels_engine - Test engine clock default levels + * @test: KUnit test context + * + * Verify that get_default_clock_levels populates 6 engine clock levels + * with the expected frequencies in kHz. + */ +static void dm_test_default_clock_levels_engine(struct kunit *test) +{ + struct dm_pp_clock_levels clks = { 0 }; + uint32_t expected[] = { 300000, 360000, 423530, 514290, 626090, 720000 }; + int i; + + get_default_clock_levels(DM_PP_CLOCK_TYPE_ENGINE_CLK, &clks); + + KUNIT_EXPECT_EQ(test, clks.num_levels, 6U); + for (i = 0; i < 6; i++) + KUNIT_EXPECT_EQ(test, clks.clocks_in_khz[i], expected[i]); +} + +/** + * dm_test_default_clock_levels_memory - Test memory clock default levels + * @test: KUnit test context + * + * Verify that get_default_clock_levels populates 2 memory clock levels + * with the expected frequencies in kHz. + */ +static void dm_test_default_clock_levels_memory(struct kunit *test) +{ + struct dm_pp_clock_levels clks = { 0 }; + + get_default_clock_levels(DM_PP_CLOCK_TYPE_MEMORY_CLK, &clks); + + KUNIT_EXPECT_EQ(test, clks.num_levels, 2U); + KUNIT_EXPECT_EQ(test, clks.clocks_in_khz[0], 333000U); + KUNIT_EXPECT_EQ(test, clks.clocks_in_khz[1], 800000U); +} + +/** + * dm_test_default_clock_levels_unknown - Test unknown clock type default + * @test: KUnit test context + * + * Verify that get_default_clock_levels sets num_levels to 0 for an + * unrecognized clock type. + */ +static void dm_test_default_clock_levels_unknown(struct kunit *test) +{ + struct dm_pp_clock_levels clks = { 0 }; + + get_default_clock_levels(DM_PP_CLOCK_TYPE_FCLK, &clks); + + KUNIT_EXPECT_EQ(test, clks.num_levels, 0U); +} + +/* ---- Tests for dc_to_pp_clock_type ---- */ + +/** + * dm_test_dc_to_pp_clock_type_display - Test display clock type mapping + * @test: KUnit test context + * + * Verify DM_PP_CLOCK_TYPE_DISPLAY_CLK maps to amd_pp_disp_clock. + */ +static void dm_test_dc_to_pp_clock_type_display(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)dc_to_pp_clock_type(DM_PP_CLOCK_TYPE_DISPLAY_CLK), + (int)amd_pp_disp_clock); +} + +/** + * dm_test_dc_to_pp_clock_type_engine - Test engine clock type mapping + * @test: KUnit test context + * + * Verify DM_PP_CLOCK_TYPE_ENGINE_CLK maps to amd_pp_sys_clock. + */ +static void dm_test_dc_to_pp_clock_type_engine(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)dc_to_pp_clock_type(DM_PP_CLOCK_TYPE_ENGINE_CLK), + (int)amd_pp_sys_clock); +} + +/** + * dm_test_dc_to_pp_clock_type_memory - Test memory clock type mapping + * @test: KUnit test context + * + * Verify DM_PP_CLOCK_TYPE_MEMORY_CLK maps to amd_pp_mem_clock. + */ +static void dm_test_dc_to_pp_clock_type_memory(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)dc_to_pp_clock_type(DM_PP_CLOCK_TYPE_MEMORY_CLK), + (int)amd_pp_mem_clock); +} + +/** + * dm_test_dc_to_pp_clock_type_dcefclk - Test DCEF clock type mapping + * @test: KUnit test context + * + * Verify DM_PP_CLOCK_TYPE_DCEFCLK maps to amd_pp_dcef_clock. + */ +static void dm_test_dc_to_pp_clock_type_dcefclk(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)dc_to_pp_clock_type(DM_PP_CLOCK_TYPE_DCEFCLK), + (int)amd_pp_dcef_clock); +} + +/** + * dm_test_dc_to_pp_clock_type_dcfclk - Test DCF clock type mapping + * @test: KUnit test context + * + * Verify DM_PP_CLOCK_TYPE_DCFCLK maps to amd_pp_dcf_clock. + */ +static void dm_test_dc_to_pp_clock_type_dcfclk(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)dc_to_pp_clock_type(DM_PP_CLOCK_TYPE_DCFCLK), + (int)amd_pp_dcf_clock); +} + +/** + * dm_test_dc_to_pp_clock_type_pixelclk - Test pixel clock type mapping + * @test: KUnit test context + * + * Verify DM_PP_CLOCK_TYPE_PIXELCLK maps to amd_pp_pixel_clock. + */ +static void dm_test_dc_to_pp_clock_type_pixelclk(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)dc_to_pp_clock_type(DM_PP_CLOCK_TYPE_PIXELCLK), + (int)amd_pp_pixel_clock); +} + +/** + * dm_test_dc_to_pp_clock_type_fclk - Test FCLK type mapping + * @test: KUnit test context + * + * Verify DM_PP_CLOCK_TYPE_FCLK maps to amd_pp_f_clock. + */ +static void dm_test_dc_to_pp_clock_type_fclk(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)dc_to_pp_clock_type(DM_PP_CLOCK_TYPE_FCLK), + (int)amd_pp_f_clock); +} + +/** + * dm_test_dc_to_pp_clock_type_phyclk - Test display PHY clock type mapping + * @test: KUnit test context + * + * Verify DM_PP_CLOCK_TYPE_DISPLAYPHYCLK maps to amd_pp_phy_clock. + */ +static void dm_test_dc_to_pp_clock_type_phyclk(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)dc_to_pp_clock_type(DM_PP_CLOCK_TYPE_DISPLAYPHYCLK), + (int)amd_pp_phy_clock); +} + +/** + * dm_test_dc_to_pp_clock_type_dppclk - Test DPP clock type mapping + * @test: KUnit test context + * + * Verify DM_PP_CLOCK_TYPE_DPPCLK maps to amd_pp_dpp_clock. + */ +static void dm_test_dc_to_pp_clock_type_dppclk(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)dc_to_pp_clock_type(DM_PP_CLOCK_TYPE_DPPCLK), + (int)amd_pp_dpp_clock); +} + +/** + * dm_test_dc_to_pp_clock_type_invalid - Test invalid clock type mapping + * @test: KUnit test context + * + * Verify that an invalid clock type value maps to 0. + */ +static void dm_test_dc_to_pp_clock_type_invalid(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)dc_to_pp_clock_type(0), 0); +} + +static struct kunit_case dm_pp_smu_test_cases[] = { + /* get_default_clock_levels */ + KUNIT_CASE(dm_test_default_clock_levels_display), + KUNIT_CASE(dm_test_default_clock_levels_engine), + KUNIT_CASE(dm_test_default_clock_levels_memory), + KUNIT_CASE(dm_test_default_clock_levels_unknown), + /* dc_to_pp_clock_type */ + KUNIT_CASE(dm_test_dc_to_pp_clock_type_display), + KUNIT_CASE(dm_test_dc_to_pp_clock_type_engine), + KUNIT_CASE(dm_test_dc_to_pp_clock_type_memory), + KUNIT_CASE(dm_test_dc_to_pp_clock_type_dcefclk), + KUNIT_CASE(dm_test_dc_to_pp_clock_type_dcfclk), + KUNIT_CASE(dm_test_dc_to_pp_clock_type_pixelclk), + KUNIT_CASE(dm_test_dc_to_pp_clock_type_fclk), + KUNIT_CASE(dm_test_dc_to_pp_clock_type_phyclk), + KUNIT_CASE(dm_test_dc_to_pp_clock_type_dppclk), + KUNIT_CASE(dm_test_dc_to_pp_clock_type_invalid), + {} +}; + +static struct kunit_suite dm_pp_smu_test_suite = { + .name = "amdgpu_dm_pp_smu", + .test_cases = dm_pp_smu_test_cases, +}; + +kunit_test_suite(dm_pp_smu_test_suite); + +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_pp_smu"); -- cgit v1.2.3 From b7e70a466b3d022c7b8f830a2235961a957b3663 Mon Sep 17 00:00:00 2001 From: Timur Kristóf Date: Sun, 31 May 2026 12:57:40 +0200 Subject: drm/amd/display: Add detect reason to handle_hpd_irq_helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This makes it possible to reuse the function for other purposes in the next few commits, such as HPD RX. Signed-off-by: Timur Kristóf Signed-off-by: Aurabindo Pillai Reviewed-by: Alex Hung Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') 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 40f32c8024a0..22cbfc159cfa 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -242,7 +242,8 @@ static void amdgpu_dm_atomic_commit_tail(struct drm_atomic_commit *state); static int amdgpu_dm_atomic_check(struct drm_device *dev, struct drm_atomic_commit *state); -static void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector); +static void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector, + enum dc_detect_reason reason); static void handle_hpd_rx_irq(void *param); static void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm, @@ -892,7 +893,7 @@ static void dmub_hpd_callback(struct amdgpu_device *adev, if (notify->type == DMUB_NOTIFICATION_HPD) { if (hpd_aconnector->dc_link->hpd_status == (notify->hpd_status == DP_HPD_PLUG)) drm_warn(adev_to_drm(adev), "DMUB reported hpd status unchanged. link_index=%u\n", link_index); - handle_hpd_irq_helper(hpd_aconnector); + handle_hpd_irq_helper(hpd_aconnector, DETECT_REASON_HPD); } else if (notify->type == DMUB_NOTIFICATION_HPD_IRQ) { handle_hpd_rx_irq(hpd_aconnector); } @@ -4357,7 +4358,8 @@ static void hdmi_hpd_debounce_work(struct work_struct *work) } } -static void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector) +static void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector, + enum dc_detect_reason reason) { struct drm_connector *connector = &aconnector->base; struct drm_device *dev = connector->dev; @@ -4404,7 +4406,8 @@ static void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector) dm_restore_drm_connector_state(dev, connector); drm_modeset_unlock_all(dev); - if (aconnector->base.force == DRM_FORCE_UNSPECIFIED) + if (aconnector->base.force == DRM_FORCE_UNSPECIFIED || + reason == DETECT_REASON_HPDRX) drm_kms_helper_connector_hotplug_event(connector); } else if (debounce_required) { /* @@ -4436,7 +4439,7 @@ static void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector) scoped_guard(mutex, &adev->dm.dc_lock) { dc_exit_ips_for_hw_access(dc); - ret = dc_link_detect(aconnector->dc_link, DETECT_REASON_HPD); + ret = dc_link_detect(aconnector->dc_link, reason); } if (ret) { /* w/a delay for certain panels */ @@ -4447,7 +4450,8 @@ static void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector) dm_restore_drm_connector_state(dev, connector); drm_modeset_unlock_all(dev); - if (aconnector->base.force == DRM_FORCE_UNSPECIFIED) + if (aconnector->base.force == DRM_FORCE_UNSPECIFIED || + reason == DETECT_REASON_HPDRX) drm_kms_helper_connector_hotplug_event(connector); } } @@ -4457,7 +4461,7 @@ static void handle_hpd_irq(void *param) { struct amdgpu_dm_connector *aconnector = (struct amdgpu_dm_connector *)param; - handle_hpd_irq_helper(aconnector); + handle_hpd_irq_helper(aconnector, DETECT_REASON_HPD); } -- cgit v1.2.3 From 60597d2cb21990face4ac60bb0f9a642c00ff6d2 Mon Sep 17 00:00:00 2001 From: Timur Kristóf Date: Sun, 31 May 2026 12:57:41 +0200 Subject: drm/amd/display: Use handle_hpd_irq_helper for HPD RX MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove duplicated code and just call handle_hpd_irq_helper with the appropriate detect reason. Signed-off-by: Timur Kristóf Signed-off-by: Aurabindo Pillai Reviewed-by: Alex Hung Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 41 +---------------------- 1 file changed, 1 insertion(+), 40 deletions(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') 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 22cbfc159cfa..f34f4e65e933 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -4492,14 +4492,12 @@ static void handle_hpd_rx_irq(void *param) struct dc_link *dc_link = aconnector->dc_link; bool is_mst_root_connector = aconnector->mst_mgr.mst_state; bool result = false; - enum dc_connection_type new_connection_type = dc_connection_none; struct amdgpu_device *adev = drm_to_adev(dev); union hpd_irq_data hpd_irq_data; bool link_loss = false; bool has_left_work = false; int idx = dc_link->link_index; struct hpd_rx_irq_offload_work_queue *offload_wq = &adev->dm.hpd_rx_offload_wq[idx]; - struct dc *dc = aconnector->dc_link->ctx->dc; memset(&hpd_irq_data, 0, sizeof(hpd_irq_data)); @@ -4568,44 +4566,7 @@ static void handle_hpd_rx_irq(void *param) out: if (result && !is_mst_root_connector) { /* Downstream Port status changed. */ - if (!dc_link_detect_connection_type(dc_link, &new_connection_type)) - drm_err(adev_to_drm(adev), "KMS: Failed to detect connector\n"); - - if (aconnector->base.force && new_connection_type == dc_connection_none) { - emulated_link_detect(dc_link); - - if (aconnector->fake_enable) - aconnector->fake_enable = false; - - amdgpu_dm_update_connector_after_detect(aconnector); - - - drm_modeset_lock_all(dev); - dm_restore_drm_connector_state(dev, connector); - drm_modeset_unlock_all(dev); - - drm_kms_helper_connector_hotplug_event(connector); - } else { - bool ret = false; - - mutex_lock(&adev->dm.dc_lock); - dc_exit_ips_for_hw_access(dc); - ret = dc_link_detect(dc_link, DETECT_REASON_HPDRX); - mutex_unlock(&adev->dm.dc_lock); - - if (ret) { - if (aconnector->fake_enable) - aconnector->fake_enable = false; - - amdgpu_dm_update_connector_after_detect(aconnector); - - drm_modeset_lock_all(dev); - dm_restore_drm_connector_state(dev, connector); - drm_modeset_unlock_all(dev); - - drm_kms_helper_connector_hotplug_event(connector); - } - } + handle_hpd_irq_helper(aconnector, DETECT_REASON_HPDRX); } if (hpd_irq_data.bytes.device_service_irq.bits.CP_IRQ) { if (adev->dm.hdcp_workqueue) -- cgit v1.2.3 From b008c67efb36b102988ea16d5019c8364170264c Mon Sep 17 00:00:00 2001 From: Rafal Ostrowski Date: Fri, 22 May 2026 08:02:16 +0200 Subject: drm/amd/display: Introduce dc_plane_cm and migrate surface update color path [Why] Begin convergence with upstream Color Manager refactor (fda768acb2a1 "drm/amd/display: Sync dcn42 with DC 3.2.373") by consolidating fragmented per-plane CM state (shaper, 3DLUT, blend, CM2) into a single dc_plane_cm structure shared by dc_plane_state and dc_surface_update. Legacy fields are gated behind TRIM_CM2 so that it keeps compatibility with other repositories. [How] Refactored to use newer structures. No functional behavior change intended. Under !TRIM_CM2 the legacy fields are still populated for compatibility with other repositories. v2: squash in conflicting types fix Reviewed-by: Dillon Varone Signed-off-by: Rafal Ostrowski Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 4 +- .../drm/amd/display/amdgpu_dm/amdgpu_dm_color.c | 69 +++-- .../drm/amd/display/amdgpu_dm/amdgpu_dm_color.h | 8 +- .../display/amdgpu_dm/tests/amdgpu_dm_color_test.c | 64 ++--- drivers/gpu/drm/amd/display/dc/core/dc.c | 130 ++++++--- drivers/gpu/drm/amd/display/dc/core/dc_surface.c | 44 ++- drivers/gpu/drm/amd/display/dc/dc.h | 72 +++-- drivers/gpu/drm/amd/display/dc/dc_types.h | 66 +++-- .../drm/amd/display/dc/hubp/dcn401/dcn401_hubp.c | 2 +- .../drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.c | 20 +- .../drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c | 10 +- .../drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.c | 29 +- .../drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c | 172 ++++++----- .../drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.h | 2 +- .../drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.c | 316 ++++++++++----------- .../drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.h | 5 +- .../drm/amd/display/dc/hwss/hw_sequencer_private.h | 3 +- drivers/gpu/drm/amd/display/dc/inc/hw/hubp.h | 2 +- 18 files changed, 588 insertions(+), 430 deletions(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') 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 f34f4e65e933..7a46c9e56d87 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -10321,9 +10321,7 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state, bundle->surface_updates[planes_count].in_transfer_func = &dc_plane->in_transfer_func; bundle->surface_updates[planes_count].gamut_remap_matrix = &dc_plane->gamut_remap_matrix; bundle->surface_updates[planes_count].hdr_mult = dc_plane->hdr_mult; - bundle->surface_updates[planes_count].func_shaper = &dc_plane->in_shaper_func; - bundle->surface_updates[planes_count].lut3d_func = &dc_plane->lut3d_func; - bundle->surface_updates[planes_count].blend_tf = &dc_plane->blend_tf; + bundle->surface_updates[planes_count].cm = &dc_plane->cm; } amdgpu_dm_plane_fill_dc_scaling_info(dm->adev, new_plane_state, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c index 86086d10c543..69a3783e5223 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c @@ -1051,26 +1051,28 @@ EXPORT_IF_KUNIT(__drm_3dlut32_to_dc_3dlut); /* amdgpu_dm_atomic_lut3d - set DRM 3D LUT to DC stream * @drm_lut3d: user 3D LUT * @drm_lut3d_size: size of 3D LUT - * @lut3d: DC 3D LUT + * @cm: DC Color Manager (includes 3D LUT) * * Map user 3D LUT data to DC 3D LUT and all necessary bits to program it * on DCN accordingly. */ STATIC_IFN_KUNIT void amdgpu_dm_atomic_lut3d(const struct drm_color_lut *drm_lut3d, uint32_t drm_lut3d_size, - struct dc_3dlut *lut) + struct dc_plane_cm *cm) { if (!drm_lut3d_size) { - lut->state.bits.initialized = 0; + cm->lut3d_func.state.bits.initialized = 0; + cm->flags.bits.lut3d_enable = 0; } else { /* Stride and bit depth are not programmable by API yet. * Therefore, only supports 17x17x17 3D LUT (12-bit). */ - lut->lut_3d.use_tetrahedral_9 = false; - lut->lut_3d.use_12bits = true; - lut->state.bits.initialized = 1; - __drm_3dlut_to_dc_3dlut(drm_lut3d, drm_lut3d_size, &lut->lut_3d, - lut->lut_3d.use_tetrahedral_9, + cm->lut3d_func.lut_3d.use_tetrahedral_9 = false; + cm->lut3d_func.lut_3d.use_12bits = true; + cm->lut3d_func.state.bits.initialized = 1; + cm->flags.bits.lut3d_enable = 1; + __drm_3dlut_to_dc_3dlut(drm_lut3d, drm_lut3d_size, &cm->lut3d_func.lut_3d, + cm->lut3d_func.lut_3d.use_tetrahedral_9, MAX_COLOR_3DLUT_BITDEPTH); } } @@ -1080,7 +1082,7 @@ STATIC_IFN_KUNIT int amdgpu_dm_atomic_shaper_lut(const struct drm_color_lut *sha bool has_rom, enum dc_transfer_func_predefined tf, uint32_t shaper_size, - struct dc_transfer_func *func_shaper) + struct dc_plane_cm *cm) { int ret = 0; @@ -1089,10 +1091,13 @@ STATIC_IFN_KUNIT int amdgpu_dm_atomic_shaper_lut(const struct drm_color_lut *sha * If user shaper LUT is set, we assume a linear color space * (linearized by degamma 1D LUT or not). */ - __set_tf_distributed_points(func_shaper, tf); - ret = __set_output_tf(func_shaper, shaper_lut, shaper_size, has_rom); + __set_tf_distributed_points(&cm->shaper_func, tf); + cm->flags.bits.shaper_enable = 1; + + ret = __set_output_tf(&cm->shaper_func, shaper_lut, shaper_size, has_rom); } else { - __set_tf_bypass(func_shaper); + __set_tf_bypass(&cm->shaper_func); + cm->flags.bits.shaper_enable = 0; } return ret; @@ -1103,7 +1108,7 @@ STATIC_IFN_KUNIT int amdgpu_dm_atomic_blend_lut(const struct drm_color_lut *blen bool has_rom, enum dc_transfer_func_predefined tf, uint32_t blend_size, - struct dc_transfer_func *func_blend) + struct dc_plane_cm *cm) { int ret = 0; @@ -1115,10 +1120,13 @@ STATIC_IFN_KUNIT int amdgpu_dm_atomic_blend_lut(const struct drm_color_lut *blen * module to fill the parameters that will be translated to HW * points. */ - __set_tf_distributed_points(func_blend, tf); - ret = __set_input_tf(NULL, func_blend, blend_lut, blend_size); + __set_tf_distributed_points(&cm->blend_func, tf); + cm->flags.bits.blend_enable = 1; + + ret = __set_input_tf(NULL, &cm->blend_func, blend_lut, blend_size); } else { - __set_tf_bypass(func_blend); + __set_tf_bypass(&cm->blend_func); + cm->flags.bits.blend_enable = 0; } return ret; @@ -1635,7 +1643,7 @@ __set_dm_plane_colorop_shaper(struct drm_plane_state *plane_state, struct drm_colorop_state *colorop_state = NULL, *new_colorop_state; struct drm_atomic_commit *state = plane_state->state; enum dc_transfer_func_predefined default_tf = TRANSFER_FUNCTION_LINEAR; - struct dc_transfer_func *tf = &dc_plane_state->in_shaper_func; + struct dc_transfer_func *tf = &dc_plane_state->cm.shaper_func; const struct drm_color_lut32 *shaper_lut; struct drm_device *dev = colorop->dev; bool enabled = false; @@ -1696,8 +1704,12 @@ __set_dm_plane_colorop_shaper(struct drm_plane_state *plane_state, } } - if (!enabled) + if (!enabled) { tf->type = TF_TYPE_BYPASS; + dc_plane_state->cm.flags.bits.shaper_enable = 0; + } else { + dc_plane_state->cm.flags.bits.shaper_enable = 1; + } return 0; } @@ -1741,7 +1753,7 @@ __set_dm_plane_colorop_3dlut(struct drm_plane_state *plane_state, { struct drm_colorop *old_colorop; struct drm_colorop_state *colorop_state = NULL, *new_colorop_state; - struct dc_transfer_func *tf = &dc_plane_state->in_shaper_func; + struct dc_transfer_func *tf = &dc_plane_state->cm.shaper_func; struct drm_atomic_commit *state = plane_state->state; const struct amdgpu_device *adev = drm_to_adev(colorop->dev); bool has_3dlut = adev->dm.dc->caps.color.dpp.hw_3d_lut || adev->dm.dc->caps.color.mpc.preblend; @@ -1769,13 +1781,15 @@ __set_dm_plane_colorop_3dlut(struct drm_plane_state *plane_state, drm_dbg(dev, "3D LUT colorop with ID: %d\n", colorop->base.id); lut3d = __extract_blob_lut32(colorop_state->data, &lut3d_size); lut3d_size = lut3d != NULL ? lut3d_size : 0; - ret = __set_colorop_3dlut(lut3d, lut3d_size, &dc_plane_state->lut3d_func); + ret = __set_colorop_3dlut(lut3d, lut3d_size, &dc_plane_state->cm.lut3d_func); if (ret) { drm_dbg(dev, "3D LUT colorop with ID: %d has LUT size = %d\n", colorop->base.id, lut3d_size); return ret; } + dc_plane_state->cm.flags.bits.lut3d_enable = 1; + /* 3D LUT requires shaper. If shaper colorop is bypassed, enable shaper curve * with TRANSFER_FUNCTION_LINEAR */ @@ -1785,6 +1799,8 @@ __set_dm_plane_colorop_3dlut(struct drm_plane_state *plane_state, tf->sdr_ref_white_level = SDR_WHITE_LEVEL_INIT_VALUE; ret = __set_output_tf_32(tf, NULL, 0, false); } + } else { + dc_plane_state->cm.flags.bits.lut3d_enable = 0; } return ret; @@ -1799,12 +1815,14 @@ __set_dm_plane_colorop_blend(struct drm_plane_state *plane_state, struct drm_colorop_state *colorop_state = NULL, *new_colorop_state; struct drm_atomic_commit *state = plane_state->state; enum dc_transfer_func_predefined default_tf = TRANSFER_FUNCTION_LINEAR; - struct dc_transfer_func *tf = &dc_plane_state->blend_tf; + struct dc_transfer_func *tf = &dc_plane_state->cm.blend_func; const struct drm_color_lut32 *blend_lut = NULL; struct drm_device *dev = colorop->dev; uint32_t blend_size = 0; int i = 0; + dc_plane_state->cm.flags.bits.blend_enable = 0; + /* 1D Curve - BLND TF */ old_colorop = colorop; for_each_new_colorop_in_state(state, colorop, new_colorop_state, i) { @@ -1821,6 +1839,7 @@ __set_dm_plane_colorop_blend(struct drm_plane_state *plane_state, tf->type = TF_TYPE_DISTRIBUTED_POINTS; tf->tf = default_tf = amdgpu_colorop_tf_to_dc_tf(colorop_state->curve_1d_type); tf->sdr_ref_white_level = SDR_WHITE_LEVEL_INIT_VALUE; + dc_plane_state->cm.flags.bits.blend_enable = 1; __set_input_tf_32(NULL, tf, blend_lut, blend_size); } @@ -1846,6 +1865,7 @@ __set_dm_plane_colorop_blend(struct drm_plane_state *plane_state, tf->type = TF_TYPE_DISTRIBUTED_POINTS; tf->tf = default_tf; tf->sdr_ref_white_level = SDR_WHITE_LEVEL_INIT_VALUE; + dc_plane_state->cm.flags.bits.blend_enable = 1; blend_lut = __extract_blob_lut32(colorop_state->data, &blend_size); blend_size = blend_lut != NULL ? blend_size : 0; @@ -1876,11 +1896,11 @@ amdgpu_dm_plane_set_color_properties(struct drm_plane_state *plane_state, lut3d = __extract_blob_lut(dm_plane_state->lut3d, &lut3d_size); lut3d_size = lut3d != NULL ? lut3d_size : 0; - amdgpu_dm_atomic_lut3d(lut3d, lut3d_size, &dc_plane_state->lut3d_func); + amdgpu_dm_atomic_lut3d(lut3d, lut3d_size, &dc_plane_state->cm); ret = amdgpu_dm_atomic_shaper_lut(shaper_lut, false, amdgpu_tf_to_dc_tf(shaper_tf), shaper_size, - &dc_plane_state->in_shaper_func); + &dc_plane_state->cm); if (ret) { drm_dbg_kms(plane_state->plane->dev, "setting plane %d shaper LUT failed.\n", @@ -1895,7 +1915,8 @@ amdgpu_dm_plane_set_color_properties(struct drm_plane_state *plane_state, ret = amdgpu_dm_atomic_blend_lut(blend_lut, false, amdgpu_tf_to_dc_tf(blend_tf), - blend_size, &dc_plane_state->blend_tf); + blend_size, &dc_plane_state->cm); + if (ret) { drm_dbg_kms(plane_state->plane->dev, "setting plane %d gamma lut failed.\n", diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.h index e4f53b7bc753..8dbbcb3ab156 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.h @@ -87,10 +87,10 @@ void __drm_3dlut32_to_dc_3dlut(const struct drm_color_lut32 *lut, struct tetrahedral_params *params, bool use_tetrahedral_9, int bit_depth); -struct dc_3dlut; +struct dc_plane_cm; void amdgpu_dm_atomic_lut3d(const struct drm_color_lut *drm_lut3d, uint32_t drm_lut3d_size, - struct dc_3dlut *lut); + struct dc_plane_cm *cm); int __set_colorop_3dlut(const struct drm_color_lut32 *drm_lut3d, uint32_t drm_lut3d_size, struct dc_3dlut *lut); @@ -105,12 +105,12 @@ int amdgpu_dm_atomic_shaper_lut(const struct drm_color_lut *shaper_lut, bool has_rom, enum dc_transfer_func_predefined tf, uint32_t shaper_size, - struct dc_transfer_func *func_shaper); + struct dc_plane_cm *cm); int amdgpu_dm_atomic_blend_lut(const struct drm_color_lut *blend_lut, bool has_rom, enum dc_transfer_func_predefined tf, uint32_t blend_size, - struct dc_transfer_func *func_blend); + struct dc_plane_cm *cm); int __set_colorop_in_tf_1d_curve(struct dc_plane_state *dc_plane_state, struct drm_colorop_state *colorop_state); #endif diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c index f943361b70e8..d64c7da20f2c 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_color_test.c @@ -1159,19 +1159,19 @@ static void dm_test_verify_lut_sizes_invalid_degamma_valid_gamma(struct kunit *t */ static void dm_test_atomic_lut3d_zero_size(struct kunit *test) { - struct dc_3dlut *lut; + struct dc_plane_cm *cm; u32 initialized; - lut = kunit_kzalloc(test, sizeof(*lut), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, lut); + cm = kunit_kzalloc(test, sizeof(*cm), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, cm); /* Pre-set initialized so we can confirm it is cleared */ - lut->state.bits.initialized = 1; + cm->lut3d_func.state.bits.initialized = 1; - amdgpu_dm_atomic_lut3d(NULL, 0, lut); + amdgpu_dm_atomic_lut3d(NULL, 0, cm); /* Copy bit-field: typeof cannot be applied to a bit-field */ - initialized = lut->state.bits.initialized; + initialized = cm->lut3d_func.state.bits.initialized; KUNIT_EXPECT_EQ(test, initialized, 0U); } @@ -1183,22 +1183,22 @@ static void dm_test_atomic_lut3d_nonzero_state_bits(struct kunit *test) { const uint32_t lut3d_size = 5; struct drm_color_lut *lut_data; - struct dc_3dlut *lut; + struct dc_plane_cm *cm; u32 initialized; lut_data = kunit_kcalloc(test, lut3d_size, sizeof(*lut_data), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, lut_data); - lut = kunit_kzalloc(test, sizeof(*lut), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, lut); + cm = kunit_kzalloc(test, sizeof(*cm), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, cm); - amdgpu_dm_atomic_lut3d(lut_data, lut3d_size, lut); + amdgpu_dm_atomic_lut3d(lut_data, lut3d_size, cm); /* Copy bit-field: typeof cannot be applied to a bit-field */ - initialized = lut->state.bits.initialized; + initialized = cm->lut3d_func.state.bits.initialized; KUNIT_EXPECT_EQ(test, initialized, 1U); - KUNIT_EXPECT_FALSE(test, lut->lut_3d.use_tetrahedral_9); - KUNIT_EXPECT_TRUE(test, lut->lut_3d.use_12bits); + KUNIT_EXPECT_FALSE(test, cm->lut3d_func.lut_3d.use_tetrahedral_9); + KUNIT_EXPECT_TRUE(test, cm->lut3d_func.lut_3d.use_12bits); } /** @@ -1209,29 +1209,29 @@ static void dm_test_atomic_lut3d_data_forwarded(struct kunit *test) { const uint32_t lut3d_size = 5; struct drm_color_lut *lut_data; - struct dc_3dlut *lut; + struct dc_plane_cm *cm; lut_data = kunit_kcalloc(test, lut3d_size, sizeof(*lut_data), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, lut_data); - lut = kunit_kzalloc(test, sizeof(*lut), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, lut); + cm = kunit_kzalloc(test, sizeof(*cm), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, cm); lut_data[0].red = 0xFFFF; lut_data[0].green = 0x8000; lut_data[0].blue = 0x4000; - amdgpu_dm_atomic_lut3d(lut_data, lut3d_size, lut); + amdgpu_dm_atomic_lut3d(lut_data, lut3d_size, cm); /* * use_tetrahedral_9 == false → data goes into tetrahedral_17. * lut[0] maps to lut0[0] (first element of the first group). */ - KUNIT_EXPECT_EQ(test, lut->lut_3d.tetrahedral_17.lut0[0].red, + KUNIT_EXPECT_EQ(test, cm->lut3d_func.lut_3d.tetrahedral_17.lut0[0].red, drm_color_lut_extract(0xFFFF, MAX_COLOR_3DLUT_BITDEPTH)); - KUNIT_EXPECT_EQ(test, lut->lut_3d.tetrahedral_17.lut0[0].green, + KUNIT_EXPECT_EQ(test, cm->lut3d_func.lut_3d.tetrahedral_17.lut0[0].green, drm_color_lut_extract(0x8000, MAX_COLOR_3DLUT_BITDEPTH)); - KUNIT_EXPECT_EQ(test, lut->lut_3d.tetrahedral_17.lut0[0].blue, + KUNIT_EXPECT_EQ(test, cm->lut3d_func.lut_3d.tetrahedral_17.lut0[0].blue, drm_color_lut_extract(0x4000, MAX_COLOR_3DLUT_BITDEPTH)); } @@ -1398,19 +1398,19 @@ static void dm_test_set_atomic_regamma_bypass(struct kunit *test) */ static void dm_test_atomic_shaper_lut_bypass(struct kunit *test) { - struct dc_transfer_func *func_shaper; + struct dc_plane_cm *cm; - func_shaper = kunit_kzalloc(test, sizeof(*func_shaper), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, func_shaper); + cm = kunit_kzalloc(test, sizeof(*cm), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, cm); /* size=0 and tf=LINEAR: must take the bypass branch */ KUNIT_EXPECT_EQ(test, amdgpu_dm_atomic_shaper_lut(NULL, false, TRANSFER_FUNCTION_LINEAR, - 0, func_shaper), + 0, cm), 0); - KUNIT_EXPECT_EQ(test, (int)func_shaper->type, (int)TF_TYPE_BYPASS); - KUNIT_EXPECT_EQ(test, (int)func_shaper->tf, (int)TRANSFER_FUNCTION_LINEAR); + KUNIT_EXPECT_EQ(test, (int)cm->shaper_func.type, (int)TF_TYPE_BYPASS); + KUNIT_EXPECT_EQ(test, (int)cm->shaper_func.tf, (int)TRANSFER_FUNCTION_LINEAR); } /** @@ -1419,19 +1419,19 @@ static void dm_test_atomic_shaper_lut_bypass(struct kunit *test) */ static void dm_test_atomic_blend_lut_bypass(struct kunit *test) { - struct dc_transfer_func *func_blend; + struct dc_plane_cm *cm; - func_blend = kunit_kzalloc(test, sizeof(*func_blend), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, func_blend); + cm = kunit_kzalloc(test, sizeof(*cm), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, cm); /* size=0 and tf=LINEAR: must take the bypass branch */ KUNIT_EXPECT_EQ(test, amdgpu_dm_atomic_blend_lut(NULL, false, TRANSFER_FUNCTION_LINEAR, - 0, func_blend), + 0, cm), 0); - KUNIT_EXPECT_EQ(test, (int)func_blend->type, (int)TF_TYPE_BYPASS); - KUNIT_EXPECT_EQ(test, (int)func_blend->tf, (int)TRANSFER_FUNCTION_LINEAR); + KUNIT_EXPECT_EQ(test, (int)cm->blend_func.type, (int)TF_TYPE_BYPASS); + KUNIT_EXPECT_EQ(test, (int)cm->blend_func.tf, (int)TRANSFER_FUNCTION_LINEAR); } /* ---- Tests for __set_colorop_in_tf_1d_curve ---- */ diff --git a/drivers/gpu/drm/amd/display/dc/core/dc.c b/drivers/gpu/drm/amd/display/dc/core/dc.c index bcdbf3471039..4220481d3960 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc.c @@ -2964,16 +2964,28 @@ static struct surface_update_descriptor det_surface_update( elevate_update_type(&overall_type, UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_STREAM); } - if (u->blend_tf || (u->gamma && dce_use_lut(u->plane_info ? u->plane_info->format : u->surface->format))) { + if ((u->cm && u->cm->flags.bits.blend_enable) || + (u->gamma && dce_use_lut(u->plane_info ? u->plane_info->format : u->surface->format))) { update_flags->bits.gamma_change = 1; elevate_update_type(&overall_type, UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_STREAM); } - if (u->lut3d_func || u->func_shaper) { + if (u->cm && (u->cm->flags.bits.lut3d_enable || u->cm->flags.bits.shaper_enable)) { update_flags->bits.lut_3d = 1; elevate_update_type(&overall_type, UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_STREAM); } + if (u->cm && u->cm->flags.bits.lut3d_dma_enable != u->surface->cm.flags.bits.lut3d_dma_enable && + u->cm->flags.bits.lut3d_enable && u->surface->cm.flags.bits.lut3d_enable) { + /* Toggling 3DLUT loading between DMA and Host is illegal */ + BREAK_TO_DEBUGGER(); + } + + if (u->cm && u->cm->flags.bits.lut3d_enable && !u->cm->flags.bits.lut3d_dma_enable) { + /* Host loading 3DLUT requires full update but only stream lock */ + elevate_update_type(&overall_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_STREAM); + } + if (u->hdr_mult.value) if (u->hdr_mult.value != u->surface->hdr_mult.value) { // TODO: Should be fast? @@ -2992,17 +3004,30 @@ static struct surface_update_descriptor det_surface_update( update_flags->bits.cm_hist_change = 1; elevate_update_type(&overall_type, UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_STREAM); } - if (u->cm2_params) { - if (u->cm2_params->component_settings.shaper_3dlut_setting != u->surface->mcm_shaper_3dlut_setting - || u->cm2_params->component_settings.lut1d_enable != u->surface->mcm_lut1d_enable - || u->cm2_params->cm2_luts.lut3d_data.lut3d_src != u->surface->mcm_luts.lut3d_data.lut3d_src) { + + if (u->cm) { + const union dc_plane_cm_flags blend_only_flags = { + .bits = { + .blend_enable = 1, + } + }; + + if (u->cm->flags.bits.shaper_enable != u->surface->cm.flags.bits.shaper_enable + || u->cm->flags.bits.blend_enable != u->surface->cm.flags.bits.blend_enable + || u->cm->flags.bits.lut3d_enable != u->surface->cm.flags.bits.lut3d_enable + || u->cm->flags.bits.lut3d_dma_enable != u->surface->cm.flags.bits.lut3d_dma_enable) { update_flags->bits.mcm_transfer_function_enable_change = 1; elevate_update_type(&overall_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL); } + + if ((u->cm->flags.all != blend_only_flags.all && u->cm->flags.all != 0) || + (u->surface->cm.flags.all != blend_only_flags.all && u->surface->cm.flags.all != 0)) { + elevate_update_type(&overall_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL); + } } if (update_flags->bits.lut_3d && - u->surface->mcm_luts.lut3d_data.lut3d_src != DC_CM2_TRANSFER_FUNC_SOURCE_VIDMEM) { + !u->surface->cm.flags.bits.lut3d_dma_enable) { elevate_update_type(&overall_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL); } @@ -3304,24 +3329,55 @@ static void copy_surface_update_to_plane( sizeof(struct dc_transfer_func_distributed_points)); } - if (srf_update->cm2_params) { - surface->mcm_shaper_3dlut_setting = srf_update->cm2_params->component_settings.shaper_3dlut_setting; - surface->mcm_lut1d_enable = srf_update->cm2_params->component_settings.lut1d_enable; - surface->mcm_luts = srf_update->cm2_params->cm2_luts; - } - - if (srf_update->func_shaper) { - memcpy(&surface->in_shaper_func, srf_update->func_shaper, - sizeof(surface->in_shaper_func)); + /* Shaper, 3DLUT, 1DLUT */ + if (srf_update->cm) { + struct kref refcount = surface->cm.refcount; + + memcpy(&surface->cm, srf_update->cm, sizeof(surface->cm)); + surface->cm.refcount = refcount; + +#ifndef TRIM_CM2 + /* Populate mcm_luts from cm for legacy consumers (dml2, hwseq) */ + surface->mcm_luts.lut1d_func = &surface->cm.blend_func; + surface->mcm_luts.shaper = &surface->cm.shaper_func; + if (srf_update->cm->flags.bits.lut3d_dma_enable) { + surface->mcm_luts.lut3d_data.lut3d_src = DC_CM2_TRANSFER_FUNC_SOURCE_VIDMEM; + surface->mcm_luts.lut3d_data.gpu_mem_params.addr = surface->cm.lut3d_dma.addr; + surface->mcm_luts.lut3d_data.gpu_mem_params.layout = + (surface->cm.lut3d_dma.swizzle == CM_LUT_3D_SWIZZLE_LINEAR_RGB) ? + DC_CM2_GPU_MEM_LAYOUT_3D_SWIZZLE_LINEAR_RGB : + (surface->cm.lut3d_dma.swizzle == CM_LUT_3D_SWIZZLE_LINEAR_BGR) ? + DC_CM2_GPU_MEM_LAYOUT_3D_SWIZZLE_LINEAR_BGR : + DC_CM2_GPU_MEM_LAYOUT_1D_PACKED_LINEAR; + surface->mcm_luts.lut3d_data.gpu_mem_params.format_params.format = + (surface->cm.lut3d_dma.format == CM_LUT_PIXEL_FORMAT_RGBA16161616_UNORM_12MSB) ? + DC_CM2_GPU_MEM_FORMAT_16161616_UNORM_12MSB : + (surface->cm.lut3d_dma.format == CM_LUT_PIXEL_FORMAT_RGBA16161616_UNORM_12LSB) ? + DC_CM2_GPU_MEM_FORMAT_16161616_UNORM_12LSB : + DC_CM2_GPU_MEM_FORMAT_16161616_FLOAT_FP1_5_10; + surface->mcm_luts.lut3d_data.gpu_mem_params.format_params.float_params.bias = + surface->cm.lut3d_dma.bias; + surface->mcm_luts.lut3d_data.gpu_mem_params.format_params.float_params.scale = + surface->cm.lut3d_dma.scale; + surface->mcm_luts.lut3d_data.gpu_mem_params.component_order = + DC_CM2_GPU_MEM_PIXEL_COMPONENT_ORDER_RGBA; + surface->mcm_luts.lut3d_data.gpu_mem_params.size = DC_CM2_GPU_MEM_SIZE_TRANSFORMED; + surface->mcm_luts.lut3d_data.mpc_3dlut_enable = (srf_update->cm->flags.bits.lut3d_enable != 0); + } else { + surface->mcm_luts.lut3d_data.lut3d_src = DC_CM2_TRANSFER_FUNC_SOURCE_SYSMEM; + surface->mcm_luts.lut3d_data.lut3d_func = &surface->cm.lut3d_func; + } - if (surface->mcm_shaper_3dlut_setting >= DC_CM2_SHAPER_3DLUT_SETTING_ENABLE_SHAPER) - surface->mcm_luts.shaper = &surface->in_shaper_func; + if (srf_update->cm->flags.bits.shaper_enable && + srf_update->cm->flags.bits.lut3d_enable) + surface->mcm_shaper_3dlut_setting = DC_CM2_SHAPER_3DLUT_SETTING_ENABLE_SHAPER_3DLUT; + else if (srf_update->cm->flags.bits.shaper_enable) + surface->mcm_shaper_3dlut_setting = DC_CM2_SHAPER_3DLUT_SETTING_ENABLE_SHAPER; + else + surface->mcm_shaper_3dlut_setting = DC_CM2_SHAPER_3DLUT_SETTING_BYPASS_ALL; +#endif /* TRIM_CM2 */ } - if (srf_update->lut3d_func) - memcpy(&surface->lut3d_func, srf_update->lut3d_func, - sizeof(surface->lut3d_func)); - if (srf_update->hdr_mult.value) surface->hdr_mult = srf_update->hdr_mult; @@ -3330,15 +3386,10 @@ static void copy_surface_update_to_plane( surface->sdr_white_level_nits = srf_update->sdr_white_level_nits; - if (srf_update->blend_tf) { - memcpy(&surface->blend_tf, srf_update->blend_tf, - sizeof(surface->blend_tf)); - - if (surface->mcm_lut1d_enable) - surface->mcm_luts.lut1d_func = &surface->blend_tf; - } - - if (srf_update->cm2_params || srf_update->blend_tf) + if (srf_update->cm && + (srf_update->cm->flags.bits.blend_enable || + srf_update->cm->flags.bits.shaper_enable || + srf_update->cm->flags.bits.lut3d_enable)) surface->lut_bank_a = !surface->lut_bank_a; if (srf_update->input_csc_color_matrix) @@ -5073,11 +5124,9 @@ static void commit_planes_for_stream(struct dc *dc, if (!should_update_pipe_for_plane(context, pipe_ctx, plane_state)) continue; - if (srf_updates[i].cm2_params && - srf_updates[i].cm2_params->cm2_luts.lut3d_data.lut3d_src == - DC_CM2_TRANSFER_FUNC_SOURCE_VIDMEM && - srf_updates[i].cm2_params->component_settings.shaper_3dlut_setting == - DC_CM2_SHAPER_3DLUT_SETTING_ENABLE_SHAPER_3DLUT && + if (srf_updates[i].cm && + srf_updates[i].cm->flags.bits.lut3d_enable && + srf_updates[i].cm->flags.bits.lut3d_dma_enable && dc->hwss.trigger_3dlut_dma_load) dc->hwss.trigger_3dlut_dma_load(dc, pipe_ctx); @@ -5792,14 +5841,9 @@ static bool full_update_required( (srf_updates[i].sdr_white_level_nits && srf_updates[i].sdr_white_level_nits != srf_updates->surface->sdr_white_level_nits) || srf_updates[i].in_transfer_func || - srf_updates[i].func_shaper || - srf_updates[i].lut3d_func || srf_updates[i].surface->force_full_update || (srf_updates[i].flip_addr && - srf_updates[i].flip_addr->address.tmz_surface != srf_updates[i].surface->address.tmz_surface) || - (srf_updates[i].cm2_params && - (srf_updates[i].cm2_params->component_settings.shaper_3dlut_setting != srf_updates[i].surface->mcm_shaper_3dlut_setting || - srf_updates[i].cm2_params->component_settings.lut1d_enable != srf_updates[i].surface->mcm_lut1d_enable)))) + srf_updates[i].flip_addr->address.tmz_surface != srf_updates[i].surface->address.tmz_surface))) return true; } @@ -7542,7 +7586,7 @@ bool dc_capture_register_software_state(struct dc *dc, struct dc_register_softwa struct dc_plane_state *plane_state = pipe_ctx->plane_state; /* MPCC blending tree and mode control - capture actual blend configuration */ - state->mpc.mpcc_mode[i] = (plane_state->blend_tf.type != TF_TYPE_BYPASS) ? 1 : 0; + state->mpc.mpcc_mode[i] = (plane_state->cm.blend_func.type != TF_TYPE_BYPASS) ? 1 : 0; state->mpc.mpcc_alpha_blend_mode[i] = plane_state->per_pixel_alpha ? 1 : 0; state->mpc.mpcc_alpha_multiplied_mode[i] = plane_state->pre_multiplied_alpha ? 1 : 0; state->mpc.mpcc_blnd_active_overlap_only[i] = 0; /* Default - no overlap restriction */ diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_surface.c b/drivers/gpu/drm/amd/display/dc/core/dc_surface.c index 72845fc788f3..88e825a6582c 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc_surface.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc_surface.c @@ -45,14 +45,13 @@ void dc_plane_construct(struct dc_context *ctx, struct dc_plane_state *plane_sta plane_state->in_transfer_func.type = TF_TYPE_BYPASS; - plane_state->in_shaper_func.type = TF_TYPE_BYPASS; - - plane_state->lut3d_func.state.raw = 0; - - plane_state->blend_tf.type = TF_TYPE_BYPASS; - plane_state->pre_multiplied_alpha = true; + /* CM */ + plane_state->cm.shaper_func.type = TF_TYPE_BYPASS; + plane_state->cm.blend_func.type = TF_TYPE_BYPASS; + plane_state->cm.lut3d_func.state.raw = 0; + plane_state->cm.flags.all = 0; } void dc_plane_destruct(struct dc_plane_state *plane_state) @@ -282,6 +281,39 @@ void dc_3dlut_func_retain(struct dc_3dlut *lut) kref_get(&lut->refcount); } +static void dc_plane_cm_free(struct kref *kref) +{ + struct dc_plane_cm *cm = container_of(kref, struct dc_plane_cm, refcount); + + kvfree(cm); +} + +struct dc_plane_cm *dc_plane_cm_create(void) +{ + struct dc_plane_cm *cm = kvzalloc(sizeof(*cm), GFP_KERNEL); + + if (cm == NULL) + goto alloc_fail; + + kref_init(&cm->refcount); + + return cm; + +alloc_fail: + return NULL; + +} + +void dc_plane_cm_release(struct dc_plane_cm *cm) +{ + kref_put(&cm->refcount, dc_plane_cm_free); +} + +void dc_plane_cm_retain(struct dc_plane_cm *cm) +{ + kref_get(&cm->refcount); +} + void dc_plane_force_dcc_and_tiling_disable(struct dc_plane_state *plane_state, bool clear_tiling) { diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h index b8ac462a676a..2a47d7ddf53b 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -1486,6 +1486,47 @@ struct dc_3dlut { struct fixed31_32 hdr_multiplier; union dc_3dlut_state state; }; + +/* 3DLUT DMA (Fast Load) params */ +struct dc_3dlut_dma { + struct dc_plane_address addr; + enum dc_cm_lut_swizzle swizzle; + enum dc_cm_lut_pixel_format format; + uint16_t bias; /* FP1.5.10 */ + uint16_t scale; /* FP1.5.10 */ + enum dc_cm_lut_size size; +}; + +/* color manager */ +union dc_plane_cm_flags { + unsigned int all; + struct { + unsigned int shaper_enable : 1; + unsigned int lut3d_enable : 1; + unsigned int blend_enable : 1; + /* whether legacy (lut3d_func) or DMA is valid */ + unsigned int lut3d_dma_enable : 1; +#if defined(CONFIG_DRM_AMD_DC_DCN4_2) + /* RMCM lut to be used instead of MCM */ + unsigned int rmcm_enable : 1; + unsigned int reserved: 27; +#else + unsigned int reserved: 28; +#endif + } bits; +}; + +struct dc_plane_cm { + struct kref refcount; + struct dc_transfer_func shaper_func; + union { + struct dc_3dlut lut3d_func; + struct dc_3dlut_dma lut3d_dma; + }; + struct dc_transfer_func blend_func; + union dc_plane_cm_flags flags; +}; + /* * This structure is filled in by dc_surface_get_status and contains * the last requested address and the currently active address so the called @@ -1564,14 +1605,22 @@ struct dc_plane_state { struct fixed31_32 hdr_mult; struct colorspace_transform gamut_remap_matrix; + enum dc_color_space color_space; + +#ifndef TRIM_CM2 // TODO: No longer used, remove struct dc_hdr_static_metadata hdr_static_ctx; - enum dc_color_space color_space; - struct dc_3dlut lut3d_func; struct dc_transfer_func in_shaper_func; struct dc_transfer_func blend_tf; + enum dc_cm2_shaper_3dlut_setting mcm_shaper_3dlut_setting; + bool mcm_lut1d_enable; + struct dc_cm2_func_luts mcm_luts; +#endif /* TRIM_CM2 */ + bool lut_bank_a; + enum mpcc_movable_cm_location mcm_location; + struct dc_plane_cm cm; struct dc_transfer_func *gamcor_tf; enum surface_pixel_format format; @@ -1608,11 +1657,6 @@ struct dc_plane_state { bool is_statically_allocated; enum chroma_cositing cositing; - enum dc_cm2_shaper_3dlut_setting mcm_shaper_3dlut_setting; - bool mcm_lut1d_enable; - struct dc_cm2_func_luts mcm_luts; - bool lut_bank_a; - enum mpcc_movable_cm_location mcm_location; struct dc_csc_transform cursor_csc_color_matrix; bool adaptive_sharpness_en; int adaptive_sharpness_policy; @@ -1976,17 +2020,7 @@ struct dc_surface_update { const struct dc_csc_transform *input_csc_color_matrix; const struct fixed31_32 *coeff_reduction_factor; - const struct dc_transfer_func *func_shaper; - const struct dc_3dlut *lut3d_func; - const struct dc_transfer_func *blend_tf; const struct colorspace_transform *gamut_remap_matrix; - /* - * Color Transformations for pre-blend MCM (Shaper, 3DLUT, 1DLUT) - * - * change cm2_params.component_settings: Full update - * change cm2_params.cm2_luts: Fast update - */ - const struct dc_cm2_parameters *cm2_params; const struct dc_plane_cm *cm; const struct dc_csc_transform *cursor_csc_color_matrix; unsigned int sdr_white_level_nits; @@ -2032,6 +2066,10 @@ struct dc_3dlut *dc_create_3dlut_func(void); void dc_3dlut_func_release(struct dc_3dlut *lut); void dc_3dlut_func_retain(struct dc_3dlut *lut); +struct dc_plane_cm *dc_plane_cm_create(void); +void dc_plane_cm_release(struct dc_plane_cm *cm); +void dc_plane_cm_retain(struct dc_plane_cm *cm); + void dc_post_update_surfaces_to_stream( struct dc *dc); diff --git a/drivers/gpu/drm/amd/display/dc/dc_types.h b/drivers/gpu/drm/amd/display/dc/dc_types.h index 4ed1efa17270..db6a89d938b6 100644 --- a/drivers/gpu/drm/amd/display/dc/dc_types.h +++ b/drivers/gpu/drm/amd/display/dc/dc_types.h @@ -1397,6 +1397,39 @@ enum dc_hpd_enable_select { HPD_EN_FOR_SECONDARY_EDP_ONLY, }; +enum dc_cm_lut_swizzle { + CM_LUT_3D_SWIZZLE_LINEAR_RGB, + CM_LUT_3D_SWIZZLE_LINEAR_BGR, + CM_LUT_1D_PACKED_LINEAR +}; + +enum dc_cm_lut_pixel_format { + CM_LUT_PIXEL_FORMAT_RGBA16161616_UNORM_12MSB, +#if defined(CONFIG_DRM_AMD_DC_DCN4_2) + CM_LUT_PIXEL_FORMAT_BGRA16161616_UNORM_12MSB, +#endif + CM_LUT_PIXEL_FORMAT_RGBA16161616_UNORM_12LSB, +#if defined(CONFIG_DRM_AMD_DC_DCN4_2) + CM_LUT_PIXEL_FORMAT_BGRA16161616_UNORM_12LSB, +#endif + CM_LUT_PIXEL_FORMAT_RGBA16161616_FLOAT_FP1_5_10, +#if defined(CONFIG_DRM_AMD_DC_DCN4_2) + CM_LUT_PIXEL_FORMAT_BGRA16161616_FLOAT_FP1_5_10 +#endif +}; + +enum dc_cm_lut_size { + CM_LUT_SIZE_NONE, + CM_LUT_SIZE_999, + CM_LUT_SIZE_171717, +#if defined(CONFIG_DRM_AMD_DC_DCN4_2) + CM_LUT_SIZE_333333, + CM_LUT_SIZE_454545, + CM_LUT_SIZE_656565, +#endif +}; + +#ifndef TRIM_CM2 enum dc_cm2_shaper_3dlut_setting { DC_CM2_SHAPER_3DLUT_SETTING_BYPASS_ALL, DC_CM2_SHAPER_3DLUT_SETTING_ENABLE_SHAPER, @@ -1421,6 +1454,16 @@ enum dc_cm2_gpu_mem_format { DC_CM2_GPU_MEM_FORMAT_16161616_FLOAT_FP1_5_10 }; +enum dc_cm2_gpu_mem_size { + DC_CM2_GPU_MEM_SIZE_171717, + DC_CM2_GPU_MEM_SIZE_333333, + DC_CM2_GPU_MEM_SIZE_454545, + DC_CM2_GPU_MEM_SIZE_656565, + DC_CM2_GPU_MEM_SIZE_TRANSFORMED, +}; +#endif /* TRIM_CM2 */ + +#ifndef TRIM_CM2 struct dc_cm2_gpu_mem_format_parameters { enum dc_cm2_gpu_mem_format format; union { @@ -1432,14 +1475,6 @@ struct dc_cm2_gpu_mem_format_parameters { }; }; -enum dc_cm2_gpu_mem_size { - DC_CM2_GPU_MEM_SIZE_171717, - DC_CM2_GPU_MEM_SIZE_333333, - DC_CM2_GPU_MEM_SIZE_454545, - DC_CM2_GPU_MEM_SIZE_656565, - DC_CM2_GPU_MEM_SIZE_TRANSFORMED, -}; - struct dc_cm2_gpu_mem_parameters { struct dc_plane_address addr; enum dc_cm2_gpu_mem_layout layout; @@ -1448,17 +1483,16 @@ struct dc_cm2_gpu_mem_parameters { enum dc_cm2_gpu_mem_size size; uint16_t bit_depth; }; +#endif /* TRIM_CM2 */ +#ifndef TRIM_CM2 enum dc_cm2_transfer_func_source { DC_CM2_TRANSFER_FUNC_SOURCE_SYSMEM, DC_CM2_TRANSFER_FUNC_SOURCE_VIDMEM }; +#endif /* TRIM_CM2 */ -struct dc_cm2_component_settings { - enum dc_cm2_shaper_3dlut_setting shaper_3dlut_setting; - bool lut1d_enable; -}; - +#ifndef TRIM_CM2 /* * All pointers in this struct must remain valid for as long as the 3DLUTs are used */ @@ -1478,11 +1512,7 @@ struct dc_cm2_func_luts { } lut3d_data; const struct dc_transfer_func *lut1d_func; }; - -struct dc_cm2_parameters { - struct dc_cm2_component_settings component_settings; - struct dc_cm2_func_luts cm2_luts; -}; +#endif /* TRIM_CM2 */ enum mall_stream_type { SUBVP_NONE, // subvp not in use diff --git a/drivers/gpu/drm/amd/display/dc/hubp/dcn401/dcn401_hubp.c b/drivers/gpu/drm/amd/display/dc/hubp/dcn401/dcn401_hubp.c index 302515128358..9965cf572354 100644 --- a/drivers/gpu/drm/amd/display/dc/hubp/dcn401/dcn401_hubp.c +++ b/drivers/gpu/drm/amd/display/dc/hubp/dcn401/dcn401_hubp.c @@ -136,7 +136,7 @@ void hubp401_program_3dlut_fl_config( uint32_t mpc_width = {(cfg->width == 17) ? 0 : 1}; uint32_t width = {cfg->width}; - if (cfg->layout == DC_CM2_GPU_MEM_LAYOUT_1D_PACKED_LINEAR) + if (cfg->layout == CM_LUT_1D_PACKED_LINEAR) width = (cfg->width == 17) ? 4916 : 35940; REG_UPDATE_2(_3DLUT_FL_CONFIG, diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.c index e6a8206f8ce0..50d039b3fb43 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.c @@ -1066,11 +1066,11 @@ bool dcn20_set_blend_lut( bool result = true; const struct pwl_params *blend_lut = NULL; - if (plane_state->blend_tf.type == TF_TYPE_HWPWL) - blend_lut = &plane_state->blend_tf.pwl; - else if (plane_state->blend_tf.type == TF_TYPE_DISTRIBUTED_POINTS) { + if (plane_state->cm.blend_func.type == TF_TYPE_HWPWL) + blend_lut = &plane_state->cm.blend_func.pwl; + else if (plane_state->cm.blend_func.type == TF_TYPE_DISTRIBUTED_POINTS) { cm_helper_translate_curve_to_hw_format(plane_state->ctx, - &plane_state->blend_tf, + &plane_state->cm.blend_func, &dpp_base->regamma_params, false); blend_lut = &dpp_base->regamma_params; } @@ -1086,19 +1086,19 @@ bool dcn20_set_shaper_3dlut( bool result = true; const struct pwl_params *shaper_lut = NULL; - if (plane_state->in_shaper_func.type == TF_TYPE_HWPWL) - shaper_lut = &plane_state->in_shaper_func.pwl; - else if (plane_state->in_shaper_func.type == TF_TYPE_DISTRIBUTED_POINTS) { + if (plane_state->cm.shaper_func.type == TF_TYPE_HWPWL) + shaper_lut = &plane_state->cm.shaper_func.pwl; + else if (plane_state->cm.shaper_func.type == TF_TYPE_DISTRIBUTED_POINTS) { cm_helper_translate_curve_to_hw_format(plane_state->ctx, - &plane_state->in_shaper_func, + &plane_state->cm.shaper_func, &dpp_base->shaper_params, true); shaper_lut = &dpp_base->shaper_params; } result = dpp_base->funcs->dpp_program_shaper_lut(dpp_base, shaper_lut); - if (plane_state->lut3d_func.state.bits.initialized == 1) + if (plane_state->cm.lut3d_func.state.bits.initialized == 1) result = dpp_base->funcs->dpp_program_3dlut(dpp_base, - &plane_state->lut3d_func.lut_3d); + &plane_state->cm.lut3d_func.lut_3d); else result = dpp_base->funcs->dpp_program_3dlut(dpp_base, NULL); diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c index a7c85a2302ab..aed9d06ec538 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c @@ -239,11 +239,13 @@ bool dcn30_set_blend_lut( bool result = true; const struct pwl_params *blend_lut = NULL; - if (plane_state->blend_tf.type == TF_TYPE_HWPWL) - blend_lut = &plane_state->blend_tf.pwl; - else if (plane_state->blend_tf.type == TF_TYPE_DISTRIBUTED_POINTS) { + if (plane_state->cm.blend_func.type == TF_TYPE_HWPWL) + blend_lut = &plane_state->cm.blend_func.pwl; + else if (plane_state->cm.blend_func.type == TF_TYPE_DISTRIBUTED_POINTS) { result = cm3_helper_translate_curve_to_hw_format(plane_state->ctx, - &plane_state->blend_tf, &dpp_base->regamma_params, false); + &plane_state->cm.blend_func, + &dpp_base->regamma_params, + false); if (!result) return result; diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.c index a3242e7521a4..34cbd90b2283 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.c @@ -490,12 +490,14 @@ bool dcn32_set_mcm_luts( const struct pwl_params *lut_params = NULL; // 1D LUT - if (plane_state->blend_tf.type == TF_TYPE_HWPWL) - lut_params = &plane_state->blend_tf.pwl; - else if (plane_state->blend_tf.type == TF_TYPE_DISTRIBUTED_POINTS) { - result = cm3_helper_translate_curve_to_hw_format(plane_state->ctx, - &plane_state->blend_tf, - &dpp_base->regamma_params, false); + if (plane_state->cm.blend_func.type == TF_TYPE_HWPWL) + lut_params = &plane_state->cm.blend_func.pwl; + else if (plane_state->cm.blend_func.type == TF_TYPE_DISTRIBUTED_POINTS) { + result = cm3_helper_translate_curve_to_hw_format( + plane_state->ctx, + &plane_state->cm.blend_func, + &dpp_base->regamma_params, + false); if (!result) return result; @@ -505,21 +507,22 @@ bool dcn32_set_mcm_luts( lut_params = NULL; // Shaper - if (plane_state->in_shaper_func.type == TF_TYPE_HWPWL) - lut_params = &plane_state->in_shaper_func.pwl; - else if (plane_state->in_shaper_func.type == TF_TYPE_DISTRIBUTED_POINTS) { + if (plane_state->cm.shaper_func.type == TF_TYPE_HWPWL) + lut_params = &plane_state->cm.shaper_func.pwl; + else if (plane_state->cm.shaper_func.type == TF_TYPE_DISTRIBUTED_POINTS) { // TODO: dpp_base replace rval = cm3_helper_translate_curve_to_hw_format(plane_state->ctx, - &plane_state->in_shaper_func, - &dpp_base->shaper_params, true); + &plane_state->cm.shaper_func, + &dpp_base->shaper_params, + true); lut_params = rval ? &dpp_base->shaper_params : NULL; } mpc->funcs->program_shaper(mpc, lut_params, mpcc_id); // 3D - if (plane_state->lut3d_func.state.bits.initialized == 1) - result = mpc->funcs->program_3dlut(mpc, &plane_state->lut3d_func.lut_3d, mpcc_id); + if (plane_state->cm.lut3d_func.state.bits.initialized == 1) + result = mpc->funcs->program_3dlut(mpc, &plane_state->cm.lut3d_func.lut_3d, mpcc_id); else result = mpc->funcs->program_3dlut(mpc, NULL, mpcc_id); diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c index 96815a92a629..49efd1f11c9a 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c @@ -410,37 +410,27 @@ static void dcn401_get_mcm_lut_xable_from_pipe_ctx(struct dc *dc, struct pipe_ct enum MCM_LUT_XABLE *lut3d_xable, enum MCM_LUT_XABLE *lut1d_xable) { - enum dc_cm2_shaper_3dlut_setting shaper_3dlut_setting = DC_CM2_SHAPER_3DLUT_SETTING_BYPASS_ALL; - bool lut1d_enable = false; struct mpc *mpc = dc->res_pool->mpc; int mpcc_id = pipe_ctx->plane_res.hubp->inst; if (!pipe_ctx->plane_state) return; - shaper_3dlut_setting = pipe_ctx->plane_state->mcm_shaper_3dlut_setting; - lut1d_enable = pipe_ctx->plane_state->mcm_lut1d_enable; + mpc->funcs->set_movable_cm_location(mpc, MPCC_MOVABLE_CM_LOCATION_BEFORE, mpcc_id); pipe_ctx->plane_state->mcm_location = MPCC_MOVABLE_CM_LOCATION_BEFORE; - *lut1d_xable = lut1d_enable ? MCM_LUT_ENABLE : MCM_LUT_DISABLE; - - switch (shaper_3dlut_setting) { - case DC_CM2_SHAPER_3DLUT_SETTING_BYPASS_ALL: - *lut3d_xable = *shaper_xable = MCM_LUT_DISABLE; - break; - case DC_CM2_SHAPER_3DLUT_SETTING_ENABLE_SHAPER: - *lut3d_xable = MCM_LUT_DISABLE; - *shaper_xable = MCM_LUT_ENABLE; - break; - case DC_CM2_SHAPER_3DLUT_SETTING_ENABLE_SHAPER_3DLUT: - *lut3d_xable = *shaper_xable = MCM_LUT_ENABLE; - break; - } + *lut1d_xable = pipe_ctx->plane_state->cm.flags.bits.blend_enable ? + MCM_LUT_ENABLE : MCM_LUT_DISABLE; + *shaper_xable = pipe_ctx->plane_state->cm.flags.bits.shaper_enable ? + MCM_LUT_ENABLE : MCM_LUT_DISABLE; + *lut3d_xable = (pipe_ctx->plane_state->cm.flags.bits.shaper_enable && + pipe_ctx->plane_state->cm.flags.bits.lut3d_enable) ? + MCM_LUT_ENABLE : MCM_LUT_DISABLE; } void dcn401_populate_mcm_luts(struct dc *dc, struct pipe_ctx *pipe_ctx, - struct dc_cm2_func_luts mcm_luts, + const struct dc_plane_cm *cm, bool lut_bank_a) { struct dpp *dpp_base = pipe_ctx->plane_res.dpp; @@ -448,14 +438,17 @@ void dcn401_populate_mcm_luts(struct dc *dc, int mpcc_id = hubp->inst; struct mpc *mpc = dc->res_pool->mpc; union mcm_lut_params m_lut_params; - enum dc_cm2_transfer_func_source lut3d_src = mcm_luts.lut3d_data.lut3d_src; + const bool lut3d_dma = !!cm->flags.bits.lut3d_dma_enable; enum hubp_3dlut_fl_format format = 0; enum hubp_3dlut_fl_mode mode; - enum hubp_3dlut_fl_width width = 0; + /* Width was previously hard-coded to TRANSFORMED via local_mcm build, + * preserve identical behavior. + */ + enum hubp_3dlut_fl_width width = hubp_3dlut_fl_width_transformed; enum hubp_3dlut_fl_addressing_mode addr_mode; - enum hubp_3dlut_fl_crossbar_bit_slice crossbar_bit_slice_y_g = 0; - enum hubp_3dlut_fl_crossbar_bit_slice crossbar_bit_slice_cb_b = 0; - enum hubp_3dlut_fl_crossbar_bit_slice crossbar_bit_slice_cr_r = 0; + enum hubp_3dlut_fl_crossbar_bit_slice crossbar_bit_slice_y_g; + enum hubp_3dlut_fl_crossbar_bit_slice crossbar_bit_slice_cb_b; + enum hubp_3dlut_fl_crossbar_bit_slice crossbar_bit_slice_cr_r; enum MCM_LUT_XABLE shaper_xable = MCM_LUT_DISABLE; enum MCM_LUT_XABLE lut3d_xable = MCM_LUT_DISABLE; enum MCM_LUT_XABLE lut1d_xable = MCM_LUT_DISABLE; @@ -464,13 +457,13 @@ void dcn401_populate_mcm_luts(struct dc *dc, dcn401_get_mcm_lut_xable_from_pipe_ctx(dc, pipe_ctx, &shaper_xable, &lut3d_xable, &lut1d_xable); /* 1D LUT */ - if (mcm_luts.lut1d_func) { + { memset(&m_lut_params, 0, sizeof(m_lut_params)); - if (mcm_luts.lut1d_func->type == TF_TYPE_HWPWL) - m_lut_params.pwl = &mcm_luts.lut1d_func->pwl; - else if (mcm_luts.lut1d_func->type == TF_TYPE_DISTRIBUTED_POINTS) { + if (cm->blend_func.type == TF_TYPE_HWPWL) + m_lut_params.pwl = &cm->blend_func.pwl; + else if (cm->blend_func.type == TF_TYPE_DISTRIBUTED_POINTS) { rval = cm3_helper_translate_curve_to_hw_format(mpc->ctx, - mcm_luts.lut1d_func, + &cm->blend_func, &dpp_base->regamma_params, false); m_lut_params.pwl = rval ? &dpp_base->regamma_params : NULL; } @@ -483,14 +476,14 @@ void dcn401_populate_mcm_luts(struct dc *dc, } /* Shaper */ - if (mcm_luts.shaper && mcm_luts.lut3d_data.mpc_3dlut_enable) { + if (cm->flags.bits.lut3d_enable) { memset(&m_lut_params, 0, sizeof(m_lut_params)); - if (mcm_luts.shaper->type == TF_TYPE_HWPWL) - m_lut_params.pwl = &mcm_luts.shaper->pwl; - else if (mcm_luts.shaper->type == TF_TYPE_DISTRIBUTED_POINTS) { + if (cm->shaper_func.type == TF_TYPE_HWPWL) + m_lut_params.pwl = &cm->shaper_func.pwl; + else if (cm->shaper_func.type == TF_TYPE_DISTRIBUTED_POINTS) { ASSERT(false); rval = cm3_helper_translate_curve_to_hw_format(mpc->ctx, - mcm_luts.shaper, + &cm->shaper_func, &dpp_base->regamma_params, true); m_lut_params.pwl = rval ? &dpp_base->regamma_params : NULL; } @@ -503,42 +496,43 @@ void dcn401_populate_mcm_luts(struct dc *dc, } /* 3DLUT */ - switch (lut3d_src) { - case DC_CM2_TRANSFER_FUNC_SOURCE_SYSMEM: + if (!lut3d_dma) { + /* SYSMEM (legacy lut3d_func) */ memset(&m_lut_params, 0, sizeof(m_lut_params)); if (hubp->funcs->hubp_enable_3dlut_fl) hubp->funcs->hubp_enable_3dlut_fl(hubp, false); - if (mcm_luts.lut3d_data.lut3d_func && mcm_luts.lut3d_data.lut3d_func->state.bits.initialized) { - m_lut_params.lut3d = &mcm_luts.lut3d_data.lut3d_func->lut_3d; + if (cm->lut3d_func.state.bits.initialized) { + m_lut_params.lut3d = &cm->lut3d_func.lut_3d; if (mpc->funcs->populate_lut) mpc->funcs->populate_lut(mpc, MCM_LUT_3DLUT, m_lut_params, lut_bank_a, mpcc_id); if (mpc->funcs->program_lut_mode) mpc->funcs->program_lut_mode(mpc, MCM_LUT_3DLUT, lut3d_xable, lut_bank_a, mpcc_id); } - break; - case DC_CM2_TRANSFER_FUNC_SOURCE_VIDMEM: - switch (mcm_luts.lut3d_data.gpu_mem_params.size) { - case DC_CM2_GPU_MEM_SIZE_333333: + } else { + /* VIDMEM (3DLUT DMA Fast Load) */ + + /* Select width based on the requested LUT size */ + switch (cm->lut3d_dma.size) { +#if defined(CONFIG_DRM_AMD_DC_DCN4_2) + case CM_LUT_SIZE_333333: if (dc->caps.color.mpc.rmcm_3d_lut_caps.lut_dim_caps.dim_33) width = hubp_3dlut_fl_width_33; break; - case DC_CM2_GPU_MEM_SIZE_171717: +#endif // CONFIG_DRM_AMD_DC_DCN4_2 + case CM_LUT_SIZE_171717: width = hubp_3dlut_fl_width_17; break; - case DC_CM2_GPU_MEM_SIZE_TRANSFORMED: - width = hubp_3dlut_fl_width_transformed; - break; default: - //TODO: handle default case + /* keep default hubp_3dlut_fl_width_transformed */ break; } //check for support if (mpc->funcs->mcm.is_config_supported && !mpc->funcs->mcm.is_config_supported(width)) - break; + return; if (mpc->funcs->program_lut_read_write_control) mpc->funcs->program_lut_read_write_control(mpc, MCM_LUT_3DLUT, lut_bank_a, mpcc_id); @@ -546,21 +540,24 @@ void dcn401_populate_mcm_luts(struct dc *dc, mpc->funcs->program_lut_mode(mpc, MCM_LUT_3DLUT, lut3d_xable, lut_bank_a, mpcc_id); if (hubp->funcs->hubp_program_3dlut_fl_addr) - hubp->funcs->hubp_program_3dlut_fl_addr(hubp, mcm_luts.lut3d_data.gpu_mem_params.addr); + hubp->funcs->hubp_program_3dlut_fl_addr(hubp, cm->lut3d_dma.addr); + /* bit_depth was previously zero-initialized in local_mcm, + * preserve identical behavior. + */ if (mpc->funcs->mcm.program_bit_depth) - mpc->funcs->mcm.program_bit_depth(mpc, mcm_luts.lut3d_data.gpu_mem_params.bit_depth, mpcc_id); + mpc->funcs->mcm.program_bit_depth(mpc, 0, mpcc_id); - switch (mcm_luts.lut3d_data.gpu_mem_params.layout) { - case DC_CM2_GPU_MEM_LAYOUT_3D_SWIZZLE_LINEAR_RGB: + switch (cm->lut3d_dma.swizzle) { + case CM_LUT_3D_SWIZZLE_LINEAR_RGB: mode = hubp_3dlut_fl_mode_native_1; addr_mode = hubp_3dlut_fl_addressing_mode_sw_linear; break; - case DC_CM2_GPU_MEM_LAYOUT_3D_SWIZZLE_LINEAR_BGR: + case CM_LUT_3D_SWIZZLE_LINEAR_BGR: mode = hubp_3dlut_fl_mode_native_2; addr_mode = hubp_3dlut_fl_addressing_mode_sw_linear; break; - case DC_CM2_GPU_MEM_LAYOUT_1D_PACKED_LINEAR: + case CM_LUT_1D_PACKED_LINEAR: mode = hubp_3dlut_fl_mode_transform; addr_mode = hubp_3dlut_fl_addressing_mode_simple_linear; break; @@ -575,40 +572,38 @@ void dcn401_populate_mcm_luts(struct dc *dc, if (hubp->funcs->hubp_program_3dlut_fl_addressing_mode) hubp->funcs->hubp_program_3dlut_fl_addressing_mode(hubp, addr_mode); - switch (mcm_luts.lut3d_data.gpu_mem_params.format_params.format) { - case DC_CM2_GPU_MEM_FORMAT_16161616_UNORM_12MSB: + switch (cm->lut3d_dma.format) { + case CM_LUT_PIXEL_FORMAT_RGBA16161616_UNORM_12MSB: format = hubp_3dlut_fl_format_unorm_12msb_bitslice; break; - case DC_CM2_GPU_MEM_FORMAT_16161616_UNORM_12LSB: + case CM_LUT_PIXEL_FORMAT_RGBA16161616_UNORM_12LSB: format = hubp_3dlut_fl_format_unorm_12lsb_bitslice; break; - case DC_CM2_GPU_MEM_FORMAT_16161616_FLOAT_FP1_5_10: + case CM_LUT_PIXEL_FORMAT_RGBA16161616_FLOAT_FP1_5_10: format = hubp_3dlut_fl_format_float_fp1_5_10; break; + default: + break; } if (hubp->funcs->hubp_program_3dlut_fl_format) hubp->funcs->hubp_program_3dlut_fl_format(hubp, format); if (hubp->funcs->hubp_update_3dlut_fl_bias_scale && mpc->funcs->mcm.program_bias_scale) { mpc->funcs->mcm.program_bias_scale(mpc, - mcm_luts.lut3d_data.gpu_mem_params.format_params.float_params.bias, - mcm_luts.lut3d_data.gpu_mem_params.format_params.float_params.scale, + cm->lut3d_dma.bias, + cm->lut3d_dma.scale, mpcc_id); hubp->funcs->hubp_update_3dlut_fl_bias_scale(hubp, - mcm_luts.lut3d_data.gpu_mem_params.format_params.float_params.bias, - mcm_luts.lut3d_data.gpu_mem_params.format_params.float_params.scale); + cm->lut3d_dma.bias, + cm->lut3d_dma.scale); } - //navi 4x has a bug and r and blue are swapped and need to be worked around here in - //TODO: need to make a method for get_xbar per asic OR do the workaround in program_crossbar for 4x - switch (mcm_luts.lut3d_data.gpu_mem_params.component_order) { - case DC_CM2_GPU_MEM_PIXEL_COMPONENT_ORDER_RGBA: - default: - crossbar_bit_slice_cr_r = hubp_3dlut_fl_crossbar_bit_slice_0_15; - crossbar_bit_slice_y_g = hubp_3dlut_fl_crossbar_bit_slice_16_31; - crossbar_bit_slice_cb_b = hubp_3dlut_fl_crossbar_bit_slice_32_47; - break; - } + /* component_order was previously hard-coded to RGBA in local_mcm, + * preserve identical behavior. + */ + crossbar_bit_slice_cr_r = hubp_3dlut_fl_crossbar_bit_slice_0_15; + crossbar_bit_slice_y_g = hubp_3dlut_fl_crossbar_bit_slice_16_31; + crossbar_bit_slice_cb_b = hubp_3dlut_fl_crossbar_bit_slice_32_47; if (hubp->funcs->hubp_program_3dlut_fl_crossbar) hubp->funcs->hubp_program_3dlut_fl_crossbar(hubp, @@ -634,8 +629,6 @@ void dcn401_populate_mcm_luts(struct dc *dc, mpc->funcs->program_lut_mode(mpc, MCM_LUT_1DLUT, MCM_LUT_DISABLE, lut_bank_a, mpcc_id); } } - break; - } } @@ -660,19 +653,19 @@ bool dcn401_set_mcm_luts(struct pipe_ctx *pipe_ctx, const struct pwl_params *lut_params = NULL; bool rval; - if (plane_state->mcm_luts.lut3d_data.lut3d_src == DC_CM2_TRANSFER_FUNC_SOURCE_VIDMEM) { - dcn401_populate_mcm_luts(dc, pipe_ctx, plane_state->mcm_luts, plane_state->lut_bank_a); + if (plane_state->cm.flags.bits.lut3d_dma_enable) { + dcn401_populate_mcm_luts(dc, pipe_ctx, &plane_state->cm, plane_state->lut_bank_a); return true; } mpc->funcs->set_movable_cm_location(mpc, MPCC_MOVABLE_CM_LOCATION_BEFORE, mpcc_id); pipe_ctx->plane_state->mcm_location = MPCC_MOVABLE_CM_LOCATION_BEFORE; // 1D LUT - if (plane_state->blend_tf.type == TF_TYPE_HWPWL) - lut_params = &plane_state->blend_tf.pwl; - else if (plane_state->blend_tf.type == TF_TYPE_DISTRIBUTED_POINTS) { + if (plane_state->cm.blend_func.type == TF_TYPE_HWPWL) + lut_params = &plane_state->cm.blend_func.pwl; + else if (plane_state->cm.blend_func.type == TF_TYPE_DISTRIBUTED_POINTS) { rval = cm3_helper_translate_curve_to_hw_format(plane_state->ctx, - &plane_state->blend_tf, + &plane_state->cm.blend_func, &dpp_base->regamma_params, false); lut_params = rval ? &dpp_base->regamma_params : NULL; } @@ -680,12 +673,12 @@ bool dcn401_set_mcm_luts(struct pipe_ctx *pipe_ctx, lut_params = NULL; // Shaper - if (plane_state->in_shaper_func.type == TF_TYPE_HWPWL) - lut_params = &plane_state->in_shaper_func.pwl; - else if (plane_state->in_shaper_func.type == TF_TYPE_DISTRIBUTED_POINTS) { + if (plane_state->cm.shaper_func.type == TF_TYPE_HWPWL) + lut_params = &plane_state->cm.shaper_func.pwl; + else if (plane_state->cm.shaper_func.type == TF_TYPE_DISTRIBUTED_POINTS) { // TODO: dpp_base replace rval = cm3_helper_translate_curve_to_hw_format(plane_state->ctx, - &plane_state->in_shaper_func, + &plane_state->cm.shaper_func, &dpp_base->shaper_params, true); lut_params = rval ? &dpp_base->shaper_params : NULL; } @@ -693,8 +686,8 @@ bool dcn401_set_mcm_luts(struct pipe_ctx *pipe_ctx, // 3D if (mpc->funcs->program_3dlut) { - if (plane_state->lut3d_func.state.bits.initialized == 1) - result &= mpc->funcs->program_3dlut(mpc, &plane_state->lut3d_func.lut_3d, mpcc_id); + if (plane_state->cm.lut3d_func.state.bits.initialized == 1) + result &= mpc->funcs->program_3dlut(mpc, &plane_state->cm.lut3d_func.lut_3d, mpcc_id); else result &= mpc->funcs->program_3dlut(mpc, NULL, mpcc_id); } @@ -1999,10 +1992,9 @@ void dcn401_perform_3dlut_wa_unlock(struct pipe_ctx *pipe_ctx) for (odm_pipe = pipe_ctx; odm_pipe != NULL; odm_pipe = odm_pipe->next_odm_pipe) { for (mpc_pipe = odm_pipe; mpc_pipe != NULL; mpc_pipe = mpc_pipe->bottom_pipe) { - if (mpc_pipe->plane_state && mpc_pipe->plane_state->mcm_luts.lut3d_data.lut3d_src - == DC_CM2_TRANSFER_FUNC_SOURCE_VIDMEM - && mpc_pipe->plane_state->mcm_shaper_3dlut_setting - == DC_CM2_SHAPER_3DLUT_SETTING_ENABLE_SHAPER_3DLUT) { + if (mpc_pipe->plane_state && + mpc_pipe->plane_state->cm.flags.bits.lut3d_enable && + mpc_pipe->plane_state->cm.flags.bits.lut3d_dma_enable) { wa_pipes[wa_pipe_ct++] = mpc_pipe; } } diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.h b/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.h index f78162ab859b..2afeafc902c7 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.h +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.h @@ -52,7 +52,7 @@ enum dc_status dcn401_enable_stream_timing( void dcn401_enable_stream(struct pipe_ctx *pipe_ctx); void dcn401_populate_mcm_luts(struct dc *dc, struct pipe_ctx *pipe_ctx, - struct dc_cm2_func_luts mcm_luts, + const struct dc_plane_cm *cm, bool lut_bank_a); void dcn401_setup_hpo_hw_control(const struct dce_hwseq *hws, bool enable); diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.c index 96e0133880e1..9cf8b379cb34 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.c @@ -401,40 +401,33 @@ void dcn42_program_cm_hist( } static void dc_get_lut_xbar( - enum dc_cm2_gpu_mem_pixel_component_order order, enum hubp_3dlut_fl_crossbar_bit_slice *cr_r, enum hubp_3dlut_fl_crossbar_bit_slice *y_g, enum hubp_3dlut_fl_crossbar_bit_slice *cb_b) { - switch (order) { - case DC_CM2_GPU_MEM_PIXEL_COMPONENT_ORDER_RGBA: - *cr_r = hubp_3dlut_fl_crossbar_bit_slice_32_47; - *y_g = hubp_3dlut_fl_crossbar_bit_slice_16_31; - *cb_b = hubp_3dlut_fl_crossbar_bit_slice_0_15; - break; - case DC_CM2_GPU_MEM_PIXEL_COMPONENT_ORDER_BGRA: - *cr_r = hubp_3dlut_fl_crossbar_bit_slice_0_15; - *y_g = hubp_3dlut_fl_crossbar_bit_slice_16_31; - *cb_b = hubp_3dlut_fl_crossbar_bit_slice_32_47; - break; - } + /* component_order was previously hard-coded to RGBA in local_mcm, + * preserve identical behavior. + */ + *cr_r = hubp_3dlut_fl_crossbar_bit_slice_32_47; + *y_g = hubp_3dlut_fl_crossbar_bit_slice_16_31; + *cb_b = hubp_3dlut_fl_crossbar_bit_slice_0_15; } static void dc_get_lut_mode( - enum dc_cm2_gpu_mem_layout layout, + enum dc_cm_lut_swizzle swizzle, enum hubp_3dlut_fl_mode *mode, enum hubp_3dlut_fl_addressing_mode *addr_mode) { - switch (layout) { - case DC_CM2_GPU_MEM_LAYOUT_3D_SWIZZLE_LINEAR_RGB: + switch (swizzle) { + case CM_LUT_3D_SWIZZLE_LINEAR_RGB: *mode = hubp_3dlut_fl_mode_native_1; *addr_mode = hubp_3dlut_fl_addressing_mode_sw_linear; break; - case DC_CM2_GPU_MEM_LAYOUT_3D_SWIZZLE_LINEAR_BGR: + case CM_LUT_3D_SWIZZLE_LINEAR_BGR: *mode = hubp_3dlut_fl_mode_native_2; *addr_mode = hubp_3dlut_fl_addressing_mode_sw_linear; break; - case DC_CM2_GPU_MEM_LAYOUT_1D_PACKED_LINEAR: + case CM_LUT_1D_PACKED_LINEAR: *mode = hubp_3dlut_fl_mode_transform; *addr_mode = hubp_3dlut_fl_addressing_mode_simple_linear; break; @@ -446,19 +439,22 @@ static void dc_get_lut_mode( } static void dc_get_lut_format( - enum dc_cm2_gpu_mem_format dc_format, + enum dc_cm_lut_pixel_format dc_format, enum hubp_3dlut_fl_format *format) { switch (dc_format) { - case DC_CM2_GPU_MEM_FORMAT_16161616_UNORM_12MSB: + case CM_LUT_PIXEL_FORMAT_RGBA16161616_UNORM_12MSB: *format = hubp_3dlut_fl_format_unorm_12msb_bitslice; break; - case DC_CM2_GPU_MEM_FORMAT_16161616_UNORM_12LSB: + case CM_LUT_PIXEL_FORMAT_RGBA16161616_UNORM_12LSB: *format = hubp_3dlut_fl_format_unorm_12lsb_bitslice; break; - case DC_CM2_GPU_MEM_FORMAT_16161616_FLOAT_FP1_5_10: + case CM_LUT_PIXEL_FORMAT_RGBA16161616_FLOAT_FP1_5_10: *format = hubp_3dlut_fl_format_float_fp1_5_10; break; + default: + *format = hubp_3dlut_fl_format_unorm_12msb_bitslice; + break; } } @@ -472,16 +468,17 @@ static bool dc_is_rmcm_3dlut_supported(struct hubp *hubp, struct mpc *mpc) return false; } -static bool is_rmcm_3dlut_fl_supported(struct dc *dc, enum dc_cm2_gpu_mem_size size) +#if defined(CONFIG_DRM_AMD_DC_DCN4_2) +static bool is_rmcm_3dlut_fl_supported(struct dc *dc) { + /* size was previously hard-coded to TRANSFORMED in local_mcm, + * which mapped to dim_17. Preserve identical behavior. + */ if (!dc->caps.color.mpc.rmcm_3d_lut_caps.dma_3d_lut) return false; - if (size == DC_CM2_GPU_MEM_SIZE_171717) - return dc->caps.color.mpc.rmcm_3d_lut_caps.lut_dim_caps.dim_17 != 0u; - else if (size == DC_CM2_GPU_MEM_SIZE_333333) - return dc->caps.color.mpc.rmcm_3d_lut_caps.lut_dim_caps.dim_33 != 0u; - return false; + return dc->caps.color.mpc.rmcm_3d_lut_caps.lut_dim_caps.dim_17 != 0u; } +#endif static void dcn42_set_mcm_location_post_blend(struct dc *dc, struct pipe_ctx *pipe_ctx, bool bPostBlend) { @@ -502,56 +499,45 @@ static void dcn42_get_mcm_lut_xable_from_pipe_ctx(struct dc *dc, struct pipe_ctx enum MCM_LUT_XABLE *lut3d_xable, enum MCM_LUT_XABLE *lut1d_xable) { - enum dc_cm2_shaper_3dlut_setting shaper_3dlut_setting = DC_CM2_SHAPER_3DLUT_SETTING_BYPASS_ALL; - bool lut1d_enable = false; struct mpc *mpc = dc->res_pool->mpc; int mpcc_id = pipe_ctx->plane_res.hubp->inst; if (!pipe_ctx->plane_state) return; - shaper_3dlut_setting = pipe_ctx->plane_state->mcm_shaper_3dlut_setting; - lut1d_enable = pipe_ctx->plane_state->mcm_lut1d_enable; + mpc->funcs->set_movable_cm_location(mpc, MPCC_MOVABLE_CM_LOCATION_BEFORE, mpcc_id); pipe_ctx->plane_state->mcm_location = MPCC_MOVABLE_CM_LOCATION_BEFORE; - *lut1d_xable = lut1d_enable ? MCM_LUT_ENABLE : MCM_LUT_DISABLE; - - switch (shaper_3dlut_setting) { - case DC_CM2_SHAPER_3DLUT_SETTING_BYPASS_ALL: - *lut3d_xable = *shaper_xable = MCM_LUT_DISABLE; - break; - case DC_CM2_SHAPER_3DLUT_SETTING_ENABLE_SHAPER: - *lut3d_xable = MCM_LUT_DISABLE; - *shaper_xable = MCM_LUT_ENABLE; - break; - case DC_CM2_SHAPER_3DLUT_SETTING_ENABLE_SHAPER_3DLUT: - *lut3d_xable = *shaper_xable = MCM_LUT_ENABLE; - break; - } + *lut1d_xable = pipe_ctx->plane_state->cm.flags.bits.blend_enable ? + MCM_LUT_ENABLE : MCM_LUT_DISABLE; + *shaper_xable = pipe_ctx->plane_state->cm.flags.bits.shaper_enable ? + MCM_LUT_ENABLE : MCM_LUT_DISABLE; + *lut3d_xable = (pipe_ctx->plane_state->cm.flags.bits.shaper_enable && + pipe_ctx->plane_state->cm.flags.bits.lut3d_enable) ? + MCM_LUT_ENABLE : MCM_LUT_DISABLE; } static void fl_get_lut_mode( - enum dc_cm2_gpu_mem_layout layout, - enum dc_cm2_gpu_mem_size size, + enum dc_cm_lut_swizzle swizzle, enum hubp_3dlut_fl_mode *mode, enum hubp_3dlut_fl_addressing_mode *addr_mode, enum hubp_3dlut_fl_width *width) { + /* size was previously hard-coded to TRANSFORMED in local_mcm, + * preserve identical behavior (transformed width). + */ *width = hubp_3dlut_fl_width_17; - if (size == DC_CM2_GPU_MEM_SIZE_333333) - *width = hubp_3dlut_fl_width_33; - - switch (layout) { - case DC_CM2_GPU_MEM_LAYOUT_3D_SWIZZLE_LINEAR_RGB: + switch (swizzle) { + case CM_LUT_3D_SWIZZLE_LINEAR_RGB: *mode = hubp_3dlut_fl_mode_native_1; *addr_mode = hubp_3dlut_fl_addressing_mode_sw_linear; break; - case DC_CM2_GPU_MEM_LAYOUT_3D_SWIZZLE_LINEAR_BGR: + case CM_LUT_3D_SWIZZLE_LINEAR_BGR: *mode = hubp_3dlut_fl_mode_native_2; *addr_mode = hubp_3dlut_fl_addressing_mode_sw_linear; break; - case DC_CM2_GPU_MEM_LAYOUT_1D_PACKED_LINEAR: + case CM_LUT_1D_PACKED_LINEAR: *mode = hubp_3dlut_fl_mode_transform; *addr_mode = hubp_3dlut_fl_addressing_mode_simple_linear; break; @@ -565,8 +551,7 @@ static void fl_get_lut_mode( bool dcn42_program_rmcm_luts( struct hubp *hubp, struct pipe_ctx *pipe_ctx, - enum dc_cm2_transfer_func_source lut3d_src, - struct dc_cm2_func_luts *mcm_luts, + const struct dc_plane_cm *cm, struct mpc *mpc, bool lut_bank_a, int mpcc_id) @@ -596,21 +581,24 @@ bool dcn42_program_rmcm_luts( if (!rmcm_3dlut) return false; - rmcm_3dlut->protection_bits = mcm_luts->lut3d_data.rmcm_tmz; + /* rmcm_tmz was previously zero-initialized in local_mcm, + * preserve identical behavior. + */ + rmcm_3dlut->protection_bits = 0; dcn42_get_mcm_lut_xable_from_pipe_ctx(dc, pipe_ctx, &shaper_xable, &lut3d_xable, &lut1d_xable); /* Shaper */ - if (mcm_luts->shaper) { + { memset(&m_lut_params, 0, sizeof(m_lut_params)); - if (mcm_luts->shaper->type == TF_TYPE_HWPWL) { - m_lut_params.pwl = &mcm_luts->shaper->pwl; - } else if (mcm_luts->shaper->type == TF_TYPE_DISTRIBUTED_POINTS) { + if (cm->shaper_func.type == TF_TYPE_HWPWL) { + m_lut_params.pwl = &cm->shaper_func.pwl; + } else if (cm->shaper_func.type == TF_TYPE_DISTRIBUTED_POINTS) { ASSERT(false); cm_helper_translate_curve_to_hw_format( dc->ctx, - mcm_luts->shaper, + &cm->shaper_func, &dpp_base->shaper_params, true); m_lut_params.pwl = &dpp_base->shaper_params; } @@ -626,15 +614,16 @@ bool dcn42_program_rmcm_luts( } /* 3DLUT */ - switch (lut3d_src) { - case DC_CM2_TRANSFER_FUNC_SOURCE_SYSMEM: + if (!cm->flags.bits.lut3d_dma_enable) { + /* SYSMEM path — no DMA 3DLUT available. + * Previously this was treated as a no-op for the DMA/VIDMEM + * programming, preserve identical behavior. + */ memset(&m_lut_params, 0, sizeof(m_lut_params)); - // Don't know what to do in this case. - //case DC_CM2_TRANSFER_FUNC_SOURCE_SYSMEM: - break; - case DC_CM2_TRANSFER_FUNC_SOURCE_VIDMEM: - fl_get_lut_mode(mcm_luts->lut3d_data.gpu_mem_params.layout, - mcm_luts->lut3d_data.gpu_mem_params.size, + } else { + /* VIDMEM (3DLUT DMA Fast Load) */ + + fl_get_lut_mode(cm->lut3d_dma.swizzle, &mode, &addr_mode, &width); @@ -646,20 +635,19 @@ bool dcn42_program_rmcm_luts( return false; // setting native or transformed mode, - dc_get_lut_mode(mcm_luts->lut3d_data.gpu_mem_params.layout, &mode, &addr_mode); + dc_get_lut_mode(cm->lut3d_dma.swizzle, &mode, &addr_mode); //seems to be only for the MCM - dc_get_lut_format(mcm_luts->lut3d_data.gpu_mem_params.format_params.format, &format); + dc_get_lut_format(cm->lut3d_dma.format, &format); dc_get_lut_xbar( - mcm_luts->lut3d_data.gpu_mem_params.component_order, &crossbar_bit_slice_cr_r, &crossbar_bit_slice_y_g, &crossbar_bit_slice_cb_b); fl_config.mode = mode; fl_config.enabled = lut3d_xable != MCM_LUT_DISABLE; - fl_config.address = mcm_luts->lut3d_data.gpu_mem_params.addr; + fl_config.address = cm->lut3d_dma.addr; fl_config.format = format; fl_config.crossbar_bit_slice_y_g = crossbar_bit_slice_y_g; fl_config.crossbar_bit_slice_cb_b = crossbar_bit_slice_cb_b; @@ -667,17 +655,20 @@ bool dcn42_program_rmcm_luts( fl_config.width = width; fl_config.protection_bits = rmcm_3dlut->protection_bits; fl_config.addr_mode = addr_mode; - fl_config.layout = mcm_luts->lut3d_data.gpu_mem_params.layout; - fl_config.bias = mcm_luts->lut3d_data.gpu_mem_params.format_params.float_params.bias; - fl_config.scale = mcm_luts->lut3d_data.gpu_mem_params.format_params.float_params.scale; + fl_config.layout = cm->lut3d_dma.swizzle; + fl_config.bias = cm->lut3d_dma.bias; + fl_config.scale = cm->lut3d_dma.scale; mpc_fl_config.enabled = fl_config.enabled; mpc_fl_config.width = width; mpc_fl_config.select_lut_bank_a = lut_bank_a; - mpc_fl_config.bit_depth = mcm_luts->lut3d_data.gpu_mem_params.bit_depth; + /* bit_depth was previously zero-initialized in local_mcm, + * preserve identical behavior. + */ + mpc_fl_config.bit_depth = 0; mpc_fl_config.hubp_index = hubp->inst; - mpc_fl_config.bias = mcm_luts->lut3d_data.gpu_mem_params.format_params.float_params.bias; - mpc_fl_config.scale = mcm_luts->lut3d_data.gpu_mem_params.format_params.float_params.scale; + mpc_fl_config.bias = cm->lut3d_dma.bias; + mpc_fl_config.scale = cm->lut3d_dma.scale; //1. power down the block mpc->funcs->rmcm.power_on_shaper_3dlut(mpc, mpcc_id, false); @@ -689,10 +680,6 @@ bool dcn42_program_rmcm_luts( //3. power on the block mpc->funcs->rmcm.power_on_shaper_3dlut(mpc, mpcc_id, true); - - break; - default: - return false; } return true; @@ -700,7 +687,7 @@ bool dcn42_program_rmcm_luts( void dcn42_populate_mcm_luts(struct dc *dc, struct pipe_ctx *pipe_ctx, - struct dc_cm2_func_luts mcm_luts, + const struct dc_plane_cm *cm, bool lut_bank_a) { struct dpp *dpp_base = pipe_ctx->plane_res.dpp; @@ -708,14 +695,17 @@ void dcn42_populate_mcm_luts(struct dc *dc, int mpcc_id = hubp->inst; struct mpc *mpc = dc->res_pool->mpc; union mcm_lut_params m_lut_params; - enum dc_cm2_transfer_func_source lut3d_src = mcm_luts.lut3d_data.lut3d_src; + const bool lut3d_dma = !!cm->flags.bits.lut3d_dma_enable; enum hubp_3dlut_fl_format format = 0; enum hubp_3dlut_fl_mode mode; - enum hubp_3dlut_fl_width width = 0; + /* Width was previously hard-coded to TRANSFORMED via local_mcm build, + * preserve identical behavior. + */ + enum hubp_3dlut_fl_width width = hubp_3dlut_fl_width_transformed; enum hubp_3dlut_fl_addressing_mode addr_mode; - enum hubp_3dlut_fl_crossbar_bit_slice crossbar_bit_slice_y_g = 0; - enum hubp_3dlut_fl_crossbar_bit_slice crossbar_bit_slice_cb_b = 0; - enum hubp_3dlut_fl_crossbar_bit_slice crossbar_bit_slice_cr_r = 0; + enum hubp_3dlut_fl_crossbar_bit_slice crossbar_bit_slice_y_g; + enum hubp_3dlut_fl_crossbar_bit_slice crossbar_bit_slice_cb_b; + enum hubp_3dlut_fl_crossbar_bit_slice crossbar_bit_slice_cr_r; enum MCM_LUT_XABLE shaper_xable = MCM_LUT_DISABLE; enum MCM_LUT_XABLE lut3d_xable = MCM_LUT_DISABLE; enum MCM_LUT_XABLE lut1d_xable = MCM_LUT_DISABLE; @@ -724,33 +714,35 @@ void dcn42_populate_mcm_luts(struct dc *dc, dcn42_get_mcm_lut_xable_from_pipe_ctx(dc, pipe_ctx, &shaper_xable, &lut3d_xable, &lut1d_xable); //MCM - setting its location (Before/After) blender - //set to post blend (true) + //mpc_mcm_post_blend was previously zero-initialized in local_mcm, + //preserve identical behavior. dcn42_set_mcm_location_post_blend( dc, pipe_ctx, - mcm_luts.lut3d_data.mpc_mcm_post_blend); + false); //RMCM - 3dLUT+Shaper - if (mcm_luts.lut3d_data.rmcm_3dlut_enable && - is_rmcm_3dlut_fl_supported(dc, mcm_luts.lut3d_data.gpu_mem_params.size)) { +#if defined(CONFIG_DRM_AMD_DC_DCN4_2) + if (cm->flags.bits.rmcm_enable && + is_rmcm_3dlut_fl_supported(dc)) { dcn42_program_rmcm_luts( hubp, pipe_ctx, - lut3d_src, - &mcm_luts, + cm, mpc, lut_bank_a, mpcc_id); } +#endif /* CONFIG_DRM_AMD_DC_DCN4_2 */ /* 1D LUT */ - if (mcm_luts.lut1d_func) { + { memset(&m_lut_params, 0, sizeof(m_lut_params)); - if (mcm_luts.lut1d_func->type == TF_TYPE_HWPWL) - m_lut_params.pwl = &mcm_luts.lut1d_func->pwl; - else if (mcm_luts.lut1d_func->type == TF_TYPE_DISTRIBUTED_POINTS) { + if (cm->blend_func.type == TF_TYPE_HWPWL) + m_lut_params.pwl = &cm->blend_func.pwl; + else if (cm->blend_func.type == TF_TYPE_DISTRIBUTED_POINTS) { rval = cm3_helper_translate_curve_to_hw_format(mpc->ctx, - mcm_luts.lut1d_func, + &cm->blend_func, &dpp_base->regamma_params, false); m_lut_params.pwl = rval ? &dpp_base->regamma_params : NULL; } @@ -763,14 +755,14 @@ void dcn42_populate_mcm_luts(struct dc *dc, } /* Shaper */ - if (mcm_luts.shaper && mcm_luts.lut3d_data.mpc_3dlut_enable) { + if (cm->flags.bits.lut3d_enable) { memset(&m_lut_params, 0, sizeof(m_lut_params)); - if (mcm_luts.shaper->type == TF_TYPE_HWPWL) - m_lut_params.pwl = &mcm_luts.shaper->pwl; - else if (mcm_luts.shaper->type == TF_TYPE_DISTRIBUTED_POINTS) { + if (cm->shaper_func.type == TF_TYPE_HWPWL) + m_lut_params.pwl = &cm->shaper_func.pwl; + else if (cm->shaper_func.type == TF_TYPE_DISTRIBUTED_POINTS) { ASSERT(false); rval = cm3_helper_translate_curve_to_hw_format(mpc->ctx, - mcm_luts.shaper, + &cm->shaper_func, &dpp_base->regamma_params, true); m_lut_params.pwl = rval ? &dpp_base->regamma_params : NULL; } @@ -783,41 +775,27 @@ void dcn42_populate_mcm_luts(struct dc *dc, } /* 3DLUT */ - switch (lut3d_src) { - case DC_CM2_TRANSFER_FUNC_SOURCE_SYSMEM: + if (!lut3d_dma) { + /* SYSMEM (legacy lut3d_func) */ memset(&m_lut_params, 0, sizeof(m_lut_params)); if (hubp->funcs->hubp_enable_3dlut_fl) hubp->funcs->hubp_enable_3dlut_fl(hubp, false); - if (mcm_luts.lut3d_data.lut3d_func && mcm_luts.lut3d_data.lut3d_func->state.bits.initialized) { - m_lut_params.lut3d = &mcm_luts.lut3d_data.lut3d_func->lut_3d; + if (cm->lut3d_func.state.bits.initialized) { + m_lut_params.lut3d = &cm->lut3d_func.lut_3d; if (mpc->funcs->populate_lut) mpc->funcs->populate_lut(mpc, MCM_LUT_3DLUT, m_lut_params, lut_bank_a, mpcc_id); if (mpc->funcs->program_lut_mode) mpc->funcs->program_lut_mode(mpc, MCM_LUT_3DLUT, lut3d_xable, lut_bank_a, mpcc_id); } - break; - case DC_CM2_TRANSFER_FUNC_SOURCE_VIDMEM: - switch (mcm_luts.lut3d_data.gpu_mem_params.size) { - case DC_CM2_GPU_MEM_SIZE_333333: - width = hubp_3dlut_fl_width_33; - break; - case DC_CM2_GPU_MEM_SIZE_171717: - width = hubp_3dlut_fl_width_17; - break; - case DC_CM2_GPU_MEM_SIZE_TRANSFORMED: - width = hubp_3dlut_fl_width_transformed; - break; - default: - //TODO: Handle default case - break; - } + } else { + /* VIDMEM (3DLUT DMA Fast Load) */ //check for support if (mpc->funcs->mcm.is_config_supported && !mpc->funcs->mcm.is_config_supported(width)) - break; + return; if (mpc->funcs->program_lut_read_write_control) mpc->funcs->program_lut_read_write_control(mpc, MCM_LUT_3DLUT, lut_bank_a, mpcc_id); @@ -825,49 +803,70 @@ void dcn42_populate_mcm_luts(struct dc *dc, mpc->funcs->program_lut_mode(mpc, MCM_LUT_3DLUT, lut3d_xable, lut_bank_a, mpcc_id); if (hubp->funcs->hubp_program_3dlut_fl_addr) - hubp->funcs->hubp_program_3dlut_fl_addr(hubp, mcm_luts.lut3d_data.gpu_mem_params.addr); + hubp->funcs->hubp_program_3dlut_fl_addr(hubp, cm->lut3d_dma.addr); + /* bit_depth was previously zero-initialized in local_mcm, + * preserve identical behavior. + */ if (mpc->funcs->mcm.program_bit_depth) - mpc->funcs->mcm.program_bit_depth(mpc, mcm_luts.lut3d_data.gpu_mem_params.bit_depth, mpcc_id); + mpc->funcs->mcm.program_bit_depth(mpc, 0, mpcc_id); - dc_get_lut_mode(mcm_luts.lut3d_data.gpu_mem_params.layout, &mode, &addr_mode); + switch (cm->lut3d_dma.swizzle) { + case CM_LUT_3D_SWIZZLE_LINEAR_RGB: + mode = hubp_3dlut_fl_mode_native_1; + addr_mode = hubp_3dlut_fl_addressing_mode_sw_linear; + break; + case CM_LUT_3D_SWIZZLE_LINEAR_BGR: + mode = hubp_3dlut_fl_mode_native_2; + addr_mode = hubp_3dlut_fl_addressing_mode_sw_linear; + break; + case CM_LUT_1D_PACKED_LINEAR: + mode = hubp_3dlut_fl_mode_transform; + addr_mode = hubp_3dlut_fl_addressing_mode_simple_linear; + break; + default: + mode = hubp_3dlut_fl_mode_disable; + addr_mode = hubp_3dlut_fl_addressing_mode_sw_linear; + break; + } if (hubp->funcs->hubp_program_3dlut_fl_mode) hubp->funcs->hubp_program_3dlut_fl_mode(hubp, mode); if (hubp->funcs->hubp_program_3dlut_fl_addressing_mode) hubp->funcs->hubp_program_3dlut_fl_addressing_mode(hubp, addr_mode); - switch (mcm_luts.lut3d_data.gpu_mem_params.format_params.format) { - case DC_CM2_GPU_MEM_FORMAT_16161616_UNORM_12MSB: + switch (cm->lut3d_dma.format) { + case CM_LUT_PIXEL_FORMAT_RGBA16161616_UNORM_12MSB: format = hubp_3dlut_fl_format_unorm_12msb_bitslice; break; - case DC_CM2_GPU_MEM_FORMAT_16161616_UNORM_12LSB: + case CM_LUT_PIXEL_FORMAT_RGBA16161616_UNORM_12LSB: format = hubp_3dlut_fl_format_unorm_12lsb_bitslice; break; - case DC_CM2_GPU_MEM_FORMAT_16161616_FLOAT_FP1_5_10: + case CM_LUT_PIXEL_FORMAT_RGBA16161616_FLOAT_FP1_5_10: format = hubp_3dlut_fl_format_float_fp1_5_10; break; + default: + break; } if (hubp->funcs->hubp_program_3dlut_fl_format) hubp->funcs->hubp_program_3dlut_fl_format(hubp, format); if (hubp->funcs->hubp_update_3dlut_fl_bias_scale && mpc->funcs->mcm.program_bias_scale) { mpc->funcs->mcm.program_bias_scale(mpc, - mcm_luts.lut3d_data.gpu_mem_params.format_params.float_params.bias, - mcm_luts.lut3d_data.gpu_mem_params.format_params.float_params.scale, + cm->lut3d_dma.bias, + cm->lut3d_dma.scale, mpcc_id); hubp->funcs->hubp_update_3dlut_fl_bias_scale(hubp, - mcm_luts.lut3d_data.gpu_mem_params.format_params.float_params.bias, - mcm_luts.lut3d_data.gpu_mem_params.format_params.float_params.scale); + cm->lut3d_dma.bias, + cm->lut3d_dma.scale); } - //navi 4x has a bug and r and blue are swapped and need to be worked around here in - //TODO: need to make a method for get_xbar per asic OR do the workaround in program_crossbar for 4x - dc_get_lut_xbar( - mcm_luts.lut3d_data.gpu_mem_params.component_order, - &crossbar_bit_slice_cr_r, - &crossbar_bit_slice_y_g, - &crossbar_bit_slice_cb_b); + /* component_order was previously hard-coded to RGBA in local_mcm, + * preserve identical behavior. + */ + crossbar_bit_slice_cr_r = hubp_3dlut_fl_crossbar_bit_slice_0_15; + crossbar_bit_slice_y_g = hubp_3dlut_fl_crossbar_bit_slice_16_31; + crossbar_bit_slice_cb_b = hubp_3dlut_fl_crossbar_bit_slice_32_47; if (hubp->funcs->hubp_program_3dlut_fl_crossbar) hubp->funcs->hubp_program_3dlut_fl_crossbar(hubp, @@ -893,7 +892,6 @@ void dcn42_populate_mcm_luts(struct dc *dc, mpc->funcs->program_lut_mode(mpc, MCM_LUT_1DLUT, MCM_LUT_DISABLE, lut_bank_a, mpcc_id); } } - break; } } @@ -908,19 +906,19 @@ bool dcn42_set_mcm_luts(struct pipe_ctx *pipe_ctx, const struct pwl_params *lut_params = NULL; bool rval; - if (plane_state->mcm_luts.lut3d_data.lut3d_src == DC_CM2_TRANSFER_FUNC_SOURCE_VIDMEM) { - dcn42_populate_mcm_luts(dc, pipe_ctx, plane_state->mcm_luts, plane_state->lut_bank_a); + if (plane_state->cm.flags.bits.lut3d_dma_enable) { + dcn42_populate_mcm_luts(dc, pipe_ctx, &plane_state->cm, plane_state->lut_bank_a); return true; } mpc->funcs->set_movable_cm_location(mpc, MPCC_MOVABLE_CM_LOCATION_BEFORE, mpcc_id); pipe_ctx->plane_state->mcm_location = MPCC_MOVABLE_CM_LOCATION_BEFORE; // 1D LUT - if (plane_state->blend_tf.type == TF_TYPE_HWPWL) - lut_params = &plane_state->blend_tf.pwl; - else if (plane_state->blend_tf.type == TF_TYPE_DISTRIBUTED_POINTS) { + if (plane_state->cm.blend_func.type == TF_TYPE_HWPWL) + lut_params = &plane_state->cm.blend_func.pwl; + else if (plane_state->cm.blend_func.type == TF_TYPE_DISTRIBUTED_POINTS) { rval = cm3_helper_translate_curve_to_hw_format(plane_state->ctx, - &plane_state->blend_tf, + &plane_state->cm.blend_func, &dpp_base->regamma_params, false); lut_params = rval ? &dpp_base->regamma_params : NULL; } @@ -928,12 +926,12 @@ bool dcn42_set_mcm_luts(struct pipe_ctx *pipe_ctx, lut_params = NULL; // Shaper - if (plane_state->in_shaper_func.type == TF_TYPE_HWPWL) - lut_params = &plane_state->in_shaper_func.pwl; - else if (plane_state->in_shaper_func.type == TF_TYPE_DISTRIBUTED_POINTS) { + if (plane_state->cm.shaper_func.type == TF_TYPE_HWPWL) + lut_params = &plane_state->cm.shaper_func.pwl; + else if (plane_state->cm.shaper_func.type == TF_TYPE_DISTRIBUTED_POINTS) { // TODO: dpp_base replace rval = cm3_helper_translate_curve_to_hw_format(plane_state->ctx, - &plane_state->in_shaper_func, + &plane_state->cm.shaper_func, &dpp_base->shaper_params, true); lut_params = rval ? &dpp_base->shaper_params : NULL; } @@ -941,8 +939,8 @@ bool dcn42_set_mcm_luts(struct pipe_ctx *pipe_ctx, // 3D if (mpc->funcs->program_3dlut) { - if (plane_state->lut3d_func.state.bits.initialized == 1) - result &= mpc->funcs->program_3dlut(mpc, &plane_state->lut3d_func.lut_3d, mpcc_id); + if (plane_state->cm.lut3d_func.state.bits.initialized == 1) + result &= mpc->funcs->program_3dlut(mpc, &plane_state->cm.lut3d_func.lut_3d, mpcc_id); else result &= mpc->funcs->program_3dlut(mpc, NULL, mpcc_id); } diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.h b/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.h index 0539ee0ffaee..c469e7535114 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.h +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.h @@ -20,14 +20,13 @@ bool dcn42_set_mcm_luts(struct pipe_ctx *pipe_ctx, void dcn42_populate_mcm_luts(struct dc *dc, struct pipe_ctx *pipe_ctx, - struct dc_cm2_func_luts mcm_luts, + const struct dc_plane_cm *cm, bool lut_bank_a); bool dcn42_program_rmcm_luts( struct hubp *hubp, struct pipe_ctx *pipe_ctx, - enum dc_cm2_transfer_func_source lut3d_src, - struct dc_cm2_func_luts *mcm_luts, + const struct dc_plane_cm *cm, struct mpc *mpc, bool lut_bank_a, int mpcc_id); diff --git a/drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer_private.h b/drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer_private.h index 63c6c841c681..b4956893ae9a 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer_private.h +++ b/drivers/gpu/drm/amd/display/dc/hwss/hw_sequencer_private.h @@ -58,6 +58,7 @@ struct dc_state; struct dc_stream_status; struct dc_writeback_info; struct dchub_init_data; +struct dc_plane_cm; struct dc_static_screen_params; struct resource_pool; struct resource_context; @@ -219,7 +220,7 @@ struct hwseq_private_funcs { struct dc_state *context); void (*populate_mcm_luts)(struct dc *dc, struct pipe_ctx *pipe_ctx, - struct dc_cm2_func_luts mcm_luts, + const struct dc_plane_cm *cm, bool lut_bank_a); void (*perform_3dlut_wa_unlock)(struct pipe_ctx *pipe_ctx); void (*wait_for_pipe_update_if_needed)(struct dc *dc, struct pipe_ctx *pipe_ctx, bool is_surface_update_only); diff --git a/drivers/gpu/drm/amd/display/dc/inc/hw/hubp.h b/drivers/gpu/drm/amd/display/dc/inc/hw/hubp.h index 1c18898aa475..6d6eda0e7e9d 100644 --- a/drivers/gpu/drm/amd/display/dc/inc/hw/hubp.h +++ b/drivers/gpu/drm/amd/display/dc/inc/hw/hubp.h @@ -108,7 +108,7 @@ struct hubp_fl_3dlut_config { uint16_t scale; struct dc_plane_address address; enum hubp_3dlut_fl_addressing_mode addr_mode; - enum dc_cm2_gpu_mem_layout layout; + enum dc_cm_lut_swizzle layout; uint8_t protection_bits; enum hubp_3dlut_fl_crossbar_bit_slice crossbar_bit_slice_y_g; enum hubp_3dlut_fl_crossbar_bit_slice crossbar_bit_slice_cb_b; -- cgit v1.2.3 From f5165625b8b27a46993e0c55b4468bd4e215f7c6 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Thu, 23 Apr 2026 18:34:52 -0600 Subject: drm/amd/display: Extract backlight code to amdgpu_dm_backlight Move backlight-related functions from amdgpu_dm.c into a new amdgpu_dm_backlight.c file to improve code organization and reduce the size of the monolithic amdgpu_dm.c. No functional change intended. Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/Makefile | 3 +- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 620 +------------------ drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 1 - .../amd/display/amdgpu_dm/amdgpu_dm_backlight.c | 660 +++++++++++++++++++++ .../amd/display/amdgpu_dm/amdgpu_dm_backlight.h | 44 ++ .../drm/amd/display/amdgpu_dm/amdgpu_dm_services.c | 1 + 6 files changed, 710 insertions(+), 619 deletions(-) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile index 54a93e4255b3..2953c59d85e7 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile @@ -41,7 +41,8 @@ AMDGPUDM = \ amdgpu_dm_quirks.o \ amdgpu_dm_wb.o \ amdgpu_dm_colorop.o \ - amdgpu_dm_ism.o + amdgpu_dm_ism.o \ + amdgpu_dm_backlight.o ifdef CONFIG_DRM_AMD_DC_FP AMDGPUDM += dc_fpu.o 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 7a46c9e56d87..3bd0ae0e54cd 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -66,6 +66,7 @@ #endif #include "amdgpu_dm_psr.h" #include "amdgpu_dm_replay.h" +#include "amdgpu_dm_backlight.h" #include "ivsrcid/ivsrcid_vislands30.h" @@ -246,10 +247,6 @@ static void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector, enum dc_detect_reason reason); static void handle_hpd_rx_irq(void *param); -static void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm, - int bl_idx, - u32 user_brightness); - static bool is_timing_unchanged_for_freesync(struct drm_crtc_state *old_crtc_state, struct drm_crtc_state *new_crtc_state); @@ -4049,74 +4046,6 @@ static void dm_set_panel_type(struct amdgpu_dm_connector *aconnector) drm_dbg_kms(aconnector->base.dev, "Panel type: %d\n", link->panel_type); } -static void update_connector_ext_caps(struct amdgpu_dm_connector *aconnector) -{ - const struct drm_panel_backlight_quirk *panel_backlight_quirk; - struct amdgpu_dm_backlight_caps *caps; - struct drm_connector *conn_base; - struct amdgpu_device *adev; - struct drm_luminance_range_info *luminance_range; - struct drm_device *drm; - - if (aconnector->bl_idx == -1 || - aconnector->dc_link->connector_signal != SIGNAL_TYPE_EDP) - return; - - conn_base = &aconnector->base; - drm = conn_base->dev; - adev = drm_to_adev(drm); - - caps = &adev->dm.backlight_caps[aconnector->bl_idx]; - caps->ext_caps = &aconnector->dc_link->dpcd_sink_ext_caps; - caps->aux_support = false; - - if (caps->ext_caps->bits.oled == 1 - /* - * || - * caps->ext_caps->bits.sdr_aux_backlight_control == 1 || - * caps->ext_caps->bits.hdr_aux_backlight_control == 1 - */) - caps->aux_support = true; - - if (amdgpu_backlight == 0) - caps->aux_support = false; - else if (amdgpu_backlight == 1) - caps->aux_support = true; - if (caps->aux_support) - aconnector->dc_link->backlight_control_type = BACKLIGHT_CONTROL_AMD_AUX; - - luminance_range = &conn_base->display_info.luminance_range; - - if (luminance_range->max_luminance) - caps->aux_max_input_signal = luminance_range->max_luminance; - else - caps->aux_max_input_signal = 512; - - if (luminance_range->min_luminance) - caps->aux_min_input_signal = luminance_range->min_luminance; - else - caps->aux_min_input_signal = 1; - - panel_backlight_quirk = - drm_get_panel_backlight_quirk(aconnector->drm_edid); - if (!IS_ERR_OR_NULL(panel_backlight_quirk)) { - if (panel_backlight_quirk->min_brightness) { - caps->min_input_signal = - panel_backlight_quirk->min_brightness - 1; - drm_info(drm, - "Applying panel backlight quirk, min_brightness: %d\n", - caps->min_input_signal); - } - if (panel_backlight_quirk->brightness_mask) { - drm_info(drm, - "Applying panel backlight quirk, brightness_mask: 0x%X\n", - panel_backlight_quirk->brightness_mask); - caps->brightness_mask = - panel_backlight_quirk->brightness_mask; - } - } -} - DEFINE_FREE(sink_release, struct dc_sink *, if (_T) dc_sink_release(_T)) void amdgpu_dm_update_connector_after_detect( @@ -4242,7 +4171,7 @@ void amdgpu_dm_update_connector_after_detect( } amdgpu_dm_update_freesync_caps(connector, aconnector->drm_edid, true); - update_connector_ext_caps(aconnector); + amdgpu_dm_update_connector_ext_caps(aconnector); dm_set_panel_type(aconnector); } else { hdmi_cec_unset_edid(aconnector); @@ -5160,420 +5089,6 @@ static int amdgpu_dm_mode_config_init(struct amdgpu_device *adev) return 0; } -#define AMDGPU_DM_DEFAULT_MIN_BACKLIGHT 12 -#define AMDGPU_DM_DEFAULT_MAX_BACKLIGHT 255 -#define AMDGPU_DM_MIN_SPREAD ((AMDGPU_DM_DEFAULT_MAX_BACKLIGHT - AMDGPU_DM_DEFAULT_MIN_BACKLIGHT) / 2) -#define AUX_BL_DEFAULT_TRANSITION_TIME_MS 50 - -void amdgpu_dm_update_backlight_caps(struct amdgpu_display_manager *dm, - int bl_idx) -{ - struct amdgpu_dm_backlight_caps *caps = &dm->backlight_caps[bl_idx]; - - if (caps->caps_valid) - return; - -#if defined(CONFIG_ACPI) - amdgpu_acpi_get_backlight_caps(caps); - - /* validate the firmware value is sane */ - if (caps->caps_valid) { - int spread = caps->max_input_signal - caps->min_input_signal; - - if (caps->max_input_signal > AMDGPU_DM_DEFAULT_MAX_BACKLIGHT || - caps->min_input_signal < 0 || - spread > AMDGPU_DM_DEFAULT_MAX_BACKLIGHT || - spread < AMDGPU_DM_MIN_SPREAD) { - drm_dbg_kms(adev_to_drm(dm->adev), "DM: Invalid backlight caps: min=%d, max=%d\n", - caps->min_input_signal, caps->max_input_signal); - caps->caps_valid = false; - } - } - - if (!caps->caps_valid) { - caps->min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT; - caps->max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; - caps->caps_valid = true; - } -#else - if (caps->aux_support) - return; - - caps->min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT; - caps->max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; - caps->caps_valid = true; -#endif -} - -static int get_brightness_range(const struct amdgpu_dm_backlight_caps *caps, - unsigned int *min, unsigned int *max) -{ - if (!caps) - return 0; - - if (caps->aux_support) { - // Firmware limits are in nits, DC API wants millinits. - *max = 1000 * caps->aux_max_input_signal; - *min = 1000 * caps->aux_min_input_signal; - } else { - // Firmware limits are 8-bit, PWM control is 16-bit. - *max = 0x101 * caps->max_input_signal; - *min = 0x101 * caps->min_input_signal; - } - return 1; -} - -/* Rescale from [min..max] to [0..AMDGPU_MAX_BL_LEVEL] */ -static inline u32 scale_input_to_fw(int min, int max, u64 input) -{ - return DIV_ROUND_CLOSEST_ULL(input * AMDGPU_MAX_BL_LEVEL, max - min); -} - -/* Rescale from [0..AMDGPU_MAX_BL_LEVEL] to [min..max] */ -static inline u32 scale_fw_to_input(int min, int max, u64 input) -{ - return min + DIV_ROUND_CLOSEST_ULL(input * (max - min), AMDGPU_MAX_BL_LEVEL); -} - -static void convert_custom_brightness(const struct amdgpu_dm_backlight_caps *caps, - unsigned int min, unsigned int max, - uint32_t *user_brightness) -{ - u32 brightness = scale_input_to_fw(min, max, *user_brightness); - u8 lower_signal, upper_signal, upper_lum, lower_lum, lum; - int left, right; - - if (amdgpu_dc_debug_mask & DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE) - return; - - if (!caps->data_points) - return; - - /* - * Handle the case where brightness is below the first data point - * Interpolate between (0,0) and (first_signal, first_lum) - */ - if (brightness < caps->luminance_data[0].input_signal) { - lum = DIV_ROUND_CLOSEST(caps->luminance_data[0].luminance * brightness, - caps->luminance_data[0].input_signal); - goto scale; - } - - left = 0; - right = caps->data_points - 1; - while (left <= right) { - int mid = left + (right - left) / 2; - u8 signal = caps->luminance_data[mid].input_signal; - - /* Exact match found */ - if (signal == brightness) { - lum = caps->luminance_data[mid].luminance; - goto scale; - } - - if (signal < brightness) - left = mid + 1; - else - right = mid - 1; - } - - /* verify bound */ - if (left >= caps->data_points) - left = caps->data_points - 1; - - /* At this point, left > right */ - lower_signal = caps->luminance_data[right].input_signal; - upper_signal = caps->luminance_data[left].input_signal; - lower_lum = caps->luminance_data[right].luminance; - upper_lum = caps->luminance_data[left].luminance; - - /* interpolate */ - if (right == left || !lower_lum) - lum = upper_lum; - else - lum = lower_lum + DIV_ROUND_CLOSEST((upper_lum - lower_lum) * - (brightness - lower_signal), - upper_signal - lower_signal); -scale: - *user_brightness = scale_fw_to_input(min, max, - DIV_ROUND_CLOSEST(lum * brightness, 101)); -} - -static u32 convert_brightness_from_user(const struct amdgpu_dm_backlight_caps *caps, - uint32_t brightness) -{ - unsigned int min, max; - - if (!get_brightness_range(caps, &min, &max)) - return brightness; - - convert_custom_brightness(caps, min, max, &brightness); - - // Rescale 0..max to min..max - return min + DIV_ROUND_CLOSEST_ULL((u64)(max - min) * brightness, max); -} - -static u32 convert_brightness_to_user(const struct amdgpu_dm_backlight_caps *caps, - uint32_t brightness) -{ - unsigned int min, max; - - if (!get_brightness_range(caps, &min, &max)) - return brightness; - - if (brightness < min) - return 0; - // Rescale min..max to 0..max - return DIV_ROUND_CLOSEST_ULL((u64)max * (brightness - min), - max - min); -} - -static struct dc_stream_state *dm_find_stream_with_link( - struct amdgpu_display_manager *dm, - struct dc_link *link) -{ - struct dc_state *cur_dc_state = dm->dc->current_state; - struct dc_stream_state *stream = NULL; - int i; - - for (i = 0; i < cur_dc_state->stream_count; i++) { - stream = cur_dc_state->streams[i]; - if (stream->link == link) - return stream; - } - - return NULL; -} - -static void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm, - int bl_idx, - u32 user_brightness) -{ - struct amdgpu_dm_backlight_caps *caps; - struct dc_link *link; - u32 brightness = 0; - bool rc = false, reallow_idle = false; - struct drm_connector *connector; - struct dc_stream_state *stream; - unsigned int min, max; - - list_for_each_entry(connector, &dm->ddev->mode_config.connector_list, head) { - struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector); - - if (aconnector->bl_idx != bl_idx) - continue; - - /* if connector is off, save the brightness for next time it's on */ - if (!aconnector->base.encoder) { - dm->brightness[bl_idx] = user_brightness; - dm->actual_brightness[bl_idx] = 0; - return; - } - } - - amdgpu_dm_update_backlight_caps(dm, bl_idx); - caps = &dm->backlight_caps[bl_idx]; - - dm->brightness[bl_idx] = user_brightness; - /* update scratch register */ - if (bl_idx == 0) - amdgpu_atombios_scratch_regs_set_backlight_level(dm->adev, dm->brightness[bl_idx]); - brightness = convert_brightness_from_user(caps, dm->brightness[bl_idx]); - link = (struct dc_link *)dm->backlight_link[bl_idx]; - - /* Apply brightness quirk */ - if (caps->brightness_mask) - brightness |= caps->brightness_mask; - - if (trace_amdgpu_dm_brightness_enabled()) { - trace_amdgpu_dm_brightness(__builtin_return_address(0), - user_brightness, - brightness, - caps->aux_support, - power_supply_is_system_supplied() > 0); - } - - stream = dm_find_stream_with_link(dm, link); - if (!stream) - return; - - mutex_lock(&dm->dc_lock); - if (dm->dc->caps.ips_support && dm->dc->ctx->dmub_srv->idle_allowed) { - dc_allow_idle_optimizations(dm->dc, false); - reallow_idle = true; - } - - if (caps->aux_support) { - rc = mod_power_set_backlight_nits(dm->power_module, stream, brightness, - AUX_BL_DEFAULT_TRANSITION_TIME_MS, false, true); - } else { - /* power module uses millipercent */ - get_brightness_range(caps, &min, &max); - brightness = DIV_ROUND_CLOSEST(brightness * 100, (max - min)) * 1000; - rc = mod_power_set_backlight_percent(dm->power_module, stream, - brightness, 0, false); - } - - /* - * Some kms clients create a ramped backlight transition effect - * by rapidly changing the backlight. Yet we must wait on dmcub - * fw to exit psr/replay before programming backlight. To - * prevent lag, keep disable psr/replay and let the next atomic - * flip clear the event. - * - * ToDo: use ISM to handle rapidly backlight change - * - * Rapidly backlight change is similar to rapidly cursor events, - * which is now handled by ISM. ISM can delay the event until system - * is really idle, so we may use ISM to handle backlight change as well. - */ - amdgpu_dm_psr_set_event(dm, stream, true, - psr_event_hw_programming, true); - amdgpu_dm_replay_set_event(dm, stream, true, - replay_event_hw_programming, true); - - if (dm->dc->caps.ips_support && reallow_idle) - dc_allow_idle_optimizations(dm->dc, true); - - mutex_unlock(&dm->dc_lock); - - if (rc) - dm->actual_brightness[bl_idx] = user_brightness; -} - -static int amdgpu_dm_backlight_update_status(struct backlight_device *bd) -{ - struct amdgpu_display_manager *dm = bl_get_data(bd); - int i; - - for (i = 0; i < dm->num_of_edps; i++) { - if (bd == dm->backlight_dev[i]) - break; - } - if (i >= AMDGPU_DM_MAX_NUM_EDP) - i = 0; - amdgpu_dm_backlight_set_level(dm, i, bd->props.brightness); - - return 0; -} - -static u32 amdgpu_dm_backlight_get_level(struct amdgpu_display_manager *dm, - int bl_idx) -{ - int ret; - struct amdgpu_dm_backlight_caps caps; - struct dc_link *link = (struct dc_link *)dm->backlight_link[bl_idx]; - - amdgpu_dm_update_backlight_caps(dm, bl_idx); - caps = dm->backlight_caps[bl_idx]; - - if (caps.aux_support) { - u32 avg, peak; - - if (!dc_link_get_backlight_level_nits(link, &avg, &peak)) - return dm->brightness[bl_idx]; - return convert_brightness_to_user(&caps, avg); - } - - ret = dc_link_get_backlight_level(link); - - if (ret == DC_ERROR_UNEXPECTED) - return dm->brightness[bl_idx]; - - return convert_brightness_to_user(&caps, ret); -} - -static int amdgpu_dm_backlight_get_brightness(struct backlight_device *bd) -{ - struct amdgpu_display_manager *dm = bl_get_data(bd); - int i; - - for (i = 0; i < dm->num_of_edps; i++) { - if (bd == dm->backlight_dev[i]) - break; - } - if (i >= AMDGPU_DM_MAX_NUM_EDP) - i = 0; - return amdgpu_dm_backlight_get_level(dm, i); -} - -static const struct backlight_ops amdgpu_dm_backlight_ops = { - .options = BL_CORE_SUSPENDRESUME, - .get_brightness = amdgpu_dm_backlight_get_brightness, - .update_status = amdgpu_dm_backlight_update_status, -}; - -static void -amdgpu_dm_register_backlight_device(struct amdgpu_dm_connector *aconnector) -{ - struct drm_device *drm = aconnector->base.dev; - struct amdgpu_display_manager *dm = &drm_to_adev(drm)->dm; - struct backlight_properties props = { 0 }; - struct amdgpu_dm_backlight_caps *caps; - char bl_name[16]; - int min, max; - int real_brightness; - int init_brightness; - - if (aconnector->bl_idx == -1) - return; - - if (!acpi_video_backlight_use_native()) { - drm_info(drm, "Skipping amdgpu DM backlight registration\n"); - /* Try registering an ACPI video backlight device instead. */ - acpi_video_register_backlight(); - return; - } - - caps = &dm->backlight_caps[aconnector->bl_idx]; - if (get_brightness_range(caps, &min, &max)) { - if (power_supply_is_system_supplied() > 0) - props.brightness = DIV_ROUND_CLOSEST((max - min) * caps->ac_level, 100); - else - props.brightness = DIV_ROUND_CLOSEST((max - min) * caps->dc_level, 100); - /* min is zero, so max needs to be adjusted */ - props.max_brightness = max - min; - drm_dbg(drm, "Backlight caps: min: %d, max: %d, ac %d, dc %d\n", min, max, - caps->ac_level, caps->dc_level); - } else - props.brightness = props.max_brightness = MAX_BACKLIGHT_LEVEL; - - init_brightness = props.brightness; - - if (caps->data_points && !(amdgpu_dc_debug_mask & DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE)) { - drm_info(drm, "Using custom brightness curve\n"); - props.scale = BACKLIGHT_SCALE_NON_LINEAR; - } else - props.scale = BACKLIGHT_SCALE_LINEAR; - props.type = BACKLIGHT_RAW; - - snprintf(bl_name, sizeof(bl_name), "amdgpu_bl%d", - drm->primary->index + aconnector->bl_idx); - - dm->backlight_dev[aconnector->bl_idx] = - backlight_device_register(bl_name, aconnector->base.kdev, dm, - &amdgpu_dm_backlight_ops, &props); - dm->brightness[aconnector->bl_idx] = props.brightness; - - if (IS_ERR(dm->backlight_dev[aconnector->bl_idx])) { - drm_err(drm, "DM: Backlight registration failed!\n"); - dm->backlight_dev[aconnector->bl_idx] = NULL; - } else { - /* - * dm->brightness[x] can be inconsistent just after startup until - * ops.get_brightness is called. - */ - real_brightness = - amdgpu_dm_backlight_ops.get_brightness(dm->backlight_dev[aconnector->bl_idx]); - - if (real_brightness != init_brightness) { - dm->actual_brightness[aconnector->bl_idx] = real_brightness; - dm->brightness[aconnector->bl_idx] = real_brightness; - } - drm_dbg_driver(drm, "DM: Registered Backlight device: %s\n", bl_name); - } -} - static int initialize_plane(struct amdgpu_display_manager *dm, struct amdgpu_mode_info *mode_info, int plane_id, enum drm_plane_type plane_type, @@ -5615,38 +5130,6 @@ static int initialize_plane(struct amdgpu_display_manager *dm, } -static void setup_backlight_device(struct amdgpu_display_manager *dm, - struct amdgpu_dm_connector *aconnector) -{ - struct amdgpu_dm_backlight_caps *caps; - struct dc_link *link = aconnector->dc_link; - int bl_idx = dm->num_of_edps; - - if (!(link->connector_signal & (SIGNAL_TYPE_EDP | SIGNAL_TYPE_LVDS)) || - link->type == dc_connection_none) - return; - - if (dm->num_of_edps >= AMDGPU_DM_MAX_NUM_EDP) { - drm_warn(adev_to_drm(dm->adev), "Too much eDP connections, skipping backlight setup for additional eDPs\n"); - return; - } - - aconnector->bl_idx = bl_idx; - - amdgpu_dm_update_backlight_caps(dm, bl_idx); - dm->backlight_link[bl_idx] = link; - dm->num_of_edps++; - - update_connector_ext_caps(aconnector); - caps = &dm->backlight_caps[aconnector->bl_idx]; - - /* Only offer ABM property when non-OLED and user didn't turn off by module parameter */ - if (caps->ext_caps && !caps->ext_caps->bits.oled && amdgpu_dm_abm_level < 0) - drm_object_attach_property(&aconnector->base.base, - dm->adev->mode_info.abm_level_property, - ABM_SYSFS_CONTROL); -} - static void amdgpu_set_panel_orientation(struct drm_connector *connector); @@ -5887,7 +5370,7 @@ static int amdgpu_dm_initialize_drm_device(struct amdgpu_device *adev) if (ret) { amdgpu_dm_update_connector_after_detect(aconnector); - setup_backlight_device(dm, aconnector); + amdgpu_dm_setup_backlight_device(dm, aconnector); /* Disable PSR if Replay can be enabled */ if (replay_feature_enabled) @@ -7964,103 +7447,6 @@ int amdgpu_dm_connector_atomic_get_property(struct drm_connector *connector, return ret; } -/** - * DOC: panel power savings - * - * The display manager allows you to set your desired **panel power savings** - * level (between 0-4, with 0 representing off), e.g. using the following:: - * - * # echo 3 > /sys/class/drm/card0-eDP-1/amdgpu/panel_power_savings - * - * Modifying this value can have implications on color accuracy, so tread - * carefully. - */ - -static ssize_t panel_power_savings_show(struct device *device, - struct device_attribute *attr, - char *buf) -{ - struct drm_connector *connector = dev_get_drvdata(device); - struct drm_device *dev = connector->dev; - u8 val; - - drm_modeset_lock(&dev->mode_config.connection_mutex, NULL); - val = to_dm_connector_state(connector->state)->abm_level == - ABM_LEVEL_IMMEDIATE_DISABLE ? 0 : - to_dm_connector_state(connector->state)->abm_level; - drm_modeset_unlock(&dev->mode_config.connection_mutex); - - return sysfs_emit(buf, "%u\n", val); -} - -static ssize_t panel_power_savings_store(struct device *device, - struct device_attribute *attr, - const char *buf, size_t count) -{ - struct drm_connector *connector = dev_get_drvdata(device); - struct drm_device *dev = connector->dev; - long val; - int ret; - - ret = kstrtol(buf, 0, &val); - - if (ret) - return ret; - - if (val < 0 || val > 4) - return -EINVAL; - - drm_modeset_lock(&dev->mode_config.connection_mutex, NULL); - if (to_dm_connector_state(connector->state)->abm_sysfs_forbidden) - ret = -EBUSY; - else - to_dm_connector_state(connector->state)->abm_level = val ?: - ABM_LEVEL_IMMEDIATE_DISABLE; - drm_modeset_unlock(&dev->mode_config.connection_mutex); - - if (ret) - return ret; - - drm_kms_helper_hotplug_event(dev); - - return count; -} - -static DEVICE_ATTR_RW(panel_power_savings); - -static struct attribute *amdgpu_attrs[] = { - &dev_attr_panel_power_savings.attr, - NULL -}; - -static const struct attribute_group amdgpu_group = { - .name = "amdgpu", - .attrs = amdgpu_attrs -}; - -static bool -amdgpu_dm_should_create_sysfs(struct amdgpu_dm_connector *amdgpu_dm_connector) -{ - if (amdgpu_dm_abm_level >= 0) - return false; - - if (amdgpu_dm_connector->base.connector_type != DRM_MODE_CONNECTOR_eDP) - return false; - - /* check for OLED panels */ - if (amdgpu_dm_connector->bl_idx >= 0) { - struct drm_device *drm = amdgpu_dm_connector->base.dev; - struct amdgpu_display_manager *dm = &drm_to_adev(drm)->dm; - struct amdgpu_dm_backlight_caps *caps; - - caps = &dm->backlight_caps[amdgpu_dm_connector->bl_idx]; - if (caps->aux_support) - return false; - } - - return true; -} - static void amdgpu_dm_connector_unregister(struct drm_connector *connector) { struct amdgpu_dm_connector *amdgpu_dm_connector = to_amdgpu_dm_connector(connector); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h index dd199e0b7922..f0e91a0a15fc 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h @@ -1167,5 +1167,4 @@ int amdgpu_dm_initialize_hdmi_connector(struct amdgpu_dm_connector *aconnector); void retrieve_dmi_info(struct amdgpu_display_manager *dm); -void amdgpu_dm_update_backlight_caps(struct amdgpu_display_manager *dm, int bl_idx); #endif /* __AMDGPU_DM_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c new file mode 100644 index 000000000000..3770e8dafdbf --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c @@ -0,0 +1,660 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright 2026 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 "dc.h" +#include "dc/dc_dmub_srv.h" +#include "dc/dc_state.h" +#include "dc/dc_stat.h" + +#include "amdgpu.h" +#include "amdgpu_display.h" +#include "amdgpu_dm.h" +#include "amdgpu_dm_backlight.h" +#include "amdgpu_dm_psr.h" +#include "amdgpu_dm_replay.h" +#include "amdgpu_atombios.h" + +#include "modules/inc/mod_power.h" + +#include +#include +#include +#include + +#include + +#include "amdgpu_dm_trace.h" +#include "amd_shared.h" + +#define AMDGPU_DM_DEFAULT_MIN_BACKLIGHT 12 +#define AMDGPU_DM_DEFAULT_MAX_BACKLIGHT 255 +#define AMDGPU_DM_MIN_SPREAD ((AMDGPU_DM_DEFAULT_MAX_BACKLIGHT - AMDGPU_DM_DEFAULT_MIN_BACKLIGHT) / 2) +#define AUX_BL_DEFAULT_TRANSITION_TIME_MS 50 + +void amdgpu_dm_update_backlight_caps(struct amdgpu_display_manager *dm, + int bl_idx) +{ + struct amdgpu_dm_backlight_caps *caps = &dm->backlight_caps[bl_idx]; + + if (caps->caps_valid) + return; + +#if defined(CONFIG_ACPI) + amdgpu_acpi_get_backlight_caps(caps); + + /* validate the firmware value is sane */ + if (caps->caps_valid) { + int spread = caps->max_input_signal - caps->min_input_signal; + + if (caps->max_input_signal > AMDGPU_DM_DEFAULT_MAX_BACKLIGHT || + caps->min_input_signal < 0 || + spread > AMDGPU_DM_DEFAULT_MAX_BACKLIGHT || + spread < AMDGPU_DM_MIN_SPREAD) { + drm_dbg_kms(adev_to_drm(dm->adev), "DM: Invalid backlight caps: min=%d, max=%d\n", + caps->min_input_signal, caps->max_input_signal); + caps->caps_valid = false; + } + } + + if (!caps->caps_valid) { + caps->min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT; + caps->max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; + caps->caps_valid = true; + } +#else + if (caps->aux_support) + return; + + caps->min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT; + caps->max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; + caps->caps_valid = true; +#endif +} + +static int get_brightness_range(const struct amdgpu_dm_backlight_caps *caps, + unsigned int *min, unsigned int *max) +{ + if (!caps) + return 0; + + if (caps->aux_support) { + /* Firmware limits are in nits, DC API wants millinits. */ + *max = 1000 * caps->aux_max_input_signal; + *min = 1000 * caps->aux_min_input_signal; + } else { + /* Firmware limits are 8-bit, PWM control is 16-bit. */ + *max = 0x101 * caps->max_input_signal; + *min = 0x101 * caps->min_input_signal; + } + return 1; +} + +/* Rescale from [min..max] to [0..AMDGPU_MAX_BL_LEVEL] */ +static inline u32 scale_input_to_fw(int min, int max, u64 input) +{ + return DIV_ROUND_CLOSEST_ULL(input * AMDGPU_MAX_BL_LEVEL, max - min); +} + +/* Rescale from [0..AMDGPU_MAX_BL_LEVEL] to [min..max] */ +static inline u32 scale_fw_to_input(int min, int max, u64 input) +{ + return min + DIV_ROUND_CLOSEST_ULL(input * (max - min), AMDGPU_MAX_BL_LEVEL); +} + +static void convert_custom_brightness(const struct amdgpu_dm_backlight_caps *caps, + unsigned int min, unsigned int max, + uint32_t *user_brightness) +{ + u32 brightness = scale_input_to_fw(min, max, *user_brightness); + u8 lower_signal, upper_signal, upper_lum, lower_lum, lum; + int left, right; + + if (amdgpu_dc_debug_mask & DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE) + return; + + if (!caps->data_points) + return; + + /* + * Handle the case where brightness is below the first data point + * Interpolate between (0,0) and (first_signal, first_lum) + */ + if (brightness < caps->luminance_data[0].input_signal) { + lum = DIV_ROUND_CLOSEST(caps->luminance_data[0].luminance * brightness, + caps->luminance_data[0].input_signal); + goto scale; + } + + left = 0; + right = caps->data_points - 1; + while (left <= right) { + int mid = left + (right - left) / 2; + u8 signal = caps->luminance_data[mid].input_signal; + + /* Exact match found */ + if (signal == brightness) { + lum = caps->luminance_data[mid].luminance; + goto scale; + } + + if (signal < brightness) + left = mid + 1; + else + right = mid - 1; + } + + /* verify bound */ + if (left >= caps->data_points) + left = caps->data_points - 1; + + /* At this point, left > right */ + lower_signal = caps->luminance_data[right].input_signal; + upper_signal = caps->luminance_data[left].input_signal; + lower_lum = caps->luminance_data[right].luminance; + upper_lum = caps->luminance_data[left].luminance; + + /* interpolate */ + if (right == left || !lower_lum) + lum = upper_lum; + else + lum = lower_lum + DIV_ROUND_CLOSEST((upper_lum - lower_lum) * + (brightness - lower_signal), + upper_signal - lower_signal); +scale: + *user_brightness = scale_fw_to_input(min, max, + DIV_ROUND_CLOSEST(lum * brightness, 101)); +} + +static u32 convert_brightness_from_user(const struct amdgpu_dm_backlight_caps *caps, + uint32_t brightness) +{ + unsigned int min, max; + + if (!get_brightness_range(caps, &min, &max)) + return brightness; + + convert_custom_brightness(caps, min, max, &brightness); + + /* Rescale 0..max to min..max */ + return min + DIV_ROUND_CLOSEST_ULL((u64)(max - min) * brightness, max); +} + +static u32 convert_brightness_to_user(const struct amdgpu_dm_backlight_caps *caps, + uint32_t brightness) +{ + unsigned int min, max; + + if (!get_brightness_range(caps, &min, &max)) + return brightness; + + if (brightness < min) + return 0; + /* Rescale min..max to 0..max */ + return DIV_ROUND_CLOSEST_ULL((u64)max * (brightness - min), + max - min); +} + +static struct dc_stream_state *dm_find_stream_with_link( + struct amdgpu_display_manager *dm, + struct dc_link *link) +{ + struct dc_state *cur_dc_state = dm->dc->current_state; + struct dc_stream_state *stream = NULL; + int i; + + for (i = 0; i < cur_dc_state->stream_count; i++) { + stream = cur_dc_state->streams[i]; + if (stream->link == link) + return stream; + } + + return NULL; +} + +void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm, + int bl_idx, + u32 user_brightness) +{ + struct amdgpu_dm_backlight_caps *caps; + struct dc_link *link; + u32 brightness = 0; + bool rc = false, reallow_idle = false; + struct drm_connector *connector; + struct dc_stream_state *stream; + unsigned int min, max; + + list_for_each_entry(connector, &dm->ddev->mode_config.connector_list, head) { + struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector); + + if (aconnector->bl_idx != bl_idx) + continue; + + /* if connector is off, save the brightness for next time it's on */ + if (!aconnector->base.encoder) { + dm->brightness[bl_idx] = user_brightness; + dm->actual_brightness[bl_idx] = 0; + return; + } + } + + amdgpu_dm_update_backlight_caps(dm, bl_idx); + caps = &dm->backlight_caps[bl_idx]; + + dm->brightness[bl_idx] = user_brightness; + /* update scratch register */ + if (bl_idx == 0) + amdgpu_atombios_scratch_regs_set_backlight_level(dm->adev, dm->brightness[bl_idx]); + brightness = convert_brightness_from_user(caps, dm->brightness[bl_idx]); + link = (struct dc_link *)dm->backlight_link[bl_idx]; + + /* Apply brightness quirk */ + if (caps->brightness_mask) + brightness |= caps->brightness_mask; + + if (trace_amdgpu_dm_brightness_enabled()) { + trace_amdgpu_dm_brightness(__builtin_return_address(0), + user_brightness, + brightness, + caps->aux_support, + power_supply_is_system_supplied() > 0); + } + + stream = dm_find_stream_with_link(dm, link); + if (!stream) + return; + + mutex_lock(&dm->dc_lock); + if (dm->dc->caps.ips_support && dm->dc->ctx->dmub_srv->idle_allowed) { + dc_allow_idle_optimizations(dm->dc, false); + reallow_idle = true; + } + + if (caps->aux_support) { + rc = mod_power_set_backlight_nits(dm->power_module, stream, brightness, + AUX_BL_DEFAULT_TRANSITION_TIME_MS, false, true); + } else { + /* power module uses millipercent */ + get_brightness_range(caps, &min, &max); + brightness = DIV_ROUND_CLOSEST(brightness * 100, (max - min)) * 1000; + rc = mod_power_set_backlight_percent(dm->power_module, stream, + brightness, 0, false); + } + + /* + * Some kms clients create a ramped backlight transition effect + * by rapidly changing the backlight. Yet we must wait on dmcub + * fw to exit psr/replay before programming backlight. To + * prevent lag, keep disable psr/replay and let the next atomic + * flip clear the event. + * + * ToDo: use ISM to handle rapidly backlight change + * + * Rapidly backlight change is similar to rapidly cursor events, + * which is now handled by ISM. ISM can delay the event until system + * is really idle, so we may use ISM to handle backlight change as well. + */ + amdgpu_dm_psr_set_event(dm, stream, true, + psr_event_hw_programming, true); + amdgpu_dm_replay_set_event(dm, stream, true, + replay_event_hw_programming, true); + + if (dm->dc->caps.ips_support && reallow_idle) + dc_allow_idle_optimizations(dm->dc, true); + + mutex_unlock(&dm->dc_lock); + + if (rc) + dm->actual_brightness[bl_idx] = user_brightness; +} + +static int amdgpu_dm_backlight_update_status(struct backlight_device *bd) +{ + struct amdgpu_display_manager *dm = bl_get_data(bd); + int i; + + for (i = 0; i < dm->num_of_edps; i++) { + if (bd == dm->backlight_dev[i]) + break; + } + if (i >= AMDGPU_DM_MAX_NUM_EDP) + i = 0; + amdgpu_dm_backlight_set_level(dm, i, bd->props.brightness); + + return 0; +} + +static u32 amdgpu_dm_backlight_get_level(struct amdgpu_display_manager *dm, + int bl_idx) +{ + int ret; + struct amdgpu_dm_backlight_caps caps; + struct dc_link *link = (struct dc_link *)dm->backlight_link[bl_idx]; + + amdgpu_dm_update_backlight_caps(dm, bl_idx); + caps = dm->backlight_caps[bl_idx]; + + if (caps.aux_support) { + u32 avg, peak; + + if (!dc_link_get_backlight_level_nits(link, &avg, &peak)) + return dm->brightness[bl_idx]; + return convert_brightness_to_user(&caps, avg); + } + + ret = dc_link_get_backlight_level(link); + + if (ret == DC_ERROR_UNEXPECTED) + return dm->brightness[bl_idx]; + + return convert_brightness_to_user(&caps, ret); +} + +static int amdgpu_dm_backlight_get_brightness(struct backlight_device *bd) +{ + struct amdgpu_display_manager *dm = bl_get_data(bd); + int i; + + for (i = 0; i < dm->num_of_edps; i++) { + if (bd == dm->backlight_dev[i]) + break; + } + if (i >= AMDGPU_DM_MAX_NUM_EDP) + i = 0; + return amdgpu_dm_backlight_get_level(dm, i); +} + +static const struct backlight_ops amdgpu_dm_backlight_ops = { + .options = BL_CORE_SUSPENDRESUME, + .get_brightness = amdgpu_dm_backlight_get_brightness, + .update_status = amdgpu_dm_backlight_update_status, +}; + +void +amdgpu_dm_register_backlight_device(struct amdgpu_dm_connector *aconnector) +{ + struct drm_device *drm = aconnector->base.dev; + struct amdgpu_display_manager *dm = &drm_to_adev(drm)->dm; + struct backlight_properties props = { 0 }; + struct amdgpu_dm_backlight_caps *caps; + char bl_name[16]; + int min, max; + int real_brightness; + int init_brightness; + + if (aconnector->bl_idx == -1) + return; + + if (!acpi_video_backlight_use_native()) { + drm_info(drm, "Skipping amdgpu DM backlight registration\n"); + /* Try registering an ACPI video backlight device instead. */ + acpi_video_register_backlight(); + return; + } + + caps = &dm->backlight_caps[aconnector->bl_idx]; + if (get_brightness_range(caps, &min, &max)) { + if (power_supply_is_system_supplied() > 0) + props.brightness = DIV_ROUND_CLOSEST((max - min) * caps->ac_level, 100); + else + props.brightness = DIV_ROUND_CLOSEST((max - min) * caps->dc_level, 100); + /* min is zero, so max needs to be adjusted */ + props.max_brightness = max - min; + drm_dbg(drm, "Backlight caps: min: %d, max: %d, ac %d, dc %d\n", min, max, + caps->ac_level, caps->dc_level); + } else + props.brightness = props.max_brightness = MAX_BACKLIGHT_LEVEL; + + init_brightness = props.brightness; + + if (caps->data_points && !(amdgpu_dc_debug_mask & DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE)) { + drm_info(drm, "Using custom brightness curve\n"); + props.scale = BACKLIGHT_SCALE_NON_LINEAR; + } else + props.scale = BACKLIGHT_SCALE_LINEAR; + props.type = BACKLIGHT_RAW; + + snprintf(bl_name, sizeof(bl_name), "amdgpu_bl%d", + drm->primary->index + aconnector->bl_idx); + + dm->backlight_dev[aconnector->bl_idx] = + backlight_device_register(bl_name, aconnector->base.kdev, dm, + &amdgpu_dm_backlight_ops, &props); + dm->brightness[aconnector->bl_idx] = props.brightness; + + if (IS_ERR(dm->backlight_dev[aconnector->bl_idx])) { + drm_err(drm, "DM: Backlight registration failed!\n"); + dm->backlight_dev[aconnector->bl_idx] = NULL; + } else { + /* + * dm->brightness[x] can be inconsistent just after startup until + * ops.get_brightness is called. + */ + real_brightness = + amdgpu_dm_backlight_ops.get_brightness(dm->backlight_dev[aconnector->bl_idx]); + + if (real_brightness != init_brightness) { + dm->actual_brightness[aconnector->bl_idx] = real_brightness; + dm->brightness[aconnector->bl_idx] = real_brightness; + } + drm_dbg_driver(drm, "DM: Registered Backlight device: %s\n", bl_name); + } +} + +void amdgpu_dm_update_connector_ext_caps(struct amdgpu_dm_connector *aconnector) +{ + const struct drm_panel_backlight_quirk *panel_backlight_quirk; + struct amdgpu_dm_backlight_caps *caps; + struct drm_connector *conn_base; + struct amdgpu_device *adev; + struct drm_luminance_range_info *luminance_range; + struct drm_device *drm; + + if (aconnector->bl_idx == -1 || + aconnector->dc_link->connector_signal != SIGNAL_TYPE_EDP) + return; + + conn_base = &aconnector->base; + drm = conn_base->dev; + adev = drm_to_adev(drm); + + caps = &adev->dm.backlight_caps[aconnector->bl_idx]; + caps->ext_caps = &aconnector->dc_link->dpcd_sink_ext_caps; + caps->aux_support = false; + + if (caps->ext_caps->bits.oled == 1 + /* + * || + * caps->ext_caps->bits.sdr_aux_backlight_control == 1 || + * caps->ext_caps->bits.hdr_aux_backlight_control == 1 + */) + caps->aux_support = true; + + if (amdgpu_backlight == 0) + caps->aux_support = false; + else if (amdgpu_backlight == 1) + caps->aux_support = true; + if (caps->aux_support) + aconnector->dc_link->backlight_control_type = BACKLIGHT_CONTROL_AMD_AUX; + + luminance_range = &conn_base->display_info.luminance_range; + + if (luminance_range->max_luminance) + caps->aux_max_input_signal = luminance_range->max_luminance; + else + caps->aux_max_input_signal = 512; + + if (luminance_range->min_luminance) + caps->aux_min_input_signal = luminance_range->min_luminance; + else + caps->aux_min_input_signal = 1; + + panel_backlight_quirk = + drm_get_panel_backlight_quirk(aconnector->drm_edid); + if (!IS_ERR_OR_NULL(panel_backlight_quirk)) { + if (panel_backlight_quirk->min_brightness) { + caps->min_input_signal = + panel_backlight_quirk->min_brightness - 1; + drm_info(drm, + "Applying panel backlight quirk, min_brightness: %d\n", + caps->min_input_signal); + } + if (panel_backlight_quirk->brightness_mask) { + drm_info(drm, + "Applying panel backlight quirk, brightness_mask: 0x%X\n", + panel_backlight_quirk->brightness_mask); + caps->brightness_mask = + panel_backlight_quirk->brightness_mask; + } + } +} + +void amdgpu_dm_setup_backlight_device(struct amdgpu_display_manager *dm, + struct amdgpu_dm_connector *aconnector) +{ + struct amdgpu_dm_backlight_caps *caps; + struct dc_link *link = aconnector->dc_link; + int bl_idx = dm->num_of_edps; + + if (!(link->connector_signal & (SIGNAL_TYPE_EDP | SIGNAL_TYPE_LVDS)) || + link->type == dc_connection_none) + return; + + if (dm->num_of_edps >= AMDGPU_DM_MAX_NUM_EDP) { + drm_warn(adev_to_drm(dm->adev), "Too much eDP connections, skipping backlight setup for additional eDPs\n"); + return; + } + + aconnector->bl_idx = bl_idx; + + amdgpu_dm_update_backlight_caps(dm, bl_idx); + dm->backlight_link[bl_idx] = link; + dm->num_of_edps++; + + amdgpu_dm_update_connector_ext_caps(aconnector); + caps = &dm->backlight_caps[aconnector->bl_idx]; + + /* Only offer ABM property when non-OLED and user didn't turn off by module parameter */ + if (caps->ext_caps && !caps->ext_caps->bits.oled && amdgpu_dm_abm_level < 0) + drm_object_attach_property(&aconnector->base.base, + dm->adev->mode_info.abm_level_property, + ABM_SYSFS_CONTROL); +} + +/** + * DOC: panel power savings + * + * The display manager allows you to set your desired **panel power savings** + * level (between 0-4, with 0 representing off), e.g. using the following:: + * + * # echo 3 > /sys/class/drm/card0-eDP-1/amdgpu/panel_power_savings + * + * Modifying this value can have implications on color accuracy, so tread + * carefully. + */ + +static ssize_t panel_power_savings_show(struct device *device, + struct device_attribute *attr, + char *buf) +{ + struct drm_connector *connector = dev_get_drvdata(device); + struct drm_device *dev = connector->dev; + u8 val; + + drm_modeset_lock(&dev->mode_config.connection_mutex, NULL); + val = to_dm_connector_state(connector->state)->abm_level == + ABM_LEVEL_IMMEDIATE_DISABLE ? 0 : + to_dm_connector_state(connector->state)->abm_level; + drm_modeset_unlock(&dev->mode_config.connection_mutex); + + return sysfs_emit(buf, "%u\n", val); +} + +static ssize_t panel_power_savings_store(struct device *device, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct drm_connector *connector = dev_get_drvdata(device); + struct drm_device *dev = connector->dev; + long val; + int ret; + + ret = kstrtol(buf, 0, &val); + + if (ret) + return ret; + + if (val < 0 || val > 4) + return -EINVAL; + + drm_modeset_lock(&dev->mode_config.connection_mutex, NULL); + if (to_dm_connector_state(connector->state)->abm_sysfs_forbidden) + ret = -EBUSY; + else + to_dm_connector_state(connector->state)->abm_level = val ?: + ABM_LEVEL_IMMEDIATE_DISABLE; + drm_modeset_unlock(&dev->mode_config.connection_mutex); + + if (ret) + return ret; + + drm_kms_helper_hotplug_event(dev); + + return count; +} + +static DEVICE_ATTR_RW(panel_power_savings); + +static struct attribute *amdgpu_attrs[] = { + &dev_attr_panel_power_savings.attr, + NULL +}; + +const struct attribute_group amdgpu_group = { + .name = "amdgpu", + .attrs = amdgpu_attrs +}; + +bool +amdgpu_dm_should_create_sysfs(struct amdgpu_dm_connector *amdgpu_dm_connector) +{ + if (amdgpu_dm_abm_level >= 0) + return false; + + if (amdgpu_dm_connector->base.connector_type != DRM_MODE_CONNECTOR_eDP) + return false; + + /* check for OLED panels */ + if (amdgpu_dm_connector->bl_idx >= 0) { + struct drm_device *drm = amdgpu_dm_connector->base.dev; + struct amdgpu_display_manager *dm = &drm_to_adev(drm)->dm; + struct amdgpu_dm_backlight_caps *caps; + + caps = &dm->backlight_caps[amdgpu_dm_connector->bl_idx]; + if (caps->aux_support) + return false; + } + + return true; +} diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h new file mode 100644 index 000000000000..acff23f9feef --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright 2026 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. + */ + +#ifndef __AMDGPU_DM_BACKLIGHT_H__ +#define __AMDGPU_DM_BACKLIGHT_H__ + +struct amdgpu_display_manager; +struct amdgpu_dm_connector; +struct drm_connector; +struct attribute_group; + +void amdgpu_dm_update_backlight_caps(struct amdgpu_display_manager *dm, + int bl_idx); +void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm, + int bl_idx, u32 user_brightness); +void amdgpu_dm_register_backlight_device(struct amdgpu_dm_connector *aconnector); +void amdgpu_dm_setup_backlight_device(struct amdgpu_display_manager *dm, + struct amdgpu_dm_connector *aconnector); +void amdgpu_dm_update_connector_ext_caps(struct amdgpu_dm_connector *aconnector); +bool amdgpu_dm_should_create_sysfs(struct amdgpu_dm_connector *aconnector); + +extern const struct attribute_group amdgpu_group; + +#endif /* __AMDGPU_DM_BACKLIGHT_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_services.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_services.c index 84dcb573d98f..0fdcf70256cc 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_services.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_services.c @@ -32,6 +32,7 @@ #include "dm_services.h" #include "amdgpu.h" #include "amdgpu_dm.h" +#include "amdgpu_dm_backlight.h" #include "amdgpu_dm_irq.h" #include "amdgpu_pm.h" #include "amdgpu_dm_trace.h" -- cgit v1.2.3 From ee55bf7d6a63f02738d79aa4368c145e97e032e3 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 27 Apr 2026 19:20:53 -0600 Subject: drm/amd/display: Extract audio code to amdgpu_dm_audio Move audio component, init/fini, ELD notification, fill_audio_info, and commit_audio functions from amdgpu_dm.c into a dedicated amdgpu_dm_audio.c file with its own header. No functional change intended. Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/Makefile | 3 +- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 268 +----------------- .../drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c | 302 +++++++++++++++++++++ .../drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h | 44 +++ 4 files changed, 350 insertions(+), 267 deletions(-) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile index 2953c59d85e7..83a7d03a0348 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile @@ -42,7 +42,8 @@ AMDGPUDM = \ amdgpu_dm_wb.o \ amdgpu_dm_colorop.o \ amdgpu_dm_ism.o \ - amdgpu_dm_backlight.o + amdgpu_dm_backlight.o \ + amdgpu_dm_audio.o ifdef CONFIG_DRM_AMD_DC_FP AMDGPUDM += dc_fpu.o 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 3bd0ae0e54cd..d72ce66e3fd4 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -67,6 +67,7 @@ #include "amdgpu_dm_psr.h" #include "amdgpu_dm_replay.h" #include "amdgpu_dm_backlight.h" +#include "amdgpu_dm_audio.h" #include "ivsrcid/ivsrcid_vislands30.h" @@ -95,7 +96,6 @@ #include #include #include -#include #include #include @@ -1110,144 +1110,6 @@ static void amdgpu_dm_fbc_init(struct drm_connector *connector) } -static int amdgpu_dm_audio_component_get_eld(struct device *kdev, int port, - int pipe, bool *enabled, - unsigned char *buf, int max_bytes) -{ - struct drm_device *dev = dev_get_drvdata(kdev); - struct amdgpu_device *adev = drm_to_adev(dev); - struct drm_connector *connector; - struct drm_connector_list_iter conn_iter; - struct amdgpu_dm_connector *aconnector; - int ret = 0; - - *enabled = false; - - mutex_lock(&adev->dm.audio_lock); - - drm_connector_list_iter_begin(dev, &conn_iter); - drm_for_each_connector_iter(connector, &conn_iter) { - - if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) - continue; - - aconnector = to_amdgpu_dm_connector(connector); - if (aconnector->audio_inst != port) - continue; - - *enabled = true; - mutex_lock(&connector->eld_mutex); - ret = drm_eld_size(connector->eld); - memcpy(buf, connector->eld, min(max_bytes, ret)); - mutex_unlock(&connector->eld_mutex); - - break; - } - drm_connector_list_iter_end(&conn_iter); - - mutex_unlock(&adev->dm.audio_lock); - - drm_dbg_kms(adev_to_drm(adev), "Get ELD : idx=%d ret=%d en=%d\n", port, ret, *enabled); - - return ret; -} - -static const struct drm_audio_component_ops amdgpu_dm_audio_component_ops = { - .get_eld = amdgpu_dm_audio_component_get_eld, -}; - -static int amdgpu_dm_audio_component_bind(struct device *kdev, - struct device *hda_kdev, void *data) -{ - struct drm_device *dev = dev_get_drvdata(kdev); - struct amdgpu_device *adev = drm_to_adev(dev); - struct drm_audio_component *acomp = data; - - acomp->ops = &amdgpu_dm_audio_component_ops; - acomp->dev = kdev; - adev->dm.audio_component = acomp; - - return 0; -} - -static void amdgpu_dm_audio_component_unbind(struct device *kdev, - struct device *hda_kdev, void *data) -{ - struct amdgpu_device *adev = drm_to_adev(dev_get_drvdata(kdev)); - struct drm_audio_component *acomp = data; - - acomp->ops = NULL; - acomp->dev = NULL; - adev->dm.audio_component = NULL; -} - -static const struct component_ops amdgpu_dm_audio_component_bind_ops = { - .bind = amdgpu_dm_audio_component_bind, - .unbind = amdgpu_dm_audio_component_unbind, -}; - -static int amdgpu_dm_audio_init(struct amdgpu_device *adev) -{ - int i, ret; - - if (!amdgpu_audio) - return 0; - - adev->mode_info.audio.enabled = true; - - adev->mode_info.audio.num_pins = adev->dm.dc->res_pool->audio_count; - - for (i = 0; i < adev->mode_info.audio.num_pins; i++) { - adev->mode_info.audio.pin[i].channels = -1; - adev->mode_info.audio.pin[i].rate = -1; - adev->mode_info.audio.pin[i].bits_per_sample = -1; - adev->mode_info.audio.pin[i].status_bits = 0; - adev->mode_info.audio.pin[i].category_code = 0; - adev->mode_info.audio.pin[i].connected = false; - adev->mode_info.audio.pin[i].id = - adev->dm.dc->res_pool->audios[i]->inst; - adev->mode_info.audio.pin[i].offset = 0; - } - - ret = component_add(adev->dev, &amdgpu_dm_audio_component_bind_ops); - if (ret < 0) - return ret; - - adev->dm.audio_registered = true; - - return 0; -} - -static void amdgpu_dm_audio_fini(struct amdgpu_device *adev) -{ - if (!amdgpu_audio) - return; - - if (!adev->mode_info.audio.enabled) - return; - - if (adev->dm.audio_registered) { - component_del(adev->dev, &amdgpu_dm_audio_component_bind_ops); - adev->dm.audio_registered = false; - } - - /* TODO: Disable audio? */ - - adev->mode_info.audio.enabled = false; -} - -static void amdgpu_dm_audio_eld_notify(struct amdgpu_device *adev, int pin) -{ - struct drm_audio_component *acomp = adev->dm.audio_component; - - if (acomp && acomp->audio_ops && acomp->audio_ops->pin_eld_notify) { - drm_dbg_kms(adev_to_drm(adev), "Notify ELD: %d\n", pin); - - acomp->audio_ops->pin_eld_notify(acomp->audio_ops->audio_ptr, - pin, -1); - } -} - static int dm_dmub_hw_init(struct amdgpu_device *adev) { const struct dmcub_firmware_header_v1_0 *hdr; @@ -6530,51 +6392,6 @@ static void fill_stream_properties_from_drm_display_mode( stream->content_type = get_output_content_type(connector_state); } -static void fill_audio_info(struct audio_info *audio_info, - const struct drm_connector *drm_connector, - const struct dc_sink *dc_sink) -{ - int i = 0; - int cea_revision = 0; - const struct dc_edid_caps *edid_caps = &dc_sink->edid_caps; - - audio_info->manufacture_id = edid_caps->manufacturer_id; - audio_info->product_id = edid_caps->product_id; - - cea_revision = drm_connector->display_info.cea_rev; - - strscpy(audio_info->display_name, - edid_caps->display_name, - AUDIO_INFO_DISPLAY_NAME_SIZE_IN_CHARS); - - if (cea_revision >= 3) { - audio_info->mode_count = edid_caps->audio_mode_count; - - for (i = 0; i < audio_info->mode_count; ++i) { - audio_info->modes[i].format_code = - (enum audio_format_code) - (edid_caps->audio_modes[i].format_code); - audio_info->modes[i].channel_count = - edid_caps->audio_modes[i].channel_count; - audio_info->modes[i].sample_rates.all = - edid_caps->audio_modes[i].sample_rate; - audio_info->modes[i].sample_size = - edid_caps->audio_modes[i].sample_size; - } - } - - audio_info->flags.all = edid_caps->speaker_flags; - - /* TODO: We only check for the progressive mode, check for interlace mode too */ - if (drm_connector->latency_present[0]) { - audio_info->video_latency = drm_connector->video_latency[0]; - audio_info->audio_latency = drm_connector->audio_latency[0]; - } - - /* TODO: For DP, video and audio latency should be calculated from DPCD caps */ - -} - static void copy_crtc_timing_for_drm_display_mode(const struct drm_display_mode *src_mode, struct drm_display_mode *dst_mode) @@ -7176,7 +6993,7 @@ create_stream_for_sink(struct drm_connector *connector, update_stream_scaling_settings(dev, &mode, dm_state, stream); - fill_audio_info( + amdgpu_dm_fill_audio_info( &stream->audio_info, connector, sink); @@ -9984,87 +9801,6 @@ cleanup: kfree(bundle); } -static void amdgpu_dm_commit_audio(struct drm_device *dev, - struct drm_atomic_commit *state) -{ - struct amdgpu_device *adev = drm_to_adev(dev); - struct amdgpu_dm_connector *aconnector; - struct drm_connector *connector; - struct drm_connector_state *old_con_state, *new_con_state; - struct drm_crtc_state *new_crtc_state; - struct dm_crtc_state *new_dm_crtc_state; - const struct dc_stream_status *status; - int i, inst; - - /* Notify device removals. */ - for_each_oldnew_connector_in_state(state, connector, old_con_state, new_con_state, i) { - if (old_con_state->crtc != new_con_state->crtc) { - /* CRTC changes require notification. */ - goto notify; - } - - if (!new_con_state->crtc) - continue; - - new_crtc_state = drm_atomic_get_new_crtc_state( - state, new_con_state->crtc); - - if (!new_crtc_state) - continue; - - if (!drm_atomic_crtc_needs_modeset(new_crtc_state)) - continue; - -notify: - if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) - continue; - - aconnector = to_amdgpu_dm_connector(connector); - - mutex_lock(&adev->dm.audio_lock); - inst = aconnector->audio_inst; - aconnector->audio_inst = -1; - mutex_unlock(&adev->dm.audio_lock); - - amdgpu_dm_audio_eld_notify(adev, inst); - } - - /* Notify audio device additions. */ - for_each_new_connector_in_state(state, connector, new_con_state, i) { - if (!new_con_state->crtc) - continue; - - new_crtc_state = drm_atomic_get_new_crtc_state( - state, new_con_state->crtc); - - if (!new_crtc_state) - continue; - - if (!drm_atomic_crtc_needs_modeset(new_crtc_state)) - continue; - - new_dm_crtc_state = to_dm_crtc_state(new_crtc_state); - if (!new_dm_crtc_state->stream) - continue; - - status = dc_stream_get_status(new_dm_crtc_state->stream); - if (!status) - continue; - - if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) - continue; - - aconnector = to_amdgpu_dm_connector(connector); - - mutex_lock(&adev->dm.audio_lock); - inst = status->audio_inst; - aconnector->audio_inst = inst; - mutex_unlock(&adev->dm.audio_lock); - - amdgpu_dm_audio_eld_notify(adev, inst); - } -} - /* * amdgpu_dm_crtc_copy_transient_flags - copy mirrored flags from DRM to DC * @crtc_state: the DRM CRTC state diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c new file mode 100644 index 000000000000..a15b7c0c9075 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c @@ -0,0 +1,302 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright 2026 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 "amdgpu.h" +#include "amdgpu_dm.h" +#include "amdgpu_dm_audio.h" +#include "dc.h" + +#include +#include +#include +#include +#include +#include + +#include "dc/inc/core_types.h" + +static int amdgpu_dm_audio_component_get_eld(struct device *kdev, int port, + int pipe, bool *enabled, + unsigned char *buf, int max_bytes) +{ + struct drm_device *dev = dev_get_drvdata(kdev); + struct amdgpu_device *adev = drm_to_adev(dev); + struct drm_connector *connector; + struct drm_connector_list_iter conn_iter; + struct amdgpu_dm_connector *aconnector; + int ret = 0; + + *enabled = false; + + mutex_lock(&adev->dm.audio_lock); + + drm_connector_list_iter_begin(dev, &conn_iter); + drm_for_each_connector_iter(connector, &conn_iter) { + + if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) + continue; + + aconnector = to_amdgpu_dm_connector(connector); + if (aconnector->audio_inst != port) + continue; + + *enabled = true; + mutex_lock(&connector->eld_mutex); + ret = drm_eld_size(connector->eld); + memcpy(buf, connector->eld, min(max_bytes, ret)); + mutex_unlock(&connector->eld_mutex); + + break; + } + drm_connector_list_iter_end(&conn_iter); + + mutex_unlock(&adev->dm.audio_lock); + + drm_dbg_kms(adev_to_drm(adev), "Get ELD : idx=%d ret=%d en=%d\n", port, ret, *enabled); + + return ret; +} + +static const struct drm_audio_component_ops amdgpu_dm_audio_component_ops = { + .get_eld = amdgpu_dm_audio_component_get_eld, +}; + +static int amdgpu_dm_audio_component_bind(struct device *kdev, + struct device *hda_kdev, void *data) +{ + struct drm_device *dev = dev_get_drvdata(kdev); + struct amdgpu_device *adev = drm_to_adev(dev); + struct drm_audio_component *acomp = data; + + acomp->ops = &amdgpu_dm_audio_component_ops; + acomp->dev = kdev; + adev->dm.audio_component = acomp; + + return 0; +} + +static void amdgpu_dm_audio_component_unbind(struct device *kdev, + struct device *hda_kdev, void *data) +{ + struct amdgpu_device *adev = drm_to_adev(dev_get_drvdata(kdev)); + struct drm_audio_component *acomp = data; + + acomp->ops = NULL; + acomp->dev = NULL; + adev->dm.audio_component = NULL; +} + +static const struct component_ops amdgpu_dm_audio_component_bind_ops = { + .bind = amdgpu_dm_audio_component_bind, + .unbind = amdgpu_dm_audio_component_unbind, +}; + +int amdgpu_dm_audio_init(struct amdgpu_device *adev) +{ + int i, ret; + + if (!amdgpu_audio) + return 0; + + adev->mode_info.audio.enabled = true; + + adev->mode_info.audio.num_pins = adev->dm.dc->res_pool->audio_count; + + for (i = 0; i < adev->mode_info.audio.num_pins; i++) { + adev->mode_info.audio.pin[i].channels = -1; + adev->mode_info.audio.pin[i].rate = -1; + adev->mode_info.audio.pin[i].bits_per_sample = -1; + adev->mode_info.audio.pin[i].status_bits = 0; + adev->mode_info.audio.pin[i].category_code = 0; + adev->mode_info.audio.pin[i].connected = false; + adev->mode_info.audio.pin[i].id = + adev->dm.dc->res_pool->audios[i]->inst; + adev->mode_info.audio.pin[i].offset = 0; + } + + ret = component_add(adev->dev, &amdgpu_dm_audio_component_bind_ops); + if (ret < 0) + return ret; + + adev->dm.audio_registered = true; + + return 0; +} + +void amdgpu_dm_audio_fini(struct amdgpu_device *adev) +{ + if (!amdgpu_audio) + return; + + if (!adev->mode_info.audio.enabled) + return; + + if (adev->dm.audio_registered) { + component_del(adev->dev, &amdgpu_dm_audio_component_bind_ops); + adev->dm.audio_registered = false; + } + + /* TODO: Disable audio? */ + + adev->mode_info.audio.enabled = false; +} + +static void amdgpu_dm_audio_eld_notify(struct amdgpu_device *adev, int pin) +{ + struct drm_audio_component *acomp = adev->dm.audio_component; + + if (acomp && acomp->audio_ops && acomp->audio_ops->pin_eld_notify) { + drm_dbg_kms(adev_to_drm(adev), "Notify ELD: %d\n", pin); + + acomp->audio_ops->pin_eld_notify(acomp->audio_ops->audio_ptr, + pin, -1); + } +} + +void amdgpu_dm_fill_audio_info(struct audio_info *audio_info, + const struct drm_connector *drm_connector, + const struct dc_sink *dc_sink) +{ + int i = 0; + int cea_revision = 0; + const struct dc_edid_caps *edid_caps = &dc_sink->edid_caps; + + audio_info->manufacture_id = edid_caps->manufacturer_id; + audio_info->product_id = edid_caps->product_id; + + cea_revision = drm_connector->display_info.cea_rev; + + strscpy(audio_info->display_name, + edid_caps->display_name, + AUDIO_INFO_DISPLAY_NAME_SIZE_IN_CHARS); + + if (cea_revision >= 3) { + audio_info->mode_count = edid_caps->audio_mode_count; + + for (i = 0; i < audio_info->mode_count; ++i) { + audio_info->modes[i].format_code = + (enum audio_format_code) + (edid_caps->audio_modes[i].format_code); + audio_info->modes[i].channel_count = + edid_caps->audio_modes[i].channel_count; + audio_info->modes[i].sample_rates.all = + edid_caps->audio_modes[i].sample_rate; + audio_info->modes[i].sample_size = + edid_caps->audio_modes[i].sample_size; + } + } + + audio_info->flags.all = edid_caps->speaker_flags; + + /* TODO: We only check for the progressive mode, check for interlace mode too */ + if (drm_connector->latency_present[0]) { + audio_info->video_latency = drm_connector->video_latency[0]; + audio_info->audio_latency = drm_connector->audio_latency[0]; + } + + /* TODO: For DP, video and audio latency should be calculated from DPCD caps */ + +} + +void amdgpu_dm_commit_audio(struct drm_device *dev, + struct drm_atomic_commit *state) +{ + struct amdgpu_device *adev = drm_to_adev(dev); + struct amdgpu_dm_connector *aconnector; + struct drm_connector *connector; + struct drm_connector_state *old_con_state, *new_con_state; + struct drm_crtc_state *new_crtc_state; + struct dm_crtc_state *new_dm_crtc_state; + const struct dc_stream_status *status; + int i, inst; + + /* Notify device removals. */ + for_each_oldnew_connector_in_state(state, connector, old_con_state, new_con_state, i) { + if (old_con_state->crtc != new_con_state->crtc) { + /* CRTC changes require notification. */ + goto notify; + } + + if (!new_con_state->crtc) + continue; + + new_crtc_state = drm_atomic_get_new_crtc_state( + state, new_con_state->crtc); + + if (!new_crtc_state) + continue; + + if (!drm_atomic_crtc_needs_modeset(new_crtc_state)) + continue; + +notify: + if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) + continue; + + aconnector = to_amdgpu_dm_connector(connector); + + mutex_lock(&adev->dm.audio_lock); + inst = aconnector->audio_inst; + aconnector->audio_inst = -1; + mutex_unlock(&adev->dm.audio_lock); + + amdgpu_dm_audio_eld_notify(adev, inst); + } + + /* Notify audio device additions. */ + for_each_new_connector_in_state(state, connector, new_con_state, i) { + if (!new_con_state->crtc) + continue; + + new_crtc_state = drm_atomic_get_new_crtc_state( + state, new_con_state->crtc); + + if (!new_crtc_state) + continue; + + if (!drm_atomic_crtc_needs_modeset(new_crtc_state)) + continue; + + new_dm_crtc_state = to_dm_crtc_state(new_crtc_state); + if (!new_dm_crtc_state->stream) + continue; + + status = dc_stream_get_status(new_dm_crtc_state->stream); + if (!status) + continue; + + if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) + continue; + + aconnector = to_amdgpu_dm_connector(connector); + + mutex_lock(&adev->dm.audio_lock); + inst = status->audio_inst; + aconnector->audio_inst = inst; + mutex_unlock(&adev->dm.audio_lock); + + amdgpu_dm_audio_eld_notify(adev, inst); + } +} diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h new file mode 100644 index 000000000000..58cce1f79ffd --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright 2026 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 __AMDGPU_DM_AUDIO_H__ +#define __AMDGPU_DM_AUDIO_H__ + +struct amdgpu_device; +struct drm_device; +struct drm_atomic_state; +struct drm_connector; +struct audio_info; +struct dc_sink; + +int amdgpu_dm_audio_init(struct amdgpu_device *adev); +void amdgpu_dm_audio_fini(struct amdgpu_device *adev); +void amdgpu_dm_commit_audio(struct drm_device *dev, + struct drm_atomic_commit *state); +void amdgpu_dm_fill_audio_info(struct audio_info *audio_info, + const struct drm_connector *drm_connector, + const struct dc_sink *dc_sink); + +#endif /* __AMDGPU_DM_AUDIO_H__ */ -- cgit v1.2.3 From 4734e045f49d9bb801b4a3eef30d6ca7fba44280 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 27 Apr 2026 20:49:30 -0600 Subject: drm/amd/display: Extract DMUB code to amdgpu_dm_dmub Move DMUB-related functions and firmware defines from amdgpu_dm.c into new amdgpu_dm_dmub.c and amdgpu_dm_dmub.h files to reduce the size of amdgpu_dm.c and improve code organization. No functional change intended. Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/Makefile | 3 +- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 927 +-------------------- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c | 924 ++++++++++++++++++++ .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.h | 68 ++ 4 files changed, 1002 insertions(+), 920 deletions(-) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.h (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile index 83a7d03a0348..a6408da05583 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile @@ -43,7 +43,8 @@ AMDGPUDM = \ amdgpu_dm_colorop.o \ amdgpu_dm_ism.o \ amdgpu_dm_backlight.o \ - amdgpu_dm_audio.o + amdgpu_dm_audio.o \ + amdgpu_dm_dmub.o ifdef CONFIG_DRM_AMD_DC_FP AMDGPUDM += dc_fpu.o 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 d72ce66e3fd4..f5766d083213 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -68,6 +68,7 @@ #include "amdgpu_dm_replay.h" #include "amdgpu_dm_backlight.h" #include "amdgpu_dm_audio.h" +#include "amdgpu_dm_dmub.h" #include "ivsrcid/ivsrcid_vislands30.h" @@ -108,60 +109,9 @@ #include "modules/inc/mod_power.h" #include "modules/power/power_helpers.h" -static_assert(AMDGPU_DMUB_NOTIFICATION_MAX == DMUB_NOTIFICATION_MAX, "AMDGPU_DMUB_NOTIFICATION_MAX mismatch"); - -#define FIRMWARE_RENOIR_DMUB "amdgpu/renoir_dmcub.bin" -MODULE_FIRMWARE(FIRMWARE_RENOIR_DMUB); -#define FIRMWARE_SIENNA_CICHLID_DMUB "amdgpu/sienna_cichlid_dmcub.bin" -MODULE_FIRMWARE(FIRMWARE_SIENNA_CICHLID_DMUB); -#define FIRMWARE_NAVY_FLOUNDER_DMUB "amdgpu/navy_flounder_dmcub.bin" -MODULE_FIRMWARE(FIRMWARE_NAVY_FLOUNDER_DMUB); -#define FIRMWARE_GREEN_SARDINE_DMUB "amdgpu/green_sardine_dmcub.bin" -MODULE_FIRMWARE(FIRMWARE_GREEN_SARDINE_DMUB); -#define FIRMWARE_VANGOGH_DMUB "amdgpu/vangogh_dmcub.bin" -MODULE_FIRMWARE(FIRMWARE_VANGOGH_DMUB); -#define FIRMWARE_DIMGREY_CAVEFISH_DMUB "amdgpu/dimgrey_cavefish_dmcub.bin" -MODULE_FIRMWARE(FIRMWARE_DIMGREY_CAVEFISH_DMUB); -#define FIRMWARE_BEIGE_GOBY_DMUB "amdgpu/beige_goby_dmcub.bin" -MODULE_FIRMWARE(FIRMWARE_BEIGE_GOBY_DMUB); -#define FIRMWARE_YELLOW_CARP_DMUB "amdgpu/yellow_carp_dmcub.bin" -MODULE_FIRMWARE(FIRMWARE_YELLOW_CARP_DMUB); -#define FIRMWARE_DCN_314_DMUB "amdgpu/dcn_3_1_4_dmcub.bin" -MODULE_FIRMWARE(FIRMWARE_DCN_314_DMUB); -#define FIRMWARE_DCN_315_DMUB "amdgpu/dcn_3_1_5_dmcub.bin" -MODULE_FIRMWARE(FIRMWARE_DCN_315_DMUB); -#define FIRMWARE_DCN316_DMUB "amdgpu/dcn_3_1_6_dmcub.bin" -MODULE_FIRMWARE(FIRMWARE_DCN316_DMUB); - -#define FIRMWARE_DCN_V3_2_0_DMCUB "amdgpu/dcn_3_2_0_dmcub.bin" -MODULE_FIRMWARE(FIRMWARE_DCN_V3_2_0_DMCUB); -#define FIRMWARE_DCN_V3_2_1_DMCUB "amdgpu/dcn_3_2_1_dmcub.bin" -MODULE_FIRMWARE(FIRMWARE_DCN_V3_2_1_DMCUB); - -#define FIRMWARE_RAVEN_DMCU "amdgpu/raven_dmcu.bin" MODULE_FIRMWARE(FIRMWARE_RAVEN_DMCU); - -#define FIRMWARE_NAVI12_DMCU "amdgpu/navi12_dmcu.bin" MODULE_FIRMWARE(FIRMWARE_NAVI12_DMCU); -#define FIRMWARE_DCN_35_DMUB "amdgpu/dcn_3_5_dmcub.bin" -MODULE_FIRMWARE(FIRMWARE_DCN_35_DMUB); - -#define FIRMWARE_DCN_351_DMUB "amdgpu/dcn_3_5_1_dmcub.bin" -MODULE_FIRMWARE(FIRMWARE_DCN_351_DMUB); - -#define FIRMWARE_DCN_36_DMUB "amdgpu/dcn_3_6_dmcub.bin" -MODULE_FIRMWARE(FIRMWARE_DCN_36_DMUB); - -#define FIRMWARE_DCN_401_DMUB "amdgpu/dcn_4_0_1_dmcub.bin" -MODULE_FIRMWARE(FIRMWARE_DCN_401_DMUB); - -#define FIRMWARE_DCN_42_DMUB "amdgpu/dcn_4_2_dmcub.bin" -MODULE_FIRMWARE(FIRMWARE_DCN_42_DMUB); - -#define FIRMWARE_DCN_42B_DMUB "amdgpu/dcn_4_2_1_dmcub.bin" -MODULE_FIRMWARE(FIRMWARE_DCN_42B_DMUB); - /** * DOC: overview * @@ -781,47 +731,6 @@ static void dm_dcn_vertical_interrupt0_high_irq(void *interrupt_params) } #endif /* CONFIG_DRM_AMD_SECURE_DISPLAY */ -/** - * dmub_aux_setconfig_callback - Callback for AUX or SET_CONFIG command. - * @adev: amdgpu_device pointer - * @notify: dmub notification structure - * - * Dmub AUX or SET_CONFIG command completion processing callback - * Copies dmub notification to DM which is to be read by AUX command. - * issuing thread and also signals the event to wake up the thread. - */ -static void dmub_aux_setconfig_callback(struct amdgpu_device *adev, - struct dmub_notification *notify) -{ - if (adev->dm.dmub_notify) - memcpy(adev->dm.dmub_notify, notify, sizeof(struct dmub_notification)); - if (notify->type == DMUB_NOTIFICATION_AUX_REPLY) - complete(&adev->dm.dmub_aux_transfer_done); -} - -static void dmub_aux_fused_io_callback(struct amdgpu_device *adev, - struct dmub_notification *notify) -{ - if (!adev || !notify) { - ASSERT(false); - return; - } - - const struct dmub_cmd_fused_request *req = ¬ify->fused_request; - const uint8_t ddc_line = req->u.aux.ddc_line; - - if (ddc_line >= ARRAY_SIZE(adev->dm.fused_io)) { - ASSERT(false); - return; - } - - struct fused_io_sync *sync = &adev->dm.fused_io[ddc_line]; - - static_assert(sizeof(*req) <= sizeof(sync->reply_data), "Size mismatch"); - memcpy(sync->reply_data, req, sizeof(*req)); - complete(&sync->replied); -} - /** * dmub_hpd_callback - DMUB HPD interrupt processing callback. * @adev: amdgpu_device pointer @@ -911,32 +820,6 @@ static void dmub_hpd_sense_callback(struct amdgpu_device *adev, drm_dbg_driver(adev_to_drm(adev), "DMUB HPD SENSE callback.\n"); } -/** - * register_dmub_notify_callback - Sets callback for DMUB notify - * @adev: amdgpu_device pointer - * @type: Type of dmub notification - * @callback: Dmub interrupt callback function - * @dmub_int_thread_offload: offload indicator - * - * API to register a dmub callback handler for a dmub notification - * Also sets indicator whether callback processing to be offloaded. - * to dmub interrupt handling thread - * Return: true if successfully registered, false if there is existing registration - */ -static bool register_dmub_notify_callback(struct amdgpu_device *adev, - enum dmub_notification_type type, - dmub_notify_interrupt_callback_t callback, - bool dmub_int_thread_offload) -{ - if (callback != NULL && type < ARRAY_SIZE(adev->dm.dmub_thread_offload)) { - adev->dm.dmub_callback[type] = callback; - adev->dm.dmub_thread_offload[type] = dmub_int_thread_offload; - } else - return false; - - return true; -} - static void dm_handle_hpd_work(struct work_struct *work) { struct dmub_hpd_work *dmub_hpd_wrk; @@ -1110,224 +993,6 @@ static void amdgpu_dm_fbc_init(struct drm_connector *connector) } -static int dm_dmub_hw_init(struct amdgpu_device *adev) -{ - const struct dmcub_firmware_header_v1_0 *hdr; - struct dmub_srv *dmub_srv = adev->dm.dmub_srv; - struct dmub_srv_fb_info *fb_info = adev->dm.dmub_fb_info; - const struct firmware *dmub_fw = adev->dm.dmub_fw; - struct dc *dc = adev->dm.dc; - struct dmcu *dmcu = adev->dm.dc->res_pool->dmcu; - struct abm *abm = adev->dm.dc->res_pool->abm; - struct dc_context *ctx = adev->dm.dc->ctx; - struct dmub_srv_hw_params hw_params; - enum dmub_status status; - const unsigned char *fw_inst_const, *fw_bss_data; - u32 i, fw_inst_const_size, fw_bss_data_size; - bool has_hw_support; - - if (!dmub_srv) - /* DMUB isn't supported on the ASIC. */ - return 0; - - if (!fb_info) { - drm_err(adev_to_drm(adev), "No framebuffer info for DMUB service.\n"); - return -EINVAL; - } - - if (!dmub_fw) { - /* Firmware required for DMUB support. */ - drm_err(adev_to_drm(adev), "No firmware provided for DMUB.\n"); - return -EINVAL; - } - - /* initialize register offsets for ASICs with runtime initialization available */ - if (dmub_srv->hw_funcs.init_reg_offsets) - dmub_srv->hw_funcs.init_reg_offsets(dmub_srv, ctx); - - status = dmub_srv_has_hw_support(dmub_srv, &has_hw_support); - if (status != DMUB_STATUS_OK) { - drm_err(adev_to_drm(adev), "Error checking HW support for DMUB: %d\n", status); - return -EINVAL; - } - - if (!has_hw_support) { - drm_info(adev_to_drm(adev), "DMUB unsupported on ASIC\n"); - return 0; - } - - /* Reset DMCUB if it was previously running - before we overwrite its memory. */ - status = dmub_srv_hw_reset(dmub_srv); - if (status != DMUB_STATUS_OK) - drm_warn(adev_to_drm(adev), "Error resetting DMUB HW: %d\n", status); - - hdr = (const struct dmcub_firmware_header_v1_0 *)dmub_fw->data; - - fw_inst_const = dmub_fw->data + - le32_to_cpu(hdr->header.ucode_array_offset_bytes) + - PSP_HEADER_BYTES_256; - - fw_bss_data = dmub_fw->data + - le32_to_cpu(hdr->header.ucode_array_offset_bytes) + - le32_to_cpu(hdr->inst_const_bytes); - - /* Copy firmware and bios info into FB memory. */ - fw_inst_const_size = adev->dm.fw_inst_size; - - fw_bss_data_size = le32_to_cpu(hdr->bss_data_bytes); - - /* if adev->firmware.load_type == AMDGPU_FW_LOAD_PSP, - * amdgpu_ucode_init_single_fw will load dmub firmware - * fw_inst_const part to cw0; otherwise, the firmware back door load - * will be done by dm_dmub_hw_init - */ - if (adev->firmware.load_type != AMDGPU_FW_LOAD_PSP) { - memcpy(fb_info->fb[DMUB_WINDOW_0_INST_CONST].cpu_addr, fw_inst_const, - fw_inst_const_size); - } - - if (fw_bss_data_size) - memcpy(fb_info->fb[DMUB_WINDOW_2_BSS_DATA].cpu_addr, - fw_bss_data, fw_bss_data_size); - - /* Copy firmware bios info into FB memory. */ - memcpy(fb_info->fb[DMUB_WINDOW_3_VBIOS].cpu_addr, adev->bios, - adev->bios_size); - - /* Reset regions that need to be reset. */ - memset(fb_info->fb[DMUB_WINDOW_4_MAILBOX].cpu_addr, 0, - fb_info->fb[DMUB_WINDOW_4_MAILBOX].size); - - memset(fb_info->fb[DMUB_WINDOW_5_TRACEBUFF].cpu_addr, 0, - fb_info->fb[DMUB_WINDOW_5_TRACEBUFF].size); - - memset(fb_info->fb[DMUB_WINDOW_6_FW_STATE].cpu_addr, 0, - fb_info->fb[DMUB_WINDOW_6_FW_STATE].size); - - memset(fb_info->fb[DMUB_WINDOW_SHARED_STATE].cpu_addr, 0, - fb_info->fb[DMUB_WINDOW_SHARED_STATE].size); - - /* Initialize hardware. */ - memset(&hw_params, 0, sizeof(hw_params)); - hw_params.soc_fb_info.fb_base = adev->gmc.fb_start; - hw_params.soc_fb_info.fb_offset = adev->vm_manager.vram_base_offset; - - /* backdoor load firmware and trigger dmub running */ - if (adev->firmware.load_type != AMDGPU_FW_LOAD_PSP) - hw_params.load_inst_const = true; - - if (dmcu) - hw_params.psp_version = dmcu->psp_version; - - for (i = 0; i < fb_info->num_fb; ++i) - hw_params.fb[i] = &fb_info->fb[i]; - - /* Enable usb4 dpia in the FW APU */ - if (dc->caps.is_apu && - dc->res_pool->usb4_dpia_count != 0 && - !dc->debug.dpia_debug.bits.disable_dpia) { - hw_params.dpia_supported = true; - hw_params.disable_dpia = dc->debug.dpia_debug.bits.disable_dpia; - hw_params.dpia_hpd_int_enable_supported = false; - hw_params.enable_non_transparent_setconfig = dc->config.consolidated_dpia_dp_lt; - hw_params.disable_dpia_bw_allocation = !dc->config.usb4_bw_alloc_support; - } - - switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) { - case IP_VERSION(3, 5, 0): - case IP_VERSION(3, 5, 1): - case IP_VERSION(3, 6, 0): - case IP_VERSION(4, 2, 0): - case IP_VERSION(4, 2, 1): - hw_params.ips_sequential_ono = adev->external_rev_id > 0x10; - hw_params.lower_hbr3_phy_ssc = true; - break; - default: - break; - } - - status = dmub_srv_hw_init(dmub_srv, &hw_params); - if (status != DMUB_STATUS_OK) { - drm_err(adev_to_drm(adev), "Error initializing DMUB HW: %d\n", status); - return -EINVAL; - } - - /* Wait for firmware load to finish. */ - status = dmub_srv_wait_for_auto_load(dmub_srv, 100000); - if (status != DMUB_STATUS_OK) - drm_warn(adev_to_drm(adev), "Wait for DMUB auto-load failed: %d\n", status); - - /* Init DMCU and ABM if available. */ - if (dmcu && abm) { - dmcu->funcs->dmcu_init(dmcu); - abm->dmcu_is_running = dmcu->funcs->is_dmcu_initialized(dmcu); - } - - if (!adev->dm.dc->ctx->dmub_srv) - adev->dm.dc->ctx->dmub_srv = dc_dmub_srv_create(adev->dm.dc, dmub_srv); - if (!adev->dm.dc->ctx->dmub_srv) { - drm_err(adev_to_drm(adev), "Couldn't allocate DC DMUB server!\n"); - return -ENOMEM; - } - - drm_info(adev_to_drm(adev), "DMUB hardware initialized: version=0x%08X\n", - adev->dm.dmcub_fw_version); - - /* Keeping sanity checks off if - * DCN31 >= 4.0.59.0 - * DCN314 >= 8.0.16.0 - * Otherwise, turn on sanity checks - */ - switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) { - case IP_VERSION(3, 1, 2): - case IP_VERSION(3, 1, 3): - if (adev->dm.dmcub_fw_version && - adev->dm.dmcub_fw_version >= DMUB_FW_VERSION(4, 0, 0) && - adev->dm.dmcub_fw_version < DMUB_FW_VERSION(4, 0, 59)) - adev->dm.dc->debug.sanity_checks = true; - break; - case IP_VERSION(3, 1, 4): - if (adev->dm.dmcub_fw_version && - adev->dm.dmcub_fw_version >= DMUB_FW_VERSION(4, 0, 0) && - adev->dm.dmcub_fw_version < DMUB_FW_VERSION(8, 0, 16)) - adev->dm.dc->debug.sanity_checks = true; - break; - default: - break; - } - - return 0; -} - -static void dm_dmub_hw_resume(struct amdgpu_device *adev) -{ - struct dmub_srv *dmub_srv = adev->dm.dmub_srv; - enum dmub_status status; - bool init; - int r; - - if (!dmub_srv) { - /* DMUB isn't supported on the ASIC. */ - return; - } - - status = dmub_srv_is_hw_init(dmub_srv, &init); - if (status != DMUB_STATUS_OK) - drm_warn(adev_to_drm(adev), "DMUB hardware init check failed: %d\n", status); - - if (status == DMUB_STATUS_OK && init) { - /* Wait for firmware load to finish. */ - status = dmub_srv_wait_for_auto_load(dmub_srv, 100000); - if (status != DMUB_STATUS_OK) - drm_warn(adev_to_drm(adev), "Wait for DMUB auto-load failed: %d\n", status); - } else { - /* Perform the full hardware initialization. */ - r = dm_dmub_hw_init(adev); - if (r) - drm_err(adev_to_drm(adev), "DMUB interface failed to initialize: status=%d\n", r); - } -} - static void mmhub_read_system_context(struct amdgpu_device *adev, struct dc_phy_addr_space_config *pa_config) { u64 pt_base; @@ -1635,119 +1300,6 @@ dm_free_gpu_mem( } -static enum dmub_status -dm_dmub_send_vbios_gpint_command(struct amdgpu_device *adev, - enum dmub_gpint_command command_code, - uint16_t param, - uint32_t timeout_us) -{ - union dmub_gpint_data_register reg, test; - uint32_t i; - - /* Assume that VBIOS DMUB is ready to take commands */ - - reg.bits.status = 1; - reg.bits.command_code = command_code; - reg.bits.param = param; - - cgs_write_register(adev->dm.cgs_device, 0x34c0 + 0x01f8, reg.all); - - for (i = 0; i < timeout_us; ++i) { - udelay(1); - - /* Check if our GPINT got acked */ - reg.bits.status = 0; - test = (union dmub_gpint_data_register) - cgs_read_register(adev->dm.cgs_device, 0x34c0 + 0x01f8); - - if (test.all == reg.all) - return DMUB_STATUS_OK; - } - - return DMUB_STATUS_TIMEOUT; -} - -static void *dm_dmub_get_vbios_bounding_box(struct amdgpu_device *adev) -{ - void *bb; - long long addr; - unsigned int bb_size; - int i = 0; - uint16_t chunk; - enum dmub_gpint_command send_addrs[] = { - DMUB_GPINT__SET_BB_ADDR_WORD0, - DMUB_GPINT__SET_BB_ADDR_WORD1, - DMUB_GPINT__SET_BB_ADDR_WORD2, - DMUB_GPINT__SET_BB_ADDR_WORD3, - }; - enum dmub_status ret; - - switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) { - case IP_VERSION(4, 0, 1): - bb_size = sizeof(struct dml2_soc_bb); - break; - case IP_VERSION(4, 2, 0): - case IP_VERSION(4, 2, 1): - bb_size = sizeof(struct dml2_soc_bb); - break; - default: - return NULL; - } - - bb = dm_allocate_gpu_mem(adev, - DC_MEM_ALLOC_TYPE_GART, - bb_size, - &addr); - if (!bb) - return NULL; - - for (i = 0; i < 4; i++) { - /* Extract 16-bit chunk */ - chunk = ((uint64_t) addr >> (i * 16)) & 0xFFFF; - /* Send the chunk */ - ret = dm_dmub_send_vbios_gpint_command(adev, send_addrs[i], chunk, 30000); - if (ret != DMUB_STATUS_OK) - goto free_bb; - } - - /* Now ask DMUB to copy the bb */ - ret = dm_dmub_send_vbios_gpint_command(adev, DMUB_GPINT__BB_COPY, 1, 200000); - if (ret != DMUB_STATUS_OK) - goto free_bb; - - return bb; - -free_bb: - dm_free_gpu_mem(adev, DC_MEM_ALLOC_TYPE_GART, (void *) bb); - return NULL; - -} - -static enum dmub_ips_disable_type dm_get_default_ips_mode( - struct amdgpu_device *adev) -{ - enum dmub_ips_disable_type ret = DMUB_IPS_ENABLE; - - switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) { - case IP_VERSION(3, 5, 0): - case IP_VERSION(3, 6, 0): - case IP_VERSION(3, 5, 1): - ret = DMUB_IPS_RCG_IN_ACTIVE_IPS2_IN_OFF; - break; - case IP_VERSION(4, 2, 0): - case IP_VERSION(4, 2, 1): - ret = DMUB_IPS_ENABLE; - break; - default: - /* ASICs older than DCN35 do not have IPSs */ - if (amdgpu_ip_version(adev, DCE_HWIP, 0) < IP_VERSION(3, 5, 0)) - ret = DMUB_IPS_DISABLE_ALL; - break; - } - - return ret; -} - static int amdgpu_dm_init_power_module(struct amdgpu_display_manager *dm) { struct mod_power_init_params init_data[MAX_NUM_EDP]; @@ -2143,8 +1695,8 @@ static int amdgpu_dm_init(struct amdgpu_device *adev) } amdgpu_dm_outbox_init(adev); - if (!register_dmub_notify_callback(adev, DMUB_NOTIFICATION_AUX_REPLY, - dmub_aux_setconfig_callback, false)) { + if (!dm_register_dmub_notify_callback(adev, DMUB_NOTIFICATION_AUX_REPLY, + dm_dmub_aux_setconfig_callback, false)) { drm_err(adev_to_drm(adev), "fail to register dmub aux callback"); goto error; } @@ -2152,8 +1704,8 @@ static int amdgpu_dm_init(struct amdgpu_device *adev) for (size_t i = 0; i < ARRAY_SIZE(adev->dm.fused_io); i++) init_completion(&adev->dm.fused_io[i].replied); - if (!register_dmub_notify_callback(adev, DMUB_NOTIFICATION_FUSED_IO, - dmub_aux_fused_io_callback, false)) { + if (!dm_register_dmub_notify_callback(adev, DMUB_NOTIFICATION_FUSED_IO, + dm_dmub_aux_fused_io_callback, false)) { drm_err(adev_to_drm(adev), "fail to register dmub fused io callback"); goto error; } @@ -2441,224 +1993,6 @@ static int load_dmcu_fw(struct amdgpu_device *adev) return 0; } -static uint32_t amdgpu_dm_dmub_reg_read(void *ctx, uint32_t address) -{ - struct amdgpu_device *adev = ctx; - - return dm_read_reg(adev->dm.dc->ctx, address); -} - -static void amdgpu_dm_dmub_reg_write(void *ctx, uint32_t address, - uint32_t value) -{ - struct amdgpu_device *adev = ctx; - - return dm_write_reg(adev->dm.dc->ctx, address, value); -} - -static int dm_dmub_sw_init(struct amdgpu_device *adev) -{ - struct dmub_srv_create_params create_params; - struct dmub_srv_fw_meta_info_params fw_meta_info_params; - struct dmub_srv_region_params region_params; - struct dmub_srv_region_info region_info; - struct dmub_srv_memory_params memory_params; - struct dmub_fw_meta_info fw_info; - struct dmub_srv_fb_info *fb_info; - struct dmub_srv *dmub_srv; - const struct dmcub_firmware_header_v1_0 *hdr; - enum dmub_asic dmub_asic; - enum dmub_status status; - static enum dmub_window_memory_type window_memory_type[DMUB_WINDOW_TOTAL] = { - DMUB_WINDOW_MEMORY_TYPE_FB, //DMUB_WINDOW_0_INST_CONST - DMUB_WINDOW_MEMORY_TYPE_FB, //DMUB_WINDOW_1_STACK - DMUB_WINDOW_MEMORY_TYPE_FB, //DMUB_WINDOW_2_BSS_DATA - DMUB_WINDOW_MEMORY_TYPE_FB, //DMUB_WINDOW_3_VBIOS - DMUB_WINDOW_MEMORY_TYPE_FB, //DMUB_WINDOW_4_MAILBOX - DMUB_WINDOW_MEMORY_TYPE_FB, //DMUB_WINDOW_5_TRACEBUFF - DMUB_WINDOW_MEMORY_TYPE_FB, //DMUB_WINDOW_6_FW_STATE - DMUB_WINDOW_MEMORY_TYPE_FB, //DMUB_WINDOW_7_SCRATCH_MEM - DMUB_WINDOW_MEMORY_TYPE_FB, //DMUB_WINDOW_IB_MEM - DMUB_WINDOW_MEMORY_TYPE_FB, //DMUB_WINDOW_SHARED_STATE - DMUB_WINDOW_MEMORY_TYPE_FB, //DMUB_WINDOW_LSDMA_BUFFER - DMUB_WINDOW_MEMORY_TYPE_FB, //DMUB_WINDOW_CURSOR_OFFLOAD - }; - int r; - - switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) { - case IP_VERSION(2, 1, 0): - dmub_asic = DMUB_ASIC_DCN21; - break; - case IP_VERSION(3, 0, 0): - dmub_asic = DMUB_ASIC_DCN30; - break; - case IP_VERSION(3, 0, 1): - dmub_asic = DMUB_ASIC_DCN301; - break; - case IP_VERSION(3, 0, 2): - dmub_asic = DMUB_ASIC_DCN302; - break; - case IP_VERSION(3, 0, 3): - dmub_asic = DMUB_ASIC_DCN303; - break; - case IP_VERSION(3, 1, 2): - case IP_VERSION(3, 1, 3): - dmub_asic = (adev->external_rev_id == YELLOW_CARP_B0) ? DMUB_ASIC_DCN31B : DMUB_ASIC_DCN31; - break; - case IP_VERSION(3, 1, 4): - dmub_asic = DMUB_ASIC_DCN314; - break; - case IP_VERSION(3, 1, 5): - dmub_asic = DMUB_ASIC_DCN315; - break; - case IP_VERSION(3, 1, 6): - dmub_asic = DMUB_ASIC_DCN316; - break; - case IP_VERSION(3, 2, 0): - dmub_asic = DMUB_ASIC_DCN32; - break; - case IP_VERSION(3, 2, 1): - dmub_asic = DMUB_ASIC_DCN321; - break; - case IP_VERSION(3, 5, 0): - case IP_VERSION(3, 5, 1): - dmub_asic = DMUB_ASIC_DCN35; - break; - case IP_VERSION(3, 6, 0): - dmub_asic = DMUB_ASIC_DCN36; - break; - case IP_VERSION(4, 0, 1): - dmub_asic = DMUB_ASIC_DCN401; - break; - case IP_VERSION(4, 2, 0): - dmub_asic = DMUB_ASIC_DCN42; - break; - case IP_VERSION(4, 2, 1): - dmub_asic = DMUB_ASIC_DCN42B; - break; - default: - /* ASIC doesn't support DMUB. */ - return 0; - } - - hdr = (const struct dmcub_firmware_header_v1_0 *)adev->dm.dmub_fw->data; - adev->dm.dmcub_fw_version = le32_to_cpu(hdr->header.ucode_version); - - if (adev->firmware.load_type == AMDGPU_FW_LOAD_PSP) { - adev->firmware.ucode[AMDGPU_UCODE_ID_DMCUB].ucode_id = - AMDGPU_UCODE_ID_DMCUB; - adev->firmware.ucode[AMDGPU_UCODE_ID_DMCUB].fw = - adev->dm.dmub_fw; - adev->firmware.fw_size += - ALIGN(le32_to_cpu(hdr->inst_const_bytes), PAGE_SIZE); - - drm_info(adev_to_drm(adev), "Loading DMUB firmware via PSP: version=0x%08X\n", - adev->dm.dmcub_fw_version); - } - - - adev->dm.dmub_srv = kzalloc_obj(*adev->dm.dmub_srv); - dmub_srv = adev->dm.dmub_srv; - - if (!dmub_srv) { - drm_err(adev_to_drm(adev), "Failed to allocate DMUB service!\n"); - return -ENOMEM; - } - - memset(&create_params, 0, sizeof(create_params)); - create_params.user_ctx = adev; - create_params.funcs.reg_read = amdgpu_dm_dmub_reg_read; - create_params.funcs.reg_write = amdgpu_dm_dmub_reg_write; - create_params.asic = dmub_asic; - - /* Create the DMUB service. */ - status = dmub_srv_create(dmub_srv, &create_params); - if (status != DMUB_STATUS_OK) { - drm_err(adev_to_drm(adev), "Error creating DMUB service: %d\n", status); - return -EINVAL; - } - - /* Extract the FW meta info. */ - memset(&fw_meta_info_params, 0, sizeof(fw_meta_info_params)); - - fw_meta_info_params.inst_const_size = le32_to_cpu(hdr->inst_const_bytes) - - PSP_HEADER_BYTES_256; - fw_meta_info_params.bss_data_size = le32_to_cpu(hdr->bss_data_bytes); - fw_meta_info_params.fw_inst_const = adev->dm.dmub_fw->data + - le32_to_cpu(hdr->header.ucode_array_offset_bytes) + - PSP_HEADER_BYTES_256; - fw_meta_info_params.fw_bss_data = fw_meta_info_params.bss_data_size ? adev->dm.dmub_fw->data + - le32_to_cpu(hdr->header.ucode_array_offset_bytes) + - le32_to_cpu(hdr->inst_const_bytes) : NULL; - fw_meta_info_params.custom_psp_footer_size = 0; - - status = dmub_srv_get_fw_meta_info_from_raw_fw(&fw_meta_info_params, &fw_info); - if (status != DMUB_STATUS_OK) { - /* Skip returning early, just log the error. */ - drm_err(adev_to_drm(adev), "Error getting DMUB FW meta info: %d\n", status); - // return -EINVAL; - } - - /* Calculate the size of all the regions for the DMUB service. */ - memset(®ion_params, 0, sizeof(region_params)); - - region_params.inst_const_size = fw_meta_info_params.inst_const_size; - region_params.bss_data_size = fw_meta_info_params.bss_data_size; - region_params.vbios_size = adev->bios_size; - region_params.fw_bss_data = fw_meta_info_params.fw_bss_data; - region_params.fw_inst_const = fw_meta_info_params.fw_inst_const; - region_params.window_memory_type = window_memory_type; - region_params.fw_info = (status == DMUB_STATUS_OK) ? &fw_info : NULL; - - status = dmub_srv_calc_region_info(dmub_srv, ®ion_params, - ®ion_info); - - if (status != DMUB_STATUS_OK) { - drm_err(adev_to_drm(adev), "Error calculating DMUB region info: %d\n", status); - return -EINVAL; - } - - /* - * Allocate a framebuffer based on the total size of all the regions. - * TODO: Move this into GART. - */ - r = amdgpu_bo_create_kernel(adev, region_info.fb_size, PAGE_SIZE, - AMDGPU_GEM_DOMAIN_VRAM | - AMDGPU_GEM_DOMAIN_GTT, - &adev->dm.dmub_bo, - &adev->dm.dmub_bo_gpu_addr, - &adev->dm.dmub_bo_cpu_addr); - if (r) - return r; - - /* Rebase the regions on the framebuffer address. */ - memset(&memory_params, 0, sizeof(memory_params)); - memory_params.cpu_fb_addr = adev->dm.dmub_bo_cpu_addr; - memory_params.gpu_fb_addr = adev->dm.dmub_bo_gpu_addr; - memory_params.region_info = ®ion_info; - memory_params.window_memory_type = window_memory_type; - - adev->dm.dmub_fb_info = kzalloc_obj(*adev->dm.dmub_fb_info); - fb_info = adev->dm.dmub_fb_info; - - if (!fb_info) { - drm_err(adev_to_drm(adev), - "Failed to allocate framebuffer info for DMUB service!\n"); - return -ENOMEM; - } - - status = dmub_srv_calc_mem_info(dmub_srv, &memory_params, fb_info); - if (status != DMUB_STATUS_OK) { - drm_err(adev_to_drm(adev), "Error calculating DMUB FB info: %d\n", status); - return -EINVAL; - } - - adev->dm.bb_from_dmub = dm_dmub_get_vbios_bounding_box(adev); - adev->dm.fw_inst_size = fw_meta_info_params.inst_const_size; - - return 0; -} - static int dm_sw_init(struct amdgpu_ip_block *ip_block) { struct amdgpu_device *adev = ip_block->adev; @@ -4382,19 +3716,19 @@ static int register_hpd_handlers(struct amdgpu_device *adev) int_params.current_polarity = INTERRUPT_POLARITY_DEFAULT; if (dc_is_dmub_outbox_supported(adev->dm.dc)) { - if (!register_dmub_notify_callback(adev, DMUB_NOTIFICATION_HPD, + if (!dm_register_dmub_notify_callback(adev, DMUB_NOTIFICATION_HPD, dmub_hpd_callback, true)) { drm_err(adev_to_drm(adev), "fail to register dmub hpd callback"); return -EINVAL; } - if (!register_dmub_notify_callback(adev, DMUB_NOTIFICATION_HPD_IRQ, + if (!dm_register_dmub_notify_callback(adev, DMUB_NOTIFICATION_HPD_IRQ, dmub_hpd_callback, true)) { drm_err(adev_to_drm(adev), "fail to register dmub hpd callback"); return -EINVAL; } - if (!register_dmub_notify_callback(adev, DMUB_NOTIFICATION_HPD_SENSE_NOTIFY, + if (!dm_register_dmub_notify_callback(adev, DMUB_NOTIFICATION_HPD_SENSE_NOTIFY, dmub_hpd_sense_callback, true)) { drm_err(adev_to_drm(adev), "fail to register dmub hpd sense callback"); return -EINVAL; @@ -5405,78 +4739,6 @@ DEVICE_ATTR_WO(s3_debug); #endif -static int dm_init_microcode(struct amdgpu_device *adev) -{ - char *fw_name_dmub; - int r; - - switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) { - case IP_VERSION(2, 1, 0): - fw_name_dmub = FIRMWARE_RENOIR_DMUB; - if (ASICREV_IS_GREEN_SARDINE(adev->external_rev_id)) - fw_name_dmub = FIRMWARE_GREEN_SARDINE_DMUB; - break; - case IP_VERSION(3, 0, 0): - if (amdgpu_ip_version(adev, GC_HWIP, 0) == IP_VERSION(10, 3, 0)) - fw_name_dmub = FIRMWARE_SIENNA_CICHLID_DMUB; - else - fw_name_dmub = FIRMWARE_NAVY_FLOUNDER_DMUB; - break; - case IP_VERSION(3, 0, 1): - fw_name_dmub = FIRMWARE_VANGOGH_DMUB; - break; - case IP_VERSION(3, 0, 2): - fw_name_dmub = FIRMWARE_DIMGREY_CAVEFISH_DMUB; - break; - case IP_VERSION(3, 0, 3): - fw_name_dmub = FIRMWARE_BEIGE_GOBY_DMUB; - break; - case IP_VERSION(3, 1, 2): - case IP_VERSION(3, 1, 3): - fw_name_dmub = FIRMWARE_YELLOW_CARP_DMUB; - break; - case IP_VERSION(3, 1, 4): - fw_name_dmub = FIRMWARE_DCN_314_DMUB; - break; - case IP_VERSION(3, 1, 5): - fw_name_dmub = FIRMWARE_DCN_315_DMUB; - break; - case IP_VERSION(3, 1, 6): - fw_name_dmub = FIRMWARE_DCN316_DMUB; - break; - case IP_VERSION(3, 2, 0): - fw_name_dmub = FIRMWARE_DCN_V3_2_0_DMCUB; - break; - case IP_VERSION(3, 2, 1): - fw_name_dmub = FIRMWARE_DCN_V3_2_1_DMCUB; - break; - case IP_VERSION(3, 5, 0): - fw_name_dmub = FIRMWARE_DCN_35_DMUB; - break; - case IP_VERSION(3, 5, 1): - fw_name_dmub = FIRMWARE_DCN_351_DMUB; - break; - case IP_VERSION(3, 6, 0): - fw_name_dmub = FIRMWARE_DCN_36_DMUB; - break; - case IP_VERSION(4, 0, 1): - fw_name_dmub = FIRMWARE_DCN_401_DMUB; - break; - case IP_VERSION(4, 2, 0): - fw_name_dmub = FIRMWARE_DCN_42_DMUB; - break; - case IP_VERSION(4, 2, 1): - fw_name_dmub = FIRMWARE_DCN_42B_DMUB; - break; - default: - /* ASIC doesn't support DMUB. */ - return 0; - } - r = amdgpu_ucode_request(adev, &adev->dm.dmub_fw, AMDGPU_UCODE_REQUIRED, - "%s", fw_name_dmub); - return r; -} - static int dm_early_init(struct amdgpu_ip_block *ip_block) { struct amdgpu_device *adev = ip_block->adev; @@ -13009,179 +12271,6 @@ uint32_t dm_read_reg_func(const struct dc_context *ctx, uint32_t address, return value; } -int amdgpu_dm_process_dmub_aux_transfer_sync( - struct dc_context *ctx, - unsigned int link_index, - struct aux_payload *payload, - enum aux_return_code_type *operation_result) -{ - struct amdgpu_device *adev = ctx->driver_context; - struct dmub_notification *p_notify = adev->dm.dmub_notify; - int ret = -1; - - mutex_lock(&adev->dm.dpia_aux_lock); - if (!dc_process_dmub_aux_transfer_async(ctx->dc, link_index, payload)) { - *operation_result = AUX_RET_ERROR_ENGINE_ACQUIRE; - goto out; - } - - if (!wait_for_completion_timeout(&adev->dm.dmub_aux_transfer_done, 10 * HZ)) { - drm_err(adev_to_drm(adev), "wait_for_completion_timeout timeout!"); - *operation_result = AUX_RET_ERROR_TIMEOUT; - goto out; - } - - if (p_notify->result != AUX_RET_SUCCESS) { - /* - * Transient states before tunneling is enabled could - * lead to this error. We can ignore this for now. - */ - if (p_notify->result == AUX_RET_ERROR_PROTOCOL_ERROR) { - drm_warn(adev_to_drm(adev), "DPIA AUX failed on 0x%x(%d), error %d\n", - payload->address, payload->length, - p_notify->result); - } - *operation_result = p_notify->result; - goto out; - } - - payload->reply[0] = adev->dm.dmub_notify->aux_reply.command & 0xF; - if (adev->dm.dmub_notify->aux_reply.command & 0xF0) - /* The reply is stored in the top nibble of the command. */ - payload->reply[0] = (adev->dm.dmub_notify->aux_reply.command >> 4) & 0xF; - - /*write req may receive a byte indicating partially written number as well*/ - if (p_notify->aux_reply.length) - memcpy(payload->data, p_notify->aux_reply.data, - p_notify->aux_reply.length); - - /* success */ - ret = p_notify->aux_reply.length; - *operation_result = p_notify->result; -out: - reinit_completion(&adev->dm.dmub_aux_transfer_done); - mutex_unlock(&adev->dm.dpia_aux_lock); - return ret; -} - -static void abort_fused_io( - struct dc_context *ctx, - const struct dmub_cmd_fused_request *request -) -{ - union dmub_rb_cmd command = { 0 }; - struct dmub_rb_cmd_fused_io *io = &command.fused_io; - - io->header.type = DMUB_CMD__FUSED_IO; - io->header.sub_type = DMUB_CMD__FUSED_IO_ABORT; - io->header.payload_bytes = sizeof(*io) - sizeof(io->header); - io->request = *request; - dm_execute_dmub_cmd(ctx, &command, DM_DMUB_WAIT_TYPE_NO_WAIT); -} - -static bool execute_fused_io( - struct amdgpu_device *dev, - struct dc_context *ctx, - union dmub_rb_cmd *commands, - uint8_t count, - uint32_t timeout_us -) -{ - const uint8_t ddc_line = commands[0].fused_io.request.u.aux.ddc_line; - - if (ddc_line >= ARRAY_SIZE(dev->dm.fused_io)) - return false; - - struct fused_io_sync *sync = &dev->dm.fused_io[ddc_line]; - struct dmub_rb_cmd_fused_io *first = &commands[0].fused_io; - const bool result = dm_execute_dmub_cmd_list(ctx, count, commands, DM_DMUB_WAIT_TYPE_WAIT_WITH_REPLY) - && first->header.ret_status - && first->request.status == FUSED_REQUEST_STATUS_SUCCESS; - - if (!result) - return false; - - while (wait_for_completion_timeout(&sync->replied, usecs_to_jiffies(timeout_us))) { - reinit_completion(&sync->replied); - - struct dmub_cmd_fused_request *reply = (struct dmub_cmd_fused_request *) sync->reply_data; - - static_assert(sizeof(*reply) <= sizeof(sync->reply_data), "Size mismatch"); - - if (reply->identifier == first->request.identifier) { - first->request = *reply; - return true; - } - } - - reinit_completion(&sync->replied); - first->request.status = FUSED_REQUEST_STATUS_TIMEOUT; - abort_fused_io(ctx, &first->request); - return false; -} - -bool amdgpu_dm_execute_fused_io( - struct amdgpu_device *dev, - struct dc_link *link, - union dmub_rb_cmd *commands, - uint8_t count, - uint32_t timeout_us) -{ - struct amdgpu_display_manager *dm = &dev->dm; - - mutex_lock(&dm->dpia_aux_lock); - - const bool result = execute_fused_io(dev, link->ctx, commands, count, timeout_us); - - mutex_unlock(&dm->dpia_aux_lock); - return result; -} - -int amdgpu_dm_process_dmub_set_config_sync( - struct dc_context *ctx, - unsigned int link_index, - struct set_config_cmd_payload *payload, - enum set_config_status *operation_result) -{ - struct amdgpu_device *adev = ctx->driver_context; - bool is_cmd_complete; - int ret; - - mutex_lock(&adev->dm.dpia_aux_lock); - is_cmd_complete = dc_process_dmub_set_config_async(ctx->dc, - link_index, payload, adev->dm.dmub_notify); - - if (is_cmd_complete || wait_for_completion_timeout(&adev->dm.dmub_aux_transfer_done, 10 * HZ)) { - ret = 0; - *operation_result = adev->dm.dmub_notify->sc_status; - } else { - drm_err(adev_to_drm(adev), "wait_for_completion_timeout timeout!"); - ret = -1; - *operation_result = SET_CONFIG_UNKNOWN_ERROR; - } - - if (!is_cmd_complete) - reinit_completion(&adev->dm.dmub_aux_transfer_done); - mutex_unlock(&adev->dm.dpia_aux_lock); - return ret; -} - -bool dm_execute_dmub_cmd(const struct dc_context *ctx, union dmub_rb_cmd *cmd, enum dm_dmub_wait_type wait_type) -{ - struct amdgpu_device *adev = ctx->driver_context; - - guard(spinlock_irqsave)(&adev->dm.dmub_lock); - return dc_dmub_srv_cmd_run(ctx->dmub_srv, cmd, wait_type); -} - -bool dm_execute_dmub_cmd_list(const struct dc_context *ctx, unsigned int count, union dmub_rb_cmd *cmd, enum dm_dmub_wait_type wait_type) -{ - struct amdgpu_device *adev = ctx->driver_context; - - guard(spinlock_irqsave)(&adev->dm.dmub_lock); - return dc_dmub_srv_cmd_run_list(ctx->dmub_srv, count, cmd, wait_type); -} - void dm_acpi_process_phy_transition_interlock( const struct dc_context *ctx, struct dm_process_phy_transition_init_params process_phy_transition_init_params) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c new file mode 100644 index 000000000000..739e685f1c3c --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c @@ -0,0 +1,924 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright 2026 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 "dm_services_types.h" +#include "dc.h" +#include "dc/inc/core_types.h" +#include "dc/dc_dmub_srv.h" +#include "dmub/dmub_srv.h" +#include "dc/inc/hw/dmcu.h" +#include "dc/inc/hw/abm.h" +#include "dal_asic_id.h" + +#include "amdgpu.h" +#include "amdgpu_display.h" +#include "amdgpu_ucode.h" +#include "amdgpu_dm.h" +#include "amdgpu_dm_dmub.h" +#include +#include + +static_assert(AMDGPU_DMUB_NOTIFICATION_MAX == DMUB_NOTIFICATION_MAX, "AMDGPU_DMUB_NOTIFICATION_MAX mismatch"); + +MODULE_FIRMWARE(FIRMWARE_RENOIR_DMUB); +MODULE_FIRMWARE(FIRMWARE_SIENNA_CICHLID_DMUB); +MODULE_FIRMWARE(FIRMWARE_NAVY_FLOUNDER_DMUB); +MODULE_FIRMWARE(FIRMWARE_GREEN_SARDINE_DMUB); +MODULE_FIRMWARE(FIRMWARE_VANGOGH_DMUB); +MODULE_FIRMWARE(FIRMWARE_DIMGREY_CAVEFISH_DMUB); +MODULE_FIRMWARE(FIRMWARE_BEIGE_GOBY_DMUB); +MODULE_FIRMWARE(FIRMWARE_YELLOW_CARP_DMUB); +MODULE_FIRMWARE(FIRMWARE_DCN_314_DMUB); +MODULE_FIRMWARE(FIRMWARE_DCN_315_DMUB); +MODULE_FIRMWARE(FIRMWARE_DCN316_DMUB); +MODULE_FIRMWARE(FIRMWARE_DCN_V3_2_0_DMCUB); +MODULE_FIRMWARE(FIRMWARE_DCN_V3_2_1_DMCUB); +MODULE_FIRMWARE(FIRMWARE_DCN_35_DMUB); +MODULE_FIRMWARE(FIRMWARE_DCN_351_DMUB); +MODULE_FIRMWARE(FIRMWARE_DCN_36_DMUB); +MODULE_FIRMWARE(FIRMWARE_DCN_401_DMUB); +MODULE_FIRMWARE(FIRMWARE_DCN_42_DMUB); +MODULE_FIRMWARE(FIRMWARE_DCN_42B_DMUB); + +/** + * dm_dmub_aux_setconfig_callback - Callback for AUX or SET_CONFIG command. + * @adev: amdgpu_device pointer + * @notify: dmub notification structure + * + * Dmub AUX or SET_CONFIG command completion processing callback + * Copies dmub notification to DM which is to be read by AUX command. + * issuing thread and also signals the event to wake up the thread. + */ +void dm_dmub_aux_setconfig_callback(struct amdgpu_device *adev, + struct dmub_notification *notify) +{ + if (adev->dm.dmub_notify) + memcpy(adev->dm.dmub_notify, notify, sizeof(struct dmub_notification)); + if (notify->type == DMUB_NOTIFICATION_AUX_REPLY) + complete(&adev->dm.dmub_aux_transfer_done); +} + +void dm_dmub_aux_fused_io_callback(struct amdgpu_device *adev, + struct dmub_notification *notify) +{ + if (!adev || !notify) { + ASSERT(false); + return; + } + + const struct dmub_cmd_fused_request *req = ¬ify->fused_request; + const uint8_t ddc_line = req->u.aux.ddc_line; + + if (ddc_line >= ARRAY_SIZE(adev->dm.fused_io)) { + ASSERT(false); + return; + } + + struct fused_io_sync *sync = &adev->dm.fused_io[ddc_line]; + + static_assert(sizeof(*req) <= sizeof(sync->reply_data), "Size mismatch"); + memcpy(sync->reply_data, req, sizeof(*req)); + complete(&sync->replied); +} + +/** + * dm_register_dmub_notify_callback - Sets callback for DMUB notify + * @adev: amdgpu_device pointer + * @type: Type of dmub notification + * @callback: Dmub interrupt callback function + * @dmub_int_thread_offload: offload indicator + * + * API to register a dmub callback handler for a dmub notification + * Also sets indicator whether callback processing to be offloaded. + * to dmub interrupt handling thread + * Return: true if successfully registered, false if there is existing registration + */ +bool dm_register_dmub_notify_callback(struct amdgpu_device *adev, + enum dmub_notification_type type, + dmub_notify_interrupt_callback_t callback, + bool dmub_int_thread_offload) +{ + if (callback != NULL && type < ARRAY_SIZE(adev->dm.dmub_thread_offload)) { + adev->dm.dmub_callback[type] = callback; + adev->dm.dmub_thread_offload[type] = dmub_int_thread_offload; + } else + return false; + + return true; +} + +int dm_dmub_hw_init(struct amdgpu_device *adev) +{ + const struct dmcub_firmware_header_v1_0 *hdr; + struct dmub_srv *dmub_srv = adev->dm.dmub_srv; + struct dmub_srv_fb_info *fb_info = adev->dm.dmub_fb_info; + const struct firmware *dmub_fw = adev->dm.dmub_fw; + struct dc *dc = adev->dm.dc; + struct dmcu *dmcu = adev->dm.dc->res_pool->dmcu; + struct abm *abm = adev->dm.dc->res_pool->abm; + struct dc_context *ctx = adev->dm.dc->ctx; + struct dmub_srv_hw_params hw_params; + enum dmub_status status; + const unsigned char *fw_inst_const, *fw_bss_data; + u32 i, fw_inst_const_size, fw_bss_data_size; + bool has_hw_support; + + if (!dmub_srv) + /* DMUB isn't supported on the ASIC. */ + return 0; + + if (!fb_info) { + drm_err(adev_to_drm(adev), "No framebuffer info for DMUB service.\n"); + return -EINVAL; + } + + if (!dmub_fw) { + /* Firmware required for DMUB support. */ + drm_err(adev_to_drm(adev), "No firmware provided for DMUB.\n"); + return -EINVAL; + } + + /* initialize register offsets for ASICs with runtime initialization available */ + if (dmub_srv->hw_funcs.init_reg_offsets) + dmub_srv->hw_funcs.init_reg_offsets(dmub_srv, ctx); + + status = dmub_srv_has_hw_support(dmub_srv, &has_hw_support); + if (status != DMUB_STATUS_OK) { + drm_err(adev_to_drm(adev), "Error checking HW support for DMUB: %d\n", status); + return -EINVAL; + } + + if (!has_hw_support) { + drm_info(adev_to_drm(adev), "DMUB unsupported on ASIC\n"); + return 0; + } + + /* Reset DMCUB if it was previously running - before we overwrite its memory. */ + status = dmub_srv_hw_reset(dmub_srv); + if (status != DMUB_STATUS_OK) + drm_warn(adev_to_drm(adev), "Error resetting DMUB HW: %d\n", status); + + hdr = (const struct dmcub_firmware_header_v1_0 *)dmub_fw->data; + + fw_inst_const = dmub_fw->data + + le32_to_cpu(hdr->header.ucode_array_offset_bytes) + + PSP_HEADER_BYTES_256; + + fw_bss_data = dmub_fw->data + + le32_to_cpu(hdr->header.ucode_array_offset_bytes) + + le32_to_cpu(hdr->inst_const_bytes); + + /* Copy firmware and bios info into FB memory. */ + fw_inst_const_size = adev->dm.fw_inst_size; + + fw_bss_data_size = le32_to_cpu(hdr->bss_data_bytes); + + /* if adev->firmware.load_type == AMDGPU_FW_LOAD_PSP, + * amdgpu_ucode_init_single_fw will load dmub firmware + * fw_inst_const part to cw0; otherwise, the firmware back door load + * will be done by dm_dmub_hw_init + */ + if (adev->firmware.load_type != AMDGPU_FW_LOAD_PSP) { + memcpy(fb_info->fb[DMUB_WINDOW_0_INST_CONST].cpu_addr, fw_inst_const, + fw_inst_const_size); + } + + if (fw_bss_data_size) + memcpy(fb_info->fb[DMUB_WINDOW_2_BSS_DATA].cpu_addr, + fw_bss_data, fw_bss_data_size); + + /* Copy firmware bios info into FB memory. */ + memcpy(fb_info->fb[DMUB_WINDOW_3_VBIOS].cpu_addr, adev->bios, + adev->bios_size); + + /* Reset regions that need to be reset. */ + memset(fb_info->fb[DMUB_WINDOW_4_MAILBOX].cpu_addr, 0, + fb_info->fb[DMUB_WINDOW_4_MAILBOX].size); + + memset(fb_info->fb[DMUB_WINDOW_5_TRACEBUFF].cpu_addr, 0, + fb_info->fb[DMUB_WINDOW_5_TRACEBUFF].size); + + memset(fb_info->fb[DMUB_WINDOW_6_FW_STATE].cpu_addr, 0, + fb_info->fb[DMUB_WINDOW_6_FW_STATE].size); + + memset(fb_info->fb[DMUB_WINDOW_SHARED_STATE].cpu_addr, 0, + fb_info->fb[DMUB_WINDOW_SHARED_STATE].size); + + /* Initialize hardware. */ + memset(&hw_params, 0, sizeof(hw_params)); + hw_params.soc_fb_info.fb_base = adev->gmc.fb_start; + hw_params.soc_fb_info.fb_offset = adev->vm_manager.vram_base_offset; + + /* backdoor load firmware and trigger dmub running */ + if (adev->firmware.load_type != AMDGPU_FW_LOAD_PSP) + hw_params.load_inst_const = true; + + if (dmcu) + hw_params.psp_version = dmcu->psp_version; + + for (i = 0; i < fb_info->num_fb; ++i) + hw_params.fb[i] = &fb_info->fb[i]; + + /* Enable usb4 dpia in the FW APU */ + if (dc->caps.is_apu && + dc->res_pool->usb4_dpia_count != 0 && + !dc->debug.dpia_debug.bits.disable_dpia) { + hw_params.dpia_supported = true; + hw_params.disable_dpia = dc->debug.dpia_debug.bits.disable_dpia; + hw_params.dpia_hpd_int_enable_supported = false; + hw_params.enable_non_transparent_setconfig = dc->config.consolidated_dpia_dp_lt; + hw_params.disable_dpia_bw_allocation = !dc->config.usb4_bw_alloc_support; + } + + switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) { + case IP_VERSION(3, 5, 0): + case IP_VERSION(3, 5, 1): + case IP_VERSION(3, 6, 0): + case IP_VERSION(4, 2, 0): + case IP_VERSION(4, 2, 1): + hw_params.ips_sequential_ono = adev->external_rev_id > 0x10; + hw_params.lower_hbr3_phy_ssc = true; + break; + default: + break; + } + + status = dmub_srv_hw_init(dmub_srv, &hw_params); + if (status != DMUB_STATUS_OK) { + drm_err(adev_to_drm(adev), "Error initializing DMUB HW: %d\n", status); + return -EINVAL; + } + + /* Wait for firmware load to finish. */ + status = dmub_srv_wait_for_auto_load(dmub_srv, 100000); + if (status != DMUB_STATUS_OK) + drm_warn(adev_to_drm(adev), "Wait for DMUB auto-load failed: %d\n", status); + + /* Init DMCU and ABM if available. */ + if (dmcu && abm) { + dmcu->funcs->dmcu_init(dmcu); + abm->dmcu_is_running = dmcu->funcs->is_dmcu_initialized(dmcu); + } + + if (!adev->dm.dc->ctx->dmub_srv) + adev->dm.dc->ctx->dmub_srv = dc_dmub_srv_create(adev->dm.dc, dmub_srv); + if (!adev->dm.dc->ctx->dmub_srv) { + drm_err(adev_to_drm(adev), "Couldn't allocate DC DMUB server!\n"); + return -ENOMEM; + } + + drm_info(adev_to_drm(adev), "DMUB hardware initialized: version=0x%08X\n", + adev->dm.dmcub_fw_version); + + /* Keeping sanity checks off if + * DCN31 >= 4.0.59.0 + * DCN314 >= 8.0.16.0 + * Otherwise, turn on sanity checks + */ + switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) { + case IP_VERSION(3, 1, 2): + case IP_VERSION(3, 1, 3): + if (adev->dm.dmcub_fw_version && + adev->dm.dmcub_fw_version >= DMUB_FW_VERSION(4, 0, 0) && + adev->dm.dmcub_fw_version < DMUB_FW_VERSION(4, 0, 59)) + adev->dm.dc->debug.sanity_checks = true; + break; + case IP_VERSION(3, 1, 4): + if (adev->dm.dmcub_fw_version && + adev->dm.dmcub_fw_version >= DMUB_FW_VERSION(4, 0, 0) && + adev->dm.dmcub_fw_version < DMUB_FW_VERSION(8, 0, 16)) + adev->dm.dc->debug.sanity_checks = true; + break; + default: + break; + } + + return 0; +} + +void dm_dmub_hw_resume(struct amdgpu_device *adev) +{ + struct dmub_srv *dmub_srv = adev->dm.dmub_srv; + enum dmub_status status; + bool init; + int r; + + if (!dmub_srv) { + /* DMUB isn't supported on the ASIC. */ + return; + } + + status = dmub_srv_is_hw_init(dmub_srv, &init); + if (status != DMUB_STATUS_OK) + drm_warn(adev_to_drm(adev), "DMUB hardware init check failed: %d\n", status); + + if (status == DMUB_STATUS_OK && init) { + /* Wait for firmware load to finish. */ + status = dmub_srv_wait_for_auto_load(dmub_srv, 100000); + if (status != DMUB_STATUS_OK) + drm_warn(adev_to_drm(adev), "Wait for DMUB auto-load failed: %d\n", status); + } else { + /* Perform the full hardware initialization. */ + r = dm_dmub_hw_init(adev); + if (r) + drm_err(adev_to_drm(adev), "DMUB interface failed to initialize: status=%d\n", r); + } +} + +static enum dmub_status +dm_dmub_send_vbios_gpint_command(struct amdgpu_device *adev, + enum dmub_gpint_command command_code, + uint16_t param, + uint32_t timeout_us) +{ + union dmub_gpint_data_register reg, test; + uint32_t i; + + /* Assume that VBIOS DMUB is ready to take commands */ + + reg.bits.status = 1; + reg.bits.command_code = command_code; + reg.bits.param = param; + + cgs_write_register(adev->dm.cgs_device, 0x34c0 + 0x01f8, reg.all); + + for (i = 0; i < timeout_us; ++i) { + udelay(1); + + /* Check if our GPINT got acked */ + reg.bits.status = 0; + test = (union dmub_gpint_data_register) + cgs_read_register(adev->dm.cgs_device, 0x34c0 + 0x01f8); + + if (test.all == reg.all) + return DMUB_STATUS_OK; + } + + return DMUB_STATUS_TIMEOUT; +} + +static void *dm_dmub_get_vbios_bounding_box(struct amdgpu_device *adev) +{ + void *bb; + long long addr; + unsigned int bb_size; + int i = 0; + uint16_t chunk; + enum dmub_gpint_command send_addrs[] = { + DMUB_GPINT__SET_BB_ADDR_WORD0, + DMUB_GPINT__SET_BB_ADDR_WORD1, + DMUB_GPINT__SET_BB_ADDR_WORD2, + DMUB_GPINT__SET_BB_ADDR_WORD3, + }; + enum dmub_status ret; + + switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) { + case IP_VERSION(4, 0, 1): + bb_size = sizeof(struct dml2_soc_bb); + break; + case IP_VERSION(4, 2, 0): + case IP_VERSION(4, 2, 1): + bb_size = sizeof(struct dml2_soc_bb); + break; + default: + return NULL; + } + + bb = dm_allocate_gpu_mem(adev, + DC_MEM_ALLOC_TYPE_GART, + bb_size, + &addr); + if (!bb) + return NULL; + + for (i = 0; i < 4; i++) { + /* Extract 16-bit chunk */ + chunk = ((uint64_t) addr >> (i * 16)) & 0xFFFF; + /* Send the chunk */ + ret = dm_dmub_send_vbios_gpint_command(adev, send_addrs[i], chunk, 30000); + if (ret != DMUB_STATUS_OK) + goto free_bb; + } + + /* Now ask DMUB to copy the bb */ + ret = dm_dmub_send_vbios_gpint_command(adev, DMUB_GPINT__BB_COPY, 1, 200000); + if (ret != DMUB_STATUS_OK) + goto free_bb; + + return bb; + +free_bb: + dm_free_gpu_mem(adev, DC_MEM_ALLOC_TYPE_GART, (void *) bb); + return NULL; + +} + +enum dmub_ips_disable_type dm_get_default_ips_mode( + struct amdgpu_device *adev) +{ + enum dmub_ips_disable_type ret = DMUB_IPS_ENABLE; + + switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) { + case IP_VERSION(3, 5, 0): + case IP_VERSION(3, 6, 0): + case IP_VERSION(3, 5, 1): + ret = DMUB_IPS_RCG_IN_ACTIVE_IPS2_IN_OFF; + break; + case IP_VERSION(4, 2, 0): + case IP_VERSION(4, 2, 1): + ret = DMUB_IPS_ENABLE; + break; + default: + /* ASICs older than DCN35 do not have IPSs */ + if (amdgpu_ip_version(adev, DCE_HWIP, 0) < IP_VERSION(3, 5, 0)) + ret = DMUB_IPS_DISABLE_ALL; + break; + } + + return ret; +} + +static uint32_t amdgpu_dm_dmub_reg_read(void *ctx, uint32_t address) +{ + struct amdgpu_device *adev = ctx; + + return dm_read_reg(adev->dm.dc->ctx, address); +} + +static void amdgpu_dm_dmub_reg_write(void *ctx, uint32_t address, + uint32_t value) +{ + struct amdgpu_device *adev = ctx; + + return dm_write_reg(adev->dm.dc->ctx, address, value); +} + +int dm_dmub_sw_init(struct amdgpu_device *adev) +{ + struct dmub_srv_create_params create_params; + struct dmub_srv_fw_meta_info_params fw_meta_info_params; + struct dmub_srv_region_params region_params; + struct dmub_srv_region_info region_info; + struct dmub_srv_memory_params memory_params; + struct dmub_fw_meta_info fw_info; + struct dmub_srv_fb_info *fb_info; + struct dmub_srv *dmub_srv; + const struct dmcub_firmware_header_v1_0 *hdr; + enum dmub_asic dmub_asic; + enum dmub_status status; + static enum dmub_window_memory_type window_memory_type[DMUB_WINDOW_TOTAL] = { + DMUB_WINDOW_MEMORY_TYPE_FB, /* DMUB_WINDOW_0_INST_CONST */ + DMUB_WINDOW_MEMORY_TYPE_FB, /* DMUB_WINDOW_1_STACK */ + DMUB_WINDOW_MEMORY_TYPE_FB, /* DMUB_WINDOW_2_BSS_DATA */ + DMUB_WINDOW_MEMORY_TYPE_FB, /* DMUB_WINDOW_3_VBIOS */ + DMUB_WINDOW_MEMORY_TYPE_FB, /* DMUB_WINDOW_4_MAILBOX */ + DMUB_WINDOW_MEMORY_TYPE_FB, /* DMUB_WINDOW_5_TRACEBUFF */ + DMUB_WINDOW_MEMORY_TYPE_FB, /* DMUB_WINDOW_6_FW_STATE */ + DMUB_WINDOW_MEMORY_TYPE_FB, /* DMUB_WINDOW_7_SCRATCH_MEM */ + DMUB_WINDOW_MEMORY_TYPE_FB, /* DMUB_WINDOW_IB_MEM */ + DMUB_WINDOW_MEMORY_TYPE_FB, /* DMUB_WINDOW_SHARED_STATE */ + DMUB_WINDOW_MEMORY_TYPE_FB, /* DMUB_WINDOW_LSDMA_BUFFER */ + DMUB_WINDOW_MEMORY_TYPE_FB, /* DMUB_WINDOW_CURSOR_OFFLOAD */ + }; + int r; + + switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) { + case IP_VERSION(2, 1, 0): + dmub_asic = DMUB_ASIC_DCN21; + break; + case IP_VERSION(3, 0, 0): + dmub_asic = DMUB_ASIC_DCN30; + break; + case IP_VERSION(3, 0, 1): + dmub_asic = DMUB_ASIC_DCN301; + break; + case IP_VERSION(3, 0, 2): + dmub_asic = DMUB_ASIC_DCN302; + break; + case IP_VERSION(3, 0, 3): + dmub_asic = DMUB_ASIC_DCN303; + break; + case IP_VERSION(3, 1, 2): + case IP_VERSION(3, 1, 3): + dmub_asic = (adev->external_rev_id == YELLOW_CARP_B0) ? DMUB_ASIC_DCN31B : DMUB_ASIC_DCN31; + break; + case IP_VERSION(3, 1, 4): + dmub_asic = DMUB_ASIC_DCN314; + break; + case IP_VERSION(3, 1, 5): + dmub_asic = DMUB_ASIC_DCN315; + break; + case IP_VERSION(3, 1, 6): + dmub_asic = DMUB_ASIC_DCN316; + break; + case IP_VERSION(3, 2, 0): + dmub_asic = DMUB_ASIC_DCN32; + break; + case IP_VERSION(3, 2, 1): + dmub_asic = DMUB_ASIC_DCN321; + break; + case IP_VERSION(3, 5, 0): + case IP_VERSION(3, 5, 1): + dmub_asic = DMUB_ASIC_DCN35; + break; + case IP_VERSION(3, 6, 0): + dmub_asic = DMUB_ASIC_DCN36; + break; + case IP_VERSION(4, 0, 1): + dmub_asic = DMUB_ASIC_DCN401; + break; + case IP_VERSION(4, 2, 0): + dmub_asic = DMUB_ASIC_DCN42; + break; + case IP_VERSION(4, 2, 1): + dmub_asic = DMUB_ASIC_DCN42B; + break; + default: + /* ASIC doesn't support DMUB. */ + return 0; + } + + hdr = (const struct dmcub_firmware_header_v1_0 *)adev->dm.dmub_fw->data; + adev->dm.dmcub_fw_version = le32_to_cpu(hdr->header.ucode_version); + + if (adev->firmware.load_type == AMDGPU_FW_LOAD_PSP) { + adev->firmware.ucode[AMDGPU_UCODE_ID_DMCUB].ucode_id = + AMDGPU_UCODE_ID_DMCUB; + adev->firmware.ucode[AMDGPU_UCODE_ID_DMCUB].fw = + adev->dm.dmub_fw; + adev->firmware.fw_size += + ALIGN(le32_to_cpu(hdr->inst_const_bytes), PAGE_SIZE); + + drm_info(adev_to_drm(adev), "Loading DMUB firmware via PSP: version=0x%08X\n", + adev->dm.dmcub_fw_version); + } + + + adev->dm.dmub_srv = kzalloc_obj(*adev->dm.dmub_srv); + dmub_srv = adev->dm.dmub_srv; + + if (!dmub_srv) { + drm_err(adev_to_drm(adev), "Failed to allocate DMUB service!\n"); + return -ENOMEM; + } + + memset(&create_params, 0, sizeof(create_params)); + create_params.user_ctx = adev; + create_params.funcs.reg_read = amdgpu_dm_dmub_reg_read; + create_params.funcs.reg_write = amdgpu_dm_dmub_reg_write; + create_params.asic = dmub_asic; + + /* Create the DMUB service. */ + status = dmub_srv_create(dmub_srv, &create_params); + if (status != DMUB_STATUS_OK) { + drm_err(adev_to_drm(adev), "Error creating DMUB service: %d\n", status); + return -EINVAL; + } + + /* Extract the FW meta info. */ + memset(&fw_meta_info_params, 0, sizeof(fw_meta_info_params)); + + fw_meta_info_params.inst_const_size = le32_to_cpu(hdr->inst_const_bytes) - + PSP_HEADER_BYTES_256; + fw_meta_info_params.bss_data_size = le32_to_cpu(hdr->bss_data_bytes); + fw_meta_info_params.fw_inst_const = adev->dm.dmub_fw->data + + le32_to_cpu(hdr->header.ucode_array_offset_bytes) + + PSP_HEADER_BYTES_256; + fw_meta_info_params.fw_bss_data = fw_meta_info_params.bss_data_size ? adev->dm.dmub_fw->data + + le32_to_cpu(hdr->header.ucode_array_offset_bytes) + + le32_to_cpu(hdr->inst_const_bytes) : NULL; + fw_meta_info_params.custom_psp_footer_size = 0; + + status = dmub_srv_get_fw_meta_info_from_raw_fw(&fw_meta_info_params, &fw_info); + if (status != DMUB_STATUS_OK) { + /* Skip returning early, just log the error. */ + drm_err(adev_to_drm(adev), "Error getting DMUB FW meta info: %d\n", status); + } + + /* Calculate the size of all the regions for the DMUB service. */ + memset(®ion_params, 0, sizeof(region_params)); + + region_params.inst_const_size = fw_meta_info_params.inst_const_size; + region_params.bss_data_size = fw_meta_info_params.bss_data_size; + region_params.vbios_size = adev->bios_size; + region_params.fw_bss_data = fw_meta_info_params.fw_bss_data; + region_params.fw_inst_const = fw_meta_info_params.fw_inst_const; + region_params.window_memory_type = window_memory_type; + region_params.fw_info = (status == DMUB_STATUS_OK) ? &fw_info : NULL; + + status = dmub_srv_calc_region_info(dmub_srv, ®ion_params, + ®ion_info); + + if (status != DMUB_STATUS_OK) { + drm_err(adev_to_drm(adev), "Error calculating DMUB region info: %d\n", status); + return -EINVAL; + } + + /* + * Allocate a framebuffer based on the total size of all the regions. + * TODO: Move this into GART. + */ + r = amdgpu_bo_create_kernel(adev, region_info.fb_size, PAGE_SIZE, + AMDGPU_GEM_DOMAIN_VRAM | + AMDGPU_GEM_DOMAIN_GTT, + &adev->dm.dmub_bo, + &adev->dm.dmub_bo_gpu_addr, + &adev->dm.dmub_bo_cpu_addr); + if (r) + return r; + + /* Rebase the regions on the framebuffer address. */ + memset(&memory_params, 0, sizeof(memory_params)); + memory_params.cpu_fb_addr = adev->dm.dmub_bo_cpu_addr; + memory_params.gpu_fb_addr = adev->dm.dmub_bo_gpu_addr; + memory_params.region_info = ®ion_info; + memory_params.window_memory_type = window_memory_type; + + adev->dm.dmub_fb_info = kzalloc_obj(*adev->dm.dmub_fb_info); + fb_info = adev->dm.dmub_fb_info; + + if (!fb_info) { + drm_err(adev_to_drm(adev), + "Failed to allocate framebuffer info for DMUB service!\n"); + return -ENOMEM; + } + + status = dmub_srv_calc_mem_info(dmub_srv, &memory_params, fb_info); + if (status != DMUB_STATUS_OK) { + drm_err(adev_to_drm(adev), "Error calculating DMUB FB info: %d\n", status); + return -EINVAL; + } + + adev->dm.bb_from_dmub = dm_dmub_get_vbios_bounding_box(adev); + adev->dm.fw_inst_size = fw_meta_info_params.inst_const_size; + + return 0; +} + +int dm_init_microcode(struct amdgpu_device *adev) +{ + char *fw_name_dmub; + int r; + + switch (amdgpu_ip_version(adev, DCE_HWIP, 0)) { + case IP_VERSION(2, 1, 0): + fw_name_dmub = FIRMWARE_RENOIR_DMUB; + if (ASICREV_IS_GREEN_SARDINE(adev->external_rev_id)) + fw_name_dmub = FIRMWARE_GREEN_SARDINE_DMUB; + break; + case IP_VERSION(3, 0, 0): + if (amdgpu_ip_version(adev, GC_HWIP, 0) == IP_VERSION(10, 3, 0)) + fw_name_dmub = FIRMWARE_SIENNA_CICHLID_DMUB; + else + fw_name_dmub = FIRMWARE_NAVY_FLOUNDER_DMUB; + break; + case IP_VERSION(3, 0, 1): + fw_name_dmub = FIRMWARE_VANGOGH_DMUB; + break; + case IP_VERSION(3, 0, 2): + fw_name_dmub = FIRMWARE_DIMGREY_CAVEFISH_DMUB; + break; + case IP_VERSION(3, 0, 3): + fw_name_dmub = FIRMWARE_BEIGE_GOBY_DMUB; + break; + case IP_VERSION(3, 1, 2): + case IP_VERSION(3, 1, 3): + fw_name_dmub = FIRMWARE_YELLOW_CARP_DMUB; + break; + case IP_VERSION(3, 1, 4): + fw_name_dmub = FIRMWARE_DCN_314_DMUB; + break; + case IP_VERSION(3, 1, 5): + fw_name_dmub = FIRMWARE_DCN_315_DMUB; + break; + case IP_VERSION(3, 1, 6): + fw_name_dmub = FIRMWARE_DCN316_DMUB; + break; + case IP_VERSION(3, 2, 0): + fw_name_dmub = FIRMWARE_DCN_V3_2_0_DMCUB; + break; + case IP_VERSION(3, 2, 1): + fw_name_dmub = FIRMWARE_DCN_V3_2_1_DMCUB; + break; + case IP_VERSION(3, 5, 0): + fw_name_dmub = FIRMWARE_DCN_35_DMUB; + break; + case IP_VERSION(3, 5, 1): + fw_name_dmub = FIRMWARE_DCN_351_DMUB; + break; + case IP_VERSION(3, 6, 0): + fw_name_dmub = FIRMWARE_DCN_36_DMUB; + break; + case IP_VERSION(4, 0, 1): + fw_name_dmub = FIRMWARE_DCN_401_DMUB; + break; + case IP_VERSION(4, 2, 0): + fw_name_dmub = FIRMWARE_DCN_42_DMUB; + break; + case IP_VERSION(4, 2, 1): + fw_name_dmub = FIRMWARE_DCN_42B_DMUB; + break; + default: + /* ASIC doesn't support DMUB. */ + return 0; + } + r = amdgpu_ucode_request(adev, &adev->dm.dmub_fw, AMDGPU_UCODE_REQUIRED, + "%s", fw_name_dmub); + return r; +} + +int amdgpu_dm_process_dmub_aux_transfer_sync( + struct dc_context *ctx, + unsigned int link_index, + struct aux_payload *payload, + enum aux_return_code_type *operation_result) +{ + struct amdgpu_device *adev = ctx->driver_context; + struct dmub_notification *p_notify = adev->dm.dmub_notify; + int ret = -1; + + mutex_lock(&adev->dm.dpia_aux_lock); + if (!dc_process_dmub_aux_transfer_async(ctx->dc, link_index, payload)) { + *operation_result = AUX_RET_ERROR_ENGINE_ACQUIRE; + goto out; + } + + if (!wait_for_completion_timeout(&adev->dm.dmub_aux_transfer_done, 10 * HZ)) { + drm_err(adev_to_drm(adev), "wait_for_completion_timeout timeout!"); + *operation_result = AUX_RET_ERROR_TIMEOUT; + goto out; + } + + if (p_notify->result != AUX_RET_SUCCESS) { + /* + * Transient states before tunneling is enabled could + * lead to this error. We can ignore this for now. + */ + if (p_notify->result == AUX_RET_ERROR_PROTOCOL_ERROR) { + drm_warn(adev_to_drm(adev), "DPIA AUX failed on 0x%x(%d), error %d\n", + payload->address, payload->length, + p_notify->result); + } + *operation_result = p_notify->result; + goto out; + } + + payload->reply[0] = adev->dm.dmub_notify->aux_reply.command & 0xF; + if (adev->dm.dmub_notify->aux_reply.command & 0xF0) + /* The reply is stored in the top nibble of the command. */ + payload->reply[0] = (adev->dm.dmub_notify->aux_reply.command >> 4) & 0xF; + + /*write req may receive a byte indicating partially written number as well*/ + if (p_notify->aux_reply.length) + memcpy(payload->data, p_notify->aux_reply.data, + p_notify->aux_reply.length); + + /* success */ + ret = p_notify->aux_reply.length; + *operation_result = p_notify->result; +out: + reinit_completion(&adev->dm.dmub_aux_transfer_done); + mutex_unlock(&adev->dm.dpia_aux_lock); + return ret; +} + +static void abort_fused_io( + struct dc_context *ctx, + const struct dmub_cmd_fused_request *request +) +{ + union dmub_rb_cmd command = { 0 }; + struct dmub_rb_cmd_fused_io *io = &command.fused_io; + + io->header.type = DMUB_CMD__FUSED_IO; + io->header.sub_type = DMUB_CMD__FUSED_IO_ABORT; + io->header.payload_bytes = sizeof(*io) - sizeof(io->header); + io->request = *request; + dm_execute_dmub_cmd(ctx, &command, DM_DMUB_WAIT_TYPE_NO_WAIT); +} + +static bool execute_fused_io( + struct amdgpu_device *dev, + struct dc_context *ctx, + union dmub_rb_cmd *commands, + uint8_t count, + uint32_t timeout_us +) +{ + const uint8_t ddc_line = commands[0].fused_io.request.u.aux.ddc_line; + + if (ddc_line >= ARRAY_SIZE(dev->dm.fused_io)) + return false; + + struct fused_io_sync *sync = &dev->dm.fused_io[ddc_line]; + struct dmub_rb_cmd_fused_io *first = &commands[0].fused_io; + const bool result = dm_execute_dmub_cmd_list(ctx, count, commands, DM_DMUB_WAIT_TYPE_WAIT_WITH_REPLY) + && first->header.ret_status + && first->request.status == FUSED_REQUEST_STATUS_SUCCESS; + + if (!result) + return false; + + while (wait_for_completion_timeout(&sync->replied, usecs_to_jiffies(timeout_us))) { + reinit_completion(&sync->replied); + + struct dmub_cmd_fused_request *reply = (struct dmub_cmd_fused_request *) sync->reply_data; + + static_assert(sizeof(*reply) <= sizeof(sync->reply_data), "Size mismatch"); + + if (reply->identifier == first->request.identifier) { + first->request = *reply; + return true; + } + } + + reinit_completion(&sync->replied); + first->request.status = FUSED_REQUEST_STATUS_TIMEOUT; + abort_fused_io(ctx, &first->request); + return false; +} + +bool amdgpu_dm_execute_fused_io( + struct amdgpu_device *dev, + struct dc_link *link, + union dmub_rb_cmd *commands, + uint8_t count, + uint32_t timeout_us) +{ + struct amdgpu_display_manager *dm = &dev->dm; + + mutex_lock(&dm->dpia_aux_lock); + + const bool result = execute_fused_io(dev, link->ctx, commands, count, timeout_us); + + mutex_unlock(&dm->dpia_aux_lock); + return result; +} + +int amdgpu_dm_process_dmub_set_config_sync( + struct dc_context *ctx, + unsigned int link_index, + struct set_config_cmd_payload *payload, + enum set_config_status *operation_result) +{ + struct amdgpu_device *adev = ctx->driver_context; + bool is_cmd_complete; + int ret; + + mutex_lock(&adev->dm.dpia_aux_lock); + is_cmd_complete = dc_process_dmub_set_config_async(ctx->dc, + link_index, payload, adev->dm.dmub_notify); + + if (is_cmd_complete || wait_for_completion_timeout(&adev->dm.dmub_aux_transfer_done, 10 * HZ)) { + ret = 0; + *operation_result = adev->dm.dmub_notify->sc_status; + } else { + drm_err(adev_to_drm(adev), "wait_for_completion_timeout timeout!"); + ret = -1; + *operation_result = SET_CONFIG_UNKNOWN_ERROR; + } + + if (!is_cmd_complete) + reinit_completion(&adev->dm.dmub_aux_transfer_done); + mutex_unlock(&adev->dm.dpia_aux_lock); + return ret; +} + +bool dm_execute_dmub_cmd(const struct dc_context *ctx, union dmub_rb_cmd *cmd, enum dm_dmub_wait_type wait_type) +{ + struct amdgpu_device *adev = ctx->driver_context; + + guard(spinlock_irqsave)(&adev->dm.dmub_lock); + return dc_dmub_srv_cmd_run(ctx->dmub_srv, cmd, wait_type); +} + +bool dm_execute_dmub_cmd_list(const struct dc_context *ctx, unsigned int count, union dmub_rb_cmd *cmd, enum dm_dmub_wait_type wait_type) +{ + struct amdgpu_device *adev = ctx->driver_context; + + guard(spinlock_irqsave)(&adev->dm.dmub_lock); + return dc_dmub_srv_cmd_run_list(ctx->dmub_srv, count, cmd, wait_type); +} diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.h new file mode 100644 index 000000000000..a4a03e40ec37 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.h @@ -0,0 +1,68 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright 2026 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 AMDGPU_DM_AMDGPU_DM_DMUB_H_ +#define AMDGPU_DM_AMDGPU_DM_DMUB_H_ + +#include "amdgpu.h" + +void dm_dmub_aux_setconfig_callback(struct amdgpu_device *adev, + struct dmub_notification *notify); +void dm_dmub_aux_fused_io_callback(struct amdgpu_device *adev, + struct dmub_notification *notify); +bool dm_register_dmub_notify_callback(struct amdgpu_device *adev, + enum dmub_notification_type type, + dmub_notify_interrupt_callback_t callback, + bool dmub_int_thread_offload); +int dm_dmub_hw_init(struct amdgpu_device *adev); +void dm_dmub_hw_resume(struct amdgpu_device *adev); +enum dmub_ips_disable_type dm_get_default_ips_mode(struct amdgpu_device *adev); +int dm_dmub_sw_init(struct amdgpu_device *adev); +int dm_init_microcode(struct amdgpu_device *adev); + +#define FIRMWARE_RENOIR_DMUB "amdgpu/renoir_dmcub.bin" +#define FIRMWARE_SIENNA_CICHLID_DMUB "amdgpu/sienna_cichlid_dmcub.bin" +#define FIRMWARE_NAVY_FLOUNDER_DMUB "amdgpu/navy_flounder_dmcub.bin" +#define FIRMWARE_GREEN_SARDINE_DMUB "amdgpu/green_sardine_dmcub.bin" +#define FIRMWARE_VANGOGH_DMUB "amdgpu/vangogh_dmcub.bin" +#define FIRMWARE_DIMGREY_CAVEFISH_DMUB "amdgpu/dimgrey_cavefish_dmcub.bin" +#define FIRMWARE_BEIGE_GOBY_DMUB "amdgpu/beige_goby_dmcub.bin" +#define FIRMWARE_YELLOW_CARP_DMUB "amdgpu/yellow_carp_dmcub.bin" +#define FIRMWARE_DCN_314_DMUB "amdgpu/dcn_3_1_4_dmcub.bin" +#define FIRMWARE_DCN_315_DMUB "amdgpu/dcn_3_1_5_dmcub.bin" +#define FIRMWARE_DCN316_DMUB "amdgpu/dcn_3_1_6_dmcub.bin" +#define FIRMWARE_DCN_V3_2_0_DMCUB "amdgpu/dcn_3_2_0_dmcub.bin" +#define FIRMWARE_DCN_V3_2_1_DMCUB "amdgpu/dcn_3_2_1_dmcub.bin" +#define FIRMWARE_DCN_35_DMUB "amdgpu/dcn_3_5_dmcub.bin" +#define FIRMWARE_DCN_351_DMUB "amdgpu/dcn_3_5_1_dmcub.bin" +#define FIRMWARE_DCN_36_DMUB "amdgpu/dcn_3_6_dmcub.bin" +#define FIRMWARE_DCN_401_DMUB "amdgpu/dcn_4_0_1_dmcub.bin" +#define FIRMWARE_DCN_42_DMUB "amdgpu/dcn_4_2_dmcub.bin" +#define FIRMWARE_DCN_42B_DMUB "amdgpu/dcn_4_2_1_dmcub.bin" +#define FIRMWARE_RAVEN_DMCU "amdgpu/raven_dmcu.bin" +#define FIRMWARE_NAVI12_DMCU "amdgpu/navi12_dmcu.bin" + +#endif /* AMDGPU_DM_AMDGPU_DM_DMUB_H_ */ -- cgit v1.2.3 From 0618dec49415706c9701ce6719b5081593037210 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Thu, 30 Apr 2026 11:23:59 -0600 Subject: drm/amd/display: Move HPD and IRQ handler code to amdgpu_dm_irq Move HPD handling (workqueue creation, debounce, handler registration) and IRQ handler callbacks (vblank, pflip, vupdate, vline0, outbox) from amdgpu_dm.c into the existing amdgpu_dm_irq.c. This keeps all IRQ-related code together rather than creating additional files. No functional change intended. Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 1542 +------------------- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 4 + .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c | 1501 ++++++++++++++++++- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h | 19 + 4 files changed, 1552 insertions(+), 1514 deletions(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') 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 f5766d083213..87a849152d81 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -193,10 +193,6 @@ static void amdgpu_dm_atomic_commit_tail(struct drm_atomic_commit *state); static int amdgpu_dm_atomic_check(struct drm_device *dev, struct drm_atomic_commit *state); -static void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector, - enum dc_detect_reason reason); -static void handle_hpd_rx_irq(void *param); - static bool is_timing_unchanged_for_freesync(struct drm_crtc_state *old_crtc_state, struct drm_crtc_state *new_crtc_state); @@ -291,27 +287,6 @@ static int dm_soft_reset(struct amdgpu_ip_block *ip_block) return 0; } -static struct amdgpu_crtc * -get_crtc_by_otg_inst(struct amdgpu_device *adev, - int otg_inst) -{ - struct drm_device *dev = adev_to_drm(adev); - struct drm_crtc *crtc; - struct amdgpu_crtc *amdgpu_crtc; - - if (WARN_ON(otg_inst == -1)) - return adev->mode_info.crtcs[0]; - - list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { - amdgpu_crtc = to_amdgpu_crtc(crtc); - - if (amdgpu_crtc->otg_inst == otg_inst) - return amdgpu_crtc; - } - - return NULL; -} - static inline bool is_dc_timing_adjust_needed(struct dm_crtc_state *old_state, struct dm_crtc_state *new_state) { @@ -378,566 +353,6 @@ static inline bool update_planes_and_stream_adapter(struct dc *dc, stream_update); } -/** - * dm_pflip_high_irq() - Handle pageflip interrupt - * @interrupt_params: ignored - * - * Handles the pageflip interrupt by notifying all interested parties - * that the pageflip has been completed. - */ -static void dm_pflip_high_irq(void *interrupt_params) -{ - struct amdgpu_crtc *amdgpu_crtc; - struct common_irq_params *irq_params = interrupt_params; - struct amdgpu_device *adev = irq_params->adev; - struct drm_device *dev = adev_to_drm(adev); - unsigned long flags; - struct drm_pending_vblank_event *e; - u32 vpos, hpos, v_blank_start, v_blank_end; - bool vrr_active; - - amdgpu_crtc = get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_PFLIP); - - /* IRQ could occur when in initial stage */ - /* TODO work and BO cleanup */ - if (amdgpu_crtc == NULL) { - drm_dbg_state(dev, "CRTC is null, returning.\n"); - return; - } - - spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags); - - if (amdgpu_crtc->pflip_status != AMDGPU_FLIP_SUBMITTED) { - drm_dbg_state(dev, - "amdgpu_crtc->pflip_status = %d != AMDGPU_FLIP_SUBMITTED(%d) on crtc:%d[%p]\n", - amdgpu_crtc->pflip_status, AMDGPU_FLIP_SUBMITTED, - amdgpu_crtc->crtc_id, amdgpu_crtc); - spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags); - return; - } - - /* page flip completed. */ - e = amdgpu_crtc->event; - amdgpu_crtc->event = NULL; - - WARN_ON(!e); - - vrr_active = amdgpu_dm_crtc_vrr_active_irq(amdgpu_crtc); - - /* Fixed refresh rate, or VRR scanout position outside front-porch? */ - if (!vrr_active || - !dc_stream_get_scanoutpos(amdgpu_crtc->dm_irq_params.stream, &v_blank_start, - &v_blank_end, &hpos, &vpos) || - (vpos < v_blank_start)) { - /* Update to correct count and vblank timestamp if racing with - * vblank irq. This also updates to the correct vblank timestamp - * even in VRR mode, as scanout is past the front-porch atm. - */ - drm_crtc_accurate_vblank_count(&amdgpu_crtc->base); - - /* Wake up userspace by sending the pageflip event with proper - * count and timestamp of vblank of flip completion. - */ - if (e) { - drm_crtc_send_vblank_event(&amdgpu_crtc->base, e); - - /* Event sent, so done with vblank for this flip */ - drm_crtc_vblank_put(&amdgpu_crtc->base); - } - } else if (e) { - /* VRR active and inside front-porch: vblank count and - * timestamp for pageflip event will only be up to date after - * drm_crtc_handle_vblank() has been executed from late vblank - * irq handler after start of back-porch (vline 0). We queue the - * pageflip event for send-out by drm_crtc_handle_vblank() with - * updated timestamp and count, once it runs after us. - * - * We need to open-code this instead of using the helper - * drm_crtc_arm_vblank_event(), as that helper would - * call drm_crtc_accurate_vblank_count(), which we must - * not call in VRR mode while we are in front-porch! - */ - - /* sequence will be replaced by real count during send-out. */ - e->sequence = drm_crtc_vblank_count(&amdgpu_crtc->base); - e->pipe = amdgpu_crtc->crtc_id; - - list_add_tail(&e->base.link, &adev_to_drm(adev)->vblank_event_list); - e = NULL; - } - - /* Keep track of vblank of this flip for flip throttling. We use the - * cooked hw counter, as that one incremented at start of this vblank - * of pageflip completion, so last_flip_vblank is the forbidden count - * for queueing new pageflips if vsync + VRR is enabled. - */ - amdgpu_crtc->dm_irq_params.last_flip_vblank = - amdgpu_get_vblank_counter_kms(&amdgpu_crtc->base); - - amdgpu_crtc->pflip_status = AMDGPU_FLIP_NONE; - spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags); - - drm_dbg_state(dev, - "crtc:%d[%p], pflip_stat:AMDGPU_FLIP_NONE, vrr[%d]-fp %d\n", - amdgpu_crtc->crtc_id, amdgpu_crtc, vrr_active, (int)!e); -} - -static void dm_handle_vmin_vmax_update(struct work_struct *offload_work) -{ - struct vupdate_offload_work *work = container_of(offload_work, struct vupdate_offload_work, work); - struct amdgpu_device *adev = work->adev; - struct dc_stream_state *stream = work->stream; - struct dc_crtc_timing_adjust *adjust = work->adjust; - - mutex_lock(&adev->dm.dc_lock); - dc_stream_adjust_vmin_vmax(adev->dm.dc, stream, adjust); - mutex_unlock(&adev->dm.dc_lock); - - dc_stream_release(stream); - kfree(work->adjust); - kfree(work); -} - -static void schedule_dc_vmin_vmax(struct amdgpu_device *adev, - struct dc_stream_state *stream, - struct dc_crtc_timing_adjust *adjust) -{ - struct vupdate_offload_work *offload_work = kzalloc_obj(*offload_work, - GFP_NOWAIT); - if (!offload_work) { - drm_dbg_driver(adev_to_drm(adev), "Failed to allocate vupdate_offload_work\n"); - return; - } - - struct dc_crtc_timing_adjust *adjust_copy = kzalloc_obj(*adjust_copy, - GFP_NOWAIT); - if (!adjust_copy) { - drm_dbg_driver(adev_to_drm(adev), "Failed to allocate adjust_copy\n"); - kfree(offload_work); - return; - } - - dc_stream_retain(stream); - memcpy(adjust_copy, adjust, sizeof(*adjust_copy)); - - INIT_WORK(&offload_work->work, dm_handle_vmin_vmax_update); - offload_work->adev = adev; - offload_work->stream = stream; - offload_work->adjust = adjust_copy; - - queue_work(system_percpu_wq, &offload_work->work); -} - -static void dm_vupdate_high_irq(void *interrupt_params) -{ - struct common_irq_params *irq_params = interrupt_params; - struct amdgpu_device *adev = irq_params->adev; - struct amdgpu_crtc *acrtc; - struct drm_device *drm_dev; - struct drm_vblank_crtc *vblank; - ktime_t frame_duration_ns, previous_timestamp; - unsigned long flags; - int vrr_active; - - acrtc = get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_VUPDATE); - - if (acrtc) { - vrr_active = amdgpu_dm_crtc_vrr_active_irq(acrtc); - drm_dev = acrtc->base.dev; - vblank = drm_crtc_vblank_crtc(&acrtc->base); - previous_timestamp = atomic64_read(&irq_params->previous_timestamp); - frame_duration_ns = vblank->time - previous_timestamp; - - if (frame_duration_ns > 0) { - trace_amdgpu_refresh_rate_track(acrtc->base.index, - frame_duration_ns, - ktime_divns(NSEC_PER_SEC, frame_duration_ns)); - atomic64_set(&irq_params->previous_timestamp, vblank->time); - } - - drm_dbg_vbl(drm_dev, - "crtc:%d, vupdate-vrr:%d\n", acrtc->crtc_id, - vrr_active); - - /* Core vblank handling is done here after end of front-porch in - * vrr mode, as vblank timestamping will give valid results - * while now done after front-porch. This will also deliver - * page-flip completion events that have been queued to us - * if a pageflip happened inside front-porch. - */ - if (vrr_active && acrtc->dm_irq_params.stream) { - bool replay_en = acrtc->dm_irq_params.stream->link->replay_settings.replay_feature_enabled; - bool psr_en = acrtc->dm_irq_params.stream->link->psr_settings.psr_feature_enabled; - bool fs_active_var_en = acrtc->dm_irq_params.freesync_config.state - == VRR_STATE_ACTIVE_VARIABLE; - - amdgpu_dm_crtc_handle_vblank(acrtc); - - /* BTR processing for pre-DCE12 ASICs */ - if (adev->family < AMDGPU_FAMILY_AI) { - spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags); - mod_freesync_handle_v_update( - adev->dm.freesync_module, - acrtc->dm_irq_params.stream, - &acrtc->dm_irq_params.vrr_params); - - if (fs_active_var_en || (!fs_active_var_en && !replay_en && !psr_en)) { - schedule_dc_vmin_vmax(adev, - acrtc->dm_irq_params.stream, - &acrtc->dm_irq_params.vrr_params.adjust); - } - spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags); - } - } - } -} - -/** - * dm_crtc_high_irq() - Handles CRTC interrupt - * @interrupt_params: used for determining the CRTC instance - * - * Handles the CRTC/VSYNC interrupt by notfying DRM's VBLANK - * event handler. - */ -static void dm_crtc_high_irq(void *interrupt_params) -{ - struct common_irq_params *irq_params = interrupt_params; - struct amdgpu_device *adev = irq_params->adev; - struct drm_writeback_job *job; - struct amdgpu_crtc *acrtc; - unsigned long flags; - int vrr_active; - - acrtc = get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_VBLANK); - if (!acrtc) - return; - - if (acrtc->wb_conn) { - spin_lock_irqsave(&acrtc->wb_conn->job_lock, flags); - - if (acrtc->wb_pending) { - job = list_first_entry_or_null(&acrtc->wb_conn->job_queue, - struct drm_writeback_job, - list_entry); - acrtc->wb_pending = false; - spin_unlock_irqrestore(&acrtc->wb_conn->job_lock, flags); - - if (job) { - unsigned int v_total, refresh_hz; - struct dc_stream_state *stream = acrtc->dm_irq_params.stream; - - v_total = stream->adjust.v_total_max ? - stream->adjust.v_total_max : stream->timing.v_total; - refresh_hz = div_u64((uint64_t) stream->timing.pix_clk_100hz * - 100LL, (v_total * stream->timing.h_total)); - mdelay(1000 / refresh_hz); - - drm_writeback_signal_completion(acrtc->wb_conn, 0); - dc_stream_fc_disable_writeback(adev->dm.dc, - acrtc->dm_irq_params.stream, 0); - } - } else - spin_unlock_irqrestore(&acrtc->wb_conn->job_lock, flags); - } - - vrr_active = amdgpu_dm_crtc_vrr_active_irq(acrtc); - - drm_dbg_vbl(adev_to_drm(adev), - "crtc:%d, vupdate-vrr:%d, planes:%d\n", acrtc->crtc_id, - vrr_active, acrtc->dm_irq_params.active_planes); - - /** - * Core vblank handling at start of front-porch is only possible - * in non-vrr mode, as only there vblank timestamping will give - * valid results while done in front-porch. Otherwise defer it - * to dm_vupdate_high_irq after end of front-porch. - */ - if (!vrr_active) - amdgpu_dm_crtc_handle_vblank(acrtc); - - /** - * Following stuff must happen at start of vblank, for crc - * computation and below-the-range btr support in vrr mode. - */ - amdgpu_dm_crtc_handle_crc_irq(&acrtc->base); - - /* BTR updates need to happen before VUPDATE on Vega and above. */ - if (adev->family < AMDGPU_FAMILY_AI) - return; - - spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags); - - if (acrtc->dm_irq_params.stream && - acrtc->dm_irq_params.vrr_params.supported) { - bool replay_en = acrtc->dm_irq_params.stream->link->replay_settings.replay_feature_enabled; - bool psr_en = acrtc->dm_irq_params.stream->link->psr_settings.psr_feature_enabled; - bool fs_active_var_en = acrtc->dm_irq_params.freesync_config.state == VRR_STATE_ACTIVE_VARIABLE; - - mod_freesync_handle_v_update(adev->dm.freesync_module, - acrtc->dm_irq_params.stream, - &acrtc->dm_irq_params.vrr_params); - - /* update vmin_vmax only if freesync is enabled, or only if PSR and REPLAY are disabled */ - if (fs_active_var_en || (!fs_active_var_en && !replay_en && !psr_en)) { - schedule_dc_vmin_vmax(adev, acrtc->dm_irq_params.stream, - &acrtc->dm_irq_params.vrr_params.adjust); - } - } - - /* - * If there aren't any active_planes then DCH HUBP may be clock-gated. - * In that case, pageflip completion interrupts won't fire and pageflip - * completion events won't get delivered. Prevent this by sending - * pending pageflip events from here if a flip is still pending. - * - * If any planes are enabled, use dm_pflip_high_irq() instead, to - * avoid race conditions between flip programming and completion, - * which could cause too early flip completion events. - */ - if (adev->family >= AMDGPU_FAMILY_RV && - acrtc->pflip_status == AMDGPU_FLIP_SUBMITTED && - acrtc->dm_irq_params.active_planes == 0) { - if (acrtc->event) { - drm_crtc_send_vblank_event(&acrtc->base, acrtc->event); - acrtc->event = NULL; - drm_crtc_vblank_put(&acrtc->base); - } - acrtc->pflip_status = AMDGPU_FLIP_NONE; - } - - spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags); -} - -#if defined(CONFIG_DRM_AMD_SECURE_DISPLAY) -/** - * dm_dcn_vertical_interrupt0_high_irq() - Handles OTG Vertical interrupt0 for - * DCN generation ASICs - * @interrupt_params: interrupt parameters - * - * Used to set crc window/read out crc value at vertical line 0 position - */ -static void dm_dcn_vertical_interrupt0_high_irq(void *interrupt_params) -{ - struct common_irq_params *irq_params = interrupt_params; - struct amdgpu_device *adev = irq_params->adev; - struct amdgpu_crtc *acrtc; - - acrtc = get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_VLINE0); - - if (!acrtc) - return; - - amdgpu_dm_crtc_handle_crc_window_irq(&acrtc->base); -} -#endif /* CONFIG_DRM_AMD_SECURE_DISPLAY */ - -/** - * dmub_hpd_callback - DMUB HPD interrupt processing callback. - * @adev: amdgpu_device pointer - * @notify: dmub notification structure - * - * Dmub Hpd interrupt processing callback. Gets displayindex through the - * ink index and calls helper to do the processing. - */ -static void dmub_hpd_callback(struct amdgpu_device *adev, - struct dmub_notification *notify) -{ - struct amdgpu_dm_connector *aconnector; - struct amdgpu_dm_connector *hpd_aconnector = NULL; - struct drm_connector *connector; - struct drm_connector_list_iter iter; - struct dc_link *link; - u8 link_index = 0; - struct drm_device *dev; - - if (adev == NULL) - return; - - if (notify == NULL) { - drm_err(adev_to_drm(adev), "DMUB HPD callback notification was NULL"); - return; - } - - if (notify->link_index > adev->dm.dc->link_count) { - drm_err(adev_to_drm(adev), "DMUB HPD index (%u)is abnormal", notify->link_index); - return; - } - - /* Skip DMUB HPD IRQ in suspend/resume. We will probe them later. */ - if (notify->type == DMUB_NOTIFICATION_HPD && adev->in_suspend) { - drm_info(adev_to_drm(adev), "Skip DMUB HPD IRQ callback in suspend/resume\n"); - return; - } - - link_index = notify->link_index; - link = adev->dm.dc->links[link_index]; - dev = adev->dm.ddev; - - drm_connector_list_iter_begin(dev, &iter); - drm_for_each_connector_iter(connector, &iter) { - - if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) - continue; - - aconnector = to_amdgpu_dm_connector(connector); - if (link && aconnector->dc_link == link) { - if (notify->type == DMUB_NOTIFICATION_HPD) - drm_info(adev_to_drm(adev), "DMUB HPD IRQ callback: link_index=%u\n", link_index); - else if (notify->type == DMUB_NOTIFICATION_HPD_IRQ) - drm_info(adev_to_drm(adev), "DMUB HPD RX IRQ callback: link_index=%u\n", link_index); - else - drm_warn(adev_to_drm(adev), "DMUB Unknown HPD callback type %d, link_index=%u\n", - notify->type, link_index); - - hpd_aconnector = aconnector; - break; - } - } - drm_connector_list_iter_end(&iter); - - if (hpd_aconnector) { - if (notify->type == DMUB_NOTIFICATION_HPD) { - if (hpd_aconnector->dc_link->hpd_status == (notify->hpd_status == DP_HPD_PLUG)) - drm_warn(adev_to_drm(adev), "DMUB reported hpd status unchanged. link_index=%u\n", link_index); - handle_hpd_irq_helper(hpd_aconnector, DETECT_REASON_HPD); - } else if (notify->type == DMUB_NOTIFICATION_HPD_IRQ) { - handle_hpd_rx_irq(hpd_aconnector); - } - } -} - -/** - * dmub_hpd_sense_callback - DMUB HPD sense processing callback. - * @adev: amdgpu_device pointer - * @notify: dmub notification structure - * - * HPD sense changes can occur during low power states and need to be - * notified from firmware to driver. - */ -static void dmub_hpd_sense_callback(struct amdgpu_device *adev, - struct dmub_notification *notify) -{ - drm_dbg_driver(adev_to_drm(adev), "DMUB HPD SENSE callback.\n"); -} - -static void dm_handle_hpd_work(struct work_struct *work) -{ - struct dmub_hpd_work *dmub_hpd_wrk; - - dmub_hpd_wrk = container_of(work, struct dmub_hpd_work, handle_hpd_work); - - if (!dmub_hpd_wrk->dmub_notify) { - drm_err(adev_to_drm(dmub_hpd_wrk->adev), "dmub_hpd_wrk dmub_notify is NULL"); - return; - } - - if (dmub_hpd_wrk->dmub_notify->type < ARRAY_SIZE(dmub_hpd_wrk->adev->dm.dmub_callback)) { - dmub_hpd_wrk->adev->dm.dmub_callback[dmub_hpd_wrk->dmub_notify->type](dmub_hpd_wrk->adev, - dmub_hpd_wrk->dmub_notify); - } - - kfree(dmub_hpd_wrk->dmub_notify); - kfree(dmub_hpd_wrk); - -} - -static const char *dmub_notification_type_str(enum dmub_notification_type e) -{ - switch (e) { - case DMUB_NOTIFICATION_NO_DATA: - return "NO_DATA"; - case DMUB_NOTIFICATION_AUX_REPLY: - return "AUX_REPLY"; - case DMUB_NOTIFICATION_HPD: - return "HPD"; - case DMUB_NOTIFICATION_HPD_IRQ: - return "HPD_IRQ"; - case DMUB_NOTIFICATION_SET_CONFIG_REPLY: - return "SET_CONFIG_REPLY"; - case DMUB_NOTIFICATION_DPIA_NOTIFICATION: - return "DPIA_NOTIFICATION"; - case DMUB_NOTIFICATION_HPD_SENSE_NOTIFY: - return "HPD_SENSE_NOTIFY"; - case DMUB_NOTIFICATION_FUSED_IO: - return "FUSED_IO"; - default: - return ""; - } -} - -#define DMUB_TRACE_MAX_READ 64 -/** - * dm_dmub_outbox1_low_irq() - Handles Outbox interrupt - * @interrupt_params: used for determining the Outbox instance - * - * Handles the Outbox Interrupt - * event handler. - */ -static void dm_dmub_outbox1_low_irq(void *interrupt_params) -{ - struct dmub_notification notify = {0}; - struct common_irq_params *irq_params = interrupt_params; - struct amdgpu_device *adev = irq_params->adev; - struct amdgpu_display_manager *dm = &adev->dm; - struct dmcub_trace_buf_entry entry = { 0 }; - u32 count = 0; - struct dmub_hpd_work *dmub_hpd_wrk; - - do { - if (dc_dmub_srv_get_dmub_outbox0_msg(dm->dc, &entry)) { - trace_amdgpu_dmub_trace_high_irq(entry.trace_code, entry.tick_count, - entry.param0, entry.param1); - - drm_dbg_driver(adev_to_drm(adev), "trace_code:%u, tick_count:%u, param0:%u, param1:%u\n", - entry.trace_code, entry.tick_count, entry.param0, entry.param1); - } else - break; - - count++; - - } while (count <= DMUB_TRACE_MAX_READ); - - if (count > DMUB_TRACE_MAX_READ) - drm_dbg_driver(adev_to_drm(adev), "Warning : count > DMUB_TRACE_MAX_READ"); - - if (dc_enable_dmub_notifications(adev->dm.dc) && - irq_params->irq_src == DC_IRQ_SOURCE_DMCUB_OUTBOX) { - - do { - dc_stat_get_dmub_notification(adev->dm.dc, ¬ify); - if (notify.type >= ARRAY_SIZE(dm->dmub_thread_offload)) { - drm_err(adev_to_drm(adev), "DM: notify type %d invalid!", notify.type); - continue; - } - if (!dm->dmub_callback[notify.type]) { - drm_warn(adev_to_drm(adev), "DMUB notification skipped due to no handler: type=%s\n", - dmub_notification_type_str(notify.type)); - continue; - } - if (dm->dmub_thread_offload[notify.type] == true) { - dmub_hpd_wrk = kzalloc_obj(*dmub_hpd_wrk, - GFP_ATOMIC); - if (!dmub_hpd_wrk) { - drm_err(adev_to_drm(adev), "Failed to allocate dmub_hpd_wrk"); - return; - } - dmub_hpd_wrk->dmub_notify = kmemdup(¬ify, sizeof(struct dmub_notification), - GFP_ATOMIC); - if (!dmub_hpd_wrk->dmub_notify) { - kfree(dmub_hpd_wrk); - drm_err(adev_to_drm(adev), "Failed to allocate dmub_hpd_wrk->dmub_notify"); - return; - } - INIT_WORK(&dmub_hpd_wrk->handle_hpd_work, dm_handle_hpd_work); - dmub_hpd_wrk->adev = adev; - queue_work(adev->dm.delayed_hpd_wq, &dmub_hpd_wrk->handle_hpd_work); - } else { - dm->dmub_callback[notify.type](adev, ¬ify); - } - } while (notify.pending_notification); - } -} - static int dm_set_clockgating_state(struct amdgpu_ip_block *ip_block, enum amd_clockgating_state state) { @@ -1070,168 +485,23 @@ static void mmhub_read_system_context(struct amdgpu_device *adev, struct dc_phy_ } -static void force_connector_state( - struct amdgpu_dm_connector *aconnector, - enum drm_connector_force force_state) -{ - struct drm_connector *connector = &aconnector->base; - - mutex_lock(&connector->dev->mode_config.mutex); - aconnector->base.force = force_state; - mutex_unlock(&connector->dev->mode_config.mutex); +struct amdgpu_stutter_quirk { + u16 chip_vendor; + u16 chip_device; + u16 subsys_vendor; + u16 subsys_device; + u8 revision; +}; - mutex_lock(&aconnector->hpd_lock); - drm_kms_helper_connector_hotplug_event(connector); - mutex_unlock(&aconnector->hpd_lock); -} +static const struct amdgpu_stutter_quirk amdgpu_stutter_quirk_list[] = { + /* https://bugzilla.kernel.org/show_bug.cgi?id=214417 */ + { 0x1002, 0x15dd, 0x1002, 0x15dd, 0xc8 }, + { 0, 0, 0, 0, 0 }, +}; -static void dm_handle_hpd_rx_offload_work(struct work_struct *work) +static bool dm_should_disable_stutter(struct pci_dev *pdev) { - struct hpd_rx_irq_offload_work *offload_work; - struct amdgpu_dm_connector *aconnector; - struct dc_link *dc_link; - struct amdgpu_device *adev; - enum dc_connection_type new_connection_type = dc_connection_none; - unsigned long flags; - union test_response test_response; - - memset(&test_response, 0, sizeof(test_response)); - - offload_work = container_of(work, struct hpd_rx_irq_offload_work, work); - aconnector = offload_work->offload_wq->aconnector; - adev = offload_work->adev; - - if (!aconnector) { - drm_err(adev_to_drm(adev), "Can't retrieve aconnector in hpd_rx_irq_offload_work"); - goto skip; - } - - dc_link = aconnector->dc_link; - - mutex_lock(&aconnector->hpd_lock); - if (!dc_link_detect_connection_type(dc_link, &new_connection_type)) - drm_err(adev_to_drm(adev), "KMS: Failed to detect connector\n"); - mutex_unlock(&aconnector->hpd_lock); - - if (new_connection_type == dc_connection_none) - goto skip; - - if (amdgpu_in_reset(adev)) - goto skip; - - if (offload_work->data.bytes.device_service_irq.bits.UP_REQ_MSG_RDY || - offload_work->data.bytes.device_service_irq.bits.DOWN_REP_MSG_RDY) { - dm_handle_mst_sideband_msg_ready_event(&aconnector->mst_mgr, DOWN_OR_UP_MSG_RDY_EVENT); - spin_lock_irqsave(&offload_work->offload_wq->offload_lock, flags); - offload_work->offload_wq->is_handling_mst_msg_rdy_event = false; - spin_unlock_irqrestore(&offload_work->offload_wq->offload_lock, flags); - goto skip; - } - - mutex_lock(&adev->dm.dc_lock); - if (offload_work->data.bytes.device_service_irq.bits.AUTOMATED_TEST) { - dc_link_dp_handle_automated_test(dc_link); - - if (aconnector->timing_changed) { - /* force connector disconnect and reconnect */ - force_connector_state(aconnector, DRM_FORCE_OFF); - msleep(100); - force_connector_state(aconnector, DRM_FORCE_UNSPECIFIED); - } - - test_response.bits.ACK = 1; - - core_link_write_dpcd( - dc_link, - DP_TEST_RESPONSE, - &test_response.raw, - sizeof(test_response)); - } else if ((dc_link->connector_signal != SIGNAL_TYPE_EDP) && - dc_link_check_link_loss_status(dc_link, &offload_work->data) && - dc_link_dp_allow_hpd_rx_irq(dc_link)) { - /* offload_work->data is from handle_hpd_rx_irq-> - * schedule_hpd_rx_offload_work.this is defer handle - * for hpd short pulse. upon here, link status may be - * changed, need get latest link status from dpcd - * registers. if link status is good, skip run link - * training again. - */ - union hpd_irq_data irq_data; - - memset(&irq_data, 0, sizeof(irq_data)); - - /* before dc_link_dp_handle_link_loss, allow new link lost handle - * request be added to work queue if link lost at end of dc_link_ - * dp_handle_link_loss - */ - spin_lock_irqsave(&offload_work->offload_wq->offload_lock, flags); - offload_work->offload_wq->is_handling_link_loss = false; - spin_unlock_irqrestore(&offload_work->offload_wq->offload_lock, flags); - - if ((dc_link_dp_read_hpd_rx_irq_data(dc_link, &irq_data) == DC_OK) && - dc_link_check_link_loss_status(dc_link, &irq_data)) - dc_link_dp_handle_link_loss(dc_link); - } - mutex_unlock(&adev->dm.dc_lock); - -skip: - kfree(offload_work); - -} - -static struct hpd_rx_irq_offload_work_queue *hpd_rx_irq_create_workqueue(struct amdgpu_device *adev) -{ - struct dc *dc = adev->dm.dc; - int max_caps = dc->caps.max_links; - int i = 0; - struct hpd_rx_irq_offload_work_queue *hpd_rx_offload_wq = NULL; - - hpd_rx_offload_wq = kzalloc_objs(*hpd_rx_offload_wq, max_caps); - - if (!hpd_rx_offload_wq) - return NULL; - - - for (i = 0; i < max_caps; i++) { - hpd_rx_offload_wq[i].wq = - create_singlethread_workqueue("amdgpu_dm_hpd_rx_offload_wq"); - - if (hpd_rx_offload_wq[i].wq == NULL) { - drm_err(adev_to_drm(adev), "create amdgpu_dm_hpd_rx_offload_wq fail!"); - goto out_err; - } - - spin_lock_init(&hpd_rx_offload_wq[i].offload_lock); - } - - return hpd_rx_offload_wq; - -out_err: - for (i = 0; i < max_caps; i++) { - if (hpd_rx_offload_wq[i].wq) - destroy_workqueue(hpd_rx_offload_wq[i].wq); - } - kfree(hpd_rx_offload_wq); - return NULL; -} - -struct amdgpu_stutter_quirk { - u16 chip_vendor; - u16 chip_device; - u16 subsys_vendor; - u16 subsys_device; - u8 revision; -}; - -static const struct amdgpu_stutter_quirk amdgpu_stutter_quirk_list[] = { - /* https://bugzilla.kernel.org/show_bug.cgi?id=214417 */ - { 0x1002, 0x15dd, 0x1002, 0x15dd, 0xc8 }, - { 0, 0, 0, 0, 0 }, -}; - -static bool dm_should_disable_stutter(struct pci_dev *pdev) -{ - const struct amdgpu_stutter_quirk *p = amdgpu_stutter_quirk_list; + const struct amdgpu_stutter_quirk *p = amdgpu_stutter_quirk_list; while (p && p->chip_device != 0) { if (pdev->vendor == p->chip_vendor && @@ -1624,7 +894,7 @@ static int amdgpu_dm_init(struct amdgpu_device *adev) dc_hardware_init(adev->dm.dc); - adev->dm.hpd_rx_offload_wq = hpd_rx_irq_create_workqueue(adev); + adev->dm.hpd_rx_offload_wq = amdgpu_dm_hpd_rx_irq_create_workqueue(adev); if (!adev->dm.hpd_rx_offload_wq) { drm_err(adev_to_drm(adev), "failed to create hpd rx offload workqueue.\n"); goto error; @@ -1711,7 +981,7 @@ static int amdgpu_dm_init(struct amdgpu_device *adev) } /* Enable outbox notification only after IRQ handlers are registered and DMUB is alive. * It is expected that DMUB will resend any pending notifications at this point. Note - * that hpd and hpd_irq handler registration are deferred to register_hpd_handlers() to + * that hpd and hpd_irq handler registration are deferred to amdgpu_dm_register_hpd_handlers() to * align legacy interface initialization sequence. Connection status will be proactivly * detected once in the amdgpu_dm_initialize_drm_device. */ @@ -2461,7 +1731,7 @@ static void dm_gpureset_toggle_interrupts(struct amdgpu_device *adev, int i = 0; for (i = 0; i < state->stream_count; i++) { - acrtc = get_crtc_by_otg_inst( + acrtc = amdgpu_dm_get_crtc_by_otg_inst( adev, state->stream_status[i].primary_otg_inst); if (acrtc && state->stream_status[i].plane_count != 0) { @@ -2538,16 +1808,6 @@ static enum dc_status amdgpu_dm_commit_zero_streams(struct dc *dc) return dc_commit_streams(dc, ¶ms); } -static void hpd_rx_irq_work_suspend(struct amdgpu_display_manager *dm) -{ - int i; - - if (dm->hpd_rx_offload_wq) { - for (i = 0; i < dm->dc->caps.max_links; i++) - flush_workqueue(dm->hpd_rx_offload_wq[i].wq); - } -} - static int dm_cache_state(struct amdgpu_device *adev) { int r; @@ -2643,7 +1903,7 @@ static int dm_suspend(struct amdgpu_ip_block *ip_block) amdgpu_dm_irq_suspend(adev); - hpd_rx_irq_work_suspend(dm); + amdgpu_dm_hpd_rx_irq_work_suspend(dm); return 0; } @@ -2669,7 +1929,7 @@ static int dm_suspend(struct amdgpu_ip_block *ip_block) scoped_guard(mutex, &dm->dc_lock) amdgpu_dm_ism_force_full_power(dm); - hpd_rx_irq_work_suspend(dm); + amdgpu_dm_hpd_rx_irq_work_suspend(dm); dc_set_power_state(dm->dc, DC_ACPI_CM_POWER_STATE_D3); @@ -2700,7 +1960,7 @@ amdgpu_dm_find_first_crtc_matching_connector(struct drm_atomic_commit *state, return NULL; } -static void emulated_link_detect(struct dc_link *link) +void amdgpu_dm_emulated_link_detect(struct dc_link *link) { struct dc_sink_init_data sink_init_data = { 0 }; struct display_sink_capability sink_caps = { 0 }; @@ -2821,8 +2081,8 @@ static void dm_gpureset_commit_state(struct dc_state *dc_state, } } -static void apply_delay_after_dpcd_poweroff(struct amdgpu_device *adev, - struct dc_sink *sink) +void amdgpu_dm_apply_delay_after_dpcd_poweroff(struct amdgpu_device *adev, + struct dc_sink *sink) { struct dc_panel_patch *ppatch = NULL; @@ -3064,14 +2324,14 @@ static int dm_resume(struct amdgpu_ip_block *ip_block) drm_err(adev_to_drm(adev), "KMS: Failed to detect connector\n"); if (aconnector->base.force && new_connection_type == dc_connection_none) { - emulated_link_detect(aconnector->dc_link); + amdgpu_dm_emulated_link_detect(aconnector->dc_link); } else { guard(mutex)(&dm->dc_lock); dc_exit_ips_for_hw_access(dm->dc); ret = dc_link_detect(aconnector->dc_link, DETECT_REASON_RESUMEFROMS3S4); if (ret) { /* w/a delay for certain panels */ - apply_delay_after_dpcd_poweroff(adev, aconnector->dc_sink); + amdgpu_dm_apply_delay_after_dpcd_poweroff(adev, aconnector->dc_sink); } } @@ -3392,750 +2652,6 @@ void amdgpu_dm_update_connector_after_detect( mutex_unlock(&dev->mode_config.mutex); } -static bool are_sinks_equal(const struct dc_sink *sink1, const struct dc_sink *sink2) -{ - if (!sink1 || !sink2) - return false; - if (sink1->sink_signal != sink2->sink_signal) - return false; - - if (sink1->dc_edid.length != sink2->dc_edid.length) - return false; - - if (memcmp(sink1->dc_edid.raw_edid, sink2->dc_edid.raw_edid, - sink1->dc_edid.length) != 0) - return false; - return true; -} - - -/** - * DOC: hdmi_hpd_debounce_work - * - * HDMI HPD debounce delay in milliseconds. When an HDMI display toggles HPD - * (such as during power save transitions), this delay determines how long to - * wait before processing the HPD event. This allows distinguishing between a - * physical unplug (>hdmi_hpd_debounce_delay) - * and a spontaneous RX HPD toggle (base; - struct drm_device *dev = connector->dev; - struct amdgpu_device *adev = drm_to_adev(dev); - struct dc *dc = aconnector->dc_link->ctx->dc; - bool fake_reconnect = false; - bool reallow_idle = false; - bool ret = false; - guard(mutex)(&aconnector->hpd_lock); - - /* Re-detect the display */ - scoped_guard(mutex, &adev->dm.dc_lock) { - if (dc->caps.ips_support && dc->ctx->dmub_srv->idle_allowed) { - dc_allow_idle_optimizations(dc, false); - reallow_idle = true; - } - ret = dc_link_detect(aconnector->dc_link, DETECT_REASON_HPD); - } - - if (ret) { - /* Apply workaround delay for certain panels */ - apply_delay_after_dpcd_poweroff(adev, aconnector->dc_sink); - /* Compare sinks to determine if this was a spontaneous HPD toggle */ - if (are_sinks_equal(aconnector->dc_link->local_sink, aconnector->hdmi_prev_sink)) { - /* - * Sinks match - this was a spontaneous HDMI HPD toggle. - */ - drm_dbg_kms(dev, "HDMI HPD: Sink unchanged after debounce, internal re-enable\n"); - fake_reconnect = true; - } - - /* Update connector state */ - amdgpu_dm_update_connector_after_detect(aconnector); - - drm_modeset_lock_all(dev); - dm_restore_drm_connector_state(dev, connector); - drm_modeset_unlock_all(dev); - - /* Only notify OS if sink actually changed */ - if (!fake_reconnect && aconnector->base.force == DRM_FORCE_UNSPECIFIED) - drm_kms_helper_hotplug_event(dev); - } - - /* Release the cached sink reference */ - if (aconnector->hdmi_prev_sink) { - dc_sink_release(aconnector->hdmi_prev_sink); - aconnector->hdmi_prev_sink = NULL; - } - - scoped_guard(mutex, &adev->dm.dc_lock) { - if (reallow_idle && dc->caps.ips_support) - dc_allow_idle_optimizations(dc, true); - } -} - -static void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector, - enum dc_detect_reason reason) -{ - struct drm_connector *connector = &aconnector->base; - struct drm_device *dev = connector->dev; - enum dc_connection_type new_connection_type = dc_connection_none; - struct amdgpu_device *adev = drm_to_adev(dev); - struct dm_connector_state *dm_con_state = to_dm_connector_state(connector->state); - struct dc *dc = aconnector->dc_link->ctx->dc; - bool ret = false; - bool debounce_required = false; - - if (adev->dm.disable_hpd_irq) - return; - - /* - * In case of failure or MST no need to update connector status or notify the OS - * since (for MST case) MST does this in its own context. - */ - guard(mutex)(&aconnector->hpd_lock); - - if (adev->dm.hdcp_workqueue) { - hdcp_reset_display(adev->dm.hdcp_workqueue, aconnector->dc_link->link_index); - dm_con_state->update_hdcp = true; - } - if (aconnector->fake_enable) - aconnector->fake_enable = false; - - aconnector->timing_changed = false; - - if (!dc_link_detect_connection_type(aconnector->dc_link, &new_connection_type)) - drm_err(adev_to_drm(adev), "KMS: Failed to detect connector\n"); - - /* - * Check for HDMI disconnect with debounce enabled. - */ - debounce_required = (aconnector->hdmi_hpd_debounce_delay_ms > 0 && - dc_is_hdmi_signal(aconnector->dc_link->connector_signal) && - new_connection_type == dc_connection_none && - aconnector->dc_link->local_sink != NULL); - - if (aconnector->base.force && new_connection_type == dc_connection_none) { - emulated_link_detect(aconnector->dc_link); - - drm_modeset_lock_all(dev); - dm_restore_drm_connector_state(dev, connector); - drm_modeset_unlock_all(dev); - - if (aconnector->base.force == DRM_FORCE_UNSPECIFIED || - reason == DETECT_REASON_HPDRX) - drm_kms_helper_connector_hotplug_event(connector); - } else if (debounce_required) { - /* - * HDMI disconnect detected - schedule delayed work instead of - * processing immediately. This allows us to coalesce spurious - * HDMI signals from physical unplugs. - */ - drm_dbg_kms(dev, "HDMI HPD: Disconnect detected, scheduling debounce work (%u ms)\n", - aconnector->hdmi_hpd_debounce_delay_ms); - - /* Cache the current sink for later comparison */ - if (aconnector->hdmi_prev_sink) - dc_sink_release(aconnector->hdmi_prev_sink); - aconnector->hdmi_prev_sink = aconnector->dc_link->local_sink; - if (aconnector->hdmi_prev_sink) - dc_sink_retain(aconnector->hdmi_prev_sink); - - /* Schedule delayed detection. */ - if (mod_delayed_work(system_percpu_wq, - &aconnector->hdmi_hpd_debounce_work, - msecs_to_jiffies(aconnector->hdmi_hpd_debounce_delay_ms))) - drm_dbg_kms(dev, "HDMI HPD: Re-scheduled debounce work\n"); - - } else { - - /* If the aconnector->hdmi_hpd_debounce_work is scheduled, exit early */ - if (delayed_work_pending(&aconnector->hdmi_hpd_debounce_work)) - return; - - scoped_guard(mutex, &adev->dm.dc_lock) { - dc_exit_ips_for_hw_access(dc); - ret = dc_link_detect(aconnector->dc_link, reason); - } - if (ret) { - /* w/a delay for certain panels */ - apply_delay_after_dpcd_poweroff(adev, aconnector->dc_sink); - amdgpu_dm_update_connector_after_detect(aconnector); - - drm_modeset_lock_all(dev); - dm_restore_drm_connector_state(dev, connector); - drm_modeset_unlock_all(dev); - - if (aconnector->base.force == DRM_FORCE_UNSPECIFIED || - reason == DETECT_REASON_HPDRX) - drm_kms_helper_connector_hotplug_event(connector); - } - } -} - -static void handle_hpd_irq(void *param) -{ - struct amdgpu_dm_connector *aconnector = (struct amdgpu_dm_connector *)param; - - handle_hpd_irq_helper(aconnector, DETECT_REASON_HPD); - -} - -static void schedule_hpd_rx_offload_work(struct amdgpu_device *adev, struct hpd_rx_irq_offload_work_queue *offload_wq, - union hpd_irq_data hpd_irq_data) -{ - struct hpd_rx_irq_offload_work *offload_work = kzalloc_obj(*offload_work); - - if (!offload_work) { - drm_err(adev_to_drm(adev), "Failed to allocate hpd_rx_irq_offload_work.\n"); - return; - } - - INIT_WORK(&offload_work->work, dm_handle_hpd_rx_offload_work); - offload_work->data = hpd_irq_data; - offload_work->offload_wq = offload_wq; - offload_work->adev = adev; - - queue_work(offload_wq->wq, &offload_work->work); - drm_dbg_kms(adev_to_drm(adev), "queue work to handle hpd_rx offload work"); -} - -static void handle_hpd_rx_irq(void *param) -{ - struct amdgpu_dm_connector *aconnector = (struct amdgpu_dm_connector *)param; - struct drm_connector *connector = &aconnector->base; - struct drm_device *dev = connector->dev; - struct dc_link *dc_link = aconnector->dc_link; - bool is_mst_root_connector = aconnector->mst_mgr.mst_state; - bool result = false; - struct amdgpu_device *adev = drm_to_adev(dev); - union hpd_irq_data hpd_irq_data; - bool link_loss = false; - bool has_left_work = false; - int idx = dc_link->link_index; - struct hpd_rx_irq_offload_work_queue *offload_wq = &adev->dm.hpd_rx_offload_wq[idx]; - - memset(&hpd_irq_data, 0, sizeof(hpd_irq_data)); - - if (adev->dm.disable_hpd_irq) - return; - - /* - * TODO:Temporary add mutex to protect hpd interrupt not have a gpio - * conflict, after implement i2c helper, this mutex should be - * retired. - */ - mutex_lock(&aconnector->hpd_lock); - - result = dc_link_handle_hpd_rx_irq(dc_link, &hpd_irq_data, - &link_loss, true, &has_left_work); - - if (!has_left_work) - goto out; - - if (hpd_irq_data.bytes.device_service_irq.bits.AUTOMATED_TEST) { - schedule_hpd_rx_offload_work(adev, offload_wq, hpd_irq_data); - goto out; - } - - if (dc_link_dp_allow_hpd_rx_irq(dc_link)) { - if (hpd_irq_data.bytes.device_service_irq.bits.UP_REQ_MSG_RDY || - hpd_irq_data.bytes.device_service_irq.bits.DOWN_REP_MSG_RDY) { - bool skip = false; - - /* - * DOWN_REP_MSG_RDY is also handled by polling method - * mgr->cbs->poll_hpd_irq() - */ - spin_lock(&offload_wq->offload_lock); - skip = offload_wq->is_handling_mst_msg_rdy_event; - - if (!skip) - offload_wq->is_handling_mst_msg_rdy_event = true; - - spin_unlock(&offload_wq->offload_lock); - - if (!skip) - schedule_hpd_rx_offload_work(adev, offload_wq, hpd_irq_data); - - goto out; - } - - if (link_loss) { - bool skip = false; - - spin_lock(&offload_wq->offload_lock); - skip = offload_wq->is_handling_link_loss; - - if (!skip) - offload_wq->is_handling_link_loss = true; - - spin_unlock(&offload_wq->offload_lock); - - if (!skip) - schedule_hpd_rx_offload_work(adev, offload_wq, hpd_irq_data); - - goto out; - } - } - -out: - if (result && !is_mst_root_connector) { - /* Downstream Port status changed. */ - handle_hpd_irq_helper(aconnector, DETECT_REASON_HPDRX); - } - if (hpd_irq_data.bytes.device_service_irq.bits.CP_IRQ) { - if (adev->dm.hdcp_workqueue) - hdcp_handle_cpirq(adev->dm.hdcp_workqueue, aconnector->base.index); - } - - if (dc_link->type != dc_connection_mst_branch) - drm_dp_cec_irq(&aconnector->dm_dp_aux.aux); - - mutex_unlock(&aconnector->hpd_lock); -} - -static int register_hpd_handlers(struct amdgpu_device *adev) -{ - struct drm_device *dev = adev_to_drm(adev); - struct drm_connector *connector; - struct amdgpu_dm_connector *aconnector; - const struct dc_link *dc_link; - struct dc_interrupt_params int_params = {0}; - - int_params.requested_polarity = INTERRUPT_POLARITY_DEFAULT; - int_params.current_polarity = INTERRUPT_POLARITY_DEFAULT; - - if (dc_is_dmub_outbox_supported(adev->dm.dc)) { - if (!dm_register_dmub_notify_callback(adev, DMUB_NOTIFICATION_HPD, - dmub_hpd_callback, true)) { - drm_err(adev_to_drm(adev), "fail to register dmub hpd callback"); - return -EINVAL; - } - - if (!dm_register_dmub_notify_callback(adev, DMUB_NOTIFICATION_HPD_IRQ, - dmub_hpd_callback, true)) { - drm_err(adev_to_drm(adev), "fail to register dmub hpd callback"); - return -EINVAL; - } - - if (!dm_register_dmub_notify_callback(adev, DMUB_NOTIFICATION_HPD_SENSE_NOTIFY, - dmub_hpd_sense_callback, true)) { - drm_err(adev_to_drm(adev), "fail to register dmub hpd sense callback"); - return -EINVAL; - } - } - - list_for_each_entry(connector, - &dev->mode_config.connector_list, head) { - - if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) - continue; - - aconnector = to_amdgpu_dm_connector(connector); - dc_link = aconnector->dc_link; - - if (dc_link->irq_source_hpd != DC_IRQ_SOURCE_INVALID) { - int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; - int_params.irq_source = dc_link->irq_source_hpd; - - if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || - int_params.irq_source < DC_IRQ_SOURCE_HPD1 || - int_params.irq_source > DC_IRQ_SOURCE_HPD6) { - drm_err(adev_to_drm(adev), "Failed to register hpd irq!\n"); - return -EINVAL; - } - - if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, - handle_hpd_irq, (void *) aconnector)) - return -ENOMEM; - } - - if (dc_link->irq_source_hpd_rx != DC_IRQ_SOURCE_INVALID) { - - /* Also register for DP short pulse (hpd_rx). */ - int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; - int_params.irq_source = dc_link->irq_source_hpd_rx; - - if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || - int_params.irq_source < DC_IRQ_SOURCE_HPD1RX || - int_params.irq_source > DC_IRQ_SOURCE_HPD6RX) { - drm_err(adev_to_drm(adev), "Failed to register hpd rx irq!\n"); - return -EINVAL; - } - - if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, - handle_hpd_rx_irq, (void *) aconnector)) - return -ENOMEM; - } - } - return 0; -} - -/* Register IRQ sources and initialize IRQ callbacks */ -static int dce110_register_irq_handlers(struct amdgpu_device *adev) -{ - struct dc *dc = adev->dm.dc; - struct common_irq_params *c_irq_params; - struct dc_interrupt_params int_params = {0}; - int r; - int i; - unsigned int src_id; - unsigned int client_id = AMDGPU_IRQ_CLIENTID_LEGACY; - /* Use different interrupts for VBLANK on DCE 6 vs. newer. */ - const unsigned int vblank_d1 = - adev->dm.dc->ctx->dce_version >= DCE_VERSION_8_0 - ? VISLANDS30_IV_SRCID_D1_VERTICAL_INTERRUPT0 : 1; - - if (adev->family >= AMDGPU_FAMILY_AI) - client_id = SOC15_IH_CLIENTID_DCE; - - int_params.requested_polarity = INTERRUPT_POLARITY_DEFAULT; - int_params.current_polarity = INTERRUPT_POLARITY_DEFAULT; - - /* - * Actions of amdgpu_irq_add_id(): - * 1. Register a set() function with base driver. - * Base driver will call set() function to enable/disable an - * interrupt in DC hardware. - * 2. Register amdgpu_dm_irq_handler(). - * Base driver will call amdgpu_dm_irq_handler() for ALL interrupts - * coming from DC hardware. - * amdgpu_dm_irq_handler() will re-direct the interrupt to DC - * for acknowledging and handling. - */ - - /* Use VBLANK interrupt */ - for (i = 0; i < adev->mode_info.num_crtc; i++) { - src_id = vblank_d1 + i; - r = amdgpu_irq_add_id(adev, client_id, src_id, &adev->crtc_irq); - if (r) { - drm_err(adev_to_drm(adev), "Failed to add crtc irq id!\n"); - return r; - } - - int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; - int_params.irq_source = - dc_interrupt_to_irq_source(dc, src_id, 0); - - if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || - int_params.irq_source < DC_IRQ_SOURCE_VBLANK1 || - int_params.irq_source > DC_IRQ_SOURCE_VBLANK6) { - drm_err(adev_to_drm(adev), "Failed to register vblank irq!\n"); - return -EINVAL; - } - - c_irq_params = &adev->dm.vblank_params[int_params.irq_source - DC_IRQ_SOURCE_VBLANK1]; - - c_irq_params->adev = adev; - c_irq_params->irq_src = int_params.irq_source; - - if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, - dm_crtc_high_irq, c_irq_params)) - return -ENOMEM; - } - - if (dc_supports_vrr(adev->dm.dc->ctx->dce_version)) { - /* Use VUPDATE interrupt */ - for (i = 0; i < adev->mode_info.num_crtc; i++) { - src_id = VISLANDS30_IV_SRCID_D1_V_UPDATE_INT + i * 2; - r = amdgpu_irq_add_id(adev, client_id, src_id, &adev->vupdate_irq); - if (r) { - drm_err(adev_to_drm(adev), "Failed to add vupdate irq id!\n"); - return r; - } - - int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; - int_params.irq_source = - dc_interrupt_to_irq_source(dc, src_id, 0); - - if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || - int_params.irq_source < DC_IRQ_SOURCE_VUPDATE1 || - int_params.irq_source > DC_IRQ_SOURCE_VUPDATE6) { - drm_err(adev_to_drm(adev), "Failed to register vupdate irq!\n"); - return -EINVAL; - } - - c_irq_params = &adev->dm.vupdate_params[ - int_params.irq_source - DC_IRQ_SOURCE_VUPDATE1]; - c_irq_params->adev = adev; - c_irq_params->irq_src = int_params.irq_source; - - if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, - dm_vupdate_high_irq, c_irq_params)) - return -ENOMEM; - } - } - - /* Use GRPH_PFLIP interrupt */ - for (i = VISLANDS30_IV_SRCID_D1_GRPH_PFLIP; - i <= VISLANDS30_IV_SRCID_D6_GRPH_PFLIP; i += 2) { - r = amdgpu_irq_add_id(adev, client_id, i, &adev->pageflip_irq); - if (r) { - drm_err(adev_to_drm(adev), "Failed to add page flip irq id!\n"); - return r; - } - - int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; - int_params.irq_source = - dc_interrupt_to_irq_source(dc, i, 0); - - if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || - int_params.irq_source < DC_IRQ_SOURCE_PFLIP_FIRST || - int_params.irq_source > DC_IRQ_SOURCE_PFLIP_LAST) { - drm_err(adev_to_drm(adev), "Failed to register pflip irq!\n"); - return -EINVAL; - } - - c_irq_params = &adev->dm.pflip_params[int_params.irq_source - DC_IRQ_SOURCE_PFLIP_FIRST]; - - c_irq_params->adev = adev; - c_irq_params->irq_src = int_params.irq_source; - - if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, - dm_pflip_high_irq, c_irq_params)) - return -ENOMEM; - } - - /* HPD */ - r = amdgpu_irq_add_id(adev, client_id, - VISLANDS30_IV_SRCID_HOTPLUG_DETECT_A, &adev->hpd_irq); - if (r) { - drm_err(adev_to_drm(adev), "Failed to add hpd irq id!\n"); - return r; - } - - r = register_hpd_handlers(adev); - - return r; -} - -/* Register IRQ sources and initialize IRQ callbacks */ -static int dcn10_register_irq_handlers(struct amdgpu_device *adev) -{ - struct dc *dc = adev->dm.dc; - struct common_irq_params *c_irq_params; - struct dc_interrupt_params int_params = {0}; - int r; - int i; -#if defined(CONFIG_DRM_AMD_SECURE_DISPLAY) - static const unsigned int vrtl_int_srcid[] = { - DCN_1_0__SRCID__OTG1_VERTICAL_INTERRUPT0_CONTROL, - DCN_1_0__SRCID__OTG2_VERTICAL_INTERRUPT0_CONTROL, - DCN_1_0__SRCID__OTG3_VERTICAL_INTERRUPT0_CONTROL, - DCN_1_0__SRCID__OTG4_VERTICAL_INTERRUPT0_CONTROL, - DCN_1_0__SRCID__OTG5_VERTICAL_INTERRUPT0_CONTROL, - DCN_1_0__SRCID__OTG6_VERTICAL_INTERRUPT0_CONTROL - }; -#endif - - int_params.requested_polarity = INTERRUPT_POLARITY_DEFAULT; - int_params.current_polarity = INTERRUPT_POLARITY_DEFAULT; - - /* - * Actions of amdgpu_irq_add_id(): - * 1. Register a set() function with base driver. - * Base driver will call set() function to enable/disable an - * interrupt in DC hardware. - * 2. Register amdgpu_dm_irq_handler(). - * Base driver will call amdgpu_dm_irq_handler() for ALL interrupts - * coming from DC hardware. - * amdgpu_dm_irq_handler() will re-direct the interrupt to DC - * for acknowledging and handling. - */ - - /* Use VSTARTUP interrupt */ - for (i = DCN_1_0__SRCID__DC_D1_OTG_VSTARTUP; - i <= DCN_1_0__SRCID__DC_D1_OTG_VSTARTUP + adev->mode_info.num_crtc - 1; - i++) { - r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, i, &adev->crtc_irq); - - if (r) { - drm_err(adev_to_drm(adev), "Failed to add crtc irq id!\n"); - return r; - } - - int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; - int_params.irq_source = - dc_interrupt_to_irq_source(dc, i, 0); - - if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || - int_params.irq_source < DC_IRQ_SOURCE_VBLANK1 || - int_params.irq_source > DC_IRQ_SOURCE_VBLANK6) { - drm_err(adev_to_drm(adev), "Failed to register vblank irq!\n"); - return -EINVAL; - } - - c_irq_params = &adev->dm.vblank_params[int_params.irq_source - DC_IRQ_SOURCE_VBLANK1]; - - c_irq_params->adev = adev; - c_irq_params->irq_src = int_params.irq_source; - - if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, - dm_crtc_high_irq, c_irq_params)) - return -ENOMEM; - } - - /* Use otg vertical line interrupt */ -#if defined(CONFIG_DRM_AMD_SECURE_DISPLAY) - for (i = 0; i <= adev->mode_info.num_crtc - 1; i++) { - r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, - vrtl_int_srcid[i], &adev->vline0_irq); - - if (r) { - drm_err(adev_to_drm(adev), "Failed to add vline0 irq id!\n"); - return r; - } - - int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; - int_params.irq_source = - dc_interrupt_to_irq_source(dc, vrtl_int_srcid[i], 0); - - if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || - int_params.irq_source < DC_IRQ_SOURCE_DC1_VLINE0 || - int_params.irq_source > DC_IRQ_SOURCE_DC6_VLINE0) { - drm_err(adev_to_drm(adev), "Failed to register vline0 irq!\n"); - return -EINVAL; - } - - c_irq_params = &adev->dm.vline0_params[int_params.irq_source - - DC_IRQ_SOURCE_DC1_VLINE0]; - - c_irq_params->adev = adev; - c_irq_params->irq_src = int_params.irq_source; - - if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, - dm_dcn_vertical_interrupt0_high_irq, - c_irq_params)) - return -ENOMEM; - } -#endif - - /* Use VUPDATE_NO_LOCK interrupt on DCN, which seems to correspond to - * the regular VUPDATE interrupt on DCE. We want DC_IRQ_SOURCE_VUPDATEx - * to trigger at end of each vblank, regardless of state of the lock, - * matching DCE behaviour. - */ - for (i = DCN_1_0__SRCID__OTG0_IHC_V_UPDATE_NO_LOCK_INTERRUPT; - i <= DCN_1_0__SRCID__OTG0_IHC_V_UPDATE_NO_LOCK_INTERRUPT + adev->mode_info.num_crtc - 1; - i++) { - r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, i, &adev->vupdate_irq); - - if (r) { - drm_err(adev_to_drm(adev), "Failed to add vupdate irq id!\n"); - return r; - } - - int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; - int_params.irq_source = - dc_interrupt_to_irq_source(dc, i, 0); - - if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || - int_params.irq_source < DC_IRQ_SOURCE_VUPDATE1 || - int_params.irq_source > DC_IRQ_SOURCE_VUPDATE6) { - drm_err(adev_to_drm(adev), "Failed to register vupdate irq!\n"); - return -EINVAL; - } - - c_irq_params = &adev->dm.vupdate_params[int_params.irq_source - DC_IRQ_SOURCE_VUPDATE1]; - - c_irq_params->adev = adev; - c_irq_params->irq_src = int_params.irq_source; - - if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, - dm_vupdate_high_irq, c_irq_params)) - return -ENOMEM; - } - - /* Use GRPH_PFLIP interrupt */ - for (i = DCN_1_0__SRCID__HUBP0_FLIP_INTERRUPT; - i <= DCN_1_0__SRCID__HUBP0_FLIP_INTERRUPT + dc->caps.max_otg_num - 1; - i++) { - r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, i, &adev->pageflip_irq); - if (r) { - drm_err(adev_to_drm(adev), "Failed to add page flip irq id!\n"); - return r; - } - - int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; - int_params.irq_source = - dc_interrupt_to_irq_source(dc, i, 0); - - if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || - int_params.irq_source < DC_IRQ_SOURCE_PFLIP_FIRST || - int_params.irq_source > DC_IRQ_SOURCE_PFLIP_LAST) { - drm_err(adev_to_drm(adev), "Failed to register pflip irq!\n"); - return -EINVAL; - } - - c_irq_params = &adev->dm.pflip_params[int_params.irq_source - DC_IRQ_SOURCE_PFLIP_FIRST]; - - c_irq_params->adev = adev; - c_irq_params->irq_src = int_params.irq_source; - - if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, - dm_pflip_high_irq, c_irq_params)) - return -ENOMEM; - } - - /* HPD */ - r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, DCN_1_0__SRCID__DC_HPD1_INT, - &adev->hpd_irq); - if (r) { - drm_err(adev_to_drm(adev), "Failed to add hpd irq id!\n"); - return r; - } - - r = register_hpd_handlers(adev); - - return r; -} -/* Register Outbox IRQ sources and initialize IRQ callbacks */ -static int register_outbox_irq_handlers(struct amdgpu_device *adev) -{ - struct dc *dc = adev->dm.dc; - struct common_irq_params *c_irq_params; - struct dc_interrupt_params int_params = {0}; - int r, i; - - int_params.requested_polarity = INTERRUPT_POLARITY_DEFAULT; - int_params.current_polarity = INTERRUPT_POLARITY_DEFAULT; - - r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, DCN_1_0__SRCID__DMCUB_OUTBOX_LOW_PRIORITY_READY_INT, - &adev->dmub_outbox_irq); - if (r) { - drm_err(adev_to_drm(adev), "Failed to add outbox irq id!\n"); - return r; - } - - if (dc->ctx->dmub_srv) { - i = DCN_1_0__SRCID__DMCUB_OUTBOX_LOW_PRIORITY_READY_INT; - int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; - int_params.irq_source = - dc_interrupt_to_irq_source(dc, i, 0); - - c_irq_params = &adev->dm.dmub_outbox_params[0]; - - c_irq_params->adev = adev; - c_irq_params->irq_src = int_params.irq_source; - - if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, - dm_dmub_outbox1_low_irq, c_irq_params)) - return -ENOMEM; - } - - return 0; -} - /* * Acquires the lock for the atomic state object and returns * the new atomic state. @@ -4441,7 +2957,7 @@ static int amdgpu_dm_initialize_drm_device(struct amdgpu_device *adev) case IP_VERSION(4, 0, 1): case IP_VERSION(4, 2, 0): case IP_VERSION(4, 2, 1): - if (register_outbox_irq_handlers(dm->adev)) { + if (amdgpu_dm_register_outbox_irq_handlers(dm->adev)) { drm_err(adev_to_drm(adev), "DM: Failed to initialize IRQ\n"); goto fail; } @@ -4554,7 +3070,7 @@ static int amdgpu_dm_initialize_drm_device(struct amdgpu_device *adev) drm_err(adev_to_drm(adev), "KMS: Failed to detect connector\n"); if (aconnector->base.force && new_connection_type == dc_connection_none) { - emulated_link_detect(link); + amdgpu_dm_emulated_link_detect(link); amdgpu_dm_update_connector_after_detect(aconnector); } else { bool ret = false; @@ -4618,7 +3134,7 @@ static int amdgpu_dm_initialize_drm_device(struct amdgpu_device *adev) case CHIP_VEGA10: case CHIP_VEGA12: case CHIP_VEGA20: - if (dce110_register_irq_handlers(dm->adev)) { + if (amdgpu_dm_dce110_register_irq_handlers(dm->adev)) { drm_err(adev_to_drm(adev), "DM: Failed to initialize IRQ\n"); goto fail; } @@ -4648,7 +3164,7 @@ static int amdgpu_dm_initialize_drm_device(struct amdgpu_device *adev) case IP_VERSION(4, 0, 1): case IP_VERSION(4, 2, 0): case IP_VERSION(4, 2, 1): - if (dcn10_register_irq_handlers(dm->adev)) { + if (amdgpu_dm_dcn10_register_irq_handlers(dm->adev)) { drm_err(adev_to_drm(adev), "DM: Failed to initialize IRQ\n"); goto fail; } @@ -7760,7 +6276,7 @@ void amdgpu_dm_connector_init_helper(struct amdgpu_display_manager *dm, if (amdgpu_hdmi_hpd_debounce_delay_ms) { aconnector->hdmi_hpd_debounce_delay_ms = min(amdgpu_hdmi_hpd_debounce_delay_ms, AMDGPU_DM_MAX_HDMI_HPD_DEBOUNCE_MS); - INIT_DELAYED_WORK(&aconnector->hdmi_hpd_debounce_work, hdmi_hpd_debounce_work); + INIT_DELAYED_WORK(&aconnector->hdmi_hpd_debounce_work, amdgpu_dm_hdmi_hpd_debounce_work); aconnector->hdmi_prev_sink = NULL; } else { aconnector->hdmi_hpd_debounce_delay_ms = 0; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h index f0e91a0a15fc..505164364e61 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h @@ -1167,4 +1167,8 @@ int amdgpu_dm_initialize_hdmi_connector(struct amdgpu_dm_connector *aconnector); void retrieve_dmi_info(struct amdgpu_display_manager *dm); +void amdgpu_dm_emulated_link_detect(struct dc_link *link); +void amdgpu_dm_apply_delay_after_dpcd_poweroff(struct amdgpu_device *adev, + struct dc_sink *sink); + #endif /* __AMDGPU_DM_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c index e49803a90eda..36c0177f5eb0 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c @@ -26,10 +26,24 @@ #include "dm_services_types.h" #include "dc.h" +#include "dc/dc_dmub_srv.h" +#include "dc/dc_stat.h" #include "amdgpu.h" +#include "amdgpu_display.h" #include "amdgpu_dm.h" #include "amdgpu_dm_irq.h" +#include "amdgpu_dm_crtc.h" +#include "amdgpu_dm_hdcp.h" +#include "amdgpu_dm_mst_types.h" +#include "amdgpu_dm_dmub.h" +#include "amdgpu_dm_trace.h" +#include "link/protocols/link_dpcd.h" +#include "link_service_types.h" +#include "ivsrcid/ivsrcid_vislands30.h" +#include "ivsrcid/dcn/irqsrcs_dcn_1_0.h" +#include "modules/inc/mod_freesync.h" +#include /** * DOC: overview @@ -55,7 +69,8 @@ * are all set to the DM generic handler amdgpu_dm_irq_handler(), which looks up * DM's IRQ tables. However, in order for base driver to recognize this hook, DM * still needs to register the IRQ with the base driver. See - * dce110_register_irq_handlers() and dcn10_register_irq_handlers(). + * amdgpu_dm_dce110_register_irq_handlers() and + * amdgpu_dm_dcn10_register_irq_handlers(). * * To expose DC's hardware interrupt toggle to the base driver, DM implements * &amdgpu_irq_src_funcs.set hooks. Base driver calls it through @@ -1020,3 +1035,1487 @@ void amdgpu_dm_hpd_fini(struct amdgpu_device *adev) if (dev->mode_config.poll_enabled) drm_kms_helper_poll_fini(dev); } + +/* ========== HPD handling ========== */ +static void force_connector_state( + struct amdgpu_dm_connector *aconnector, + enum drm_connector_force force_state) +{ + struct drm_connector *connector = &aconnector->base; + + mutex_lock(&connector->dev->mode_config.mutex); + aconnector->base.force = force_state; + mutex_unlock(&connector->dev->mode_config.mutex); + + mutex_lock(&aconnector->hpd_lock); + drm_kms_helper_connector_hotplug_event(connector); + mutex_unlock(&aconnector->hpd_lock); +} + +static void dm_handle_hpd_rx_offload_work(struct work_struct *work) +{ + struct hpd_rx_irq_offload_work *offload_work; + struct amdgpu_dm_connector *aconnector; + struct dc_link *dc_link; + struct amdgpu_device *adev; + enum dc_connection_type new_connection_type = dc_connection_none; + unsigned long flags; + union test_response test_response; + + memset(&test_response, 0, sizeof(test_response)); + + offload_work = container_of(work, struct hpd_rx_irq_offload_work, work); + aconnector = offload_work->offload_wq->aconnector; + adev = offload_work->adev; + + if (!aconnector) { + drm_err(adev_to_drm(adev), "Can't retrieve aconnector in hpd_rx_irq_offload_work"); + goto skip; + } + + dc_link = aconnector->dc_link; + + mutex_lock(&aconnector->hpd_lock); + if (!dc_link_detect_connection_type(dc_link, &new_connection_type)) + drm_err(adev_to_drm(adev), "KMS: Failed to detect connector\n"); + mutex_unlock(&aconnector->hpd_lock); + + if (new_connection_type == dc_connection_none) + goto skip; + + if (amdgpu_in_reset(adev)) + goto skip; + + if (offload_work->data.bytes.device_service_irq.bits.UP_REQ_MSG_RDY || + offload_work->data.bytes.device_service_irq.bits.DOWN_REP_MSG_RDY) { + dm_handle_mst_sideband_msg_ready_event(&aconnector->mst_mgr, DOWN_OR_UP_MSG_RDY_EVENT); + spin_lock_irqsave(&offload_work->offload_wq->offload_lock, flags); + offload_work->offload_wq->is_handling_mst_msg_rdy_event = false; + spin_unlock_irqrestore(&offload_work->offload_wq->offload_lock, flags); + goto skip; + } + + mutex_lock(&adev->dm.dc_lock); + if (offload_work->data.bytes.device_service_irq.bits.AUTOMATED_TEST) { + dc_link_dp_handle_automated_test(dc_link); + + if (aconnector->timing_changed) { + /* force connector disconnect and reconnect */ + force_connector_state(aconnector, DRM_FORCE_OFF); + msleep(100); + force_connector_state(aconnector, DRM_FORCE_UNSPECIFIED); + } + + test_response.bits.ACK = 1; + + core_link_write_dpcd( + dc_link, + DP_TEST_RESPONSE, + &test_response.raw, + sizeof(test_response)); + } else if ((dc_link->connector_signal != SIGNAL_TYPE_EDP) && + dc_link_check_link_loss_status(dc_link, &offload_work->data) && + dc_link_dp_allow_hpd_rx_irq(dc_link)) { + /* offload_work->data is from handle_hpd_rx_irq-> + * schedule_hpd_rx_offload_work.this is defer handle + * for hpd short pulse. upon here, link status may be + * changed, need get latest link status from dpcd + * registers. if link status is good, skip run link + * training again. + */ + union hpd_irq_data irq_data; + + memset(&irq_data, 0, sizeof(irq_data)); + + /* before dc_link_dp_handle_link_loss, allow new link lost handle + * request be added to work queue if link lost at end of dc_link_ + * dp_handle_link_loss + */ + spin_lock_irqsave(&offload_work->offload_wq->offload_lock, flags); + offload_work->offload_wq->is_handling_link_loss = false; + spin_unlock_irqrestore(&offload_work->offload_wq->offload_lock, flags); + + if ((dc_link_dp_read_hpd_rx_irq_data(dc_link, &irq_data) == DC_OK) && + dc_link_check_link_loss_status(dc_link, &irq_data)) + dc_link_dp_handle_link_loss(dc_link); + } + mutex_unlock(&adev->dm.dc_lock); + +skip: + kfree(offload_work); + +} + +struct hpd_rx_irq_offload_work_queue *amdgpu_dm_hpd_rx_irq_create_workqueue(struct amdgpu_device *adev) +{ + struct dc *dc = adev->dm.dc; + int max_caps = dc->caps.max_links; + int i = 0; + struct hpd_rx_irq_offload_work_queue *hpd_rx_offload_wq = NULL; + + hpd_rx_offload_wq = kzalloc_objs(*hpd_rx_offload_wq, max_caps); + + if (!hpd_rx_offload_wq) + return NULL; + + + for (i = 0; i < max_caps; i++) { + hpd_rx_offload_wq[i].wq = + create_singlethread_workqueue("amdgpu_dm_hpd_rx_offload_wq"); + + if (hpd_rx_offload_wq[i].wq == NULL) { + drm_err(adev_to_drm(adev), "create amdgpu_dm_hpd_rx_offload_wq fail!"); + goto out_err; + } + + spin_lock_init(&hpd_rx_offload_wq[i].offload_lock); + } + + return hpd_rx_offload_wq; + +out_err: + for (i = 0; i < max_caps; i++) { + if (hpd_rx_offload_wq[i].wq) + destroy_workqueue(hpd_rx_offload_wq[i].wq); + } + kfree(hpd_rx_offload_wq); + return NULL; +} + +void amdgpu_dm_hpd_rx_irq_work_suspend(struct amdgpu_display_manager *dm) +{ + int i; + + if (dm->hpd_rx_offload_wq) { + for (i = 0; i < dm->dc->caps.max_links; i++) + flush_workqueue(dm->hpd_rx_offload_wq[i].wq); + } +} + +static bool are_sinks_equal(const struct dc_sink *sink1, const struct dc_sink *sink2) +{ + if (!sink1 || !sink2) + return false; + if (sink1->sink_signal != sink2->sink_signal) + return false; + + if (sink1->dc_edid.length != sink2->dc_edid.length) + return false; + + if (memcmp(sink1->dc_edid.raw_edid, sink2->dc_edid.raw_edid, + sink1->dc_edid.length) != 0) + return false; + return true; +} + + +/** + * DOC: amdgpu_dm_hdmi_hpd_debounce_work + * + * HDMI HPD debounce delay in milliseconds. When an HDMI display toggles HPD + * (such as during power save transitions), this delay determines how long to + * wait before processing the HPD event. This allows distinguishing between a + * physical unplug (>hdmi_hpd_debounce_delay) + * and a spontaneous RX HPD toggle (base; + struct drm_device *dev = connector->dev; + struct amdgpu_device *adev = drm_to_adev(dev); + struct dc *dc = aconnector->dc_link->ctx->dc; + bool fake_reconnect = false; + bool reallow_idle = false; + bool ret = false; + + guard(mutex)(&aconnector->hpd_lock); + + /* Re-detect the display */ + scoped_guard(mutex, &adev->dm.dc_lock) { + if (dc->caps.ips_support && dc->ctx->dmub_srv->idle_allowed) { + dc_allow_idle_optimizations(dc, false); + reallow_idle = true; + } + ret = dc_link_detect(aconnector->dc_link, DETECT_REASON_HPD); + } + + if (ret) { + /* Apply workaround delay for certain panels */ + amdgpu_dm_apply_delay_after_dpcd_poweroff(adev, aconnector->dc_sink); + /* Compare sinks to determine if this was a spontaneous HPD toggle */ + if (are_sinks_equal(aconnector->dc_link->local_sink, aconnector->hdmi_prev_sink)) { + /* + * Sinks match - this was a spontaneous HDMI HPD toggle. + */ + drm_dbg_kms(dev, "HDMI HPD: Sink unchanged after debounce, internal re-enable\n"); + fake_reconnect = true; + } + + /* Update connector state */ + amdgpu_dm_update_connector_after_detect(aconnector); + + drm_modeset_lock_all(dev); + dm_restore_drm_connector_state(dev, connector); + drm_modeset_unlock_all(dev); + + /* Only notify OS if sink actually changed */ + if (!fake_reconnect && aconnector->base.force == DRM_FORCE_UNSPECIFIED) + drm_kms_helper_hotplug_event(dev); + } + + /* Release the cached sink reference */ + if (aconnector->hdmi_prev_sink) { + dc_sink_release(aconnector->hdmi_prev_sink); + aconnector->hdmi_prev_sink = NULL; + } + + scoped_guard(mutex, &adev->dm.dc_lock) { + if (reallow_idle && dc->caps.ips_support) + dc_allow_idle_optimizations(dc, true); + } +} + +static void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector, + enum dc_detect_reason reason) +{ + struct drm_connector *connector = &aconnector->base; + struct drm_device *dev = connector->dev; + enum dc_connection_type new_connection_type = dc_connection_none; + struct amdgpu_device *adev = drm_to_adev(dev); + struct dm_connector_state *dm_con_state = to_dm_connector_state(connector->state); + struct dc *dc = aconnector->dc_link->ctx->dc; + bool ret = false; + bool debounce_required = false; + + if (adev->dm.disable_hpd_irq) + return; + + /* + * In case of failure or MST no need to update connector status or notify the OS + * since (for MST case) MST does this in its own context. + */ + guard(mutex)(&aconnector->hpd_lock); + + if (adev->dm.hdcp_workqueue) { + hdcp_reset_display(adev->dm.hdcp_workqueue, aconnector->dc_link->link_index); + dm_con_state->update_hdcp = true; + } + if (aconnector->fake_enable) + aconnector->fake_enable = false; + + aconnector->timing_changed = false; + + if (!dc_link_detect_connection_type(aconnector->dc_link, &new_connection_type)) + drm_err(adev_to_drm(adev), "KMS: Failed to detect connector\n"); + + /* + * Check for HDMI disconnect with debounce enabled. + */ + debounce_required = (aconnector->hdmi_hpd_debounce_delay_ms > 0 && + dc_is_hdmi_signal(aconnector->dc_link->connector_signal) && + new_connection_type == dc_connection_none && + aconnector->dc_link->local_sink != NULL); + + if (aconnector->base.force && new_connection_type == dc_connection_none) { + amdgpu_dm_emulated_link_detect(aconnector->dc_link); + + drm_modeset_lock_all(dev); + dm_restore_drm_connector_state(dev, connector); + drm_modeset_unlock_all(dev); + + if (aconnector->base.force == DRM_FORCE_UNSPECIFIED || + reason == DETECT_REASON_HPDRX) + drm_kms_helper_connector_hotplug_event(connector); + } else if (debounce_required) { + /* + * HDMI disconnect detected - schedule delayed work instead of + * processing immediately. This allows us to coalesce spurious + * HDMI signals from physical unplugs. + */ + drm_dbg_kms(dev, "HDMI HPD: Disconnect detected, scheduling debounce work (%u ms)\n", + aconnector->hdmi_hpd_debounce_delay_ms); + + /* Cache the current sink for later comparison */ + if (aconnector->hdmi_prev_sink) + dc_sink_release(aconnector->hdmi_prev_sink); + aconnector->hdmi_prev_sink = aconnector->dc_link->local_sink; + if (aconnector->hdmi_prev_sink) + dc_sink_retain(aconnector->hdmi_prev_sink); + + /* Schedule delayed detection. */ + if (mod_delayed_work(system_percpu_wq, + &aconnector->hdmi_hpd_debounce_work, + msecs_to_jiffies(aconnector->hdmi_hpd_debounce_delay_ms))) + drm_dbg_kms(dev, "HDMI HPD: Re-scheduled debounce work\n"); + + } else { + + /* If the aconnector->hdmi_hpd_debounce_work is scheduled, exit early */ + if (delayed_work_pending(&aconnector->hdmi_hpd_debounce_work)) + return; + + scoped_guard(mutex, &adev->dm.dc_lock) { + dc_exit_ips_for_hw_access(dc); + ret = dc_link_detect(aconnector->dc_link, reason); + } + if (ret) { + /* w/a delay for certain panels */ + amdgpu_dm_apply_delay_after_dpcd_poweroff(adev, aconnector->dc_sink); + amdgpu_dm_update_connector_after_detect(aconnector); + + drm_modeset_lock_all(dev); + dm_restore_drm_connector_state(dev, connector); + drm_modeset_unlock_all(dev); + + if (aconnector->base.force == DRM_FORCE_UNSPECIFIED || + reason == DETECT_REASON_HPDRX) + drm_kms_helper_connector_hotplug_event(connector); + } + } +} + +static void handle_hpd_irq(void *param) +{ + struct amdgpu_dm_connector *aconnector = (struct amdgpu_dm_connector *)param; + + handle_hpd_irq_helper(aconnector, DETECT_REASON_HPD); + +} + +static void schedule_hpd_rx_offload_work(struct amdgpu_device *adev, struct hpd_rx_irq_offload_work_queue *offload_wq, + union hpd_irq_data hpd_irq_data) +{ + struct hpd_rx_irq_offload_work *offload_work = kzalloc_obj(*offload_work); + + if (!offload_work) { + drm_err(adev_to_drm(adev), "Failed to allocate hpd_rx_irq_offload_work.\n"); + return; + } + + INIT_WORK(&offload_work->work, dm_handle_hpd_rx_offload_work); + offload_work->data = hpd_irq_data; + offload_work->offload_wq = offload_wq; + offload_work->adev = adev; + + queue_work(offload_wq->wq, &offload_work->work); + drm_dbg_kms(adev_to_drm(adev), "queue work to handle hpd_rx offload work"); +} + +static void handle_hpd_rx_irq(void *param) +{ + struct amdgpu_dm_connector *aconnector = (struct amdgpu_dm_connector *)param; + struct drm_connector *connector = &aconnector->base; + struct drm_device *dev = connector->dev; + struct dc_link *dc_link = aconnector->dc_link; + bool is_mst_root_connector = aconnector->mst_mgr.mst_state; + bool result = false; + struct amdgpu_device *adev = drm_to_adev(dev); + union hpd_irq_data hpd_irq_data; + bool link_loss = false; + bool has_left_work = false; + int idx = dc_link->link_index; + struct hpd_rx_irq_offload_work_queue *offload_wq = &adev->dm.hpd_rx_offload_wq[idx]; + + memset(&hpd_irq_data, 0, sizeof(hpd_irq_data)); + + if (adev->dm.disable_hpd_irq) + return; + + /* + * TODO:Temporary add mutex to protect hpd interrupt not have a gpio + * conflict, after implement i2c helper, this mutex should be + * retired. + */ + mutex_lock(&aconnector->hpd_lock); + + result = dc_link_handle_hpd_rx_irq(dc_link, &hpd_irq_data, + &link_loss, true, &has_left_work); + + if (!has_left_work) + goto out; + + if (hpd_irq_data.bytes.device_service_irq.bits.AUTOMATED_TEST) { + schedule_hpd_rx_offload_work(adev, offload_wq, hpd_irq_data); + goto out; + } + + if (dc_link_dp_allow_hpd_rx_irq(dc_link)) { + if (hpd_irq_data.bytes.device_service_irq.bits.UP_REQ_MSG_RDY || + hpd_irq_data.bytes.device_service_irq.bits.DOWN_REP_MSG_RDY) { + bool skip = false; + + /* + * DOWN_REP_MSG_RDY is also handled by polling method + * mgr->cbs->poll_hpd_irq() + */ + spin_lock(&offload_wq->offload_lock); + skip = offload_wq->is_handling_mst_msg_rdy_event; + + if (!skip) + offload_wq->is_handling_mst_msg_rdy_event = true; + + spin_unlock(&offload_wq->offload_lock); + + if (!skip) + schedule_hpd_rx_offload_work(adev, offload_wq, hpd_irq_data); + + goto out; + } + + if (link_loss) { + bool skip = false; + + spin_lock(&offload_wq->offload_lock); + skip = offload_wq->is_handling_link_loss; + + if (!skip) + offload_wq->is_handling_link_loss = true; + + spin_unlock(&offload_wq->offload_lock); + + if (!skip) + schedule_hpd_rx_offload_work(adev, offload_wq, hpd_irq_data); + + goto out; + } + } + +out: + if (result && !is_mst_root_connector) { + /* Downstream Port status changed. */ + handle_hpd_irq_helper(aconnector, DETECT_REASON_HPDRX); + } + if (hpd_irq_data.bytes.device_service_irq.bits.CP_IRQ) { + if (adev->dm.hdcp_workqueue) + hdcp_handle_cpirq(adev->dm.hdcp_workqueue, aconnector->base.index); + } + + if (dc_link->type != dc_connection_mst_branch) + drm_dp_cec_irq(&aconnector->dm_dp_aux.aux); + + mutex_unlock(&aconnector->hpd_lock); +} + +/** + * dmub_hpd_callback - DMUB HPD interrupt processing callback. + * @adev: amdgpu_device pointer + * @notify: dmub notification structure + * + * Dmub Hpd interrupt processing callback. Gets displayindex through the + * ink index and calls helper to do the processing. + */ +static void dmub_hpd_callback(struct amdgpu_device *adev, + struct dmub_notification *notify) +{ + struct amdgpu_dm_connector *aconnector; + struct amdgpu_dm_connector *hpd_aconnector = NULL; + struct drm_connector *connector; + struct drm_connector_list_iter iter; + struct dc_link *link; + u8 link_index = 0; + struct drm_device *dev; + + if (adev == NULL) + return; + + if (notify == NULL) { + drm_err(adev_to_drm(adev), "DMUB HPD callback notification was NULL"); + return; + } + + if (notify->link_index > adev->dm.dc->link_count) { + drm_err(adev_to_drm(adev), "DMUB HPD index (%u)is abnormal", notify->link_index); + return; + } + + /* Skip DMUB HPD IRQ in suspend/resume. We will probe them later. */ + if (notify->type == DMUB_NOTIFICATION_HPD && adev->in_suspend) { + drm_info(adev_to_drm(adev), "Skip DMUB HPD IRQ callback in suspend/resume\n"); + return; + } + + link_index = notify->link_index; + link = adev->dm.dc->links[link_index]; + dev = adev->dm.ddev; + + drm_connector_list_iter_begin(dev, &iter); + drm_for_each_connector_iter(connector, &iter) { + + if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) + continue; + + aconnector = to_amdgpu_dm_connector(connector); + if (link && aconnector->dc_link == link) { + if (notify->type == DMUB_NOTIFICATION_HPD) + drm_info(adev_to_drm(adev), "DMUB HPD IRQ callback: link_index=%u\n", link_index); + else if (notify->type == DMUB_NOTIFICATION_HPD_IRQ) + drm_info(adev_to_drm(adev), "DMUB HPD RX IRQ callback: link_index=%u\n", link_index); + else + drm_warn(adev_to_drm(adev), "DMUB Unknown HPD callback type %d, link_index=%u\n", + notify->type, link_index); + + hpd_aconnector = aconnector; + break; + } + } + drm_connector_list_iter_end(&iter); + + if (hpd_aconnector) { + if (notify->type == DMUB_NOTIFICATION_HPD) { + if (hpd_aconnector->dc_link->hpd_status == (notify->hpd_status == DP_HPD_PLUG)) + drm_warn(adev_to_drm(adev), "DMUB reported hpd status unchanged. link_index=%u\n", link_index); + handle_hpd_irq_helper(hpd_aconnector, DETECT_REASON_HPD); + } else if (notify->type == DMUB_NOTIFICATION_HPD_IRQ) { + handle_hpd_rx_irq(hpd_aconnector); + } + } +} + +/** + * dmub_hpd_sense_callback - DMUB HPD sense processing callback. + * @adev: amdgpu_device pointer + * @notify: dmub notification structure + * + * HPD sense changes can occur during low power states and need to be + * notified from firmware to driver. + */ +static void dmub_hpd_sense_callback(struct amdgpu_device *adev, + struct dmub_notification *notify) +{ + drm_dbg_driver(adev_to_drm(adev), "DMUB HPD SENSE callback.\n"); +} + +int amdgpu_dm_register_hpd_handlers(struct amdgpu_device *adev) +{ + struct drm_device *dev = adev_to_drm(adev); + struct drm_connector *connector; + struct amdgpu_dm_connector *aconnector; + const struct dc_link *dc_link; + struct dc_interrupt_params int_params = {0}; + + int_params.requested_polarity = INTERRUPT_POLARITY_DEFAULT; + int_params.current_polarity = INTERRUPT_POLARITY_DEFAULT; + + if (dc_is_dmub_outbox_supported(adev->dm.dc)) { + if (!dm_register_dmub_notify_callback(adev, DMUB_NOTIFICATION_HPD, + dmub_hpd_callback, true)) { + drm_err(adev_to_drm(adev), "fail to register dmub hpd callback"); + return -EINVAL; + } + + if (!dm_register_dmub_notify_callback(adev, DMUB_NOTIFICATION_HPD_IRQ, + dmub_hpd_callback, true)) { + drm_err(adev_to_drm(adev), "fail to register dmub hpd callback"); + return -EINVAL; + } + + if (!dm_register_dmub_notify_callback(adev, DMUB_NOTIFICATION_HPD_SENSE_NOTIFY, + dmub_hpd_sense_callback, true)) { + drm_err(adev_to_drm(adev), "fail to register dmub hpd sense callback"); + return -EINVAL; + } + } + + list_for_each_entry(connector, + &dev->mode_config.connector_list, head) { + + if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) + continue; + + aconnector = to_amdgpu_dm_connector(connector); + dc_link = aconnector->dc_link; + + if (dc_link->irq_source_hpd != DC_IRQ_SOURCE_INVALID) { + int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; + int_params.irq_source = dc_link->irq_source_hpd; + + if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || + int_params.irq_source < DC_IRQ_SOURCE_HPD1 || + int_params.irq_source > DC_IRQ_SOURCE_HPD6) { + drm_err(adev_to_drm(adev), "Failed to register hpd irq!\n"); + return -EINVAL; + } + + if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, + handle_hpd_irq, (void *) aconnector)) + return -ENOMEM; + } + + if (dc_link->irq_source_hpd_rx != DC_IRQ_SOURCE_INVALID) { + + /* Also register for DP short pulse (hpd_rx). */ + int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; + int_params.irq_source = dc_link->irq_source_hpd_rx; + + if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || + int_params.irq_source < DC_IRQ_SOURCE_HPD1RX || + int_params.irq_source > DC_IRQ_SOURCE_HPD6RX) { + drm_err(adev_to_drm(adev), "Failed to register hpd rx irq!\n"); + return -EINVAL; + } + + if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, + handle_hpd_rx_irq, (void *) aconnector)) + return -ENOMEM; + } + } + return 0; +} + +/* ========== IRQ handlers ========== */ +struct amdgpu_crtc * +amdgpu_dm_get_crtc_by_otg_inst(struct amdgpu_device *adev, + int otg_inst) +{ + struct drm_device *dev = adev_to_drm(adev); + struct drm_crtc *crtc; + struct amdgpu_crtc *amdgpu_crtc; + + if (WARN_ON(otg_inst == -1)) + return adev->mode_info.crtcs[0]; + + list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { + amdgpu_crtc = to_amdgpu_crtc(crtc); + + if (amdgpu_crtc->otg_inst == otg_inst) + return amdgpu_crtc; + } + + return NULL; +} + +/** + * dm_pflip_high_irq() - Handle pageflip interrupt + * @interrupt_params: ignored + * + * Handles the pageflip interrupt by notifying all interested parties + * that the pageflip has been completed. + */ +static void dm_pflip_high_irq(void *interrupt_params) +{ + struct amdgpu_crtc *amdgpu_crtc; + struct common_irq_params *irq_params = interrupt_params; + struct amdgpu_device *adev = irq_params->adev; + struct drm_device *dev = adev_to_drm(adev); + unsigned long flags; + struct drm_pending_vblank_event *e; + u32 vpos, hpos, v_blank_start, v_blank_end; + bool vrr_active; + + amdgpu_crtc = amdgpu_dm_get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_PFLIP); + + /* IRQ could occur when in initial stage */ + /* TODO work and BO cleanup */ + if (amdgpu_crtc == NULL) { + drm_dbg_state(dev, "CRTC is null, returning.\n"); + return; + } + + spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags); + + if (amdgpu_crtc->pflip_status != AMDGPU_FLIP_SUBMITTED) { + drm_dbg_state(dev, + "amdgpu_crtc->pflip_status = %d != AMDGPU_FLIP_SUBMITTED(%d) on crtc:%d[%p]\n", + amdgpu_crtc->pflip_status, AMDGPU_FLIP_SUBMITTED, + amdgpu_crtc->crtc_id, amdgpu_crtc); + spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags); + return; + } + + /* page flip completed. */ + e = amdgpu_crtc->event; + amdgpu_crtc->event = NULL; + + WARN_ON(!e); + + vrr_active = amdgpu_dm_crtc_vrr_active_irq(amdgpu_crtc); + + /* Fixed refresh rate, or VRR scanout position outside front-porch? */ + if (!vrr_active || + !dc_stream_get_scanoutpos(amdgpu_crtc->dm_irq_params.stream, &v_blank_start, + &v_blank_end, &hpos, &vpos) || + (vpos < v_blank_start)) { + /* Update to correct count and vblank timestamp if racing with + * vblank irq. This also updates to the correct vblank timestamp + * even in VRR mode, as scanout is past the front-porch atm. + */ + drm_crtc_accurate_vblank_count(&amdgpu_crtc->base); + + /* Wake up userspace by sending the pageflip event with proper + * count and timestamp of vblank of flip completion. + */ + if (e) { + drm_crtc_send_vblank_event(&amdgpu_crtc->base, e); + + /* Event sent, so done with vblank for this flip */ + drm_crtc_vblank_put(&amdgpu_crtc->base); + } + } else if (e) { + /* VRR active and inside front-porch: vblank count and + * timestamp for pageflip event will only be up to date after + * drm_crtc_handle_vblank() has been executed from late vblank + * irq handler after start of back-porch (vline 0). We queue the + * pageflip event for send-out by drm_crtc_handle_vblank() with + * updated timestamp and count, once it runs after us. + * + * We need to open-code this instead of using the helper + * drm_crtc_arm_vblank_event(), as that helper would + * call drm_crtc_accurate_vblank_count(), which we must + * not call in VRR mode while we are in front-porch! + */ + + /* sequence will be replaced by real count during send-out. */ + e->sequence = drm_crtc_vblank_count(&amdgpu_crtc->base); + e->pipe = amdgpu_crtc->crtc_id; + + list_add_tail(&e->base.link, &adev_to_drm(adev)->vblank_event_list); + e = NULL; + } + + /* Keep track of vblank of this flip for flip throttling. We use the + * cooked hw counter, as that one incremented at start of this vblank + * of pageflip completion, so last_flip_vblank is the forbidden count + * for queueing new pageflips if vsync + VRR is enabled. + */ + amdgpu_crtc->dm_irq_params.last_flip_vblank = + amdgpu_get_vblank_counter_kms(&amdgpu_crtc->base); + + amdgpu_crtc->pflip_status = AMDGPU_FLIP_NONE; + spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags); + + drm_dbg_state(dev, + "crtc:%d[%p], pflip_stat:AMDGPU_FLIP_NONE, vrr[%d]-fp %d\n", + amdgpu_crtc->crtc_id, amdgpu_crtc, vrr_active, (int)!e); +} + +static void dm_handle_vmin_vmax_update(struct work_struct *offload_work) +{ + struct vupdate_offload_work *work = container_of(offload_work, struct vupdate_offload_work, work); + struct amdgpu_device *adev = work->adev; + struct dc_stream_state *stream = work->stream; + struct dc_crtc_timing_adjust *adjust = work->adjust; + + mutex_lock(&adev->dm.dc_lock); + dc_stream_adjust_vmin_vmax(adev->dm.dc, stream, adjust); + mutex_unlock(&adev->dm.dc_lock); + + dc_stream_release(stream); + kfree(work->adjust); + kfree(work); +} + +static void schedule_dc_vmin_vmax(struct amdgpu_device *adev, + struct dc_stream_state *stream, + struct dc_crtc_timing_adjust *adjust) +{ + struct vupdate_offload_work *offload_work = kzalloc_obj(*offload_work, + GFP_NOWAIT); + if (!offload_work) { + drm_dbg_driver(adev_to_drm(adev), "Failed to allocate vupdate_offload_work\n"); + return; + } + + struct dc_crtc_timing_adjust *adjust_copy = kzalloc_obj(*adjust_copy, + GFP_NOWAIT); + if (!adjust_copy) { + drm_dbg_driver(adev_to_drm(adev), "Failed to allocate adjust_copy\n"); + kfree(offload_work); + return; + } + + dc_stream_retain(stream); + memcpy(adjust_copy, adjust, sizeof(*adjust_copy)); + + INIT_WORK(&offload_work->work, dm_handle_vmin_vmax_update); + offload_work->adev = adev; + offload_work->stream = stream; + offload_work->adjust = adjust_copy; + + queue_work(system_percpu_wq, &offload_work->work); +} + +static void dm_vupdate_high_irq(void *interrupt_params) +{ + struct common_irq_params *irq_params = interrupt_params; + struct amdgpu_device *adev = irq_params->adev; + struct amdgpu_crtc *acrtc; + struct drm_device *drm_dev; + struct drm_vblank_crtc *vblank; + ktime_t frame_duration_ns, previous_timestamp; + unsigned long flags; + int vrr_active; + + acrtc = amdgpu_dm_get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_VUPDATE); + + if (acrtc) { + vrr_active = amdgpu_dm_crtc_vrr_active_irq(acrtc); + drm_dev = acrtc->base.dev; + vblank = drm_crtc_vblank_crtc(&acrtc->base); + previous_timestamp = atomic64_read(&irq_params->previous_timestamp); + frame_duration_ns = vblank->time - previous_timestamp; + + if (frame_duration_ns > 0) { + trace_amdgpu_refresh_rate_track(acrtc->base.index, + frame_duration_ns, + ktime_divns(NSEC_PER_SEC, frame_duration_ns)); + atomic64_set(&irq_params->previous_timestamp, vblank->time); + } + + drm_dbg_vbl(drm_dev, + "crtc:%d, vupdate-vrr:%d\n", acrtc->crtc_id, + vrr_active); + + /* Core vblank handling is done here after end of front-porch in + * vrr mode, as vblank timestamping will give valid results + * while now done after front-porch. This will also deliver + * page-flip completion events that have been queued to us + * if a pageflip happened inside front-porch. + */ + if (vrr_active && acrtc->dm_irq_params.stream) { + bool replay_en = acrtc->dm_irq_params.stream->link->replay_settings.replay_feature_enabled; + bool psr_en = acrtc->dm_irq_params.stream->link->psr_settings.psr_feature_enabled; + bool fs_active_var_en = acrtc->dm_irq_params.freesync_config.state + == VRR_STATE_ACTIVE_VARIABLE; + + amdgpu_dm_crtc_handle_vblank(acrtc); + + /* BTR processing for pre-DCE12 ASICs */ + if (adev->family < AMDGPU_FAMILY_AI) { + spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags); + mod_freesync_handle_v_update( + adev->dm.freesync_module, + acrtc->dm_irq_params.stream, + &acrtc->dm_irq_params.vrr_params); + + if (fs_active_var_en || (!fs_active_var_en && !replay_en && !psr_en)) { + schedule_dc_vmin_vmax(adev, + acrtc->dm_irq_params.stream, + &acrtc->dm_irq_params.vrr_params.adjust); + } + spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags); + } + } + } +} + +/** + * dm_crtc_high_irq() - Handles CRTC interrupt + * @interrupt_params: used for determining the CRTC instance + * + * Handles the CRTC/VSYNC interrupt by notfying DRM's VBLANK + * event handler. + */ +static void dm_crtc_high_irq(void *interrupt_params) +{ + struct common_irq_params *irq_params = interrupt_params; + struct amdgpu_device *adev = irq_params->adev; + struct drm_writeback_job *job; + struct amdgpu_crtc *acrtc; + unsigned long flags; + int vrr_active; + + acrtc = amdgpu_dm_get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_VBLANK); + if (!acrtc) + return; + + if (acrtc->wb_conn) { + spin_lock_irqsave(&acrtc->wb_conn->job_lock, flags); + + if (acrtc->wb_pending) { + job = list_first_entry_or_null(&acrtc->wb_conn->job_queue, + struct drm_writeback_job, + list_entry); + acrtc->wb_pending = false; + spin_unlock_irqrestore(&acrtc->wb_conn->job_lock, flags); + + if (job) { + unsigned int v_total, refresh_hz; + struct dc_stream_state *stream = acrtc->dm_irq_params.stream; + + v_total = stream->adjust.v_total_max ? + stream->adjust.v_total_max : stream->timing.v_total; + refresh_hz = div_u64((uint64_t) stream->timing.pix_clk_100hz * + 100LL, (v_total * stream->timing.h_total)); + mdelay(1000 / refresh_hz); + + drm_writeback_signal_completion(acrtc->wb_conn, 0); + dc_stream_fc_disable_writeback(adev->dm.dc, + acrtc->dm_irq_params.stream, 0); + } + } else + spin_unlock_irqrestore(&acrtc->wb_conn->job_lock, flags); + } + + vrr_active = amdgpu_dm_crtc_vrr_active_irq(acrtc); + + drm_dbg_vbl(adev_to_drm(adev), + "crtc:%d, vupdate-vrr:%d, planes:%d\n", acrtc->crtc_id, + vrr_active, acrtc->dm_irq_params.active_planes); + + /** + * Core vblank handling at start of front-porch is only possible + * in non-vrr mode, as only there vblank timestamping will give + * valid results while done in front-porch. Otherwise defer it + * to dm_vupdate_high_irq after end of front-porch. + */ + if (!vrr_active) + amdgpu_dm_crtc_handle_vblank(acrtc); + + /** + * Following stuff must happen at start of vblank, for crc + * computation and below-the-range btr support in vrr mode. + */ + amdgpu_dm_crtc_handle_crc_irq(&acrtc->base); + + /* BTR updates need to happen before VUPDATE on Vega and above. */ + if (adev->family < AMDGPU_FAMILY_AI) + return; + + spin_lock_irqsave(&adev_to_drm(adev)->event_lock, flags); + + if (acrtc->dm_irq_params.stream && + acrtc->dm_irq_params.vrr_params.supported) { + bool replay_en = acrtc->dm_irq_params.stream->link->replay_settings.replay_feature_enabled; + bool psr_en = acrtc->dm_irq_params.stream->link->psr_settings.psr_feature_enabled; + bool fs_active_var_en = acrtc->dm_irq_params.freesync_config.state == VRR_STATE_ACTIVE_VARIABLE; + + mod_freesync_handle_v_update(adev->dm.freesync_module, + acrtc->dm_irq_params.stream, + &acrtc->dm_irq_params.vrr_params); + + /* update vmin_vmax only if freesync is enabled, or only if PSR and REPLAY are disabled */ + if (fs_active_var_en || (!fs_active_var_en && !replay_en && !psr_en)) { + schedule_dc_vmin_vmax(adev, acrtc->dm_irq_params.stream, + &acrtc->dm_irq_params.vrr_params.adjust); + } + } + + /* + * If there aren't any active_planes then DCH HUBP may be clock-gated. + * In that case, pageflip completion interrupts won't fire and pageflip + * completion events won't get delivered. Prevent this by sending + * pending pageflip events from here if a flip is still pending. + * + * If any planes are enabled, use dm_pflip_high_irq() instead, to + * avoid race conditions between flip programming and completion, + * which could cause too early flip completion events. + */ + if (adev->family >= AMDGPU_FAMILY_RV && + acrtc->pflip_status == AMDGPU_FLIP_SUBMITTED && + acrtc->dm_irq_params.active_planes == 0) { + if (acrtc->event) { + drm_crtc_send_vblank_event(&acrtc->base, acrtc->event); + acrtc->event = NULL; + drm_crtc_vblank_put(&acrtc->base); + } + acrtc->pflip_status = AMDGPU_FLIP_NONE; + } + + spin_unlock_irqrestore(&adev_to_drm(adev)->event_lock, flags); +} + +#if defined(CONFIG_DRM_AMD_SECURE_DISPLAY) +/** + * dm_dcn_vertical_interrupt0_high_irq() - Handles OTG Vertical interrupt0 for + * DCN generation ASICs + * @interrupt_params: interrupt parameters + * + * Used to set crc window/read out crc value at vertical line 0 position + */ +static void dm_dcn_vertical_interrupt0_high_irq(void *interrupt_params) +{ + struct common_irq_params *irq_params = interrupt_params; + struct amdgpu_device *adev = irq_params->adev; + struct amdgpu_crtc *acrtc; + + acrtc = amdgpu_dm_get_crtc_by_otg_inst(adev, irq_params->irq_src - IRQ_TYPE_VLINE0); + + if (!acrtc) + return; + + amdgpu_dm_crtc_handle_crc_window_irq(&acrtc->base); +} +#endif /* CONFIG_DRM_AMD_SECURE_DISPLAY */ + +static void dm_handle_hpd_work(struct work_struct *work) +{ + struct dmub_hpd_work *dmub_hpd_wrk; + + dmub_hpd_wrk = container_of(work, struct dmub_hpd_work, handle_hpd_work); + + if (!dmub_hpd_wrk->dmub_notify) { + drm_err(adev_to_drm(dmub_hpd_wrk->adev), "dmub_hpd_wrk dmub_notify is NULL"); + return; + } + + if (dmub_hpd_wrk->dmub_notify->type < ARRAY_SIZE(dmub_hpd_wrk->adev->dm.dmub_callback)) { + dmub_hpd_wrk->adev->dm.dmub_callback[dmub_hpd_wrk->dmub_notify->type](dmub_hpd_wrk->adev, + dmub_hpd_wrk->dmub_notify); + } + + kfree(dmub_hpd_wrk->dmub_notify); + kfree(dmub_hpd_wrk); + +} + +static const char *dmub_notification_type_str(enum dmub_notification_type e) +{ + switch (e) { + case DMUB_NOTIFICATION_NO_DATA: + return "NO_DATA"; + case DMUB_NOTIFICATION_AUX_REPLY: + return "AUX_REPLY"; + case DMUB_NOTIFICATION_HPD: + return "HPD"; + case DMUB_NOTIFICATION_HPD_IRQ: + return "HPD_IRQ"; + case DMUB_NOTIFICATION_SET_CONFIG_REPLY: + return "SET_CONFIG_REPLY"; + case DMUB_NOTIFICATION_DPIA_NOTIFICATION: + return "DPIA_NOTIFICATION"; + case DMUB_NOTIFICATION_HPD_SENSE_NOTIFY: + return "HPD_SENSE_NOTIFY"; + case DMUB_NOTIFICATION_FUSED_IO: + return "FUSED_IO"; + default: + return ""; + } +} + +#define DMUB_TRACE_MAX_READ 64 +/** + * dm_dmub_outbox1_low_irq() - Handles Outbox interrupt + * @interrupt_params: used for determining the Outbox instance + * + * Handles the Outbox Interrupt + * event handler. + */ +static void dm_dmub_outbox1_low_irq(void *interrupt_params) +{ + struct dmub_notification notify = {0}; + struct common_irq_params *irq_params = interrupt_params; + struct amdgpu_device *adev = irq_params->adev; + struct amdgpu_display_manager *dm = &adev->dm; + struct dmcub_trace_buf_entry entry = { 0 }; + u32 count = 0; + struct dmub_hpd_work *dmub_hpd_wrk; + + do { + if (dc_dmub_srv_get_dmub_outbox0_msg(dm->dc, &entry)) { + trace_amdgpu_dmub_trace_high_irq(entry.trace_code, entry.tick_count, + entry.param0, entry.param1); + + drm_dbg_driver(adev_to_drm(adev), "trace_code:%u, tick_count:%u, param0:%u, param1:%u\n", + entry.trace_code, entry.tick_count, entry.param0, entry.param1); + } else + break; + + count++; + + } while (count <= DMUB_TRACE_MAX_READ); + + if (count > DMUB_TRACE_MAX_READ) + drm_dbg_driver(adev_to_drm(adev), "Warning : count > DMUB_TRACE_MAX_READ"); + + if (dc_enable_dmub_notifications(adev->dm.dc) && + irq_params->irq_src == DC_IRQ_SOURCE_DMCUB_OUTBOX) { + + do { + dc_stat_get_dmub_notification(adev->dm.dc, ¬ify); + if (notify.type >= ARRAY_SIZE(dm->dmub_thread_offload)) { + drm_err(adev_to_drm(adev), "DM: notify type %d invalid!", notify.type); + continue; + } + if (!dm->dmub_callback[notify.type]) { + drm_warn(adev_to_drm(adev), "DMUB notification skipped due to no handler: type=%s\n", + dmub_notification_type_str(notify.type)); + continue; + } + if (dm->dmub_thread_offload[notify.type] == true) { + dmub_hpd_wrk = kzalloc_obj(*dmub_hpd_wrk, + GFP_ATOMIC); + if (!dmub_hpd_wrk) { + drm_err(adev_to_drm(adev), "Failed to allocate dmub_hpd_wrk"); + return; + } + dmub_hpd_wrk->dmub_notify = kmemdup(¬ify, sizeof(struct dmub_notification), + GFP_ATOMIC); + if (!dmub_hpd_wrk->dmub_notify) { + kfree(dmub_hpd_wrk); + drm_err(adev_to_drm(adev), "Failed to allocate dmub_hpd_wrk->dmub_notify"); + return; + } + INIT_WORK(&dmub_hpd_wrk->handle_hpd_work, dm_handle_hpd_work); + dmub_hpd_wrk->adev = adev; + queue_work(adev->dm.delayed_hpd_wq, &dmub_hpd_wrk->handle_hpd_work); + } else { + dm->dmub_callback[notify.type](adev, ¬ify); + } + } while (notify.pending_notification); + } +} + +/* Register IRQ sources and initialize IRQ callbacks */ +int amdgpu_dm_dce110_register_irq_handlers(struct amdgpu_device *adev) +{ + struct dc *dc = adev->dm.dc; + struct common_irq_params *c_irq_params; + struct dc_interrupt_params int_params = {0}; + int r; + int i; + unsigned int src_id; + unsigned int client_id = AMDGPU_IRQ_CLIENTID_LEGACY; + /* Use different interrupts for VBLANK on DCE 6 vs. newer. */ + const unsigned int vblank_d1 = + adev->dm.dc->ctx->dce_version >= DCE_VERSION_8_0 + ? VISLANDS30_IV_SRCID_D1_VERTICAL_INTERRUPT0 : 1; + + if (adev->family >= AMDGPU_FAMILY_AI) + client_id = SOC15_IH_CLIENTID_DCE; + + int_params.requested_polarity = INTERRUPT_POLARITY_DEFAULT; + int_params.current_polarity = INTERRUPT_POLARITY_DEFAULT; + + /* + * Actions of amdgpu_irq_add_id(): + * 1. Register a set() function with base driver. + * Base driver will call set() function to enable/disable an + * interrupt in DC hardware. + * 2. Register amdgpu_dm_irq_handler(). + * Base driver will call amdgpu_dm_irq_handler() for ALL interrupts + * coming from DC hardware. + * amdgpu_dm_irq_handler() will re-direct the interrupt to DC + * for acknowledging and handling. + */ + + /* Use VBLANK interrupt */ + for (i = 0; i < adev->mode_info.num_crtc; i++) { + src_id = vblank_d1 + i; + r = amdgpu_irq_add_id(adev, client_id, src_id, &adev->crtc_irq); + if (r) { + drm_err(adev_to_drm(adev), "Failed to add crtc irq id!\n"); + return r; + } + + int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; + int_params.irq_source = + dc_interrupt_to_irq_source(dc, src_id, 0); + + if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || + int_params.irq_source < DC_IRQ_SOURCE_VBLANK1 || + int_params.irq_source > DC_IRQ_SOURCE_VBLANK6) { + drm_err(adev_to_drm(adev), "Failed to register vblank irq!\n"); + return -EINVAL; + } + + c_irq_params = &adev->dm.vblank_params[int_params.irq_source - DC_IRQ_SOURCE_VBLANK1]; + + c_irq_params->adev = adev; + c_irq_params->irq_src = int_params.irq_source; + + if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_crtc_high_irq, c_irq_params)) + return -ENOMEM; + } + + if (dc_supports_vrr(adev->dm.dc->ctx->dce_version)) { + /* Use VUPDATE interrupt */ + for (i = 0; i < adev->mode_info.num_crtc; i++) { + src_id = VISLANDS30_IV_SRCID_D1_V_UPDATE_INT + i * 2; + r = amdgpu_irq_add_id(adev, client_id, src_id, &adev->vupdate_irq); + if (r) { + drm_err(adev_to_drm(adev), "Failed to add vupdate irq id!\n"); + return r; + } + + int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; + int_params.irq_source = + dc_interrupt_to_irq_source(dc, src_id, 0); + + if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || + int_params.irq_source < DC_IRQ_SOURCE_VUPDATE1 || + int_params.irq_source > DC_IRQ_SOURCE_VUPDATE6) { + drm_err(adev_to_drm(adev), "Failed to register vupdate irq!\n"); + return -EINVAL; + } + + c_irq_params = &adev->dm.vupdate_params[ + int_params.irq_source - DC_IRQ_SOURCE_VUPDATE1]; + c_irq_params->adev = adev; + c_irq_params->irq_src = int_params.irq_source; + + if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_vupdate_high_irq, c_irq_params)) + return -ENOMEM; + } + } + + /* Use GRPH_PFLIP interrupt */ + for (i = VISLANDS30_IV_SRCID_D1_GRPH_PFLIP; + i <= VISLANDS30_IV_SRCID_D6_GRPH_PFLIP; i += 2) { + r = amdgpu_irq_add_id(adev, client_id, i, &adev->pageflip_irq); + if (r) { + drm_err(adev_to_drm(adev), "Failed to add page flip irq id!\n"); + return r; + } + + int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; + int_params.irq_source = + dc_interrupt_to_irq_source(dc, i, 0); + + if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || + int_params.irq_source < DC_IRQ_SOURCE_PFLIP_FIRST || + int_params.irq_source > DC_IRQ_SOURCE_PFLIP_LAST) { + drm_err(adev_to_drm(adev), "Failed to register pflip irq!\n"); + return -EINVAL; + } + + c_irq_params = &adev->dm.pflip_params[int_params.irq_source - DC_IRQ_SOURCE_PFLIP_FIRST]; + + c_irq_params->adev = adev; + c_irq_params->irq_src = int_params.irq_source; + + if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_pflip_high_irq, c_irq_params)) + return -ENOMEM; + } + + /* HPD */ + r = amdgpu_irq_add_id(adev, client_id, + VISLANDS30_IV_SRCID_HOTPLUG_DETECT_A, &adev->hpd_irq); + if (r) { + drm_err(adev_to_drm(adev), "Failed to add hpd irq id!\n"); + return r; + } + + r = amdgpu_dm_register_hpd_handlers(adev); + + return r; +} + +/* Register IRQ sources and initialize IRQ callbacks */ +int amdgpu_dm_dcn10_register_irq_handlers(struct amdgpu_device *adev) +{ + struct dc *dc = adev->dm.dc; + struct common_irq_params *c_irq_params; + struct dc_interrupt_params int_params = {0}; + int r; + int i; +#if defined(CONFIG_DRM_AMD_SECURE_DISPLAY) + static const unsigned int vrtl_int_srcid[] = { + DCN_1_0__SRCID__OTG1_VERTICAL_INTERRUPT0_CONTROL, + DCN_1_0__SRCID__OTG2_VERTICAL_INTERRUPT0_CONTROL, + DCN_1_0__SRCID__OTG3_VERTICAL_INTERRUPT0_CONTROL, + DCN_1_0__SRCID__OTG4_VERTICAL_INTERRUPT0_CONTROL, + DCN_1_0__SRCID__OTG5_VERTICAL_INTERRUPT0_CONTROL, + DCN_1_0__SRCID__OTG6_VERTICAL_INTERRUPT0_CONTROL + }; +#endif + + int_params.requested_polarity = INTERRUPT_POLARITY_DEFAULT; + int_params.current_polarity = INTERRUPT_POLARITY_DEFAULT; + + /* + * Actions of amdgpu_irq_add_id(): + * 1. Register a set() function with base driver. + * Base driver will call set() function to enable/disable an + * interrupt in DC hardware. + * 2. Register amdgpu_dm_irq_handler(). + * Base driver will call amdgpu_dm_irq_handler() for ALL interrupts + * coming from DC hardware. + * amdgpu_dm_irq_handler() will re-direct the interrupt to DC + * for acknowledging and handling. + */ + + /* Use VSTARTUP interrupt */ + for (i = DCN_1_0__SRCID__DC_D1_OTG_VSTARTUP; + i <= DCN_1_0__SRCID__DC_D1_OTG_VSTARTUP + adev->mode_info.num_crtc - 1; + i++) { + r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, i, &adev->crtc_irq); + + if (r) { + drm_err(adev_to_drm(adev), "Failed to add crtc irq id!\n"); + return r; + } + + int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; + int_params.irq_source = + dc_interrupt_to_irq_source(dc, i, 0); + + if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || + int_params.irq_source < DC_IRQ_SOURCE_VBLANK1 || + int_params.irq_source > DC_IRQ_SOURCE_VBLANK6) { + drm_err(adev_to_drm(adev), "Failed to register vblank irq!\n"); + return -EINVAL; + } + + c_irq_params = &adev->dm.vblank_params[int_params.irq_source - DC_IRQ_SOURCE_VBLANK1]; + + c_irq_params->adev = adev; + c_irq_params->irq_src = int_params.irq_source; + + if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_crtc_high_irq, c_irq_params)) + return -ENOMEM; + } + + /* Use otg vertical line interrupt */ +#if defined(CONFIG_DRM_AMD_SECURE_DISPLAY) + for (i = 0; i <= adev->mode_info.num_crtc - 1; i++) { + r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, + vrtl_int_srcid[i], &adev->vline0_irq); + + if (r) { + drm_err(adev_to_drm(adev), "Failed to add vline0 irq id!\n"); + return r; + } + + int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; + int_params.irq_source = + dc_interrupt_to_irq_source(dc, vrtl_int_srcid[i], 0); + + if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || + int_params.irq_source < DC_IRQ_SOURCE_DC1_VLINE0 || + int_params.irq_source > DC_IRQ_SOURCE_DC6_VLINE0) { + drm_err(adev_to_drm(adev), "Failed to register vline0 irq!\n"); + return -EINVAL; + } + + c_irq_params = &adev->dm.vline0_params[int_params.irq_source + - DC_IRQ_SOURCE_DC1_VLINE0]; + + c_irq_params->adev = adev; + c_irq_params->irq_src = int_params.irq_source; + + if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_dcn_vertical_interrupt0_high_irq, + c_irq_params)) + return -ENOMEM; + } +#endif + + /* Use VUPDATE_NO_LOCK interrupt on DCN, which seems to correspond to + * the regular VUPDATE interrupt on DCE. We want DC_IRQ_SOURCE_VUPDATEx + * to trigger at end of each vblank, regardless of state of the lock, + * matching DCE behaviour. + */ + for (i = DCN_1_0__SRCID__OTG0_IHC_V_UPDATE_NO_LOCK_INTERRUPT; + i <= DCN_1_0__SRCID__OTG0_IHC_V_UPDATE_NO_LOCK_INTERRUPT + adev->mode_info.num_crtc - 1; + i++) { + r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, i, &adev->vupdate_irq); + + if (r) { + drm_err(adev_to_drm(adev), "Failed to add vupdate irq id!\n"); + return r; + } + + int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; + int_params.irq_source = + dc_interrupt_to_irq_source(dc, i, 0); + + if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || + int_params.irq_source < DC_IRQ_SOURCE_VUPDATE1 || + int_params.irq_source > DC_IRQ_SOURCE_VUPDATE6) { + drm_err(adev_to_drm(adev), "Failed to register vupdate irq!\n"); + return -EINVAL; + } + + c_irq_params = &adev->dm.vupdate_params[int_params.irq_source - DC_IRQ_SOURCE_VUPDATE1]; + + c_irq_params->adev = adev; + c_irq_params->irq_src = int_params.irq_source; + + if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_vupdate_high_irq, c_irq_params)) + return -ENOMEM; + } + + /* Use GRPH_PFLIP interrupt */ + for (i = DCN_1_0__SRCID__HUBP0_FLIP_INTERRUPT; + i <= DCN_1_0__SRCID__HUBP0_FLIP_INTERRUPT + dc->caps.max_otg_num - 1; + i++) { + r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, i, &adev->pageflip_irq); + if (r) { + drm_err(adev_to_drm(adev), "Failed to add page flip irq id!\n"); + return r; + } + + int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; + int_params.irq_source = + dc_interrupt_to_irq_source(dc, i, 0); + + if (int_params.irq_source == DC_IRQ_SOURCE_INVALID || + int_params.irq_source < DC_IRQ_SOURCE_PFLIP_FIRST || + int_params.irq_source > DC_IRQ_SOURCE_PFLIP_LAST) { + drm_err(adev_to_drm(adev), "Failed to register pflip irq!\n"); + return -EINVAL; + } + + c_irq_params = &adev->dm.pflip_params[int_params.irq_source - DC_IRQ_SOURCE_PFLIP_FIRST]; + + c_irq_params->adev = adev; + c_irq_params->irq_src = int_params.irq_source; + + if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_pflip_high_irq, c_irq_params)) + return -ENOMEM; + } + + /* HPD */ + r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, DCN_1_0__SRCID__DC_HPD1_INT, + &adev->hpd_irq); + if (r) { + drm_err(adev_to_drm(adev), "Failed to add hpd irq id!\n"); + return r; + } + + r = amdgpu_dm_register_hpd_handlers(adev); + + return r; +} + +/* Register Outbox IRQ sources and initialize IRQ callbacks */ +int amdgpu_dm_register_outbox_irq_handlers(struct amdgpu_device *adev) +{ + struct dc *dc = adev->dm.dc; + struct common_irq_params *c_irq_params; + struct dc_interrupt_params int_params = {0}; + int r, i; + + int_params.requested_polarity = INTERRUPT_POLARITY_DEFAULT; + int_params.current_polarity = INTERRUPT_POLARITY_DEFAULT; + + r = amdgpu_irq_add_id(adev, SOC15_IH_CLIENTID_DCE, DCN_1_0__SRCID__DMCUB_OUTBOX_LOW_PRIORITY_READY_INT, + &adev->dmub_outbox_irq); + if (r) { + drm_err(adev_to_drm(adev), "Failed to add outbox irq id!\n"); + return r; + } + + if (dc->ctx->dmub_srv) { + i = DCN_1_0__SRCID__DMCUB_OUTBOX_LOW_PRIORITY_READY_INT; + int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; + int_params.irq_source = + dc_interrupt_to_irq_source(dc, i, 0); + + c_irq_params = &adev->dm.dmub_outbox_params[0]; + + c_irq_params->adev = adev; + c_irq_params->irq_src = int_params.irq_source; + + if (!amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_dmub_outbox1_low_irq, c_irq_params)) + return -ENOMEM; + } + + return 0; +} diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h index 4f6b58f4f90d..ba6968f5626f 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h @@ -27,6 +27,12 @@ #include "irq_types.h" /* DAL irq definitions */ +struct amdgpu_device; +struct amdgpu_crtc; +struct amdgpu_display_manager; +struct hpd_rx_irq_offload_work_queue; +struct work_struct; + /* * Display Manager IRQ-related interfaces (for use by DAL). */ @@ -101,4 +107,17 @@ void amdgpu_dm_irq_suspend(struct amdgpu_device *adev); void amdgpu_dm_irq_resume_early(struct amdgpu_device *adev); void amdgpu_dm_irq_resume_late(struct amdgpu_device *adev); +/* HPD handling */ +struct hpd_rx_irq_offload_work_queue *amdgpu_dm_hpd_rx_irq_create_workqueue(struct amdgpu_device *adev); +void amdgpu_dm_hpd_rx_irq_work_suspend(struct amdgpu_display_manager *dm); +int amdgpu_dm_register_hpd_handlers(struct amdgpu_device *adev); +void amdgpu_dm_hdmi_hpd_debounce_work(struct work_struct *work); + +/* IRQ handlers */ +struct amdgpu_crtc *amdgpu_dm_get_crtc_by_otg_inst(struct amdgpu_device *adev, + int otg_inst); +int amdgpu_dm_dce110_register_irq_handlers(struct amdgpu_device *adev); +int amdgpu_dm_dcn10_register_irq_handlers(struct amdgpu_device *adev); +int amdgpu_dm_register_outbox_irq_handlers(struct amdgpu_device *adev); + #endif /* __AMDGPU_DM_IRQ_H__ */ -- cgit v1.2.3 From 0e967e086e7519966816b76a6309b4516d365aa5 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 27 Apr 2026 22:32:01 -0600 Subject: drm/amd/display: Extract connector and encoder code to amdgpu_dm_connector Move connector lifecycle functions (init, detect, mode validation, property handling, EDID parsing, hotplug processing) and encoder functions (init, destroy, atomic_check, helper_funcs) from amdgpu_dm.c to amdgpu_dm_connector.c. No functional change intended. Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/Makefile | 3 +- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 3743 +------------------- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 50 +- .../amd/display/amdgpu_dm/amdgpu_dm_connector.c | 3575 +++++++++++++++++++ .../amd/display/amdgpu_dm/amdgpu_dm_connector.h | 147 + .../drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c | 2 +- .../amd/display/amdgpu_dm/amdgpu_dm_mst_types.c | 2 +- 7 files changed, 3848 insertions(+), 3674 deletions(-) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile index a6408da05583..d83878e35b61 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/Makefile @@ -44,7 +44,8 @@ AMDGPUDM = \ amdgpu_dm_ism.o \ amdgpu_dm_backlight.o \ amdgpu_dm_audio.o \ - amdgpu_dm_dmub.o + amdgpu_dm_dmub.o \ + amdgpu_dm_connector.o ifdef CONFIG_DRM_AMD_DC_FP AMDGPUDM += dc_fpu.o 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 87a849152d81..f3833e038e99 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -69,6 +69,7 @@ #include "amdgpu_dm_backlight.h" #include "amdgpu_dm_audio.h" #include "amdgpu_dm_dmub.h" +#include "amdgpu_dm_connector.h" #include "ivsrcid/ivsrcid_vislands30.h" @@ -125,46 +126,7 @@ MODULE_FIRMWARE(FIRMWARE_NAVI12_DMCU); /* basic init/fini API */ static int amdgpu_dm_init(struct amdgpu_device *adev); static void amdgpu_dm_fini(struct amdgpu_device *adev); -static bool is_freesync_video_mode(const struct drm_display_mode *mode, struct amdgpu_dm_connector *aconnector); static void reset_freesync_config_for_crtc(struct dm_crtc_state *new_crtc_state); -static struct amdgpu_i2c_adapter * -create_i2c(struct ddc_service *ddc_service, bool oem); - -static enum drm_mode_subconnector get_subconnector_type(struct dc_link *link) -{ - switch (link->dpcd_caps.dongle_type) { - case DISPLAY_DONGLE_NONE: - return DRM_MODE_SUBCONNECTOR_Native; - case DISPLAY_DONGLE_DP_VGA_CONVERTER: - return DRM_MODE_SUBCONNECTOR_VGA; - case DISPLAY_DONGLE_DP_DVI_CONVERTER: - case DISPLAY_DONGLE_DP_DVI_DONGLE: - return DRM_MODE_SUBCONNECTOR_DVID; - case DISPLAY_DONGLE_DP_HDMI_CONVERTER: - case DISPLAY_DONGLE_DP_HDMI_DONGLE: - return DRM_MODE_SUBCONNECTOR_HDMIA; - case DISPLAY_DONGLE_DP_HDMI_MISMATCHED_DONGLE: - default: - return DRM_MODE_SUBCONNECTOR_Unknown; - } -} - -static void update_subconnector_property(struct amdgpu_dm_connector *aconnector) -{ - struct dc_link *link = aconnector->dc_link; - struct drm_connector *connector = &aconnector->base; - enum drm_mode_subconnector subconnector = DRM_MODE_SUBCONNECTOR_Unknown; - - if (connector->connector_type != DRM_MODE_CONNECTOR_DisplayPort) - return; - - if (aconnector->dc_sink) - subconnector = get_subconnector_type(link); - - drm_object_property_set_value(&connector->base, - connector->dev->mode_config.dp_subconnector_property, - subconnector); -} /* * initializes drm_device display related structures, based on the information @@ -177,18 +139,9 @@ static int amdgpu_dm_initialize_drm_device(struct amdgpu_device *adev); /* removes and deallocates the drm structures, created by the above function */ static void amdgpu_dm_destroy_drm_device(struct amdgpu_display_manager *dm); -static int amdgpu_dm_connector_init(struct amdgpu_display_manager *dm, - struct amdgpu_dm_connector *amdgpu_dm_connector, - u32 link_index, - struct amdgpu_encoder *amdgpu_encoder); -static int amdgpu_dm_encoder_init(struct drm_device *dev, - struct amdgpu_encoder *aencoder, - uint32_t link_index); - -static int amdgpu_dm_connector_get_modes(struct drm_connector *connector); - static int amdgpu_dm_atomic_setup_commit(struct drm_atomic_commit *state); static void amdgpu_dm_atomic_commit_tail(struct drm_atomic_commit *state); +static void dm_enable_per_frame_crtc_master_sync(struct dc_state *context); static int amdgpu_dm_atomic_check(struct drm_device *dev, struct drm_atomic_commit *state); @@ -196,6 +149,13 @@ static int amdgpu_dm_atomic_check(struct drm_device *dev, static bool is_timing_unchanged_for_freesync(struct drm_crtc_state *old_crtc_state, struct drm_crtc_state *new_crtc_state); + +static inline void amdgpu_dm_exit_ips_for_hw_access(struct dc *dc) +{ + if (dc->ctx->dmub_srv && !dc->ctx->dmub_srv->idle_exit_counter) + dc_exit_ips_for_hw_access(dc); +} + /* * dm_vblank_get_counter * @@ -369,45 +329,6 @@ static int dm_set_powergating_state(struct amdgpu_ip_block *ip_block, static int dm_early_init(struct amdgpu_ip_block *ip_block); /* Allocate memory for FBC compressed data */ -static void amdgpu_dm_fbc_init(struct drm_connector *connector) -{ - struct amdgpu_device *adev = drm_to_adev(connector->dev); - struct dm_compressor_info *compressor = &adev->dm.compressor; - struct amdgpu_dm_connector *aconn = to_amdgpu_dm_connector(connector); - struct drm_display_mode *mode; - unsigned long max_size = 0; - - if (adev->dm.dc->fbc_compressor == NULL) - return; - - if (aconn->dc_link->connector_signal != SIGNAL_TYPE_EDP) - return; - - if (compressor->bo_ptr) - return; - - - list_for_each_entry(mode, &connector->modes, head) { - if (max_size < (unsigned long) mode->htotal * mode->vtotal) - max_size = (unsigned long) mode->htotal * mode->vtotal; - } - - if (max_size) { - int r = amdgpu_bo_create_kernel(adev, max_size * 4, PAGE_SIZE, - AMDGPU_GEM_DOMAIN_GTT, &compressor->bo_ptr, - &compressor->gpu_addr, &compressor->cpu_addr); - - if (r) - drm_err(adev_to_drm(adev), "DM: Failed to initialize FBC\n"); - else { - adev->dm.dc->ctx->fbc_gpu_addr = compressor->gpu_addr; - drm_info(adev_to_drm(adev), "DM: FBC alloc %lu\n", max_size*4); - } - - } - -} - static void mmhub_read_system_context(struct amdgpu_device *adev, struct dc_phy_addr_space_config *pa_config) { u64 pt_base; @@ -634,40 +555,6 @@ static int amdgpu_dm_init_power_module(struct amdgpu_display_manager *dm) return 0; } -static void hdmi_frl_status_polling_work(struct work_struct *work) -{ - struct amdgpu_display_manager *dm = - container_of(to_delayed_work(work), struct amdgpu_display_manager, - hdmi_frl_status_polling_work); - struct dc *dc = dm->dc; - struct dc_link *dc_link; - bool link_update = false; - - for (int i = 0; i < MAX_LINKS; i++) { - dc_link = dc->links[i]; - - if (!dc_link || !dc_link->local_sink) - continue; - - if (!dc_is_hdmi_signal(dc_link->connector_signal)) - continue; - - if (dc_link->connector_signal != SIGNAL_TYPE_HDMI_FRL) - continue; - - link_update = dc_link_frl_poll_status_flag(dc_link); - if (link_update) { - mutex_lock(&dm->dc_lock); - dc_link_detect(dc_link, DETECT_REASON_RETRAIN); - mutex_unlock(&dm->dc_lock); - } - } - - queue_delayed_work(dm->hdmi_frl_status_polling_wq, - &dm->hdmi_frl_status_polling_work, - msecs_to_jiffies(dm->hdmi_frl_status_polling_delay_ms)); -} - static int amdgpu_dm_init(struct amdgpu_device *adev) { struct dc_init_data init_data; @@ -947,8 +834,6 @@ static int amdgpu_dm_init(struct amdgpu_device *adev) create_singlethread_workqueue("hdmi_frl_status_polling_workqueue"); if (!adev->dm.hdmi_frl_status_polling_wq) drm_err(adev_to_drm(adev), "failed to initialize hdmi_frl_status_polling_workqueue\n"); - adev->dm.hdmi_frl_status_polling_delay_ms = 200; - INIT_DELAYED_WORK(&adev->dm.hdmi_frl_status_polling_work, hdmi_frl_status_polling_work); } if (dc_is_dmub_outbox_supported(adev->dm.dc)) { init_completion(&adev->dm.dmub_aux_transfer_done); @@ -981,9 +866,10 @@ static int amdgpu_dm_init(struct amdgpu_device *adev) } /* Enable outbox notification only after IRQ handlers are registered and DMUB is alive. * It is expected that DMUB will resend any pending notifications at this point. Note - * that hpd and hpd_irq handler registration are deferred to amdgpu_dm_register_hpd_handlers() to - * align legacy interface initialization sequence. Connection status will be proactivly - * detected once in the amdgpu_dm_initialize_drm_device. + * that hpd and hpd_irq handler registration are deferred to + * amdgpu_dm_register_hpd_handlers() to align legacy interface initialization + * sequence. Connection status will be proactivly detected once in the + * amdgpu_dm_initialize_drm_device. */ dc_enable_dmub_outbox(adev->dm.dc); @@ -1316,41 +1202,6 @@ static int dm_sw_fini(struct amdgpu_ip_block *ip_block) return 0; } -static int detect_mst_link_for_all_connectors(struct drm_device *dev) -{ - struct amdgpu_dm_connector *aconnector; - struct drm_connector *connector; - struct drm_connector_list_iter iter; - int ret = 0; - - drm_connector_list_iter_begin(dev, &iter); - drm_for_each_connector_iter(connector, &iter) { - - if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) - continue; - - aconnector = to_amdgpu_dm_connector(connector); - if (aconnector->dc_link->type == dc_connection_mst_branch && - aconnector->mst_mgr.aux) { - drm_dbg_kms(dev, "DM_MST: starting TM on aconnector: %p [id: %d]\n", - aconnector, - aconnector->base.base.id); - - ret = drm_dp_mst_topology_mgr_set_mst(&aconnector->mst_mgr, true); - if (ret < 0) { - drm_err(dev, "DM_MST: Failed to start MST\n"); - aconnector->dc_link->type = - dc_connection_single; - ret = dm_helpers_dp_mst_stop_top_mgr(aconnector->dc_link->ctx, - aconnector->dc_link); - break; - } - } - } - drm_connector_list_iter_end(&iter); - - return ret; -} static void amdgpu_dm_boot_time_crc_init(struct amdgpu_device *adev) { @@ -1448,7 +1299,7 @@ static int dm_late_init(struct amdgpu_ip_block *ip_block) } } - return detect_mst_link_for_all_connectors(adev_to_drm(adev)); + return amdgpu_dm_detect_mst_link_for_all_connectors(adev_to_drm(adev)); } static void resume_mst_branch_status(struct drm_dp_mst_topology_mgr *mgr) @@ -1502,48 +1353,6 @@ out_fail: mutex_unlock(&mgr->lock); } -void hdmi_cec_unset_edid(struct amdgpu_dm_connector *aconnector) -{ - struct cec_notifier *n = aconnector->notifier; - - if (!n) - return; - - cec_notifier_phys_addr_invalidate(n); -} - -void hdmi_cec_set_edid(struct amdgpu_dm_connector *aconnector) -{ - struct drm_connector *connector = &aconnector->base; - struct cec_notifier *n = aconnector->notifier; - - if (!n) - return; - - cec_notifier_set_phys_addr(n, - connector->display_info.source_physical_address); -} - -static void s3_handle_hdmi_cec(struct drm_device *ddev, bool suspend) -{ - struct amdgpu_dm_connector *aconnector; - struct drm_connector *connector; - struct drm_connector_list_iter conn_iter; - - drm_connector_list_iter_begin(ddev, &conn_iter); - drm_for_each_connector_iter(connector, &conn_iter) { - if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) - continue; - - aconnector = to_amdgpu_dm_connector(connector); - if (suspend) - hdmi_cec_unset_edid(aconnector); - else - hdmi_cec_set_edid(aconnector); - } - drm_connector_list_iter_end(&conn_iter); -} - static void s3_handle_mst(struct drm_device *dev, bool suspend) { struct amdgpu_dm_connector *aconnector; @@ -1646,7 +1455,7 @@ static int dm_oem_i2c_hw_init(struct amdgpu_device *adev) oem_ddc_service = dc_get_oem_i2c_device(adev->dm.dc); if (oem_ddc_service) { - oem_i2c = create_i2c(oem_ddc_service, true); + oem_i2c = amdgpu_dm_create_i2c(oem_ddc_service, true); if (!oem_i2c) { drm_info(adev_to_drm(adev), "Failed to create oem i2c adapter data\n"); return -ENOMEM; @@ -1915,7 +1724,7 @@ static int dm_suspend(struct amdgpu_ip_block *ip_block) return r; } - s3_handle_hdmi_cec(adev_to_drm(adev), true); + amdgpu_dm_s3_handle_hdmi_cec(adev_to_drm(adev), true); s3_handle_mst(adev_to_drm(adev), true); @@ -1941,25 +1750,6 @@ static int dm_suspend(struct amdgpu_ip_block *ip_block) return 0; } -struct drm_connector * -amdgpu_dm_find_first_crtc_matching_connector(struct drm_atomic_commit *state, - struct drm_crtc *crtc) -{ - u32 i; - struct drm_connector_state *new_con_state; - struct drm_connector *connector; - struct drm_crtc *crtc_from_state; - - for_each_new_connector_in_state(state, connector, new_con_state, i) { - crtc_from_state = new_con_state->crtc; - - if (crtc_from_state == crtc) - return connector; - } - - return NULL; -} - void amdgpu_dm_emulated_link_detect(struct dc_link *link) { struct dc_sink_init_data sink_init_data = { 0 }; @@ -2289,7 +2079,7 @@ static int dm_resume(struct amdgpu_ip_block *ip_block) */ amdgpu_dm_irq_resume_early(adev); - s3_handle_hdmi_cec(ddev, false); + amdgpu_dm_s3_handle_hdmi_cec(ddev, false); /* On resume we need to rewrite the MSTM control bits to enable MST*/ s3_handle_mst(ddev, false); @@ -2442,216 +2232,6 @@ static struct drm_mode_config_helper_funcs amdgpu_dm_mode_config_helperfuncs = { .atomic_commit_setup = amdgpu_dm_atomic_setup_commit, }; -#define DDC_MANUFACTURERNAME_SAMSUNG 0x2D4C - -static void dm_set_panel_type(struct amdgpu_dm_connector *aconnector) -{ - struct drm_connector *connector = &aconnector->base; - struct drm_display_info *display_info = &connector->display_info; - struct dc_link *link = aconnector->dc_link; - struct amdgpu_device *adev; - - adev = drm_to_adev(connector->dev); - - link->panel_type = PANEL_TYPE_NONE; - - switch (display_info->amd_vsdb.panel_type) { - case AMD_VSDB_PANEL_TYPE_OLED: - link->panel_type = PANEL_TYPE_OLED; - break; - case AMD_VSDB_PANEL_TYPE_MINILED: - link->panel_type = PANEL_TYPE_MINILED; - break; - } - - /* If VSDB didn't determine panel type, check DPCD ext caps */ - if (link->panel_type == PANEL_TYPE_NONE) { - if (link->dpcd_sink_ext_caps.bits.miniled == 1) - link->panel_type = PANEL_TYPE_MINILED; - if (link->dpcd_sink_ext_caps.bits.oled == 1) - link->panel_type = PANEL_TYPE_OLED; - } - - /* - * TODO: get panel type from DID2 that has device technology field - * to specify if it's OLED or not. But we need to wait for DID2 - * support in DC and EDID parser to be able to use it here. - */ - - if (link->panel_type == PANEL_TYPE_NONE) { - struct drm_amd_vsdb_info *vsdb = &display_info->amd_vsdb; - u32 lum1_max = vsdb->luminance_range1.max_luminance; - u32 lum2_max = vsdb->luminance_range2.max_luminance; - - if (vsdb->version && link->local_sink && - link->local_sink->edid_caps.manufacturer_id == - DDC_MANUFACTURERNAME_SAMSUNG && - lum1_max >= ((lum2_max * 3) / 2)) - link->panel_type = PANEL_TYPE_MINILED; - } - - if (link->panel_type == PANEL_TYPE_OLED) - drm_object_property_set_value(&connector->base, - adev_to_drm(adev)->mode_config.panel_type_property, - DRM_MODE_PANEL_TYPE_OLED); - else - drm_object_property_set_value(&connector->base, - adev_to_drm(adev)->mode_config.panel_type_property, - DRM_MODE_PANEL_TYPE_UNKNOWN); - - drm_dbg_kms(aconnector->base.dev, "Panel type: %d\n", link->panel_type); -} - -DEFINE_FREE(sink_release, struct dc_sink *, if (_T) dc_sink_release(_T)) - -void amdgpu_dm_update_connector_after_detect( - struct amdgpu_dm_connector *aconnector) -{ - struct drm_connector *connector = &aconnector->base; - struct dc_sink *sink __free(sink_release) = NULL; - struct drm_device *dev = connector->dev; - - /* MST handled by drm_mst framework */ - if (aconnector->mst_mgr.mst_state == true) - return; - - sink = aconnector->dc_link->local_sink; - if (sink) - dc_sink_retain(sink); - - /* - * Edid mgmt connector gets first update only in mode_valid hook and then - * the connector sink is set to either fake or physical sink depends on link status. - * Skip if already done during boot. - */ - if (aconnector->base.force != DRM_FORCE_UNSPECIFIED - && aconnector->dc_em_sink) { - - /* - * For S3 resume with headless use eml_sink to fake stream - * because on resume connector->sink is set to NULL - */ - guard(mutex)(&dev->mode_config.mutex); - - if (sink) { - if (aconnector->dc_sink) { - amdgpu_dm_update_freesync_caps(connector, NULL, true); - /* - * retain and release below are used to - * bump up refcount for sink because the link doesn't point - * to it anymore after disconnect, so on next crtc to connector - * reshuffle by UMD we will get into unwanted dc_sink release - */ - dc_sink_release(aconnector->dc_sink); - } - aconnector->dc_sink = sink; - dc_sink_retain(aconnector->dc_sink); - amdgpu_dm_update_freesync_caps(connector, - aconnector->drm_edid, true); - } else { - amdgpu_dm_update_freesync_caps(connector, NULL, true); - if (!aconnector->dc_sink) { - aconnector->dc_sink = aconnector->dc_em_sink; - dc_sink_retain(aconnector->dc_sink); - } - } - - return; - } - - /* - * TODO: temporary guard to look for proper fix - * if this sink is MST sink, we should not do anything - */ - if (sink && sink->sink_signal == SIGNAL_TYPE_DISPLAY_PORT_MST) - return; - - if (aconnector->dc_sink == sink) { - /* - * We got a DP short pulse (Link Loss, DP CTS, etc...). - * Do nothing!! - */ - drm_dbg_kms(dev, "DCHPD: connector_id=%d: dc_sink didn't change.\n", - aconnector->connector_id); - return; - } - - drm_dbg_kms(dev, "DCHPD: connector_id=%d: Old sink=%p New sink=%p\n", - aconnector->connector_id, aconnector->dc_sink, sink); - - /* When polling, DRM has already locked the mutex for us. */ - if (!drm_kms_helper_is_poll_worker()) - mutex_lock(&dev->mode_config.mutex); - - /* - * 1. Update status of the drm connector - * 2. Send an event and let userspace tell us what to do - */ - if (sink) { - /* - * TODO: check if we still need the S3 mode update workaround. - * If yes, put it here. - */ - if (aconnector->dc_sink) { - amdgpu_dm_update_freesync_caps(connector, NULL, true); - dc_sink_release(aconnector->dc_sink); - } - - aconnector->dc_sink = sink; - dc_sink_retain(aconnector->dc_sink); - drm_edid_free(aconnector->drm_edid); - aconnector->drm_edid = NULL; - if (sink->dc_edid.length == 0) { - hdmi_cec_unset_edid(aconnector); - if (aconnector->dc_link->aux_mode) { - drm_dp_cec_unset_edid(&aconnector->dm_dp_aux.aux); - } - } else { - const struct edid *edid = (const struct edid *)sink->dc_edid.raw_edid; - - aconnector->drm_edid = drm_edid_alloc(edid, sink->dc_edid.length); - drm_edid_connector_update(connector, aconnector->drm_edid); - - hdmi_cec_set_edid(aconnector); - if (aconnector->dc_link->aux_mode) - drm_dp_cec_attach(&aconnector->dm_dp_aux.aux, - connector->display_info.source_physical_address); - } - - if (!aconnector->timing_requested) { - aconnector->timing_requested = - kzalloc_obj(struct dc_crtc_timing); - if (!aconnector->timing_requested) - drm_err(dev, - "failed to create aconnector->requested_timing\n"); - } - - amdgpu_dm_update_freesync_caps(connector, aconnector->drm_edid, true); - amdgpu_dm_update_connector_ext_caps(aconnector); - dm_set_panel_type(aconnector); - } else { - hdmi_cec_unset_edid(aconnector); - drm_dp_cec_unset_edid(&aconnector->dm_dp_aux.aux); - amdgpu_dm_update_freesync_caps(connector, NULL, true); - aconnector->num_modes = 0; - dc_sink_release(aconnector->dc_sink); - aconnector->dc_sink = NULL; - drm_edid_free(aconnector->drm_edid); - aconnector->drm_edid = NULL; - kfree(aconnector->timing_requested); - aconnector->timing_requested = NULL; - /* Set CP to DESIRED if it was ENABLED, so we can re-enable it again on hotplug */ - if (connector->state->content_protection == DRM_MODE_CONTENT_PROTECTION_ENABLED) - connector->state->content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; - } - - update_subconnector_property(aconnector); - - /* When polling, the mutex will be unlocked for us by DRM. */ - if (!drm_kms_helper_is_poll_worker()) - mutex_unlock(&dev->mode_config.mutex); -} - /* * Acquires the lock for the atomic state object and returns * the new atomic state. @@ -2842,10 +2422,6 @@ static int initialize_plane(struct amdgpu_display_manager *dm, } -static void amdgpu_set_panel_orientation(struct drm_connector *connector); - - - /* * In this architecture, the association * connector -> encoder -> crtc @@ -3410,16 +2986,6 @@ static bool modereset_required(struct drm_crtc_state *crtc_state) return !crtc_state->active && drm_atomic_crtc_needs_modeset(crtc_state); } -static void amdgpu_dm_encoder_destroy(struct drm_encoder *encoder) -{ - drm_encoder_cleanup(encoder); - kfree(encoder); -} - -static const struct drm_encoder_funcs amdgpu_dm_encoder_funcs = { - .destroy = amdgpu_dm_encoder_destroy, -}; - static int fill_plane_color_attributes(const struct drm_plane_state *plane_state, const enum surface_pixel_format format, @@ -3803,7 +3369,7 @@ ffu: &flip_addrs->dirty_rect_count, true); } -static void update_stream_scaling_settings(struct drm_device *dev, +void amdgpu_dm_update_stream_scaling_settings(struct drm_device *dev, const struct drm_display_mode *mode, const struct dm_connector_state *dm_state, struct dc_stream_state *stream) @@ -3859,1952 +3425,37 @@ static void update_stream_scaling_settings(struct drm_device *dev, } -static enum dc_color_depth -convert_color_depth_from_display_info(const struct drm_connector *connector, - bool is_y420, int requested_bpc) +static int dm_update_mst_vcpi_slots_for_dsc(struct drm_atomic_commit *state, + struct dc_state *dc_state, + struct dsc_mst_fairness_vars *vars) { - u8 bpc; - - if (is_y420) { - bpc = 8; - - /* Cap display bpc based on HDMI 2.0 HF-VSDB */ - if (connector->display_info.hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_48) - bpc = 16; - else if (connector->display_info.hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_36) - bpc = 12; - else if (connector->display_info.hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_30) - bpc = 10; - } else { - bpc = (uint8_t)connector->display_info.bpc; - /* Assume 8 bpc by default if no bpc is specified. */ - bpc = bpc ? bpc : 8; - } + struct dc_stream_state *stream = NULL; + struct drm_connector *connector; + struct drm_connector_state *new_con_state; + struct amdgpu_dm_connector *aconnector; + struct dm_connector_state *dm_conn_state; + int i, j, ret; + int vcpi, pbn_div, pbn = 0, slot_num = 0; - if (requested_bpc > 0) { - /* - * Cap display bpc based on the user requested value. - * - * The value for state->max_bpc may not correctly updated - * depending on when the connector gets added to the state - * or if this was called outside of atomic check, so it - * can't be used directly. - */ - bpc = min_t(u8, bpc, requested_bpc); + for_each_new_connector_in_state(state, connector, new_con_state, i) { - /* Round down to the nearest even number. */ - bpc = bpc - (bpc & 1); - } + if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) + continue; - switch (bpc) { - case 0: - /* - * Temporary Work around, DRM doesn't parse color depth for - * EDID revision before 1.4 - * TODO: Fix edid parsing - */ - return COLOR_DEPTH_888; - case 6: - return COLOR_DEPTH_666; - case 8: - return COLOR_DEPTH_888; - case 10: - return COLOR_DEPTH_101010; - case 12: - return COLOR_DEPTH_121212; - case 14: - return COLOR_DEPTH_141414; - case 16: - return COLOR_DEPTH_161616; - default: - return COLOR_DEPTH_UNDEFINED; - } -} + aconnector = to_amdgpu_dm_connector(connector); -static enum dc_aspect_ratio -get_aspect_ratio(const struct drm_display_mode *mode_in) -{ - /* 1-1 mapping, since both enums follow the HDMI spec. */ - return (enum dc_aspect_ratio) mode_in->picture_aspect_ratio; -} + if (!aconnector->mst_output_port) + continue; -static enum dc_color_space -get_output_color_space(const struct dc_crtc_timing *dc_crtc_timing, - const struct drm_connector_state *connector_state) -{ - enum dc_color_space color_space = COLOR_SPACE_SRGB; + if (!new_con_state || !new_con_state->crtc) + continue; - switch (connector_state->colorspace) { - case DRM_MODE_COLORIMETRY_BT601_YCC: - if (dc_crtc_timing->flags.Y_ONLY) - color_space = COLOR_SPACE_YCBCR601_LIMITED; - else - color_space = COLOR_SPACE_YCBCR601; - break; - case DRM_MODE_COLORIMETRY_BT709_YCC: - if (dc_crtc_timing->flags.Y_ONLY) - color_space = COLOR_SPACE_YCBCR709_LIMITED; - else - color_space = COLOR_SPACE_YCBCR709; - break; - case DRM_MODE_COLORIMETRY_OPRGB: - color_space = COLOR_SPACE_ADOBERGB; - break; - case DRM_MODE_COLORIMETRY_BT2020_RGB: - case DRM_MODE_COLORIMETRY_BT2020_YCC: - if (dc_crtc_timing->pixel_encoding == PIXEL_ENCODING_RGB) - color_space = COLOR_SPACE_2020_RGB_FULLRANGE; - else - color_space = COLOR_SPACE_2020_YCBCR_LIMITED; - break; - case DRM_MODE_COLORIMETRY_DEFAULT: // ITU601 - default: - if (dc_crtc_timing->pixel_encoding == PIXEL_ENCODING_RGB) { - color_space = COLOR_SPACE_SRGB; - if (connector_state->hdmi.broadcast_rgb == DRM_HDMI_BROADCAST_RGB_LIMITED) - color_space = COLOR_SPACE_SRGB_LIMITED; - /* - * 27030khz is the separation point between HDTV and SDTV - * according to HDMI spec, we use YCbCr709 and YCbCr601 - * respectively - */ - } else if (dc_crtc_timing->pix_clk_100hz > 270300) { - if (dc_crtc_timing->flags.Y_ONLY) - color_space = - COLOR_SPACE_YCBCR709_LIMITED; - else - color_space = COLOR_SPACE_YCBCR709; - } else { - if (dc_crtc_timing->flags.Y_ONLY) - color_space = - COLOR_SPACE_YCBCR601_LIMITED; - else - color_space = COLOR_SPACE_YCBCR601; - } - break; - } + dm_conn_state = to_dm_connector_state(new_con_state); - return color_space; -} - -static enum display_content_type -get_output_content_type(const struct drm_connector_state *connector_state) -{ - switch (connector_state->content_type) { - default: - case DRM_MODE_CONTENT_TYPE_NO_DATA: - return DISPLAY_CONTENT_TYPE_NO_DATA; - case DRM_MODE_CONTENT_TYPE_GRAPHICS: - return DISPLAY_CONTENT_TYPE_GRAPHICS; - case DRM_MODE_CONTENT_TYPE_PHOTO: - return DISPLAY_CONTENT_TYPE_PHOTO; - case DRM_MODE_CONTENT_TYPE_CINEMA: - return DISPLAY_CONTENT_TYPE_CINEMA; - case DRM_MODE_CONTENT_TYPE_GAME: - return DISPLAY_CONTENT_TYPE_GAME; - } -} - -static bool adjust_colour_depth_from_display_info( - struct dc_crtc_timing *timing_out, - const struct drm_display_info *info) -{ - enum dc_color_depth depth = timing_out->display_color_depth; - int normalized_clk; - - do { - normalized_clk = timing_out->pix_clk_100hz / 10; - /* YCbCr 4:2:0 requires additional adjustment of 1/2 */ - if (timing_out->pixel_encoding == PIXEL_ENCODING_YCBCR420) - normalized_clk /= 2; - /* Adjusting pix clock following on HDMI spec based on colour depth */ - switch (depth) { - case COLOR_DEPTH_888: - break; - case COLOR_DEPTH_101010: - normalized_clk = (normalized_clk * 30) / 24; - break; - case COLOR_DEPTH_121212: - normalized_clk = (normalized_clk * 36) / 24; - break; - case COLOR_DEPTH_161616: - normalized_clk = (normalized_clk * 48) / 24; - break; - default: - /* The above depths are the only ones valid for HDMI. */ - return false; - } - if (normalized_clk <= info->max_tmds_clock) { - timing_out->display_color_depth = depth; - return true; - } - } while (--depth > COLOR_DEPTH_666); - return false; -} - -static void fill_stream_properties_from_drm_display_mode( - struct dc_stream_state *stream, - const struct drm_display_mode *mode_in, - const struct drm_connector *connector, - const struct drm_connector_state *connector_state, - const struct dc_stream_state *old_stream, - int requested_bpc) -{ - struct dc_crtc_timing *timing_out = &stream->timing; - const struct drm_display_info *info = &connector->display_info; - struct amdgpu_dm_connector *aconnector = NULL; - struct hdmi_vendor_infoframe hv_frame; - struct hdmi_avi_infoframe avi_frame; - ssize_t err; - - if (connector->connector_type != DRM_MODE_CONNECTOR_WRITEBACK) - aconnector = to_amdgpu_dm_connector(connector); - - memset(&hv_frame, 0, sizeof(hv_frame)); - memset(&avi_frame, 0, sizeof(avi_frame)); - - timing_out->h_border_left = 0; - timing_out->h_border_right = 0; - timing_out->v_border_top = 0; - timing_out->v_border_bottom = 0; - /* TODO: un-hardcode */ - if (drm_mode_is_420_only(info, mode_in) - && (stream->signal == SIGNAL_TYPE_HDMI_TYPE_A || - stream->signal == SIGNAL_TYPE_HDMI_FRL) - && aconnector - && aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR420) - timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR420; - else if (drm_mode_is_420_also(info, mode_in) - && aconnector - && (aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR420 - || aconnector->force_yuv420_output)) - timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR420; - else if ((connector->display_info.color_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422)) - && aconnector - && (aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR422 - || aconnector->force_yuv422_output)) - timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR422; - else if ((connector->display_info.color_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444)) - && (stream->signal == SIGNAL_TYPE_HDMI_TYPE_A || - stream->signal == SIGNAL_TYPE_HDMI_FRL) - && aconnector - && aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR444) - timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR444; - else - timing_out->pixel_encoding = PIXEL_ENCODING_RGB; - - timing_out->timing_3d_format = TIMING_3D_FORMAT_NONE; - timing_out->display_color_depth = convert_color_depth_from_display_info( - connector, - (timing_out->pixel_encoding == PIXEL_ENCODING_YCBCR420), - requested_bpc); - timing_out->scan_type = SCANNING_TYPE_NODATA; - timing_out->hdmi_vic = 0; - - if (old_stream) { - timing_out->vic = old_stream->timing.vic; - timing_out->flags.HSYNC_POSITIVE_POLARITY = old_stream->timing.flags.HSYNC_POSITIVE_POLARITY; - timing_out->flags.VSYNC_POSITIVE_POLARITY = old_stream->timing.flags.VSYNC_POSITIVE_POLARITY; - } else { - timing_out->vic = drm_match_cea_mode(mode_in); - if (mode_in->flags & DRM_MODE_FLAG_PHSYNC) - timing_out->flags.HSYNC_POSITIVE_POLARITY = 1; - if (mode_in->flags & DRM_MODE_FLAG_PVSYNC) - timing_out->flags.VSYNC_POSITIVE_POLARITY = 1; - } - - if (stream->signal == SIGNAL_TYPE_HDMI_TYPE_A || - stream->signal == SIGNAL_TYPE_HDMI_FRL) { - err = drm_hdmi_avi_infoframe_from_display_mode(&avi_frame, - (struct drm_connector *)connector, - mode_in); - if (err < 0) - drm_warn_once(connector->dev, "Failed to setup avi infoframe on connector %s: %zd\n", - connector->name, err); - timing_out->vic = avi_frame.video_code; - err = drm_hdmi_vendor_infoframe_from_display_mode(&hv_frame, - (struct drm_connector *)connector, - mode_in); - if (err < 0) - drm_warn_once(connector->dev, "Failed to setup vendor infoframe on connector %s: %zd\n", - connector->name, err); - timing_out->hdmi_vic = hv_frame.vic; - } - - if (aconnector && is_freesync_video_mode(mode_in, aconnector)) { - timing_out->h_addressable = mode_in->hdisplay; - timing_out->h_total = mode_in->htotal; - timing_out->h_sync_width = mode_in->hsync_end - mode_in->hsync_start; - timing_out->h_front_porch = mode_in->hsync_start - mode_in->hdisplay; - timing_out->v_total = mode_in->vtotal; - timing_out->v_addressable = mode_in->vdisplay; - timing_out->v_front_porch = mode_in->vsync_start - mode_in->vdisplay; - timing_out->v_sync_width = mode_in->vsync_end - mode_in->vsync_start; - timing_out->pix_clk_100hz = mode_in->clock * 10; - } else { - timing_out->h_addressable = mode_in->crtc_hdisplay; - timing_out->h_total = mode_in->crtc_htotal; - timing_out->h_sync_width = mode_in->crtc_hsync_end - mode_in->crtc_hsync_start; - timing_out->h_front_porch = mode_in->crtc_hsync_start - mode_in->crtc_hdisplay; - timing_out->v_total = mode_in->crtc_vtotal; - timing_out->v_addressable = mode_in->crtc_vdisplay; - timing_out->v_front_porch = mode_in->crtc_vsync_start - mode_in->crtc_vdisplay; - timing_out->v_sync_width = mode_in->crtc_vsync_end - mode_in->crtc_vsync_start; - timing_out->pix_clk_100hz = mode_in->crtc_clock * 10; - } - - timing_out->aspect_ratio = get_aspect_ratio(mode_in); - - stream->out_transfer_func.type = TF_TYPE_PREDEFINED; - stream->out_transfer_func.tf = TRANSFER_FUNCTION_SRGB; - if (stream->signal == SIGNAL_TYPE_HDMI_TYPE_A) { - if (!adjust_colour_depth_from_display_info(timing_out, info) && - drm_mode_is_420_also(info, mode_in) && - timing_out->pixel_encoding != PIXEL_ENCODING_YCBCR420) { - timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR420; - adjust_colour_depth_from_display_info(timing_out, info); - } - } - - stream->output_color_space = get_output_color_space(timing_out, connector_state); - stream->content_type = get_output_content_type(connector_state); -} - -static void -copy_crtc_timing_for_drm_display_mode(const struct drm_display_mode *src_mode, - struct drm_display_mode *dst_mode) -{ - dst_mode->crtc_hdisplay = src_mode->crtc_hdisplay; - dst_mode->crtc_vdisplay = src_mode->crtc_vdisplay; - dst_mode->crtc_clock = src_mode->crtc_clock; - dst_mode->crtc_hblank_start = src_mode->crtc_hblank_start; - dst_mode->crtc_hblank_end = src_mode->crtc_hblank_end; - dst_mode->crtc_hsync_start = src_mode->crtc_hsync_start; - dst_mode->crtc_hsync_end = src_mode->crtc_hsync_end; - dst_mode->crtc_htotal = src_mode->crtc_htotal; - dst_mode->crtc_hskew = src_mode->crtc_hskew; - dst_mode->crtc_vblank_start = src_mode->crtc_vblank_start; - dst_mode->crtc_vblank_end = src_mode->crtc_vblank_end; - dst_mode->crtc_vsync_start = src_mode->crtc_vsync_start; - dst_mode->crtc_vsync_end = src_mode->crtc_vsync_end; - dst_mode->crtc_vtotal = src_mode->crtc_vtotal; -} - -static void -decide_crtc_timing_for_drm_display_mode(struct drm_display_mode *drm_mode, - const struct drm_display_mode *native_mode, - bool scale_enabled) -{ - if (scale_enabled || ( - native_mode->clock == drm_mode->clock && - native_mode->htotal == drm_mode->htotal && - native_mode->vtotal == drm_mode->vtotal)) { - if (native_mode->crtc_clock) - copy_crtc_timing_for_drm_display_mode(native_mode, drm_mode); - } else { - /* no scaling nor amdgpu inserted, no need to patch */ - } -} - -static struct dc_sink * -create_fake_sink(struct drm_device *dev, struct dc_link *link) -{ - struct dc_sink_init_data sink_init_data = { 0 }; - struct dc_sink *sink = NULL; - - sink_init_data.link = link; - sink_init_data.sink_signal = link->connector_signal; - - sink = dc_sink_create(&sink_init_data); - if (!sink) { - drm_err(dev, "Failed to create sink!\n"); - return NULL; - } - sink->sink_signal = SIGNAL_TYPE_VIRTUAL; - - return sink; -} - -static void set_multisync_trigger_params( - struct dc_stream_state *stream) -{ - struct dc_stream_state *master = NULL; - - if (stream->triggered_crtc_reset.enabled) { - master = stream->triggered_crtc_reset.event_source; - stream->triggered_crtc_reset.event = - master->timing.flags.VSYNC_POSITIVE_POLARITY ? - CRTC_EVENT_VSYNC_RISING : CRTC_EVENT_VSYNC_FALLING; - stream->triggered_crtc_reset.delay = TRIGGER_DELAY_NEXT_PIXEL; - } -} - -static void set_master_stream(struct dc_stream_state *stream_set[], - int stream_count) -{ - int j, highest_rfr = 0, master_stream = 0; - - for (j = 0; j < stream_count; j++) { - if (stream_set[j] && stream_set[j]->triggered_crtc_reset.enabled) { - int refresh_rate = 0; - - refresh_rate = (stream_set[j]->timing.pix_clk_100hz*100)/ - (stream_set[j]->timing.h_total*stream_set[j]->timing.v_total); - if (refresh_rate > highest_rfr) { - highest_rfr = refresh_rate; - master_stream = j; - } - } - } - for (j = 0; j < stream_count; j++) { - if (stream_set[j]) - stream_set[j]->triggered_crtc_reset.event_source = stream_set[master_stream]; - } -} - -static void dm_enable_per_frame_crtc_master_sync(struct dc_state *context) -{ - int i = 0; - struct dc_stream_state *stream; - - if (context->stream_count < 2) - return; - for (i = 0; i < context->stream_count ; i++) { - if (!context->streams[i]) - continue; - /* - * TODO: add a function to read AMD VSDB bits and set - * crtc_sync_master.multi_sync_enabled flag - * For now it's set to false - */ - } - - set_master_stream(context->streams, context->stream_count); - - for (i = 0; i < context->stream_count ; i++) { - stream = context->streams[i]; - - if (!stream) - continue; - - set_multisync_trigger_params(stream); - } -} - -/** - * DOC: FreeSync Video - * - * When a userspace application wants to play a video, the content follows a - * standard format definition that usually specifies the FPS for that format. - * The below list illustrates some video format and the expected FPS, - * respectively: - * - * - TV/NTSC (23.976 FPS) - * - Cinema (24 FPS) - * - TV/PAL (25 FPS) - * - TV/NTSC (29.97 FPS) - * - TV/NTSC (30 FPS) - * - Cinema HFR (48 FPS) - * - TV/PAL (50 FPS) - * - Commonly used (60 FPS) - * - Multiples of 24 (48,72,96 FPS) - * - * The list of standards video format is not huge and can be added to the - * connector modeset list beforehand. With that, userspace can leverage - * FreeSync to extends the front porch in order to attain the target refresh - * rate. Such a switch will happen seamlessly, without screen blanking or - * reprogramming of the output in any other way. If the userspace requests a - * modesetting change compatible with FreeSync modes that only differ in the - * refresh rate, DC will skip the full update and avoid blink during the - * transition. For example, the video player can change the modesetting from - * 60Hz to 30Hz for playing TV/NTSC content when it goes full screen without - * causing any display blink. This same concept can be applied to a mode - * setting change. - */ -static struct drm_display_mode * -get_highest_refresh_rate_mode(struct amdgpu_dm_connector *aconnector, - bool use_probed_modes) -{ - struct drm_display_mode *m, *m_pref = NULL; - u16 current_refresh, highest_refresh; - struct list_head *list_head = use_probed_modes ? - &aconnector->base.probed_modes : - &aconnector->base.modes; - - if (aconnector->base.connector_type == DRM_MODE_CONNECTOR_WRITEBACK) - return NULL; - - if (aconnector->freesync_vid_base.clock != 0) - return &aconnector->freesync_vid_base; - - /* Find the preferred mode */ - list_for_each_entry(m, list_head, head) { - if (m->type & DRM_MODE_TYPE_PREFERRED) { - m_pref = m; - break; - } - } - - if (!m_pref) { - /* Probably an EDID with no preferred mode. Fallback to first entry */ - m_pref = list_first_entry_or_null( - &aconnector->base.modes, struct drm_display_mode, head); - if (!m_pref) { - drm_dbg_driver(aconnector->base.dev, "No preferred mode found in EDID\n"); - return NULL; - } - } - - highest_refresh = drm_mode_vrefresh(m_pref); - - /* - * Find the mode with highest refresh rate with same resolution. - * For some monitors, preferred mode is not the mode with highest - * supported refresh rate. - */ - list_for_each_entry(m, list_head, head) { - current_refresh = drm_mode_vrefresh(m); - - if (m->hdisplay == m_pref->hdisplay && - m->vdisplay == m_pref->vdisplay && - highest_refresh < current_refresh) { - highest_refresh = current_refresh; - m_pref = m; - } - } - - drm_mode_copy(&aconnector->freesync_vid_base, m_pref); - return m_pref; -} - -static bool is_freesync_video_mode(const struct drm_display_mode *mode, - struct amdgpu_dm_connector *aconnector) -{ - struct drm_display_mode *high_mode; - int timing_diff; - - high_mode = get_highest_refresh_rate_mode(aconnector, false); - if (!high_mode || !mode) - return false; - - timing_diff = high_mode->vtotal - mode->vtotal; - - if (high_mode->clock == 0 || high_mode->clock != mode->clock || - high_mode->hdisplay != mode->hdisplay || - high_mode->vdisplay != mode->vdisplay || - high_mode->hsync_start != mode->hsync_start || - high_mode->hsync_end != mode->hsync_end || - high_mode->htotal != mode->htotal || - high_mode->hskew != mode->hskew || - high_mode->vscan != mode->vscan || - high_mode->vsync_start - mode->vsync_start != timing_diff || - high_mode->vsync_end - mode->vsync_end != timing_diff) - return false; - else - return true; -} - -#if defined(CONFIG_DRM_AMD_DC_FP) -static void update_dsc_caps(struct amdgpu_dm_connector *aconnector, - struct dc_sink *sink, struct dc_stream_state *stream, - struct dsc_dec_dpcd_caps *dsc_caps) -{ - stream->timing.flags.DSC = 0; - dsc_caps->is_dsc_supported = false; - - if (aconnector->dc_link && (sink->sink_signal == SIGNAL_TYPE_DISPLAY_PORT || - sink->sink_signal == SIGNAL_TYPE_EDP)) { - if (sink->link->dpcd_caps.dongle_type == DISPLAY_DONGLE_NONE) - dc_dsc_parse_dsc_dpcd(aconnector->dc_link->ctx->dc, - aconnector->dc_link->dpcd_caps.dsc_caps.dsc_basic_caps.raw, - aconnector->dc_link->dpcd_caps.dsc_caps.dsc_branch_decoder_caps.raw, - dsc_caps); - else if (sink->link->dpcd_caps.dongle_type == DISPLAY_DONGLE_DP_HDMI_CONVERTER) { - if (aconnector->dc_link->dpcd_caps.dsc_caps.dsc_basic_caps.fields.dsc_support.DSC_PASSTHROUGH_SUPPORT && - !aconnector->dsc_settings.dsc_force_disable_passthrough && - aconnector->dc_link->dpcd_caps.dongle_caps.dp_hdmi_frl_max_link_bw_in_kbps > 0 && - sink->edid_caps.frl_dsc_support && - sink->edid_caps.max_frl_rate > 0 && - sink->edid_caps.frl_dsc_max_frl_rate > 0) - dc_dsc_parse_dsc_edid(aconnector->dc_link->ctx->dc, &sink->edid_caps, dsc_caps); - else - dc_dsc_parse_dsc_dpcd(aconnector->dc_link->ctx->dc, - aconnector->dc_link->dpcd_caps.dsc_caps.dsc_basic_caps.raw, - aconnector->dc_link->dpcd_caps.dsc_caps.dsc_branch_decoder_caps.raw, - dsc_caps); - } - } else if (aconnector->dc_link && sink->sink_signal == SIGNAL_TYPE_HDMI_FRL) { - if (sink->edid_caps.frl_dsc_support && - sink->edid_caps.max_frl_rate > 0 && - sink->edid_caps.frl_dsc_max_frl_rate > 0) - dc_dsc_parse_dsc_edid(aconnector->dc_link->ctx->dc, &sink->edid_caps, dsc_caps); - } -} - -static void apply_dsc_policy_for_edp(struct amdgpu_dm_connector *aconnector, - struct dc_sink *sink, struct dc_stream_state *stream, - struct dsc_dec_dpcd_caps *dsc_caps, - uint32_t max_dsc_target_bpp_limit_override) -{ - const struct dc_link_settings *verified_link_cap = NULL; - u32 link_bw_in_kbps; - u32 edp_min_bpp_x16, edp_max_bpp_x16; - struct dc *dc = sink->ctx->dc; - struct dc_dsc_bw_range bw_range = {0}; - struct dc_dsc_config dsc_cfg = {0}; - struct dc_dsc_config_options dsc_options = {0}; - - dc_dsc_get_default_config_option(dc, &dsc_options); - dsc_options.max_target_bpp_limit_override_x16 = max_dsc_target_bpp_limit_override * 16; - - verified_link_cap = dc_link_get_link_cap(stream->link); - link_bw_in_kbps = dc_link_bandwidth_kbps(stream->link, verified_link_cap); - edp_min_bpp_x16 = 8 * 16; - edp_max_bpp_x16 = 8 * 16; - - if (edp_max_bpp_x16 > dsc_caps->edp_max_bits_per_pixel) - edp_max_bpp_x16 = dsc_caps->edp_max_bits_per_pixel; - - if (edp_max_bpp_x16 < edp_min_bpp_x16) - edp_min_bpp_x16 = edp_max_bpp_x16; - - if (dc_dsc_compute_bandwidth_range(dc->res_pool->dscs[0], - dc->debug.dsc_min_slice_height_override, - edp_min_bpp_x16, edp_max_bpp_x16, - dsc_caps, - &stream->timing, - dc_link_get_highest_encoding_format(aconnector->dc_link), - &bw_range)) { - - if (bw_range.max_kbps < link_bw_in_kbps) { - if (dc_dsc_compute_config(dc->res_pool->dscs[0], - dsc_caps, - &dsc_options, - 0, - &stream->timing, - dc_link_get_highest_encoding_format(aconnector->dc_link), - &dsc_cfg)) { - stream->timing.dsc_cfg = dsc_cfg; - stream->timing.flags.DSC = 1; - stream->timing.dsc_cfg.bits_per_pixel = edp_max_bpp_x16; - } - return; - } - } - - if (dc_dsc_compute_config(dc->res_pool->dscs[0], - dsc_caps, - &dsc_options, - link_bw_in_kbps, - &stream->timing, - dc_link_get_highest_encoding_format(aconnector->dc_link), - &dsc_cfg)) { - stream->timing.dsc_cfg = dsc_cfg; - stream->timing.flags.DSC = 1; - } -} - -static void apply_dsc_policy_for_stream(struct amdgpu_dm_connector *aconnector, - struct dc_sink *sink, struct dc_stream_state *stream, - struct dsc_dec_dpcd_caps *dsc_caps) -{ - struct drm_connector *drm_connector = &aconnector->base; - u32 link_bandwidth_kbps; - struct dc *dc = sink->ctx->dc; - const struct dc_hdmi_frl_link_settings *frl_verified_link_cap = NULL; - u32 converter_bw_in_kbps; - u32 sink_bw_in_kbps; - u32 dsc_sink_bw_in_kbps; - u32 max_supported_bw_in_kbps, timing_bw_in_kbps; - u32 dsc_max_supported_bw_in_kbps; - u32 max_dsc_target_bpp_limit_override = - drm_connector->display_info.max_dsc_bpp; - struct dc_dsc_config_options dsc_options = {0}; - - dc_dsc_get_default_config_option(dc, &dsc_options); - dsc_options.max_target_bpp_limit_override_x16 = max_dsc_target_bpp_limit_override * 16; - - link_bandwidth_kbps = dc_link_bandwidth_kbps(aconnector->dc_link, - dc_link_get_link_cap(aconnector->dc_link)); - - /* Set DSC policy according to dsc_clock_en */ - dc_dsc_policy_set_enable_dsc_when_not_needed( - aconnector->dsc_settings.dsc_force_enable == DSC_CLK_FORCE_ENABLE); - - if (sink->sink_signal == SIGNAL_TYPE_EDP && - !aconnector->dc_link->panel_config.dsc.disable_dsc_edp && - dc->caps.edp_dsc_support && aconnector->dsc_settings.dsc_force_enable != DSC_CLK_FORCE_DISABLE) { - - apply_dsc_policy_for_edp(aconnector, sink, stream, dsc_caps, max_dsc_target_bpp_limit_override); - - } else if (sink->sink_signal == SIGNAL_TYPE_DISPLAY_PORT) { - if (sink->link->dpcd_caps.dongle_type == DISPLAY_DONGLE_NONE) { - if (dc_dsc_compute_config(aconnector->dc_link->ctx->dc->res_pool->dscs[0], - dsc_caps, - &dsc_options, - link_bandwidth_kbps, - &stream->timing, - dc_link_get_highest_encoding_format(aconnector->dc_link), - &stream->timing.dsc_cfg)) { - stream->timing.flags.DSC = 1; - drm_dbg_driver(drm_connector->dev, "%s: SST_DSC [%s] DSC is selected from SST RX\n", - __func__, drm_connector->name); - } - } else if (sink->link->dpcd_caps.dongle_type == DISPLAY_DONGLE_DP_HDMI_CONVERTER) { - timing_bw_in_kbps = dc_bandwidth_in_kbps_from_timing(&stream->timing, - dc_link_get_highest_encoding_format(aconnector->dc_link)); - converter_bw_in_kbps = aconnector->dc_link->dpcd_caps.dongle_caps.dp_hdmi_frl_max_link_bw_in_kbps; - sink_bw_in_kbps = dc_link_bw_kbps_from_raw_frl_link_rate_data(dc, sink->edid_caps.max_frl_rate); - dsc_sink_bw_in_kbps = dc_link_bw_kbps_from_raw_frl_link_rate_data(dc, sink->edid_caps.frl_dsc_max_frl_rate); - - if (dsc_caps->is_frl) { - max_supported_bw_in_kbps = min(link_bandwidth_kbps, converter_bw_in_kbps); - max_supported_bw_in_kbps = min(max_supported_bw_in_kbps, sink_bw_in_kbps); - dsc_max_supported_bw_in_kbps = min(max_supported_bw_in_kbps, dsc_sink_bw_in_kbps); - } else { - max_supported_bw_in_kbps = link_bandwidth_kbps; - dsc_max_supported_bw_in_kbps = link_bandwidth_kbps; - } - - if (timing_bw_in_kbps > max_supported_bw_in_kbps && - max_supported_bw_in_kbps > 0 && - dsc_max_supported_bw_in_kbps > 0) - if (dc_dsc_compute_config(aconnector->dc_link->ctx->dc->res_pool->dscs[0], - dsc_caps, - &dsc_options, - dsc_max_supported_bw_in_kbps, - &stream->timing, - dc_link_get_highest_encoding_format(aconnector->dc_link), - &stream->timing.dsc_cfg)) { - stream->timing.flags.DSC = 1; - drm_dbg_driver(drm_connector->dev, "%s: SST_DSC [%s] DSC is selected from %s\n", - __func__, drm_connector->name, - (dsc_caps->is_frl == 1) ? "HDMI FRL RX" : "DP-HDMI PCON"); - } - } - } - else if (aconnector->dc_link && sink->sink_signal == SIGNAL_TYPE_HDMI_FRL) { - struct dc_dsc_policy dsc_policy = {0}; - - frl_verified_link_cap = dc_link_get_frl_link_cap(stream->link); - if (frl_verified_link_cap->frl_link_rate != HDMI_FRL_LINK_RATE_DISABLE && - aconnector->dc_link->frl_flags.force_frl_dsc) { - dc_dsc_policy_set_enable_dsc_when_not_needed(true); - dc_dsc_get_policy_for_timing(&stream->timing, 0, &dsc_policy, dc_link_get_highest_encoding_format(stream->link)); - } - - timing_bw_in_kbps = dc_bandwidth_in_kbps_from_timing(&stream->timing, DC_LINK_ENCODING_HDMI_FRL); - link_bandwidth_kbps = dc_link_frl_bandwidth_kbps(stream->link, frl_verified_link_cap->frl_link_rate); - dsc_sink_bw_in_kbps = dc_link_bw_kbps_from_raw_frl_link_rate_data(dc, sink->edid_caps.frl_dsc_max_frl_rate); - - if ((timing_bw_in_kbps > link_bandwidth_kbps && dsc_sink_bw_in_kbps > 0) || - (dsc_policy.enable_dsc_when_not_needed || dsc_options.force_dsc_when_not_needed)) { - if (dc_dsc_compute_config(aconnector->dc_link->ctx->dc->res_pool->dscs[0], - dsc_caps, - &dsc_options, - dsc_sink_bw_in_kbps, - &stream->timing, - dc_link_get_highest_encoding_format(aconnector->dc_link), - &stream->timing.dsc_cfg)) { - stream->timing.flags.DSC = 1; - drm_dbg_driver(drm_connector->dev, "%s: HDMI_FRL_DSC [%s] DSC is selected from HDMI FRL RX\n", - __func__, drm_connector->name); - } - } - } - - /* Overwrite the stream flag if DSC is enabled through debugfs */ - if (aconnector->dsc_settings.dsc_force_enable == DSC_CLK_FORCE_ENABLE) - stream->timing.flags.DSC = 1; - - if (stream->timing.flags.DSC && aconnector->dsc_settings.dsc_num_slices_h) - stream->timing.dsc_cfg.num_slices_h = aconnector->dsc_settings.dsc_num_slices_h; - - if (stream->timing.flags.DSC && aconnector->dsc_settings.dsc_num_slices_v) - stream->timing.dsc_cfg.num_slices_v = aconnector->dsc_settings.dsc_num_slices_v; - - if (stream->timing.flags.DSC && aconnector->dsc_settings.dsc_bits_per_pixel) - stream->timing.dsc_cfg.bits_per_pixel = aconnector->dsc_settings.dsc_bits_per_pixel; -} -#endif - -static struct dc_stream_state * -create_stream_for_sink(struct drm_connector *connector, - const struct drm_display_mode *drm_mode, - const struct dm_connector_state *dm_state, - const struct dc_stream_state *old_stream, - int requested_bpc) -{ - struct drm_device *dev = connector->dev; - struct amdgpu_dm_connector *aconnector = NULL; - struct drm_display_mode *preferred_mode = NULL; - const struct drm_connector_state *con_state = &dm_state->base; - struct dc_stream_state *stream = NULL; - struct drm_display_mode mode; - struct drm_display_mode saved_mode; - struct drm_display_mode *freesync_mode = NULL; - bool native_mode_found = false; - bool recalculate_timing = false; - bool scale = dm_state->scaling != RMX_OFF; - int mode_refresh; - int preferred_refresh = 0; - enum color_transfer_func tf = TRANSFER_FUNC_UNKNOWN; -#if defined(CONFIG_DRM_AMD_DC_FP) - struct dsc_dec_dpcd_caps dsc_caps = {0}; -#endif - struct dc_link *link = NULL; - struct dc_sink *sink = NULL; - - drm_mode_init(&mode, drm_mode); - memset(&saved_mode, 0, sizeof(saved_mode)); - - if (connector->connector_type != DRM_MODE_CONNECTOR_WRITEBACK) { - aconnector = NULL; - aconnector = to_amdgpu_dm_connector(connector); - link = aconnector->dc_link; - } else { - struct drm_writeback_connector *wbcon = NULL; - struct amdgpu_dm_wb_connector *dm_wbcon = NULL; - - wbcon = drm_connector_to_writeback(connector); - dm_wbcon = to_amdgpu_dm_wb_connector(wbcon); - link = dm_wbcon->link; - } - - if (!aconnector || !aconnector->dc_sink) { - sink = create_fake_sink(dev, link); - if (!sink) - return stream; - - } else { - sink = aconnector->dc_sink; - dc_sink_retain(sink); - } - - stream = dc_create_stream_for_sink(sink); - - if (stream == NULL) { - drm_err(dev, "Failed to create stream for sink!\n"); - goto finish; - } - - /* We leave this NULL for writeback connectors */ - stream->dm_stream_context = aconnector; - - stream->timing.flags.LTE_340MCSC_SCRAMBLE = - connector->display_info.hdmi.scdc.scrambling.low_rates; - - list_for_each_entry(preferred_mode, &connector->modes, head) { - /* Search for preferred mode */ - if (preferred_mode->type & DRM_MODE_TYPE_PREFERRED) { - native_mode_found = true; - break; - } - } - if (!native_mode_found) - preferred_mode = list_first_entry_or_null( - &connector->modes, - struct drm_display_mode, - head); - - mode_refresh = drm_mode_vrefresh(&mode); - - if (preferred_mode == NULL) { - /* - * This may not be an error, the use case is when we have no - * usermode calls to reset and set mode upon hotplug. In this - * case, we call set mode ourselves to restore the previous mode - * and the modelist may not be filled in time. - */ - drm_dbg_driver(dev, "No preferred mode found\n"); - } else if (aconnector) { - recalculate_timing = amdgpu_freesync_vid_mode && - is_freesync_video_mode(&mode, aconnector); - if (recalculate_timing) { - freesync_mode = get_highest_refresh_rate_mode(aconnector, false); - drm_mode_copy(&saved_mode, &mode); - saved_mode.picture_aspect_ratio = mode.picture_aspect_ratio; - drm_mode_copy(&mode, freesync_mode); - mode.picture_aspect_ratio = saved_mode.picture_aspect_ratio; - } else { - decide_crtc_timing_for_drm_display_mode( - &mode, preferred_mode, scale); - - preferred_refresh = drm_mode_vrefresh(preferred_mode); - } - } - - if (recalculate_timing) - drm_mode_set_crtcinfo(&saved_mode, 0); - - /* - * If scaling is enabled and refresh rate didn't change - * we copy the vic and polarities of the old timings - */ - if (!scale || mode_refresh != preferred_refresh) - fill_stream_properties_from_drm_display_mode( - stream, &mode, connector, con_state, NULL, - requested_bpc); - else - fill_stream_properties_from_drm_display_mode( - stream, &mode, connector, con_state, old_stream, - requested_bpc); - - /* The rest isn't needed for writeback connectors */ - if (!aconnector) - goto finish; - - if (aconnector->timing_changed) { - drm_dbg(aconnector->base.dev, - "overriding timing for automated test, bpc %d, changing to %d\n", - stream->timing.display_color_depth, - aconnector->timing_requested->display_color_depth); - stream->timing = *aconnector->timing_requested; - } - -#if defined(CONFIG_DRM_AMD_DC_FP) - /* SST DSC determination policy */ - update_dsc_caps(aconnector, sink, stream, &dsc_caps); - if (aconnector->dsc_settings.dsc_force_enable != DSC_CLK_FORCE_DISABLE && dsc_caps.is_dsc_supported) - apply_dsc_policy_for_stream(aconnector, sink, stream, &dsc_caps); -#endif - - update_stream_scaling_settings(dev, &mode, dm_state, stream); - - amdgpu_dm_fill_audio_info( - &stream->audio_info, - connector, - sink); - - update_stream_signal(stream, sink); - - if (stream->signal == SIGNAL_TYPE_HDMI_TYPE_A || - stream->signal == SIGNAL_TYPE_HDMI_FRL) - mod_build_hf_vsif_infopacket(stream, &stream->vsp_infopacket, false, false); - - if (stream->signal == SIGNAL_TYPE_DISPLAY_PORT || - stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST || - stream->signal == SIGNAL_TYPE_EDP) { - const struct dc_edid_caps *edid_caps; - unsigned int disable_colorimetry = 0; - - if (aconnector->dc_sink) { - edid_caps = &aconnector->dc_sink->edid_caps; - disable_colorimetry = edid_caps->panel_patch.disable_colorimetry; - } - - // - // should decide stream support vsc sdp colorimetry capability - // before building vsc info packet - // - stream->use_vsc_sdp_for_colorimetry = stream->link->dpcd_caps.dpcd_rev.raw >= 0x14 && - stream->link->dpcd_caps.dprx_feature.bits.VSC_SDP_COLORIMETRY_SUPPORTED && - !disable_colorimetry; - - if (stream->out_transfer_func.tf == TRANSFER_FUNCTION_GAMMA22) - tf = TRANSFER_FUNC_GAMMA_22; - mod_build_vsc_infopacket(stream, &stream->vsc_infopacket, stream->output_color_space, tf); - aconnector->sr_skip_count = AMDGPU_DM_PSR_ENTRY_DELAY; - - } -finish: - dc_sink_release(sink); - - return stream; -} - -/** - * amdgpu_dm_connector_poll - Poll a connector to see if it's connected to a display - * @aconnector: DM connector to poll (owns @base drm_connector and @dc_link) - * @force: if true, force polling even when DAC load detection was used - * - * Used for connectors that don't support HPD (hotplug detection) to - * periodically check whether the connector is connected to a display. - * - * When connection was determined via DAC load detection, we avoid - * re-running it on normal polls to prevent visible glitches, unless - * @force is set. - * - * Return: The probed connector status (connected/disconnected/unknown). - */ -static enum drm_connector_status -amdgpu_dm_connector_poll(struct amdgpu_dm_connector *aconnector, bool force) -{ - struct drm_connector *connector = &aconnector->base; - struct drm_device *dev = connector->dev; - struct amdgpu_device *adev = drm_to_adev(dev); - struct dc_link *link = aconnector->dc_link; - enum dc_connection_type conn_type = dc_connection_none; - enum drm_connector_status status = connector_status_disconnected; - - /* When we determined the connection using DAC load detection, - * do NOT poll the connector do detect disconnect because - * that would run DAC load detection again which can cause - * visible visual glitches. - * - * Only allow to poll such a connector again when forcing. - */ - if (!force && link->local_sink && link->type == dc_connection_analog_load) - return connector->status; - - mutex_lock(&aconnector->hpd_lock); - - if (dc_link_detect_connection_type(aconnector->dc_link, &conn_type) && - conn_type != dc_connection_none) { - mutex_lock(&adev->dm.dc_lock); - - /* Only call full link detection when a sink isn't created yet, - * ie. just when the display is plugged in, otherwise we risk flickering. - */ - if (link->local_sink || - dc_link_detect(link, DETECT_REASON_HPD)) - status = connector_status_connected; - - mutex_unlock(&adev->dm.dc_lock); - } - - if (connector->status != status) { - if (status == connector_status_disconnected) { - if (link->local_sink) - dc_sink_release(link->local_sink); - - link->local_sink = NULL; - link->dpcd_sink_count = 0; - link->type = dc_connection_none; - } - - amdgpu_dm_update_connector_after_detect(aconnector); - } - - mutex_unlock(&aconnector->hpd_lock); - return status; -} - -/** - * amdgpu_dm_connector_detect() - Detect whether a DRM connector is connected to a display - * - * A connector is considered connected when it has a sink that is not NULL. - * For connectors that support HPD (hotplug detection), the connection is - * handled in the HPD interrupt. - * For connectors that may not support HPD, such as analog connectors, - * DRM will call this function repeatedly to poll them. - * - * Notes: - * 1. This interface is NOT called in context of HPD irq. - * 2. This interface *is called* in context of user-mode ioctl. Which - * makes it a bad place for *any* MST-related activity. - * - * @connector: The DRM connector we are checking. We convert it to - * amdgpu_dm_connector so we can read the DC link and state. - * @force: If true, do a full detect again. This is used even when - * a lighter check would normally be used to avoid flicker. - * - * Return: The connector status (connected, disconnected, or unknown). - * - */ -static enum drm_connector_status -amdgpu_dm_connector_detect(struct drm_connector *connector, bool force) -{ - struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector); - - update_subconnector_property(aconnector); - - if (aconnector->base.force == DRM_FORCE_ON || - aconnector->base.force == DRM_FORCE_ON_DIGITAL) - return connector_status_connected; - else if (aconnector->base.force == DRM_FORCE_OFF) - return connector_status_disconnected; - - /* Poll analog connectors and only when either - * disconnected or connected to an analog display. - */ - if (drm_kms_helper_is_poll_worker() && - dc_connector_supports_analog(aconnector->dc_link->link_id.id) && - (!aconnector->dc_sink || aconnector->dc_sink->edid_caps.analog)) - return amdgpu_dm_connector_poll(aconnector, force); - - return (aconnector->dc_sink ? connector_status_connected : - connector_status_disconnected); -} - -int amdgpu_dm_connector_atomic_set_property(struct drm_connector *connector, - struct drm_connector_state *connector_state, - struct drm_property *property, - uint64_t val) -{ - struct drm_device *dev = connector->dev; - struct amdgpu_device *adev = drm_to_adev(dev); - struct dm_connector_state *dm_old_state = - to_dm_connector_state(connector->state); - struct dm_connector_state *dm_new_state = - to_dm_connector_state(connector_state); - - int ret = -EINVAL; - - if (property == dev->mode_config.scaling_mode_property) { - enum amdgpu_rmx_type rmx_type; - - switch (val) { - case DRM_MODE_SCALE_CENTER: - rmx_type = RMX_CENTER; - break; - case DRM_MODE_SCALE_ASPECT: - rmx_type = RMX_ASPECT; - break; - case DRM_MODE_SCALE_FULLSCREEN: - rmx_type = RMX_FULL; - break; - case DRM_MODE_SCALE_NONE: - default: - rmx_type = RMX_OFF; - break; - } - - if (dm_old_state->scaling == rmx_type) - return 0; - - dm_new_state->scaling = rmx_type; - ret = 0; - } else if (property == adev->mode_info.underscan_hborder_property) { - dm_new_state->underscan_hborder = val; - ret = 0; - } else if (property == adev->mode_info.underscan_vborder_property) { - dm_new_state->underscan_vborder = val; - ret = 0; - } else if (property == adev->mode_info.underscan_property) { - dm_new_state->underscan_enable = val; - ret = 0; - } else if (property == adev->mode_info.abm_level_property) { - switch (val) { - case ABM_SYSFS_CONTROL: - dm_new_state->abm_sysfs_forbidden = false; - break; - case ABM_LEVEL_OFF: - dm_new_state->abm_sysfs_forbidden = true; - dm_new_state->abm_level = ABM_LEVEL_IMMEDIATE_DISABLE; - break; - default: - dm_new_state->abm_sysfs_forbidden = true; - dm_new_state->abm_level = val; - } - ret = 0; - } - - return ret; -} - -int amdgpu_dm_connector_atomic_get_property(struct drm_connector *connector, - const struct drm_connector_state *state, - struct drm_property *property, - uint64_t *val) -{ - struct drm_device *dev = connector->dev; - struct amdgpu_device *adev = drm_to_adev(dev); - struct dm_connector_state *dm_state = - to_dm_connector_state(state); - int ret = -EINVAL; - - if (property == dev->mode_config.scaling_mode_property) { - switch (dm_state->scaling) { - case RMX_CENTER: - *val = DRM_MODE_SCALE_CENTER; - break; - case RMX_ASPECT: - *val = DRM_MODE_SCALE_ASPECT; - break; - case RMX_FULL: - *val = DRM_MODE_SCALE_FULLSCREEN; - break; - case RMX_OFF: - default: - *val = DRM_MODE_SCALE_NONE; - break; - } - ret = 0; - } else if (property == adev->mode_info.underscan_hborder_property) { - *val = dm_state->underscan_hborder; - ret = 0; - } else if (property == adev->mode_info.underscan_vborder_property) { - *val = dm_state->underscan_vborder; - ret = 0; - } else if (property == adev->mode_info.underscan_property) { - *val = dm_state->underscan_enable; - ret = 0; - } else if (property == adev->mode_info.abm_level_property) { - if (!dm_state->abm_sysfs_forbidden) - *val = ABM_SYSFS_CONTROL; - else - *val = (dm_state->abm_level != ABM_LEVEL_IMMEDIATE_DISABLE) ? - dm_state->abm_level : 0; - ret = 0; - } - - return ret; -} - -static void amdgpu_dm_connector_unregister(struct drm_connector *connector) -{ - struct amdgpu_dm_connector *amdgpu_dm_connector = to_amdgpu_dm_connector(connector); - - if (amdgpu_dm_should_create_sysfs(amdgpu_dm_connector)) - sysfs_remove_group(&connector->kdev->kobj, &amdgpu_group); - - cec_notifier_conn_unregister(amdgpu_dm_connector->notifier); - drm_dp_aux_unregister(&amdgpu_dm_connector->dm_dp_aux.aux); -} - -static void amdgpu_dm_connector_destroy(struct drm_connector *connector) -{ - struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector); - struct amdgpu_device *adev = drm_to_adev(connector->dev); - struct amdgpu_display_manager *dm = &adev->dm; - - /* - * Call only if mst_mgr was initialized before since it's not done - * for all connector types. - */ - if (aconnector->mst_mgr.dev) - drm_dp_mst_topology_mgr_destroy(&aconnector->mst_mgr); - - /* Cancel and flush any pending HDMI HPD debounce work */ - if (aconnector->hdmi_hpd_debounce_delay_ms) { - cancel_delayed_work_sync(&aconnector->hdmi_hpd_debounce_work); - if (aconnector->hdmi_prev_sink) { - dc_sink_release(aconnector->hdmi_prev_sink); - aconnector->hdmi_prev_sink = NULL; - } - } - - if (aconnector->bl_idx != -1) { - backlight_device_unregister(dm->backlight_dev[aconnector->bl_idx]); - dm->backlight_dev[aconnector->bl_idx] = NULL; - } - - if (aconnector->dc_em_sink) - dc_sink_release(aconnector->dc_em_sink); - aconnector->dc_em_sink = NULL; - if (aconnector->dc_sink) - dc_sink_release(aconnector->dc_sink); - aconnector->dc_sink = NULL; - - drm_dp_cec_unregister_connector(&aconnector->dm_dp_aux.aux); - drm_connector_unregister(connector); - drm_connector_cleanup(connector); - kfree(aconnector->dm_dp_aux.aux.name); - - kfree(connector); -} - -void amdgpu_dm_connector_funcs_reset(struct drm_connector *connector) -{ - struct dm_connector_state *state = - to_dm_connector_state(connector->state); - - if (connector->state) - __drm_atomic_helper_connector_destroy_state(connector->state); - - kfree(state); - - state = kzalloc_obj(*state); - - if (state) { - state->scaling = RMX_OFF; - state->underscan_enable = false; - state->underscan_hborder = 0; - state->underscan_vborder = 0; - state->base.max_requested_bpc = 8; - state->vcpi_slots = 0; - state->pbn = 0; - - if (connector->connector_type == DRM_MODE_CONNECTOR_eDP) { - if (amdgpu_dm_abm_level <= 0) - state->abm_level = ABM_LEVEL_IMMEDIATE_DISABLE; - else - state->abm_level = amdgpu_dm_abm_level; - } - - __drm_atomic_helper_connector_reset(connector, &state->base); - } -} - -struct drm_connector_state * -amdgpu_dm_connector_atomic_duplicate_state(struct drm_connector *connector) -{ - struct dm_connector_state *state = - to_dm_connector_state(connector->state); - - struct dm_connector_state *new_state = - kmemdup(state, sizeof(*state), GFP_KERNEL); - - if (!new_state) - return NULL; - - __drm_atomic_helper_connector_duplicate_state(connector, &new_state->base); - - new_state->freesync_capable = state->freesync_capable; - new_state->abm_level = state->abm_level; - new_state->scaling = state->scaling; - new_state->underscan_enable = state->underscan_enable; - new_state->underscan_hborder = state->underscan_hborder; - new_state->underscan_vborder = state->underscan_vborder; - new_state->vcpi_slots = state->vcpi_slots; - new_state->pbn = state->pbn; - return &new_state->base; -} - -static int -amdgpu_dm_connector_late_register(struct drm_connector *connector) -{ - struct amdgpu_dm_connector *amdgpu_dm_connector = - to_amdgpu_dm_connector(connector); - int r; - - if (amdgpu_dm_should_create_sysfs(amdgpu_dm_connector)) { - r = sysfs_create_group(&connector->kdev->kobj, - &amdgpu_group); - if (r) - return r; - } - - amdgpu_dm_register_backlight_device(amdgpu_dm_connector); - - if ((connector->connector_type == DRM_MODE_CONNECTOR_DisplayPort) || - (connector->connector_type == DRM_MODE_CONNECTOR_eDP)) { - amdgpu_dm_connector->dm_dp_aux.aux.dev = connector->kdev; - r = drm_dp_aux_register(&amdgpu_dm_connector->dm_dp_aux.aux); - if (r) - return r; - } - -#if defined(CONFIG_DEBUG_FS) - connector_debugfs_init(amdgpu_dm_connector); -#endif - - return 0; -} - -static void amdgpu_dm_connector_funcs_force(struct drm_connector *connector) -{ - struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector); - struct dc_link *dc_link = aconnector->dc_link; - struct dc_sink *dc_em_sink = aconnector->dc_em_sink; - const struct drm_edid *drm_edid; - struct i2c_adapter *ddc; - struct drm_device *dev = connector->dev; - - if (dc_link && dc_link->aux_mode) - ddc = &aconnector->dm_dp_aux.aux.ddc; - else - ddc = &aconnector->i2c->base; - - drm_edid = drm_edid_read_ddc(connector, ddc); - drm_edid_connector_update(connector, drm_edid); - if (!drm_edid) { - drm_err(dev, "No EDID found on connector: %s.\n", connector->name); - return; - } - - aconnector->drm_edid = drm_edid; - /* Update emulated (virtual) sink's EDID */ - if (dc_em_sink && dc_link) { - // FIXME: Get rid of drm_edid_raw() - const struct edid *edid = drm_edid_raw(drm_edid); - - memset(&dc_em_sink->edid_caps, 0, sizeof(struct dc_edid_caps)); - memmove(dc_em_sink->dc_edid.raw_edid, edid, - (edid->extensions + 1) * EDID_LENGTH); - dm_helpers_parse_edid_caps( - dc_link, - &dc_em_sink->dc_edid, - &dc_em_sink->edid_caps); - } -} - -static const struct drm_connector_funcs amdgpu_dm_connector_funcs = { - .reset = amdgpu_dm_connector_funcs_reset, - .detect = amdgpu_dm_connector_detect, - .fill_modes = drm_helper_probe_single_connector_modes, - .destroy = amdgpu_dm_connector_destroy, - .atomic_duplicate_state = amdgpu_dm_connector_atomic_duplicate_state, - .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, - .atomic_set_property = amdgpu_dm_connector_atomic_set_property, - .atomic_get_property = amdgpu_dm_connector_atomic_get_property, - .late_register = amdgpu_dm_connector_late_register, - .early_unregister = amdgpu_dm_connector_unregister, - .force = amdgpu_dm_connector_funcs_force -}; - -static int get_modes(struct drm_connector *connector) -{ - return amdgpu_dm_connector_get_modes(connector); -} - -static void create_eml_sink(struct amdgpu_dm_connector *aconnector) -{ - struct drm_connector *connector = &aconnector->base; - struct dc_link *dc_link = aconnector->dc_link; - struct dc_sink_init_data init_params = { - .link = aconnector->dc_link, - .sink_signal = SIGNAL_TYPE_VIRTUAL - }; - const struct drm_edid *drm_edid; - const struct edid *edid; - struct i2c_adapter *ddc; - - if (dc_link && dc_link->aux_mode) - ddc = &aconnector->dm_dp_aux.aux.ddc; - else - ddc = &aconnector->i2c->base; - - drm_edid = drm_edid_read_ddc(connector, ddc); - drm_edid_connector_update(connector, drm_edid); - if (!drm_edid) { - drm_err(connector->dev, "No EDID found on connector: %s.\n", connector->name); - return; - } - - if (connector->display_info.is_hdmi) - init_params.sink_signal = SIGNAL_TYPE_HDMI_TYPE_A; - - aconnector->drm_edid = drm_edid; - - edid = drm_edid_raw(drm_edid); // FIXME: Get rid of drm_edid_raw() - aconnector->dc_em_sink = dc_link_add_remote_sink( - aconnector->dc_link, - (uint8_t *)edid, - (edid->extensions + 1) * EDID_LENGTH, - &init_params); - - if (aconnector->base.force == DRM_FORCE_ON) { - aconnector->dc_sink = aconnector->dc_link->local_sink ? - aconnector->dc_link->local_sink : - aconnector->dc_em_sink; - if (aconnector->dc_sink) - dc_sink_retain(aconnector->dc_sink); - } -} - -static void handle_edid_mgmt(struct amdgpu_dm_connector *aconnector) -{ - struct dc_link *link = (struct dc_link *)aconnector->dc_link; - - /* - * In case of headless boot with force on for DP managed connector - * Those settings have to be != 0 to get initial modeset - */ - if (link->connector_signal == SIGNAL_TYPE_DISPLAY_PORT) { - link->verified_link_cap.lane_count = LANE_COUNT_FOUR; - link->verified_link_cap.link_rate = LINK_RATE_HIGH2; - } - - create_eml_sink(aconnector); -} - -static enum dc_status dm_validate_stream_and_context(struct dc *dc, - struct dc_stream_state *stream) -{ - enum dc_status dc_result = DC_ERROR_UNEXPECTED; - struct dc_plane_state *dc_plane_state = NULL; - struct dc_state *dc_state = NULL; - - if (!stream) - goto cleanup; - - dc_plane_state = dc_create_plane_state(dc); - if (!dc_plane_state) - goto cleanup; - - dc_state = dc_state_create(dc, NULL); - if (!dc_state) - goto cleanup; - - /* populate stream to plane */ - dc_plane_state->src_rect.height = stream->src.height; - dc_plane_state->src_rect.width = stream->src.width; - dc_plane_state->dst_rect.height = stream->src.height; - dc_plane_state->dst_rect.width = stream->src.width; - dc_plane_state->clip_rect.height = stream->src.height; - dc_plane_state->clip_rect.width = stream->src.width; - dc_plane_state->plane_size.surface_pitch = ((stream->src.width + 255) / 256) * 256; - dc_plane_state->plane_size.surface_size.height = stream->src.height; - dc_plane_state->plane_size.surface_size.width = stream->src.width; - dc_plane_state->plane_size.chroma_size.height = stream->src.height; - dc_plane_state->plane_size.chroma_size.width = stream->src.width; - dc_plane_state->format = SURFACE_PIXEL_FORMAT_GRPH_ARGB8888; - dc_plane_state->tiling_info.gfx9.swizzle = DC_SW_UNKNOWN; - dc_plane_state->rotation = ROTATION_ANGLE_0; - dc_plane_state->is_tiling_rotated = false; - dc_plane_state->tiling_info.gfx8.array_mode = DC_ARRAY_LINEAR_GENERAL; - - dc_result = dc_validate_stream(dc, stream); - if (dc_result == DC_OK) - dc_result = dc_validate_plane(dc, dc_plane_state); - - if (dc_result == DC_OK) - dc_result = dc_state_add_stream(dc, dc_state, stream); - - if (dc_result == DC_OK && !dc_state_add_plane( - dc, - stream, - dc_plane_state, - dc_state)) - dc_result = DC_FAIL_ATTACH_SURFACES; - - if (dc_result == DC_OK) - dc_result = dc_validate_global_state(dc, dc_state, DC_VALIDATE_MODE_ONLY); - -cleanup: - if (dc_state) - dc_state_release(dc_state); - - if (dc_plane_state) - dc_plane_state_release(dc_plane_state); - - return dc_result; -} - -struct dc_stream_state * -create_validate_stream_for_sink(struct drm_connector *connector, - const struct drm_display_mode *drm_mode, - const struct dm_connector_state *dm_state, - const struct dc_stream_state *old_stream) -{ - struct amdgpu_dm_connector *aconnector = NULL; - struct amdgpu_device *adev = drm_to_adev(connector->dev); - struct dc_stream_state *stream; - const struct drm_connector_state *drm_state = dm_state ? &dm_state->base : NULL; - int requested_bpc = drm_state ? drm_state->max_requested_bpc : 8; - enum dc_status dc_result = DC_OK; - uint8_t bpc_limit = 6; - - if (!dm_state) - return NULL; - - if (connector->connector_type != DRM_MODE_CONNECTOR_WRITEBACK) - aconnector = to_amdgpu_dm_connector(connector); - - if (aconnector && - (aconnector->dc_link->connector_signal == SIGNAL_TYPE_HDMI_TYPE_A || - aconnector->dc_link->connector_signal == SIGNAL_TYPE_HDMI_FRL || - aconnector->dc_link->dpcd_caps.dongle_type == DISPLAY_DONGLE_DP_HDMI_CONVERTER)) - bpc_limit = 8; - - do { - drm_dbg_kms(connector->dev, "Trying with %d bpc\n", requested_bpc); - stream = create_stream_for_sink(connector, drm_mode, - dm_state, old_stream, - requested_bpc); - if (stream == NULL) { - drm_err(adev_to_drm(adev), "Failed to create stream for sink!\n"); - break; - } - - dc_result = dc_validate_stream(adev->dm.dc, stream); - - if (!aconnector) /* writeback connector */ - return stream; - - if (dc_result == DC_OK && stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST) - dc_result = dm_dp_mst_is_port_support_mode(aconnector, stream); - - if (dc_result == DC_OK) - dc_result = dm_validate_stream_and_context(adev->dm.dc, stream); - - if (dc_result != DC_OK) { - drm_dbg_kms(connector->dev, "Pruned mode %d x %d (clk %d) %s %s -- %s\n", - drm_mode->hdisplay, - drm_mode->vdisplay, - drm_mode->clock, - dc_pixel_encoding_to_str(stream->timing.pixel_encoding), - dc_color_depth_to_str(stream->timing.display_color_depth), - dc_status_to_str(dc_result)); - - dc_stream_release(stream); - stream = NULL; - requested_bpc -= 2; /* lower bpc to retry validation */ - } - - } while (stream == NULL && requested_bpc >= bpc_limit); - - switch (dc_result) { - /* - * If we failed to validate DP bandwidth stream with the requested RGB color depth, - * we try to fallback and configure in order: - * YUV422 (8bpc, 6bpc) - * YUV420 (8bpc, 6bpc) - */ - case DC_FAIL_ENC_VALIDATE: - case DC_EXCEED_DONGLE_CAP: - case DC_NO_DP_LINK_BANDWIDTH: - /* recursively entered twice and already tried both YUV422 and YUV420 */ - if (aconnector->force_yuv422_output && aconnector->force_yuv420_output) - break; - /* first failure; try YUV422 */ - if (!aconnector->force_yuv422_output) { - drm_dbg_kms(connector->dev, "%s:%d Validation failed with %d, retrying w/ YUV422\n", - __func__, __LINE__, dc_result); - aconnector->force_yuv422_output = true; - /* recursively entered and YUV422 failed, try YUV420 */ - } else if (!aconnector->force_yuv420_output) { - drm_dbg_kms(connector->dev, "%s:%d Validation failed with %d, retrying w/ YUV420\n", - __func__, __LINE__, dc_result); - aconnector->force_yuv420_output = true; - } - stream = create_validate_stream_for_sink(connector, drm_mode, - dm_state, old_stream); - aconnector->force_yuv422_output = false; - aconnector->force_yuv420_output = false; - break; - case DC_OK: - break; - default: - drm_dbg_kms(connector->dev, "%s:%d Unhandled validation failure %d\n", - __func__, __LINE__, dc_result); - break; - } - - return stream; -} - -enum drm_mode_status amdgpu_dm_connector_mode_valid(struct drm_connector *connector, - const struct drm_display_mode *mode) -{ - int result = MODE_ERROR; - struct dc_sink *dc_sink; - struct drm_display_mode *test_mode; - /* TODO: Unhardcode stream count */ - struct dc_stream_state *stream; - /* we always have an amdgpu_dm_connector here since we got - * here via the amdgpu_dm_connector_helper_funcs - */ - struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector); - - if ((mode->flags & DRM_MODE_FLAG_INTERLACE) || - (mode->flags & DRM_MODE_FLAG_DBLSCAN)) - return result; - - /* - * Only run this the first time mode_valid is called to initilialize - * EDID mgmt - */ - if (aconnector->base.force != DRM_FORCE_UNSPECIFIED && - !aconnector->dc_em_sink) - handle_edid_mgmt(aconnector); - - dc_sink = to_amdgpu_dm_connector(connector)->dc_sink; - - if (dc_sink == NULL && aconnector->base.force != DRM_FORCE_ON_DIGITAL && - aconnector->base.force != DRM_FORCE_ON) { - drm_err(connector->dev, "dc_sink is NULL!\n"); - goto fail; - } - - test_mode = drm_mode_duplicate(connector->dev, mode); - if (!test_mode) - goto fail; - - drm_mode_set_crtcinfo(test_mode, 0); - - stream = create_validate_stream_for_sink(connector, test_mode, - to_dm_connector_state(connector->state), - NULL); - drm_mode_destroy(connector->dev, test_mode); - if (stream) { - dc_stream_release(stream); - result = MODE_OK; - } - -fail: - /* TODO: error handling*/ - return result; -} - -static int fill_hdr_info_packet(const struct drm_connector_state *state, - struct dc_info_packet *out) -{ - struct hdmi_drm_infoframe frame; - unsigned char buf[30]; /* 26 + 4 */ - ssize_t len; - int ret, i; - - memset(out, 0, sizeof(*out)); - - if (!state->hdr_output_metadata) - return 0; - - ret = drm_hdmi_infoframe_set_hdr_metadata(&frame, state); - if (ret) - return ret; - - len = hdmi_drm_infoframe_pack_only(&frame, buf, sizeof(buf)); - if (len < 0) - return (int)len; - - /* Static metadata is a fixed 26 bytes + 4 byte header. */ - if (len != 30) - return -EINVAL; - - /* Prepare the infopacket for DC. */ - switch (state->connector->connector_type) { - case DRM_MODE_CONNECTOR_HDMIA: - out->hb0 = 0x87; /* type */ - out->hb1 = 0x01; /* version */ - out->hb2 = 0x1A; /* length */ - out->sb[0] = buf[3]; /* checksum */ - i = 1; - break; - - case DRM_MODE_CONNECTOR_DisplayPort: - case DRM_MODE_CONNECTOR_eDP: - out->hb0 = 0x00; /* sdp id, zero */ - out->hb1 = 0x87; /* type */ - out->hb2 = 0x1D; /* payload len - 1 */ - out->hb3 = (0x13 << 2); /* sdp version */ - out->sb[0] = 0x01; /* version */ - out->sb[1] = 0x1A; /* length */ - i = 2; - break; - - default: - return -EINVAL; - } - - memcpy(&out->sb[i], &buf[4], 26); - out->valid = true; - - print_hex_dump(KERN_DEBUG, "HDR SB:", DUMP_PREFIX_NONE, 16, 1, out->sb, - sizeof(out->sb), false); - - return 0; -} - -static int -amdgpu_dm_connector_atomic_check(struct drm_connector *conn, - struct drm_atomic_commit *state) -{ - struct drm_connector_state *new_con_state = - drm_atomic_get_new_connector_state(state, conn); - struct drm_connector_state *old_con_state = - drm_atomic_get_old_connector_state(state, conn); - struct drm_crtc *crtc = new_con_state->crtc; - struct drm_crtc_state *new_crtc_state; - struct amdgpu_dm_connector *aconn = to_amdgpu_dm_connector(conn); - int ret; - - if (WARN_ON(unlikely(!old_con_state || !new_con_state))) - return -EINVAL; - - trace_amdgpu_dm_connector_atomic_check(new_con_state); - - if (conn->connector_type == DRM_MODE_CONNECTOR_DisplayPort) { - ret = drm_dp_mst_root_conn_atomic_check(new_con_state, &aconn->mst_mgr); - if (ret < 0) - return ret; - } - - if (!crtc) - return 0; - - if (new_con_state->privacy_screen_sw_state != old_con_state->privacy_screen_sw_state) { - new_crtc_state = drm_atomic_get_crtc_state(state, crtc); - if (IS_ERR(new_crtc_state)) - return PTR_ERR(new_crtc_state); - - new_crtc_state->mode_changed = true; - } - - if (new_con_state->colorspace != old_con_state->colorspace) { - new_crtc_state = drm_atomic_get_crtc_state(state, crtc); - if (IS_ERR(new_crtc_state)) - return PTR_ERR(new_crtc_state); - - new_crtc_state->mode_changed = true; - } - - if (new_con_state->content_type != old_con_state->content_type) { - new_crtc_state = drm_atomic_get_crtc_state(state, crtc); - if (IS_ERR(new_crtc_state)) - return PTR_ERR(new_crtc_state); - - new_crtc_state->mode_changed = true; - } - - if (!drm_connector_atomic_hdr_metadata_equal(old_con_state, new_con_state)) { - struct dc_info_packet hdr_infopacket; - - ret = fill_hdr_info_packet(new_con_state, &hdr_infopacket); - if (ret) - return ret; - - new_crtc_state = drm_atomic_get_crtc_state(state, crtc); - if (IS_ERR(new_crtc_state)) - return PTR_ERR(new_crtc_state); - - /* - * DC considers the stream backends changed if the - * static metadata changes. Forcing the modeset also - * gives a simple way for userspace to switch from - * 8bpc to 10bpc when setting the metadata to enter - * or exit HDR. - * - * Changing the static metadata after it's been - * set is permissible, however. So only force a - * modeset if we're entering or exiting HDR. - */ - new_crtc_state->mode_changed = new_crtc_state->mode_changed || - !old_con_state->hdr_output_metadata || - !new_con_state->hdr_output_metadata; - } - - return 0; -} - -static const struct drm_connector_helper_funcs -amdgpu_dm_connector_helper_funcs = { - /* - * If hotplugging a second bigger display in FB Con mode, bigger resolution - * modes will be filtered by drm_mode_validate_size(), and those modes - * are missing after user start lightdm. So we need to renew modes list. - * in get_modes call back, not just return the modes count - */ - .get_modes = get_modes, - .mode_valid = amdgpu_dm_connector_mode_valid, - .atomic_check = amdgpu_dm_connector_atomic_check, -}; - -static void dm_encoder_helper_disable(struct drm_encoder *encoder) -{ - -} - -int convert_dc_color_depth_into_bpc(enum dc_color_depth display_color_depth) -{ - switch (display_color_depth) { - case COLOR_DEPTH_666: - return 6; - case COLOR_DEPTH_888: - return 8; - case COLOR_DEPTH_101010: - return 10; - case COLOR_DEPTH_121212: - return 12; - case COLOR_DEPTH_141414: - return 14; - case COLOR_DEPTH_161616: - return 16; - default: - break; - } - return 0; -} - -static int dm_encoder_helper_atomic_check(struct drm_encoder *encoder, - struct drm_crtc_state *crtc_state, - struct drm_connector_state *conn_state) -{ - struct drm_atomic_commit *state = crtc_state->state; - struct drm_connector *connector = conn_state->connector; - struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector); - struct dm_connector_state *dm_new_connector_state = to_dm_connector_state(conn_state); - const struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode; - struct drm_dp_mst_topology_mgr *mst_mgr; - struct drm_dp_mst_port *mst_port; - struct drm_dp_mst_topology_state *mst_state; - enum dc_color_depth color_depth; - int clock, bpp = 0; - bool is_y420 = false; - - if ((connector->connector_type == DRM_MODE_CONNECTOR_eDP) || - (connector->connector_type == DRM_MODE_CONNECTOR_LVDS)) { - struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); - struct drm_display_mode *native_mode = &amdgpu_encoder->native_mode; - enum drm_mode_status result; - - result = drm_crtc_helper_mode_valid_fixed(encoder->crtc, adjusted_mode, native_mode); - if (result != MODE_OK && dm_new_connector_state->scaling == RMX_OFF) { - drm_dbg_driver(encoder->dev, - "mode %dx%d@%dHz is not native, enabling scaling\n", - adjusted_mode->hdisplay, adjusted_mode->vdisplay, - drm_mode_vrefresh(adjusted_mode)); - dm_new_connector_state->scaling = RMX_ASPECT; - } - return 0; - } - - if (!aconnector->mst_output_port) - return 0; - - mst_port = aconnector->mst_output_port; - mst_mgr = &aconnector->mst_root->mst_mgr; - - if (!crtc_state->connectors_changed && !crtc_state->mode_changed) - return 0; - - mst_state = drm_atomic_get_mst_topology_state(state, mst_mgr); - if (IS_ERR(mst_state)) - return PTR_ERR(mst_state); - - mst_state->pbn_div.full = dm_mst_get_pbn_divider(aconnector->mst_root->dc_link); - - if (!state->duplicated) { - int max_bpc = conn_state->max_requested_bpc; - - is_y420 = drm_mode_is_420_also(&connector->display_info, adjusted_mode) && - aconnector->force_yuv420_output; - color_depth = convert_color_depth_from_display_info(connector, - is_y420, - max_bpc); - bpp = convert_dc_color_depth_into_bpc(color_depth) * 3; - clock = adjusted_mode->clock; - dm_new_connector_state->pbn = drm_dp_calc_pbn_mode(clock, bpp << 4); - } - - dm_new_connector_state->vcpi_slots = - drm_dp_atomic_find_time_slots(state, mst_mgr, mst_port, - dm_new_connector_state->pbn); - if (dm_new_connector_state->vcpi_slots < 0) { - drm_dbg_atomic(connector->dev, "failed finding vcpi slots: %d\n", (int)dm_new_connector_state->vcpi_slots); - return dm_new_connector_state->vcpi_slots; - } - return 0; -} - -const struct drm_encoder_helper_funcs amdgpu_dm_encoder_helper_funcs = { - .disable = dm_encoder_helper_disable, - .atomic_check = dm_encoder_helper_atomic_check -}; - -static int dm_update_mst_vcpi_slots_for_dsc(struct drm_atomic_commit *state, - struct dc_state *dc_state, - struct dsc_mst_fairness_vars *vars) -{ - struct dc_stream_state *stream = NULL; - struct drm_connector *connector; - struct drm_connector_state *new_con_state; - struct amdgpu_dm_connector *aconnector; - struct dm_connector_state *dm_conn_state; - int i, j, ret; - int vcpi, pbn_div, pbn = 0, slot_num = 0; - - for_each_new_connector_in_state(state, connector, new_con_state, i) { - - if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) - continue; - - aconnector = to_amdgpu_dm_connector(connector); - - if (!aconnector->mst_output_port) - continue; - - if (!new_con_state || !new_con_state->crtc) - continue; - - dm_conn_state = to_dm_connector_state(new_con_state); - - for (j = 0; j < dc_state->stream_count; j++) { - stream = dc_state->streams[j]; - if (!stream) - continue; + for (j = 0; j < dc_state->stream_count; j++) { + stream = dc_state->streams[j]; + if (!stream) + continue; if ((struct amdgpu_dm_connector *)stream->dm_stream_context == aconnector) break; @@ -5843,759 +3494,12 @@ static int dm_update_mst_vcpi_slots_for_dsc(struct drm_atomic_commit *state, vcpi = drm_dp_mst_atomic_enable_dsc(state, aconnector->mst_output_port, pbn, true); if (vcpi < 0) - return vcpi; - - dm_conn_state->pbn = pbn; - dm_conn_state->vcpi_slots = vcpi; - } - return 0; -} - -static int to_drm_connector_type(enum signal_type st, uint32_t connector_id) -{ - switch (st) { - case SIGNAL_TYPE_HDMI_TYPE_A: - return DRM_MODE_CONNECTOR_HDMIA; - case SIGNAL_TYPE_EDP: - return DRM_MODE_CONNECTOR_eDP; - case SIGNAL_TYPE_LVDS: - return DRM_MODE_CONNECTOR_LVDS; - case SIGNAL_TYPE_RGB: - return DRM_MODE_CONNECTOR_VGA; - case SIGNAL_TYPE_DISPLAY_PORT: - case SIGNAL_TYPE_DISPLAY_PORT_MST: - /* External DP bridges have a different connector type. */ - if (connector_id == CONNECTOR_ID_VGA) - return DRM_MODE_CONNECTOR_VGA; - else if (connector_id == CONNECTOR_ID_LVDS) - return DRM_MODE_CONNECTOR_LVDS; - - return DRM_MODE_CONNECTOR_DisplayPort; - case SIGNAL_TYPE_DVI_DUAL_LINK: - case SIGNAL_TYPE_DVI_SINGLE_LINK: - if (connector_id == CONNECTOR_ID_SINGLE_LINK_DVII || - connector_id == CONNECTOR_ID_DUAL_LINK_DVII) - return DRM_MODE_CONNECTOR_DVII; - - return DRM_MODE_CONNECTOR_DVID; - case SIGNAL_TYPE_VIRTUAL: - return DRM_MODE_CONNECTOR_VIRTUAL; - - default: - return DRM_MODE_CONNECTOR_Unknown; - } -} - -static struct drm_encoder *amdgpu_dm_connector_to_encoder(struct drm_connector *connector) -{ - struct drm_encoder *encoder; - - /* There is only one encoder per connector */ - drm_connector_for_each_possible_encoder(connector, encoder) - return encoder; - - return NULL; -} - -static void amdgpu_dm_get_native_mode(struct drm_connector *connector) -{ - struct drm_encoder *encoder; - struct amdgpu_encoder *amdgpu_encoder; - - encoder = amdgpu_dm_connector_to_encoder(connector); - - if (encoder == NULL) - return; - - amdgpu_encoder = to_amdgpu_encoder(encoder); - - amdgpu_encoder->native_mode.clock = 0; - - if (!list_empty(&connector->probed_modes)) { - struct drm_display_mode *preferred_mode = NULL; - - list_for_each_entry(preferred_mode, - &connector->probed_modes, - head) { - if (preferred_mode->type & DRM_MODE_TYPE_PREFERRED) - amdgpu_encoder->native_mode = *preferred_mode; - - break; - } - - } -} - -static struct drm_display_mode * -amdgpu_dm_create_common_mode(struct drm_encoder *encoder, - const char *name, - int hdisplay, int vdisplay) -{ - struct drm_device *dev = encoder->dev; - struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); - struct drm_display_mode *mode = NULL; - struct drm_display_mode *native_mode = &amdgpu_encoder->native_mode; - - mode = drm_mode_duplicate(dev, native_mode); - - if (mode == NULL) - return NULL; - - mode->hdisplay = hdisplay; - mode->vdisplay = vdisplay; - mode->type &= ~DRM_MODE_TYPE_PREFERRED; - strscpy(mode->name, name, DRM_DISPLAY_MODE_LEN); - - return mode; - -} - -static const struct amdgpu_dm_mode_size { - char name[DRM_DISPLAY_MODE_LEN]; - int w; - int h; -} common_modes[] = { - { "640x480", 640, 480}, - { "800x600", 800, 600}, - { "1024x768", 1024, 768}, - { "1280x720", 1280, 720}, - { "1280x800", 1280, 800}, - {"1280x1024", 1280, 1024}, - { "1440x900", 1440, 900}, - {"1680x1050", 1680, 1050}, - {"1600x1200", 1600, 1200}, - {"1920x1080", 1920, 1080}, - {"1920x1200", 1920, 1200} -}; - -static void amdgpu_dm_connector_add_common_modes(struct drm_encoder *encoder, - struct drm_connector *connector) -{ - struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); - struct drm_display_mode *mode = NULL; - struct drm_display_mode *native_mode = &amdgpu_encoder->native_mode; - struct amdgpu_dm_connector *amdgpu_dm_connector = - to_amdgpu_dm_connector(connector); - int i; - int n; - - if ((connector->connector_type != DRM_MODE_CONNECTOR_eDP) && - (connector->connector_type != DRM_MODE_CONNECTOR_LVDS)) - return; - - n = ARRAY_SIZE(common_modes); - - for (i = 0; i < n; i++) { - struct drm_display_mode *curmode = NULL; - bool mode_existed = false; - - if (common_modes[i].w > native_mode->hdisplay || - common_modes[i].h > native_mode->vdisplay || - (common_modes[i].w == native_mode->hdisplay && - common_modes[i].h == native_mode->vdisplay)) - continue; - - list_for_each_entry(curmode, &connector->probed_modes, head) { - if (common_modes[i].w == curmode->hdisplay && - common_modes[i].h == curmode->vdisplay) { - mode_existed = true; - break; - } - } - - if (mode_existed) - continue; - - mode = amdgpu_dm_create_common_mode(encoder, - common_modes[i].name, common_modes[i].w, - common_modes[i].h); - if (!mode) - continue; - - drm_mode_probed_add(connector, mode); - amdgpu_dm_connector->num_modes++; - } -} - -static void amdgpu_set_panel_orientation(struct drm_connector *connector) -{ - struct drm_encoder *encoder; - struct amdgpu_encoder *amdgpu_encoder; - const struct drm_display_mode *native_mode; - - if (connector->connector_type != DRM_MODE_CONNECTOR_eDP && - connector->connector_type != DRM_MODE_CONNECTOR_LVDS) - return; - - mutex_lock(&connector->dev->mode_config.mutex); - amdgpu_dm_connector_get_modes(connector); - mutex_unlock(&connector->dev->mode_config.mutex); - - encoder = amdgpu_dm_connector_to_encoder(connector); - if (!encoder) - return; - - amdgpu_encoder = to_amdgpu_encoder(encoder); - - native_mode = &amdgpu_encoder->native_mode; - if (native_mode->hdisplay == 0 || native_mode->vdisplay == 0) - return; - - drm_connector_set_panel_orientation_with_quirk(connector, - DRM_MODE_PANEL_ORIENTATION_UNKNOWN, - native_mode->hdisplay, - native_mode->vdisplay); -} - -static void amdgpu_dm_connector_ddc_get_modes(struct drm_connector *connector, - const struct drm_edid *drm_edid) -{ - struct amdgpu_dm_connector *amdgpu_dm_connector = - to_amdgpu_dm_connector(connector); - - if (drm_edid) { - /* empty probed_modes */ - INIT_LIST_HEAD(&connector->probed_modes); - amdgpu_dm_connector->num_modes = - drm_edid_connector_add_modes(connector); - - /* sorting the probed modes before calling function - * amdgpu_dm_get_native_mode() since EDID can have - * more than one preferred mode. The modes that are - * later in the probed mode list could be of higher - * and preferred resolution. For example, 3840x2160 - * resolution in base EDID preferred timing and 4096x2160 - * preferred resolution in DID extension block later. - */ - drm_mode_sort(&connector->probed_modes); - amdgpu_dm_get_native_mode(connector); - - /* Freesync capabilities are reset by calling - * drm_edid_connector_add_modes() and need to be - * restored here. - */ - amdgpu_dm_update_freesync_caps(connector, drm_edid, false); - } else { - amdgpu_dm_connector->num_modes = 0; - } -} - -static bool is_duplicate_mode(struct amdgpu_dm_connector *aconnector, - struct drm_display_mode *mode) -{ - struct drm_display_mode *m; - - list_for_each_entry(m, &aconnector->base.probed_modes, head) { - if (drm_mode_equal(m, mode)) - return true; - } - - return false; -} - -static uint add_fs_modes(struct amdgpu_dm_connector *aconnector) -{ - const struct drm_display_mode *m; - struct drm_display_mode *new_mode; - uint i; - u32 new_modes_count = 0; - - /* Standard FPS values - * - * 23.976 - TV/NTSC - * 24 - Cinema - * 25 - TV/PAL - * 29.97 - TV/NTSC - * 30 - TV/NTSC - * 48 - Cinema HFR - * 50 - TV/PAL - * 60 - Commonly used - * 48,72,96,120 - Multiples of 24 - */ - static const u32 common_rates[] = { - 23976, 24000, 25000, 29970, 30000, - 48000, 50000, 60000, 72000, 96000, 120000 - }; - - /* - * Find mode with highest refresh rate with the same resolution - * as the preferred mode. Some monitors report a preferred mode - * with lower resolution than the highest refresh rate supported. - */ - - m = get_highest_refresh_rate_mode(aconnector, true); - if (!m) - return 0; - - for (i = 0; i < ARRAY_SIZE(common_rates); i++) { - u64 target_vtotal, target_vtotal_diff; - u64 num, den; - - if (drm_mode_vrefresh(m) * 1000 < common_rates[i]) - continue; - - if (common_rates[i] < aconnector->min_vfreq * 1000 || - common_rates[i] > aconnector->max_vfreq * 1000) - continue; - - num = (unsigned long long)m->clock * 1000 * 1000; - den = common_rates[i] * (unsigned long long)m->htotal; - target_vtotal = div_u64(num, den); - target_vtotal_diff = target_vtotal - m->vtotal; - - /* Check for illegal modes */ - if (m->vsync_start + target_vtotal_diff < m->vdisplay || - m->vsync_end + target_vtotal_diff < m->vsync_start || - m->vtotal + target_vtotal_diff < m->vsync_end) - continue; - - new_mode = drm_mode_duplicate(aconnector->base.dev, m); - if (!new_mode) - goto out; - - new_mode->vtotal += (u16)target_vtotal_diff; - new_mode->vsync_start += (u16)target_vtotal_diff; - new_mode->vsync_end += (u16)target_vtotal_diff; - new_mode->type &= ~DRM_MODE_TYPE_PREFERRED; - new_mode->type |= DRM_MODE_TYPE_DRIVER; - - if (!is_duplicate_mode(aconnector, new_mode)) { - drm_mode_probed_add(&aconnector->base, new_mode); - new_modes_count += 1; - } else - drm_mode_destroy(aconnector->base.dev, new_mode); - } - out: - return new_modes_count; -} - -static void amdgpu_dm_connector_add_freesync_modes(struct drm_connector *connector, - const struct drm_edid *drm_edid) -{ - struct amdgpu_dm_connector *amdgpu_dm_connector = - to_amdgpu_dm_connector(connector); - - if (!(amdgpu_freesync_vid_mode && drm_edid)) - return; - - if (!amdgpu_dm_connector->dc_sink || !amdgpu_dm_connector->dc_link) - return; - - if (!dc_supports_vrr(amdgpu_dm_connector->dc_sink->ctx->dce_version)) - return; - - if (dc_connector_supports_analog(amdgpu_dm_connector->dc_link->link_id.id) && - amdgpu_dm_connector->dc_sink->edid_caps.analog) - return; - - if (amdgpu_dm_connector->max_vfreq - amdgpu_dm_connector->min_vfreq > 10) - amdgpu_dm_connector->num_modes += - add_fs_modes(amdgpu_dm_connector); -} - -static int amdgpu_dm_connector_get_modes(struct drm_connector *connector) -{ - struct amdgpu_dm_connector *amdgpu_dm_connector = - to_amdgpu_dm_connector(connector); - struct dc_link *dc_link = amdgpu_dm_connector->dc_link; - struct drm_encoder *encoder; - const struct drm_edid *drm_edid = amdgpu_dm_connector->drm_edid; - struct dc_link_settings *verified_link_cap = &dc_link->verified_link_cap; - const struct dc *dc = dc_link->dc; - - encoder = amdgpu_dm_connector_to_encoder(connector); - - if (!drm_edid) { - amdgpu_dm_connector->num_modes = - drm_add_modes_noedid(connector, 640, 480); - if (dc->link_srv->dp_get_encoding_format(verified_link_cap) == DP_128b_132b_ENCODING) - amdgpu_dm_connector->num_modes += - drm_add_modes_noedid(connector, 1920, 1080); - - if (amdgpu_dm_connector->dc_sink && - amdgpu_dm_connector->dc_sink->edid_caps.analog && - dc_connector_supports_analog(dc_link->link_id.id)) { - /* Analog monitor connected by DAC load detection. - * Add common modes. It will be up to the user to select one that works. - */ - for (int i = 0; i < ARRAY_SIZE(common_modes); i++) - amdgpu_dm_connector->num_modes += drm_add_modes_noedid( - connector, common_modes[i].w, common_modes[i].h); - } - } else { - amdgpu_dm_connector_ddc_get_modes(connector, drm_edid); - if (encoder) - amdgpu_dm_connector_add_common_modes(encoder, connector); - amdgpu_dm_connector_add_freesync_modes(connector, drm_edid); - } - amdgpu_dm_fbc_init(connector); - - return amdgpu_dm_connector->num_modes; -} - -static const u32 supported_colorspaces = - BIT(DRM_MODE_COLORIMETRY_BT709_YCC) | - BIT(DRM_MODE_COLORIMETRY_OPRGB) | - BIT(DRM_MODE_COLORIMETRY_BT2020_RGB) | - BIT(DRM_MODE_COLORIMETRY_BT2020_YCC); - -void amdgpu_dm_connector_init_helper(struct amdgpu_display_manager *dm, - struct amdgpu_dm_connector *aconnector, - int connector_type, - struct dc_link *link, - int link_index) -{ - struct amdgpu_device *adev = drm_to_adev(dm->ddev); - - /* - * Some of the properties below require access to state, like bpc. - * Allocate some default initial connector state with our reset helper. - */ - if (aconnector->base.funcs->reset) - aconnector->base.funcs->reset(&aconnector->base); - - aconnector->connector_id = link_index; - aconnector->bl_idx = -1; - aconnector->dc_link = link; - aconnector->base.interlace_allowed = false; - aconnector->base.doublescan_allowed = false; - aconnector->base.stereo_allowed = false; - aconnector->base.dpms = DRM_MODE_DPMS_OFF; - aconnector->hpd.hpd = AMDGPU_HPD_NONE; /* not used */ - aconnector->audio_inst = -1; - aconnector->pack_sdp_v1_3 = false; - aconnector->as_type = ADAPTIVE_SYNC_TYPE_NONE; - memset(&aconnector->vsdb_info, 0, sizeof(aconnector->vsdb_info)); - mutex_init(&aconnector->hpd_lock); - mutex_init(&aconnector->handle_mst_msg_ready); - - /* - * If HDMI HPD debounce delay is set, use the minimum between selected - * value and AMDGPU_DM_MAX_HDMI_HPD_DEBOUNCE_MS - */ - if (amdgpu_hdmi_hpd_debounce_delay_ms) { - aconnector->hdmi_hpd_debounce_delay_ms = min(amdgpu_hdmi_hpd_debounce_delay_ms, - AMDGPU_DM_MAX_HDMI_HPD_DEBOUNCE_MS); - INIT_DELAYED_WORK(&aconnector->hdmi_hpd_debounce_work, amdgpu_dm_hdmi_hpd_debounce_work); - aconnector->hdmi_prev_sink = NULL; - } else { - aconnector->hdmi_hpd_debounce_delay_ms = 0; - } - - /* - * configure support HPD hot plug connector_>polled default value is 0 - * which means HPD hot plug not supported - */ - switch (connector_type) { - case DRM_MODE_CONNECTOR_HDMIA: - aconnector->base.polled = DRM_CONNECTOR_POLL_HPD; - aconnector->base.ycbcr_420_allowed = - link->link_enc->features.hdmi_ycbcr420_supported ? true : false; - break; - case DRM_MODE_CONNECTOR_DisplayPort: - aconnector->base.polled = DRM_CONNECTOR_POLL_HPD; - link->link_enc = link_enc_cfg_get_link_enc(link); - ASSERT(link->link_enc); - if (link->link_enc) - aconnector->base.ycbcr_420_allowed = - link->link_enc->features.dp_ycbcr420_supported ? true : false; - break; - case DRM_MODE_CONNECTOR_DVID: - aconnector->base.polled = DRM_CONNECTOR_POLL_HPD; - break; - case DRM_MODE_CONNECTOR_DVII: - case DRM_MODE_CONNECTOR_VGA: - aconnector->base.polled = - DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT; - break; - default: - break; - } - - drm_object_attach_property(&aconnector->base.base, - dm->ddev->mode_config.scaling_mode_property, - DRM_MODE_SCALE_NONE); - - if (connector_type == DRM_MODE_CONNECTOR_HDMIA - || (connector_type == DRM_MODE_CONNECTOR_DisplayPort && !aconnector->mst_root)) - drm_connector_attach_broadcast_rgb_property(&aconnector->base); - - drm_object_attach_property(&aconnector->base.base, - adev->mode_info.underscan_property, - UNDERSCAN_OFF); - drm_object_attach_property(&aconnector->base.base, - adev->mode_info.underscan_hborder_property, - 0); - drm_object_attach_property(&aconnector->base.base, - adev->mode_info.underscan_vborder_property, - 0); - - if (!aconnector->mst_root) - drm_connector_attach_max_bpc_property(&aconnector->base, 8, 16); - - aconnector->base.state->max_bpc = 16; - aconnector->base.state->max_requested_bpc = aconnector->base.state->max_bpc; - - if (connector_type == DRM_MODE_CONNECTOR_HDMIA) { - /* Content Type is currently only implemented for HDMI. */ - drm_connector_attach_content_type_property(&aconnector->base); - } - - if (connector_type == DRM_MODE_CONNECTOR_HDMIA) { - if (!drm_mode_create_hdmi_colorspace_property(&aconnector->base, supported_colorspaces)) - drm_connector_attach_colorspace_property(&aconnector->base); - } else if ((connector_type == DRM_MODE_CONNECTOR_DisplayPort && !aconnector->mst_root) || - connector_type == DRM_MODE_CONNECTOR_eDP) { - if (!drm_mode_create_dp_colorspace_property(&aconnector->base, supported_colorspaces)) - drm_connector_attach_colorspace_property(&aconnector->base); - } - - if (connector_type == DRM_MODE_CONNECTOR_HDMIA || - connector_type == DRM_MODE_CONNECTOR_DisplayPort || - connector_type == DRM_MODE_CONNECTOR_eDP) { - drm_connector_attach_hdr_output_metadata_property(&aconnector->base); - - if (!aconnector->mst_root) - drm_connector_attach_vrr_capable_property(&aconnector->base); - - if (adev->dm.hdcp_workqueue) - drm_connector_attach_content_protection_property(&aconnector->base, true); - } - - if (connector_type == DRM_MODE_CONNECTOR_eDP) { - struct drm_privacy_screen *privacy_screen; - - drm_connector_attach_panel_type_property(&aconnector->base); - - privacy_screen = drm_privacy_screen_get(adev_to_drm(adev)->dev, NULL); - if (!IS_ERR(privacy_screen)) { - drm_connector_attach_privacy_screen_provider(&aconnector->base, - privacy_screen); - } else if (PTR_ERR(privacy_screen) != -ENODEV) { - drm_warn(adev_to_drm(adev), "Error getting privacy-screen\n"); - } - } -} - -static int amdgpu_dm_i2c_xfer(struct i2c_adapter *i2c_adap, - struct i2c_msg *msgs, int num) -{ - struct amdgpu_i2c_adapter *i2c = i2c_get_adapdata(i2c_adap); - struct ddc_service *ddc_service = i2c->ddc_service; - struct i2c_command cmd; - int i; - int result = -EIO; - - if (!ddc_service->ddc_pin) - return result; - - cmd.payloads = kzalloc_objs(struct i2c_payload, num); - - if (!cmd.payloads) - return result; - - cmd.number_of_payloads = num; - cmd.engine = I2C_COMMAND_ENGINE_DEFAULT; - cmd.speed = 100; - - for (i = 0; i < num; i++) { - cmd.payloads[i].write = !(msgs[i].flags & I2C_M_RD); - cmd.payloads[i].address = msgs[i].addr; - cmd.payloads[i].length = msgs[i].len; - cmd.payloads[i].data = msgs[i].buf; - } - - if (i2c->oem) { - if (dc_submit_i2c_oem( - ddc_service->ctx->dc, - &cmd)) - result = num; - } else { - if (dc_submit_i2c( - ddc_service->ctx->dc, - ddc_service->link->link_index, - &cmd)) - result = num; - } - - kfree(cmd.payloads); - return result; -} - -static u32 amdgpu_dm_i2c_func(struct i2c_adapter *adap) -{ - return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; -} - -static const struct i2c_algorithm amdgpu_dm_i2c_algo = { - .master_xfer = amdgpu_dm_i2c_xfer, - .functionality = amdgpu_dm_i2c_func, -}; - -static struct amdgpu_i2c_adapter * -create_i2c(struct ddc_service *ddc_service, bool oem) -{ - struct amdgpu_device *adev = ddc_service->ctx->driver_context; - struct amdgpu_i2c_adapter *i2c; - - i2c = kzalloc_obj(struct amdgpu_i2c_adapter); - if (!i2c) - return NULL; - i2c->base.owner = THIS_MODULE; - i2c->base.dev.parent = &adev->pdev->dev; - i2c->base.algo = &amdgpu_dm_i2c_algo; - if (oem) - snprintf(i2c->base.name, sizeof(i2c->base.name), "AMDGPU DM i2c OEM bus"); - else - snprintf(i2c->base.name, sizeof(i2c->base.name), "AMDGPU DM i2c hw bus %d", - ddc_service->link->link_index); - i2c_set_adapdata(&i2c->base, i2c); - i2c->ddc_service = ddc_service; - i2c->oem = oem; - - return i2c; -} - -int amdgpu_dm_initialize_hdmi_connector(struct amdgpu_dm_connector *aconnector) -{ - struct cec_connector_info conn_info; - struct drm_device *ddev = aconnector->base.dev; - struct device *hdmi_dev = ddev->dev; - - if (amdgpu_dc_debug_mask & DC_DISABLE_HDMI_CEC) { - drm_info(ddev, "HDMI-CEC feature masked\n"); - return -EINVAL; - } - - cec_fill_conn_info_from_drm(&conn_info, &aconnector->base); - aconnector->notifier = - cec_notifier_conn_register(hdmi_dev, NULL, &conn_info); - if (!aconnector->notifier) { - drm_err(ddev, "Failed to create cec notifier\n"); - return -ENOMEM; - } - - return 0; -} - -/* - * Note: this function assumes that dc_link_detect() was called for the - * dc_link which will be represented by this aconnector. - */ -static int amdgpu_dm_connector_init(struct amdgpu_display_manager *dm, - struct amdgpu_dm_connector *aconnector, - u32 link_index, - struct amdgpu_encoder *aencoder) -{ - int res = 0; - int connector_type; - struct dc *dc = dm->dc; - struct dc_link *link = dc_get_link_at_index(dc, link_index); - struct amdgpu_i2c_adapter *i2c; - - /* Not needed for writeback connector */ - link->priv = aconnector; - - - i2c = create_i2c(link->ddc, false); - if (!i2c) { - drm_err(adev_to_drm(dm->adev), "Failed to create i2c adapter data\n"); - return -ENOMEM; - } - - aconnector->i2c = i2c; - res = devm_i2c_add_adapter(dm->adev->dev, &i2c->base); - - if (res) { - drm_err(adev_to_drm(dm->adev), "Failed to register hw i2c %d\n", link->link_index); - goto out_free; - } - - connector_type = to_drm_connector_type(link->connector_signal, link->link_id.id); - - res = drm_connector_init_with_ddc( - dm->ddev, - &aconnector->base, - &amdgpu_dm_connector_funcs, - connector_type, - &i2c->base); - - if (res) { - drm_err(adev_to_drm(dm->adev), "connector_init failed\n"); - aconnector->connector_id = -1; - goto out_free; - } - - drm_connector_helper_add( - &aconnector->base, - &amdgpu_dm_connector_helper_funcs); - - amdgpu_dm_connector_init_helper( - dm, - aconnector, - connector_type, - link, - link_index); - - drm_connector_attach_encoder( - &aconnector->base, &aencoder->base); - - if (connector_type == DRM_MODE_CONNECTOR_HDMIA || - connector_type == DRM_MODE_CONNECTOR_HDMIB) - amdgpu_dm_initialize_hdmi_connector(aconnector); - - if (dc_is_dp_signal(link->connector_signal)) - amdgpu_dm_initialize_dp_connector(dm, aconnector, link->link_index); - -out_free: - if (res) { - kfree(i2c); - aconnector->i2c = NULL; - } - return res; -} - -int amdgpu_dm_get_encoder_crtc_mask(struct amdgpu_device *adev) -{ - switch (adev->mode_info.num_crtc) { - case 1: - return 0x1; - case 2: - return 0x3; - case 3: - return 0x7; - case 4: - return 0xf; - case 5: - return 0x1f; - case 6: - default: - return 0x3f; - } -} - -static int amdgpu_dm_encoder_init(struct drm_device *dev, - struct amdgpu_encoder *aencoder, - uint32_t link_index) -{ - struct amdgpu_device *adev = drm_to_adev(dev); - - int res = drm_encoder_init(dev, - &aencoder->base, - &amdgpu_dm_encoder_funcs, - DRM_MODE_ENCODER_TMDS, - NULL); - - aencoder->base.possible_crtcs = amdgpu_dm_get_encoder_crtc_mask(adev); - - if (!res) - aencoder->encoder_id = link_index; - else - aencoder->encoder_id = -1; - - drm_encoder_helper_add(&aencoder->base, &amdgpu_dm_encoder_helper_funcs); + return vcpi; - return res; + dm_conn_state->pbn = pbn; + dm_conn_state->vcpi_slots = vcpi; + } + return 0; } static void manage_dm_interrupts(struct amdgpu_device *adev, @@ -8176,6 +5080,72 @@ static int amdgpu_dm_atomic_setup_commit(struct drm_atomic_commit *state) return 0; } +static void set_multisync_trigger_params( + struct dc_stream_state *stream) +{ + struct dc_stream_state *master = NULL; + + if (stream->triggered_crtc_reset.enabled) { + master = stream->triggered_crtc_reset.event_source; + stream->triggered_crtc_reset.event = + master->timing.flags.VSYNC_POSITIVE_POLARITY ? + CRTC_EVENT_VSYNC_RISING : CRTC_EVENT_VSYNC_FALLING; + stream->triggered_crtc_reset.delay = TRIGGER_DELAY_NEXT_PIXEL; + } +} + +static void set_master_stream(struct dc_stream_state *stream_set[], + int stream_count) +{ + int j, highest_rfr = 0, master_stream = 0; + + for (j = 0; j < stream_count; j++) { + if (stream_set[j] && stream_set[j]->triggered_crtc_reset.enabled) { + int refresh_rate = 0; + + refresh_rate = (stream_set[j]->timing.pix_clk_100hz*100)/ + (stream_set[j]->timing.h_total*stream_set[j]->timing.v_total); + if (refresh_rate > highest_rfr) { + highest_rfr = refresh_rate; + master_stream = j; + } + } + } + for (j = 0; j < stream_count; j++) { + if (stream_set[j]) + stream_set[j]->triggered_crtc_reset.event_source = stream_set[master_stream]; + } +} + +static void dm_enable_per_frame_crtc_master_sync(struct dc_state *context) +{ + int i = 0; + struct dc_stream_state *stream; + + if (context->stream_count < 2) + return; + for (i = 0; i < context->stream_count ; i++) { + if (!context->streams[i]) + continue; + /* + * TODO: add a function to read AMD VSDB bits and set + * crtc_sync_master.multi_sync_enabled flag + * For now it's set to false + */ + } + + set_master_stream(context->streams, context->stream_count); + + for (i = 0; i < context->stream_count ; i++) { + stream = context->streams[i]; + + if (!stream) + continue; + + set_multisync_trigger_params(stream); + } +} + /** * amdgpu_dm_atomic_commit_tail() - AMDgpu DM's commit tail implementation. * @state: The atomic state to commit @@ -8244,7 +5214,7 @@ static void amdgpu_dm_atomic_commit_tail(struct drm_atomic_commit *state) if ((new_con_state->hdmi.broadcast_rgb != old_con_state->hdmi.broadcast_rgb) && (dm_old_crtc_state->stream->output_color_space != - get_output_color_space(&dm_new_crtc_state->stream->timing, new_con_state))) + amdgpu_dm_get_output_color_space(&dm_new_crtc_state->stream->timing, new_con_state))) output_color_space_changed = true; abm_changed = dm_new_crtc_state->abm_level != @@ -8258,7 +5228,7 @@ static void amdgpu_dm_atomic_commit_tail(struct drm_atomic_commit *state) stream_update.stream = dm_new_crtc_state->stream; if (scaling_changed) { - update_stream_scaling_settings(dev, &dm_new_con_state->base.crtc->mode, + amdgpu_dm_update_stream_scaling_settings(dev, &dm_new_con_state->base.crtc->mode, dm_new_con_state, dm_new_crtc_state->stream); stream_update.src = dm_new_crtc_state->stream->src; @@ -8267,7 +5237,7 @@ static void amdgpu_dm_atomic_commit_tail(struct drm_atomic_commit *state) if (output_color_space_changed) { dm_new_crtc_state->stream->output_color_space - = get_output_color_space(&dm_new_crtc_state->stream->timing, new_con_state); + = amdgpu_dm_get_output_color_space(&dm_new_crtc_state->stream->timing, new_con_state); stream_update.output_color_space = &dm_new_crtc_state->stream->output_color_space; } @@ -8279,7 +5249,7 @@ static void amdgpu_dm_atomic_commit_tail(struct drm_atomic_commit *state) } if (hdr_changed) { - fill_hdr_info_packet(new_con_state, &hdr_packet); + amdgpu_dm_fill_hdr_info_packet(new_con_state, &hdr_packet); stream_update.hdr_static_metadata = &hdr_packet; } @@ -8487,104 +5457,6 @@ static void amdgpu_dm_atomic_commit_tail(struct drm_atomic_commit *state) trace_amdgpu_dm_atomic_commit_tail_finish(state); } -static int dm_force_atomic_commit(struct drm_connector *connector) -{ - int ret = 0; - struct drm_device *ddev = connector->dev; - struct drm_atomic_commit *state = drm_atomic_commit_alloc(ddev); - struct amdgpu_crtc *disconnected_acrtc = to_amdgpu_crtc(connector->encoder->crtc); - struct drm_plane *plane = disconnected_acrtc->base.primary; - struct drm_connector_state *conn_state; - struct drm_crtc_state *crtc_state; - struct drm_plane_state *plane_state; - - if (!state) - return -ENOMEM; - - state->acquire_ctx = ddev->mode_config.acquire_ctx; - - /* Construct an atomic state to restore previous display setting */ - - /* - * Attach connectors to drm_atomic_commit - */ - conn_state = drm_atomic_get_connector_state(state, connector); - - /* Check for error in getting connector state */ - if (IS_ERR(conn_state)) { - ret = PTR_ERR(conn_state); - goto out; - } - - /* Attach crtc to drm_atomic_commit*/ - crtc_state = drm_atomic_get_crtc_state(state, &disconnected_acrtc->base); - - /* Check for error in getting crtc state */ - if (IS_ERR(crtc_state)) { - ret = PTR_ERR(crtc_state); - goto out; - } - - /* force a restore */ - crtc_state->mode_changed = true; - - /* Attach plane to drm_atomic_commit */ - plane_state = drm_atomic_get_plane_state(state, plane); - - /* Check for error in getting plane state */ - if (IS_ERR(plane_state)) { - ret = PTR_ERR(plane_state); - goto out; - } - - /* Call commit internally with the state we just constructed */ - ret = drm_atomic_commit(state); - -out: - drm_atomic_commit_put(state); - if (ret) - drm_err(ddev, "Restoring old state failed with %i\n", ret); - - return ret; -} - -/* - * This function handles all cases when set mode does not come upon hotplug. - * This includes when a display is unplugged then plugged back into the - * same port and when running without usermode desktop manager supprot - */ -void dm_restore_drm_connector_state(struct drm_device *dev, - struct drm_connector *connector) -{ - struct amdgpu_dm_connector *aconnector; - struct amdgpu_crtc *disconnected_acrtc; - struct dm_crtc_state *acrtc_state; - - if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) - return; - - aconnector = to_amdgpu_dm_connector(connector); - - if (!aconnector->dc_sink || !connector->state || !connector->encoder) - return; - - disconnected_acrtc = to_amdgpu_crtc(connector->encoder->crtc); - if (!disconnected_acrtc) - return; - - acrtc_state = to_dm_crtc_state(disconnected_acrtc->base.state); - if (!acrtc_state->stream) - return; - - /* - * If the previous sink is not released and different from the current, - * we deduce we are in a state where we can not rely on usermode call - * to turn on the display, so we do it here - */ - if (acrtc_state->stream->sink != aconnector->dc_sink) - dm_force_atomic_commit(&aconnector->base); -} - /* * Grabs all modesetting locks to serialize against any blocking commits, * Waits for completion of all non blocking commits. @@ -8786,7 +5658,7 @@ static int dm_update_crtc_state(struct amdgpu_display_manager *dm, if (!drm_atomic_crtc_needs_modeset(new_crtc_state)) goto skip_modeset; - new_stream = create_validate_stream_for_sink(connector, + new_stream = amdgpu_dm_create_validate_stream_for_sink(connector, &new_crtc_state->mode, dm_new_conn_state, dm_old_crtc_state->stream); @@ -8814,7 +5686,7 @@ static int dm_update_crtc_state(struct amdgpu_display_manager *dm, dm_new_crtc_state->abm_level = dm_new_conn_state->abm_level; - ret = fill_hdr_info_packet(drm_new_conn_state, + ret = amdgpu_dm_fill_hdr_info_packet(drm_new_conn_state, &new_stream->hdr_static_metadata); if (ret) goto fail; @@ -8883,11 +5755,11 @@ static int dm_update_crtc_state(struct amdgpu_display_manager *dm, goto skip_modeset; } else if (amdgpu_freesync_vid_mode && aconnector && - is_freesync_video_mode(&new_crtc_state->mode, + amdgpu_dm_is_freesync_video_mode(&new_crtc_state->mode, aconnector)) { struct drm_display_mode *high_mode; - high_mode = get_highest_refresh_rate_mode(aconnector, false); + high_mode = amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false); if (!drm_mode_equal(&new_crtc_state->mode, high_mode)) set_freesync_fixed_config(dm_new_crtc_state); } @@ -8979,7 +5851,7 @@ skip_modeset: /* Scaling or underscan settings */ if (is_scaling_state_different(dm_old_conn_state, dm_new_conn_state) || drm_atomic_crtc_needs_modeset(new_crtc_state)) - update_stream_scaling_settings(adev_to_drm(adev), + amdgpu_dm_update_stream_scaling_settings(adev_to_drm(adev), &new_crtc_state->mode, dm_new_conn_state, dm_new_crtc_state->stream); /* ABM settings */ @@ -10358,373 +7230,6 @@ fail: return ret; } -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) -{ - u8 *edid_ext = NULL; - int i; - int j = 0; - u16 min_vfreq; - u16 max_vfreq; - - if (!edid || !edid->extensions) - return; - - /* Find DisplayID extension */ - for (i = 0; i < edid->extensions; i++) { - edid_ext = (void *)(edid + (i + 1)); - if (edid_ext[0] == DISPLAYID_EXT) - break; - } - - if (i == edid->extensions) - return; - - while (j < EDID_LENGTH) { - /* Get dynamic video timing range from DisplayID if available */ - if (EDID_LENGTH - j > 13 && edid_ext[j] == 0x25 && - (edid_ext[j+1] & 0xFE) == 0 && (edid_ext[j+2] == 9)) { - min_vfreq = edid_ext[j+9]; - if (edid_ext[j+1] & 7) - max_vfreq = edid_ext[j+10] + ((edid_ext[j+11] & 3) << 8); - else - max_vfreq = edid_ext[j+10]; - - if (max_vfreq && min_vfreq) { - connector->display_info.monitor_range.max_vfreq = max_vfreq; - connector->display_info.monitor_range.min_vfreq = min_vfreq; - - return; - } - } - j++; - } -} - -static int get_amd_vsdb(struct amdgpu_dm_connector *aconnector, - struct amdgpu_hdmi_vsdb_info *vsdb_info) -{ - struct drm_connector *connector = &aconnector->base; - - vsdb_info->replay_mode = connector->display_info.amd_vsdb.replay_mode; - vsdb_info->amd_vsdb_version = connector->display_info.amd_vsdb.version; - - 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 - * - * @connector: Connector to query. - * @drm_edid: DRM EDID from monitor - * @do_mccs: Controls whether MCCS (Monitor Control Command Set) over - * DDC (Display Data Channel) transactions are performed. When true, - * the driver queries the monitor to get or update additional FreeSync - * capability information. When false, these transactions are skipped. - * - * Amdgpu supports Freesync in DP and HDMI displays, and it is required to keep - * track of some of the display information in the internal data struct used by - * amdgpu_dm. This function checks which type of connector we need to set the - * FreeSync parameters. - */ -void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, - const struct drm_edid *drm_edid, bool do_mccs) -{ - int i = 0; - struct amdgpu_dm_connector *amdgpu_dm_connector = - to_amdgpu_dm_connector(connector); - struct dm_connector_state *dm_con_state = NULL; - struct dc_sink *sink; - struct amdgpu_device *adev = drm_to_adev(connector->dev); - struct amdgpu_hdmi_vsdb_info vsdb_info = {0}; - const struct edid *edid; - bool freesync_capable = false; - enum adaptive_sync_type as_type = ADAPTIVE_SYNC_TYPE_NONE; - - if (!connector->state) { - drm_err(adev_to_drm(adev), "%s - Connector has no state", __func__); - goto update; - } - - sink = amdgpu_dm_connector->dc_sink ? - amdgpu_dm_connector->dc_sink : - amdgpu_dm_connector->dc_em_sink; - - drm_edid_connector_update(connector, drm_edid); - - if (!drm_edid || !sink) { - dm_con_state = to_dm_connector_state(connector->state); - - amdgpu_dm_connector->min_vfreq = 0; - amdgpu_dm_connector->max_vfreq = 0; - freesync_capable = false; - - goto update; - } - - dm_con_state = to_dm_connector_state(connector->state); - - if (!adev->dm.freesync_module || !dc_supports_vrr(sink->ctx->dce_version)) - goto update; - - edid = drm_edid_raw(drm_edid); // FIXME: Get rid of drm_edid_raw() - - /* Some eDP panels only have the refresh rate range info in DisplayID */ - if ((connector->display_info.monitor_range.min_vfreq == 0 || - 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; - } - - get_amd_vsdb(amdgpu_dm_connector, &vsdb_info); - - 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 (drm_edid && sink->sink_signal == SIGNAL_TYPE_HDMI_TYPE_A) { - i = parse_hdmi_amd_vsdb(amdgpu_dm_connector, edid, &vsdb_info); - if (i >= 0) { - 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 (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 = parse_hdmi_amd_vsdb(amdgpu_dm_connector, edid, &vsdb_info); - if (i >= 0) { - amdgpu_dm_connector->vsdb_info = vsdb_info; - 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; - - 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; - } - } - } - - /* Handle MCCS */ - if (do_mccs) { - dm_helpers_read_mccs_caps(adev->dm.dc->ctx, amdgpu_dm_connector->dc_link, sink); - - if (sink->edid_caps.freesync_vcp_code && !sink->mccs_caps.freesync_supported) - freesync_capable = false; - - if (sink->mccs_caps.freesync_supported && freesync_capable) - dm_helpers_mccs_vcp_set(adev->dm.dc->ctx, amdgpu_dm_connector->dc_link, sink); - } - -update: - if (dm_con_state) - dm_con_state->freesync_capable = freesync_capable; - - if (connector->state && amdgpu_dm_connector->dc_link && !freesync_capable && - amdgpu_dm_connector->dc_link->replay_settings.config.replay_supported) { - amdgpu_dm_connector->dc_link->replay_settings.config.replay_supported = false; - amdgpu_dm_connector->dc_link->replay_settings.replay_feature_enabled = false; - } - - if (connector->vrr_capable_property) - drm_connector_set_vrr_capable_property(connector, - freesync_capable); -} - void amdgpu_dm_trigger_timing_sync(struct drm_device *dev) { struct amdgpu_device *adev = drm_to_adev(dev); @@ -10744,12 +7249,6 @@ void amdgpu_dm_trigger_timing_sync(struct drm_device *dev) mutex_unlock(&adev->dm.dc_lock); } -static inline void amdgpu_dm_exit_ips_for_hw_access(struct dc *dc) -{ - if (dc->ctx->dmub_srv && !dc->ctx->dmub_srv->idle_exit_counter) - dc_exit_ips_for_hw_access(dc); -} - void dm_write_reg_func(const struct dc_context *ctx, uint32_t address, u32 value, const char *func_name) { diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h index 505164364e61..c0144d14b793 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h @@ -1061,35 +1061,7 @@ struct dm_connector_state { #define to_dm_connector_state(x)\ container_of((x), struct dm_connector_state, base) -void amdgpu_dm_connector_funcs_reset(struct drm_connector *connector); -struct drm_connector_state * -amdgpu_dm_connector_atomic_duplicate_state(struct drm_connector *connector); -int amdgpu_dm_connector_atomic_set_property(struct drm_connector *connector, - struct drm_connector_state *state, - struct drm_property *property, - uint64_t val); - -int amdgpu_dm_connector_atomic_get_property(struct drm_connector *connector, - const struct drm_connector_state *state, - struct drm_property *property, - uint64_t *val); - -int amdgpu_dm_get_encoder_crtc_mask(struct amdgpu_device *adev); - -void amdgpu_dm_connector_init_helper(struct amdgpu_display_manager *dm, - struct amdgpu_dm_connector *aconnector, - int connector_type, - struct dc_link *link, - int link_index); - -enum drm_mode_status amdgpu_dm_connector_mode_valid(struct drm_connector *connector, - const struct drm_display_mode *mode); - -void dm_restore_drm_connector_state(struct drm_device *dev, - struct drm_connector *connector); - -void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, - const struct drm_edid *drm_edid, bool do_mccs); +#include "amdgpu_dm_connector.h" void amdgpu_dm_trigger_timing_sync(struct drm_device *dev); @@ -1113,14 +1085,9 @@ int amdgpu_dm_update_plane_color_mgmt(struct dm_crtc_state *crtc, struct drm_plane_state *plane_state, struct dc_plane_state *dc_plane_state); -void amdgpu_dm_update_connector_after_detect( - struct amdgpu_dm_connector *aconnector); - void populate_hdmi_info_from_connector(bool enable_frl, struct drm_hdmi_info *info, struct dc_edid_caps *edid_caps); -extern const struct drm_encoder_helper_funcs amdgpu_dm_encoder_helper_funcs; - int amdgpu_dm_process_dmub_aux_transfer_sync(struct dc_context *ctx, unsigned int link_index, struct aux_payload *payload, enum aux_return_code_type *operation_result); @@ -1135,20 +1102,9 @@ bool amdgpu_dm_execute_fused_io( int amdgpu_dm_process_dmub_set_config_sync(struct dc_context *ctx, unsigned int link_index, struct set_config_cmd_payload *payload, enum set_config_status *operation_result); -struct dc_stream_state * - create_validate_stream_for_sink(struct drm_connector *connector, - const struct drm_display_mode *drm_mode, - const struct dm_connector_state *dm_state, - const struct dc_stream_state *old_stream); - int dm_atomic_get_state(struct drm_atomic_commit *state, struct dm_atomic_state **dm_state); -struct drm_connector * -amdgpu_dm_find_first_crtc_matching_connector(struct drm_atomic_commit *state, - struct drm_crtc *crtc); - -int convert_dc_color_depth_into_bpc(enum dc_color_depth display_color_depth); struct idle_workqueue *idle_create_workqueue(struct amdgpu_device *adev); void *dm_allocate_gpu_mem(struct amdgpu_device *adev, @@ -1161,10 +1117,6 @@ void dm_free_gpu_mem(struct amdgpu_device *adev, bool amdgpu_dm_is_headless(struct amdgpu_device *adev); -void hdmi_cec_set_edid(struct amdgpu_dm_connector *aconnector); -void hdmi_cec_unset_edid(struct amdgpu_dm_connector *aconnector); -int amdgpu_dm_initialize_hdmi_connector(struct amdgpu_dm_connector *aconnector); - void retrieve_dmi_info(struct amdgpu_display_manager *dm); void amdgpu_dm_emulated_link_detect(struct dc_link *link); 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 new file mode 100644 index 000000000000..f239ce767bff --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c @@ -0,0 +1,3575 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright 2026 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 "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" +#include "dc/inc/core_types.h" +#include "link_enc_cfg.h" +#include "link/protocols/link_dpcd.h" +#include "link_service_types.h" +#include "link/protocols/link_dp_capability.h" +#include "link/protocols/link_ddc.h" + +#include "amdgpu.h" +#include "amdgpu_display.h" +#include "amdgpu_dm.h" +#include "amdgpu_dm_connector.h" +#include "amdgpu_dm_plane.h" +#include "amdgpu_dm_crtc.h" +#include "amdgpu_dm_wb.h" +#include "amdgpu_dm_mst_types.h" +#if defined(CONFIG_DEBUG_FS) +#include "amdgpu_dm_debugfs.h" +#endif +#include "amdgpu_dm_backlight.h" +#include "amdgpu_dm_audio.h" +#include "amdgpu_dm_irq.h" +#include "amdgpu_dm_psr.h" +#include "dm_helpers.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "modules/inc/mod_freesync.h" +#include "modules/inc/mod_power.h" + +#include "amdgpu_dm_trace.h" + +/* Encoder functions */ + +static void amdgpu_dm_encoder_destroy(struct drm_encoder *encoder) +{ + drm_encoder_cleanup(encoder); + kfree(encoder); +} + +static const struct drm_encoder_funcs amdgpu_dm_encoder_funcs = { + .destroy = amdgpu_dm_encoder_destroy, +}; + +static void dm_encoder_helper_disable(struct drm_encoder *encoder) +{ +} + +static int dm_encoder_helper_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct drm_atomic_commit *state = crtc_state->state; + struct drm_connector *connector = conn_state->connector; + struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector); + struct dm_connector_state *dm_new_connector_state = to_dm_connector_state(conn_state); + const struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode; + struct drm_dp_mst_topology_mgr *mst_mgr; + struct drm_dp_mst_port *mst_port; + struct drm_dp_mst_topology_state *mst_state; + enum dc_color_depth color_depth; + int clock, bpp = 0; + bool is_y420 = false; + + if ((connector->connector_type == DRM_MODE_CONNECTOR_eDP) || + (connector->connector_type == DRM_MODE_CONNECTOR_LVDS)) { + struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); + struct drm_display_mode *native_mode = &amdgpu_encoder->native_mode; + enum drm_mode_status result; + + result = drm_crtc_helper_mode_valid_fixed(encoder->crtc, adjusted_mode, native_mode); + if (result != MODE_OK && dm_new_connector_state->scaling == RMX_OFF) { + drm_dbg_driver(encoder->dev, + "mode %dx%d@%dHz is not native, enabling scaling\n", + adjusted_mode->hdisplay, adjusted_mode->vdisplay, + drm_mode_vrefresh(adjusted_mode)); + dm_new_connector_state->scaling = RMX_ASPECT; + } + return 0; + } + + if (!aconnector->mst_output_port) + return 0; + + mst_port = aconnector->mst_output_port; + mst_mgr = &aconnector->mst_root->mst_mgr; + + if (!crtc_state->connectors_changed && !crtc_state->mode_changed) + return 0; + + mst_state = drm_atomic_get_mst_topology_state(state, mst_mgr); + if (IS_ERR(mst_state)) + return PTR_ERR(mst_state); + + mst_state->pbn_div.full = dm_mst_get_pbn_divider(aconnector->mst_root->dc_link); + + if (!state->duplicated) { + int max_bpc = conn_state->max_requested_bpc; + + is_y420 = drm_mode_is_420_also(&connector->display_info, adjusted_mode) && + aconnector->force_yuv420_output; + color_depth = amdgpu_dm_convert_color_depth_from_display_info(connector, + is_y420, + max_bpc); + bpp = amdgpu_dm_convert_dc_color_depth_into_bpc(color_depth) * 3; + clock = adjusted_mode->clock; + dm_new_connector_state->pbn = drm_dp_calc_pbn_mode(clock, bpp << 4); + } + + dm_new_connector_state->vcpi_slots = + drm_dp_atomic_find_time_slots(state, mst_mgr, mst_port, + dm_new_connector_state->pbn); + if (dm_new_connector_state->vcpi_slots < 0) { + drm_dbg_atomic(connector->dev, "failed finding vcpi slots: %d\n", (int)dm_new_connector_state->vcpi_slots); + return dm_new_connector_state->vcpi_slots; + } + return 0; +} + +const struct drm_encoder_helper_funcs amdgpu_dm_encoder_helper_funcs = { + .disable = dm_encoder_helper_disable, + .atomic_check = dm_encoder_helper_atomic_check +}; + +int amdgpu_dm_get_encoder_crtc_mask(struct amdgpu_device *adev) +{ + switch (adev->mode_info.num_crtc) { + case 1: + return 0x1; + case 2: + return 0x3; + case 3: + return 0x7; + case 4: + return 0xf; + case 5: + return 0x1f; + case 6: + default: + return 0x3f; + } +} + +int amdgpu_dm_encoder_init(struct drm_device *dev, + struct amdgpu_encoder *aencoder, + uint32_t link_index) +{ + struct amdgpu_device *adev = drm_to_adev(dev); + + int res = drm_encoder_init(dev, + &aencoder->base, + &amdgpu_dm_encoder_funcs, + DRM_MODE_ENCODER_TMDS, + NULL); + + aencoder->base.possible_crtcs = amdgpu_dm_get_encoder_crtc_mask(adev); + + if (!res) + aencoder->encoder_id = link_index; + else + aencoder->encoder_id = -1; + + drm_encoder_helper_add(&aencoder->base, &amdgpu_dm_encoder_helper_funcs); + + return res; +} + +static enum drm_mode_subconnector get_subconnector_type(struct dc_link *link) +{ + switch (link->dpcd_caps.dongle_type) { + case DISPLAY_DONGLE_NONE: + return DRM_MODE_SUBCONNECTOR_Native; + case DISPLAY_DONGLE_DP_VGA_CONVERTER: + return DRM_MODE_SUBCONNECTOR_VGA; + case DISPLAY_DONGLE_DP_DVI_CONVERTER: + case DISPLAY_DONGLE_DP_DVI_DONGLE: + return DRM_MODE_SUBCONNECTOR_DVID; + case DISPLAY_DONGLE_DP_HDMI_CONVERTER: + case DISPLAY_DONGLE_DP_HDMI_DONGLE: + return DRM_MODE_SUBCONNECTOR_HDMIA; + case DISPLAY_DONGLE_DP_HDMI_MISMATCHED_DONGLE: + default: + return DRM_MODE_SUBCONNECTOR_Unknown; + } +} + +static void update_subconnector_property(struct amdgpu_dm_connector *aconnector) +{ + struct dc_link *link = aconnector->dc_link; + struct drm_connector *connector = &aconnector->base; + enum drm_mode_subconnector subconnector = DRM_MODE_SUBCONNECTOR_Unknown; + + if (connector->connector_type != DRM_MODE_CONNECTOR_DisplayPort) + return; + + if (aconnector->dc_sink) + subconnector = get_subconnector_type(link); + + drm_object_property_set_value(&connector->base, + connector->dev->mode_config.dp_subconnector_property, + subconnector); +} + +static int amdgpu_dm_connector_get_modes(struct drm_connector *connector); + +static void amdgpu_dm_fbc_init(struct drm_connector *connector) +{ + struct amdgpu_device *adev = drm_to_adev(connector->dev); + struct dm_compressor_info *compressor = &adev->dm.compressor; + struct amdgpu_dm_connector *aconn = to_amdgpu_dm_connector(connector); + struct drm_display_mode *mode; + unsigned long max_size = 0; + + if (adev->dm.dc->fbc_compressor == NULL) + return; + + if (aconn->dc_link->connector_signal != SIGNAL_TYPE_EDP) + return; + + if (compressor->bo_ptr) + return; + + + list_for_each_entry(mode, &connector->modes, head) { + if (max_size < (unsigned long) mode->htotal * mode->vtotal) + max_size = (unsigned long) mode->htotal * mode->vtotal; + } + + if (max_size) { + int r = amdgpu_bo_create_kernel(adev, max_size * 4, PAGE_SIZE, + AMDGPU_GEM_DOMAIN_GTT, &compressor->bo_ptr, + &compressor->gpu_addr, &compressor->cpu_addr); + + if (r) + drm_err(adev_to_drm(adev), "DM: Failed to initialize FBC\n"); + else { + adev->dm.dc->ctx->fbc_gpu_addr = compressor->gpu_addr; + drm_info(adev_to_drm(adev), "DM: FBC alloc %lu\n", max_size*4); + } + + } + +} + + +int amdgpu_dm_detect_mst_link_for_all_connectors(struct drm_device *dev) +{ + struct amdgpu_dm_connector *aconnector; + struct drm_connector *connector; + struct drm_connector_list_iter iter; + int ret = 0; + + drm_connector_list_iter_begin(dev, &iter); + drm_for_each_connector_iter(connector, &iter) { + + if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) + continue; + + aconnector = to_amdgpu_dm_connector(connector); + if (aconnector->dc_link->type == dc_connection_mst_branch && + aconnector->mst_mgr.aux) { + drm_dbg_kms(dev, "DM_MST: starting TM on aconnector: %p [id: %d]\n", + aconnector, + aconnector->base.base.id); + + ret = drm_dp_mst_topology_mgr_set_mst(&aconnector->mst_mgr, true); + if (ret < 0) { + drm_err(dev, "DM_MST: Failed to start MST\n"); + aconnector->dc_link->type = + dc_connection_single; + ret = dm_helpers_dp_mst_stop_top_mgr(aconnector->dc_link->ctx, + aconnector->dc_link); + break; + } + } + } + drm_connector_list_iter_end(&iter); + + return ret; +} + +static void hdmi_cec_unset_edid(struct amdgpu_dm_connector *aconnector) +{ + struct cec_notifier *n = aconnector->notifier; + + if (!n) + return; + + cec_notifier_phys_addr_invalidate(n); +} + +void amdgpu_dm_hdmi_cec_set_edid(struct amdgpu_dm_connector *aconnector) +{ + struct drm_connector *connector = &aconnector->base; + struct cec_notifier *n = aconnector->notifier; + + if (!n) + return; + + cec_notifier_set_phys_addr(n, + connector->display_info.source_physical_address); +} + +void amdgpu_dm_s3_handle_hdmi_cec(struct drm_device *ddev, bool suspend) +{ + struct amdgpu_dm_connector *aconnector; + struct drm_connector *connector; + struct drm_connector_list_iter conn_iter; + + drm_connector_list_iter_begin(ddev, &conn_iter); + drm_for_each_connector_iter(connector, &conn_iter) { + if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) + continue; + + aconnector = to_amdgpu_dm_connector(connector); + if (suspend) + hdmi_cec_unset_edid(aconnector); + else + amdgpu_dm_hdmi_cec_set_edid(aconnector); + } + drm_connector_list_iter_end(&conn_iter); +} + + +struct drm_connector * +amdgpu_dm_find_first_crtc_matching_connector(struct drm_atomic_commit *state, + struct drm_crtc *crtc) +{ + u32 i; + struct drm_connector_state *new_con_state; + struct drm_connector *connector; + struct drm_crtc *crtc_from_state; + + for_each_new_connector_in_state(state, connector, new_con_state, i) { + crtc_from_state = new_con_state->crtc; + + if (crtc_from_state == crtc) + return connector; + } + + return NULL; +} + +static void dm_set_panel_type(struct amdgpu_dm_connector *aconnector) +{ + struct drm_connector *connector = &aconnector->base; + struct drm_display_info *display_info = &connector->display_info; + struct dc_link *link = aconnector->dc_link; + struct amdgpu_device *adev; + + adev = drm_to_adev(connector->dev); + + link->panel_type = PANEL_TYPE_NONE; + + switch (display_info->amd_vsdb.panel_type) { + case AMD_VSDB_PANEL_TYPE_OLED: + link->panel_type = PANEL_TYPE_OLED; + break; + case AMD_VSDB_PANEL_TYPE_MINILED: + link->panel_type = PANEL_TYPE_MINILED; + break; + } + + /* If VSDB didn't determine panel type, check DPCD ext caps */ + if (link->panel_type == PANEL_TYPE_NONE) { + if (link->dpcd_sink_ext_caps.bits.miniled == 1) + link->panel_type = PANEL_TYPE_MINILED; + if (link->dpcd_sink_ext_caps.bits.oled == 1) + link->panel_type = PANEL_TYPE_OLED; + } + + /* + * TODO: get panel type from DID2 that has device technology field + * to specify if it's OLED or not. But we need to wait for DID2 + * support in DC and EDID parser to be able to use it here. + */ + + if (link->panel_type == PANEL_TYPE_NONE) { + struct drm_amd_vsdb_info *vsdb = &display_info->amd_vsdb; + u32 lum1_max = vsdb->luminance_range1.max_luminance; + u32 lum2_max = vsdb->luminance_range2.max_luminance; + + if (vsdb->version && link->local_sink && + link->local_sink->edid_caps.manufacturer_id == + DDC_MANUFACTURERNAME_SAMSUNG && + lum1_max >= ((lum2_max * 3) / 2)) + link->panel_type = PANEL_TYPE_MINILED; + } + + if (link->panel_type == PANEL_TYPE_OLED) + drm_object_property_set_value(&connector->base, + adev_to_drm(adev)->mode_config.panel_type_property, + DRM_MODE_PANEL_TYPE_OLED); + else + drm_object_property_set_value(&connector->base, + adev_to_drm(adev)->mode_config.panel_type_property, + DRM_MODE_PANEL_TYPE_UNKNOWN); + + drm_dbg_kms(aconnector->base.dev, "Panel type: %d\n", link->panel_type); +} + +DEFINE_FREE(sink_release, struct dc_sink *, if (_T) dc_sink_release(_T)) + +void amdgpu_dm_update_connector_after_detect( + struct amdgpu_dm_connector *aconnector) +{ + struct drm_connector *connector = &aconnector->base; + struct dc_sink *sink __free(sink_release) = NULL; + struct drm_device *dev = connector->dev; + + /* MST handled by drm_mst framework */ + if (aconnector->mst_mgr.mst_state == true) + return; + + sink = aconnector->dc_link->local_sink; + if (sink) + dc_sink_retain(sink); + + /* + * Edid mgmt connector gets first update only in mode_valid hook and then + * the connector sink is set to either fake or physical sink depends on link status. + * Skip if already done during boot. + */ + if (aconnector->base.force != DRM_FORCE_UNSPECIFIED + && aconnector->dc_em_sink) { + + /* + * For S3 resume with headless use eml_sink to fake stream + * because on resume connector->sink is set to NULL + */ + guard(mutex)(&dev->mode_config.mutex); + + if (sink) { + if (aconnector->dc_sink) { + amdgpu_dm_update_freesync_caps(connector, NULL, true); + /* + * retain and release below are used to + * bump up refcount for sink because the link doesn't point + * to it anymore after disconnect, so on next crtc to connector + * reshuffle by UMD we will get into unwanted dc_sink release + */ + dc_sink_release(aconnector->dc_sink); + } + aconnector->dc_sink = sink; + dc_sink_retain(aconnector->dc_sink); + amdgpu_dm_update_freesync_caps(connector, + aconnector->drm_edid, true); + } else { + amdgpu_dm_update_freesync_caps(connector, NULL, true); + if (!aconnector->dc_sink) { + aconnector->dc_sink = aconnector->dc_em_sink; + dc_sink_retain(aconnector->dc_sink); + } + } + + return; + } + + /* + * TODO: temporary guard to look for proper fix + * if this sink is MST sink, we should not do anything + */ + if (sink && sink->sink_signal == SIGNAL_TYPE_DISPLAY_PORT_MST) + return; + + if (aconnector->dc_sink == sink) { + /* + * We got a DP short pulse (Link Loss, DP CTS, etc...). + * Do nothing!! + */ + drm_dbg_kms(dev, "DCHPD: connector_id=%d: dc_sink didn't change.\n", + aconnector->connector_id); + return; + } + + drm_dbg_kms(dev, "DCHPD: connector_id=%d: Old sink=%p New sink=%p\n", + aconnector->connector_id, aconnector->dc_sink, sink); + + /* When polling, DRM has already locked the mutex for us. */ + if (!drm_kms_helper_is_poll_worker()) + mutex_lock(&dev->mode_config.mutex); + + /* + * 1. Update status of the drm connector + * 2. Send an event and let userspace tell us what to do + */ + if (sink) { + /* + * TODO: check if we still need the S3 mode update workaround. + * If yes, put it here. + */ + if (aconnector->dc_sink) { + amdgpu_dm_update_freesync_caps(connector, NULL, true); + dc_sink_release(aconnector->dc_sink); + } + + aconnector->dc_sink = sink; + dc_sink_retain(aconnector->dc_sink); + drm_edid_free(aconnector->drm_edid); + aconnector->drm_edid = NULL; + if (sink->dc_edid.length == 0) { + hdmi_cec_unset_edid(aconnector); + if (aconnector->dc_link->aux_mode) + drm_dp_cec_unset_edid(&aconnector->dm_dp_aux.aux); + } else { + const struct edid *edid = (const struct edid *)sink->dc_edid.raw_edid; + + aconnector->drm_edid = drm_edid_alloc(edid, sink->dc_edid.length); + drm_edid_connector_update(connector, aconnector->drm_edid); + + amdgpu_dm_hdmi_cec_set_edid(aconnector); + if (aconnector->dc_link->aux_mode) + drm_dp_cec_attach(&aconnector->dm_dp_aux.aux, + connector->display_info.source_physical_address); + } + + if (!aconnector->timing_requested) { + aconnector->timing_requested = + kzalloc_obj(struct dc_crtc_timing); + if (!aconnector->timing_requested) + drm_err(dev, + "failed to create aconnector->requested_timing\n"); + } + + amdgpu_dm_update_freesync_caps(connector, aconnector->drm_edid, true); + amdgpu_dm_update_connector_ext_caps(aconnector); + dm_set_panel_type(aconnector); + } else { + hdmi_cec_unset_edid(aconnector); + drm_dp_cec_unset_edid(&aconnector->dm_dp_aux.aux); + amdgpu_dm_update_freesync_caps(connector, NULL, true); + aconnector->num_modes = 0; + dc_sink_release(aconnector->dc_sink); + aconnector->dc_sink = NULL; + drm_edid_free(aconnector->drm_edid); + aconnector->drm_edid = NULL; + kfree(aconnector->timing_requested); + aconnector->timing_requested = NULL; + /* Set CP to DESIRED if it was ENABLED, so we can re-enable it again on hotplug */ + if (connector->state->content_protection == DRM_MODE_CONTENT_PROTECTION_ENABLED) + connector->state->content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; + } + + update_subconnector_property(aconnector); + + /* When polling, the mutex will be unlocked for us by DRM. */ + if (!drm_kms_helper_is_poll_worker()) + mutex_unlock(&dev->mode_config.mutex); +} + +enum dc_color_depth +amdgpu_dm_convert_color_depth_from_display_info(const struct drm_connector *connector, + bool is_y420, int requested_bpc) +{ + u8 bpc; + + if (is_y420) { + bpc = 8; + + /* Cap display bpc based on HDMI 2.0 HF-VSDB */ + if (connector->display_info.hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_48) + bpc = 16; + else if (connector->display_info.hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_36) + bpc = 12; + else if (connector->display_info.hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_30) + bpc = 10; + } else { + bpc = (uint8_t)connector->display_info.bpc; + /* Assume 8 bpc by default if no bpc is specified. */ + bpc = bpc ? bpc : 8; + } + + if (requested_bpc > 0) { + /* + * Cap display bpc based on the user requested value. + * + * The value for state->max_bpc may not correctly updated + * depending on when the connector gets added to the state + * or if this was called outside of atomic check, so it + * can't be used directly. + */ + bpc = min_t(u8, bpc, requested_bpc); + + /* Round down to the nearest even number. */ + bpc = bpc - (bpc & 1); + } + + switch (bpc) { + case 0: + /* + * Temporary Work around, DRM doesn't parse color depth for + * EDID revision before 1.4 + * TODO: Fix edid parsing + */ + return COLOR_DEPTH_888; + case 6: + return COLOR_DEPTH_666; + case 8: + return COLOR_DEPTH_888; + case 10: + return COLOR_DEPTH_101010; + case 12: + return COLOR_DEPTH_121212; + case 14: + return COLOR_DEPTH_141414; + case 16: + return COLOR_DEPTH_161616; + default: + return COLOR_DEPTH_UNDEFINED; + } +} + +static enum dc_aspect_ratio +get_aspect_ratio(const struct drm_display_mode *mode_in) +{ + /* 1-1 mapping, since both enums follow the HDMI spec. */ + return (enum dc_aspect_ratio) mode_in->picture_aspect_ratio; +} + +enum dc_color_space +amdgpu_dm_get_output_color_space(const struct dc_crtc_timing *dc_crtc_timing, + const struct drm_connector_state *connector_state) +{ + enum dc_color_space color_space = COLOR_SPACE_SRGB; + + switch (connector_state->colorspace) { + case DRM_MODE_COLORIMETRY_BT601_YCC: + if (dc_crtc_timing->flags.Y_ONLY) + color_space = COLOR_SPACE_YCBCR601_LIMITED; + else + color_space = COLOR_SPACE_YCBCR601; + break; + case DRM_MODE_COLORIMETRY_BT709_YCC: + if (dc_crtc_timing->flags.Y_ONLY) + color_space = COLOR_SPACE_YCBCR709_LIMITED; + else + color_space = COLOR_SPACE_YCBCR709; + break; + case DRM_MODE_COLORIMETRY_OPRGB: + color_space = COLOR_SPACE_ADOBERGB; + break; + case DRM_MODE_COLORIMETRY_BT2020_RGB: + case DRM_MODE_COLORIMETRY_BT2020_YCC: + if (dc_crtc_timing->pixel_encoding == PIXEL_ENCODING_RGB) + color_space = COLOR_SPACE_2020_RGB_FULLRANGE; + else + color_space = COLOR_SPACE_2020_YCBCR_LIMITED; + break; + case DRM_MODE_COLORIMETRY_DEFAULT: /* ITU601 */ + default: + if (dc_crtc_timing->pixel_encoding == PIXEL_ENCODING_RGB) { + color_space = COLOR_SPACE_SRGB; + if (connector_state->hdmi.broadcast_rgb == DRM_HDMI_BROADCAST_RGB_LIMITED) + color_space = COLOR_SPACE_SRGB_LIMITED; + /* + * 27030khz is the separation point between HDTV and SDTV + * according to HDMI spec, we use YCbCr709 and YCbCr601 + * respectively + */ + } else if (dc_crtc_timing->pix_clk_100hz > 270300) { + if (dc_crtc_timing->flags.Y_ONLY) + color_space = + COLOR_SPACE_YCBCR709_LIMITED; + else + color_space = COLOR_SPACE_YCBCR709; + } else { + if (dc_crtc_timing->flags.Y_ONLY) + color_space = + COLOR_SPACE_YCBCR601_LIMITED; + else + color_space = COLOR_SPACE_YCBCR601; + } + break; + } + + return color_space; +} + +static enum display_content_type +get_output_content_type(const struct drm_connector_state *connector_state) +{ + switch (connector_state->content_type) { + default: + case DRM_MODE_CONTENT_TYPE_NO_DATA: + return DISPLAY_CONTENT_TYPE_NO_DATA; + case DRM_MODE_CONTENT_TYPE_GRAPHICS: + return DISPLAY_CONTENT_TYPE_GRAPHICS; + case DRM_MODE_CONTENT_TYPE_PHOTO: + return DISPLAY_CONTENT_TYPE_PHOTO; + case DRM_MODE_CONTENT_TYPE_CINEMA: + return DISPLAY_CONTENT_TYPE_CINEMA; + case DRM_MODE_CONTENT_TYPE_GAME: + return DISPLAY_CONTENT_TYPE_GAME; + } +} + +static bool adjust_colour_depth_from_display_info( + struct dc_crtc_timing *timing_out, + const struct drm_display_info *info) +{ + enum dc_color_depth depth = timing_out->display_color_depth; + int normalized_clk; + + do { + normalized_clk = timing_out->pix_clk_100hz / 10; + /* YCbCr 4:2:0 requires additional adjustment of 1/2 */ + if (timing_out->pixel_encoding == PIXEL_ENCODING_YCBCR420) + normalized_clk /= 2; + /* Adjusting pix clock following on HDMI spec based on colour depth */ + switch (depth) { + case COLOR_DEPTH_888: + break; + case COLOR_DEPTH_101010: + normalized_clk = (normalized_clk * 30) / 24; + break; + case COLOR_DEPTH_121212: + normalized_clk = (normalized_clk * 36) / 24; + break; + case COLOR_DEPTH_161616: + normalized_clk = (normalized_clk * 48) / 24; + break; + default: + /* The above depths are the only ones valid for HDMI. */ + return false; + } + if (normalized_clk <= info->max_tmds_clock) { + timing_out->display_color_depth = depth; + return true; + } + } while (--depth > COLOR_DEPTH_666); + return false; +} + +static void fill_stream_properties_from_drm_display_mode( + struct dc_stream_state *stream, + const struct drm_display_mode *mode_in, + const struct drm_connector *connector, + const struct drm_connector_state *connector_state, + const struct dc_stream_state *old_stream, + int requested_bpc) +{ + struct dc_crtc_timing *timing_out = &stream->timing; + const struct drm_display_info *info = &connector->display_info; + struct amdgpu_dm_connector *aconnector = NULL; + struct hdmi_vendor_infoframe hv_frame; + struct hdmi_avi_infoframe avi_frame; + ssize_t err; + + if (connector->connector_type != DRM_MODE_CONNECTOR_WRITEBACK) + aconnector = to_amdgpu_dm_connector(connector); + + memset(&hv_frame, 0, sizeof(hv_frame)); + memset(&avi_frame, 0, sizeof(avi_frame)); + + timing_out->h_border_left = 0; + timing_out->h_border_right = 0; + timing_out->v_border_top = 0; + timing_out->v_border_bottom = 0; + /* TODO: un-hardcode */ + if (drm_mode_is_420_only(info, mode_in) + && (stream->signal == SIGNAL_TYPE_HDMI_TYPE_A || + stream->signal == SIGNAL_TYPE_HDMI_FRL) + && aconnector + && aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR420) + timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR420; + else if (drm_mode_is_420_also(info, mode_in) + && aconnector + && (aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR420 + || aconnector->force_yuv420_output)) + timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR420; + else if ((connector->display_info.color_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422)) + && aconnector + && (aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR422 + || aconnector->force_yuv422_output)) + timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR422; + else if ((connector->display_info.color_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444)) + && (stream->signal == SIGNAL_TYPE_HDMI_TYPE_A || + stream->signal == SIGNAL_TYPE_HDMI_FRL) + && aconnector + && aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR444) + timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR444; + else + timing_out->pixel_encoding = PIXEL_ENCODING_RGB; + + timing_out->timing_3d_format = TIMING_3D_FORMAT_NONE; + timing_out->display_color_depth = amdgpu_dm_convert_color_depth_from_display_info( + connector, + (timing_out->pixel_encoding == PIXEL_ENCODING_YCBCR420), + requested_bpc); + timing_out->scan_type = SCANNING_TYPE_NODATA; + timing_out->hdmi_vic = 0; + + if (old_stream) { + timing_out->vic = old_stream->timing.vic; + timing_out->flags.HSYNC_POSITIVE_POLARITY = old_stream->timing.flags.HSYNC_POSITIVE_POLARITY; + timing_out->flags.VSYNC_POSITIVE_POLARITY = old_stream->timing.flags.VSYNC_POSITIVE_POLARITY; + } else { + timing_out->vic = drm_match_cea_mode(mode_in); + if (mode_in->flags & DRM_MODE_FLAG_PHSYNC) + timing_out->flags.HSYNC_POSITIVE_POLARITY = 1; + if (mode_in->flags & DRM_MODE_FLAG_PVSYNC) + timing_out->flags.VSYNC_POSITIVE_POLARITY = 1; + } + + if (stream->signal == SIGNAL_TYPE_HDMI_TYPE_A || + stream->signal == SIGNAL_TYPE_HDMI_FRL) { + err = drm_hdmi_avi_infoframe_from_display_mode(&avi_frame, + (struct drm_connector *)connector, + mode_in); + if (err < 0) + drm_warn_once(connector->dev, "Failed to setup avi infoframe on connector %s: %zd\n", + connector->name, err); + timing_out->vic = avi_frame.video_code; + err = drm_hdmi_vendor_infoframe_from_display_mode(&hv_frame, + (struct drm_connector *)connector, + mode_in); + if (err < 0) + drm_warn_once(connector->dev, "Failed to setup vendor infoframe on connector %s: %zd\n", + connector->name, err); + timing_out->hdmi_vic = hv_frame.vic; + } + + if (aconnector && amdgpu_dm_is_freesync_video_mode(mode_in, aconnector)) { + timing_out->h_addressable = mode_in->hdisplay; + timing_out->h_total = mode_in->htotal; + timing_out->h_sync_width = mode_in->hsync_end - mode_in->hsync_start; + timing_out->h_front_porch = mode_in->hsync_start - mode_in->hdisplay; + timing_out->v_total = mode_in->vtotal; + timing_out->v_addressable = mode_in->vdisplay; + timing_out->v_front_porch = mode_in->vsync_start - mode_in->vdisplay; + timing_out->v_sync_width = mode_in->vsync_end - mode_in->vsync_start; + timing_out->pix_clk_100hz = mode_in->clock * 10; + } else { + timing_out->h_addressable = mode_in->crtc_hdisplay; + timing_out->h_total = mode_in->crtc_htotal; + timing_out->h_sync_width = mode_in->crtc_hsync_end - mode_in->crtc_hsync_start; + timing_out->h_front_porch = mode_in->crtc_hsync_start - mode_in->crtc_hdisplay; + timing_out->v_total = mode_in->crtc_vtotal; + timing_out->v_addressable = mode_in->crtc_vdisplay; + timing_out->v_front_porch = mode_in->crtc_vsync_start - mode_in->crtc_vdisplay; + timing_out->v_sync_width = mode_in->crtc_vsync_end - mode_in->crtc_vsync_start; + timing_out->pix_clk_100hz = mode_in->crtc_clock * 10; + } + + timing_out->aspect_ratio = get_aspect_ratio(mode_in); + + stream->out_transfer_func.type = TF_TYPE_PREDEFINED; + stream->out_transfer_func.tf = TRANSFER_FUNCTION_SRGB; + if (stream->signal == SIGNAL_TYPE_HDMI_TYPE_A) { + if (!adjust_colour_depth_from_display_info(timing_out, info) && + drm_mode_is_420_also(info, mode_in) && + timing_out->pixel_encoding != PIXEL_ENCODING_YCBCR420) { + timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR420; + adjust_colour_depth_from_display_info(timing_out, info); + } + } + + stream->output_color_space = amdgpu_dm_get_output_color_space(timing_out, connector_state); + stream->content_type = get_output_content_type(connector_state); +} + +static void +copy_crtc_timing_for_drm_display_mode(const struct drm_display_mode *src_mode, + struct drm_display_mode *dst_mode) +{ + dst_mode->crtc_hdisplay = src_mode->crtc_hdisplay; + dst_mode->crtc_vdisplay = src_mode->crtc_vdisplay; + dst_mode->crtc_clock = src_mode->crtc_clock; + dst_mode->crtc_hblank_start = src_mode->crtc_hblank_start; + dst_mode->crtc_hblank_end = src_mode->crtc_hblank_end; + dst_mode->crtc_hsync_start = src_mode->crtc_hsync_start; + dst_mode->crtc_hsync_end = src_mode->crtc_hsync_end; + dst_mode->crtc_htotal = src_mode->crtc_htotal; + dst_mode->crtc_hskew = src_mode->crtc_hskew; + dst_mode->crtc_vblank_start = src_mode->crtc_vblank_start; + dst_mode->crtc_vblank_end = src_mode->crtc_vblank_end; + dst_mode->crtc_vsync_start = src_mode->crtc_vsync_start; + dst_mode->crtc_vsync_end = src_mode->crtc_vsync_end; + dst_mode->crtc_vtotal = src_mode->crtc_vtotal; +} + +static void +decide_crtc_timing_for_drm_display_mode(struct drm_display_mode *drm_mode, + const struct drm_display_mode *native_mode, + bool scale_enabled) +{ + if (scale_enabled || ( + native_mode->clock == drm_mode->clock && + native_mode->htotal == drm_mode->htotal && + native_mode->vtotal == drm_mode->vtotal)) { + if (native_mode->crtc_clock) + copy_crtc_timing_for_drm_display_mode(native_mode, drm_mode); + } else { + /* no scaling nor amdgpu inserted, no need to patch */ + } +} + +static struct dc_sink * +create_fake_sink(struct drm_device *dev, struct dc_link *link) +{ + struct dc_sink_init_data sink_init_data = { 0 }; + struct dc_sink *sink = NULL; + + sink_init_data.link = link; + sink_init_data.sink_signal = link->connector_signal; + + sink = dc_sink_create(&sink_init_data); + if (!sink) { + drm_err(dev, "Failed to create sink!\n"); + return NULL; + } + sink->sink_signal = SIGNAL_TYPE_VIRTUAL; + + return sink; +} + +/** + * DOC: FreeSync Video + * + * When a userspace application wants to play a video, the content follows a + * standard format definition that usually specifies the FPS for that format. + * The below list illustrates some video format and the expected FPS, + * respectively: + * + * - TV/NTSC (23.976 FPS) + * - Cinema (24 FPS) + * - TV/PAL (25 FPS) + * - TV/NTSC (29.97 FPS) + * - TV/NTSC (30 FPS) + * - Cinema HFR (48 FPS) + * - TV/PAL (50 FPS) + * - Commonly used (60 FPS) + * - Multiples of 24 (48,72,96 FPS) + * + * The list of standards video format is not huge and can be added to the + * connector modeset list beforehand. With that, userspace can leverage + * FreeSync to extends the front porch in order to attain the target refresh + * rate. Such a switch will happen seamlessly, without screen blanking or + * reprogramming of the output in any other way. If the userspace requests a + * modesetting change compatible with FreeSync modes that only differ in the + * refresh rate, DC will skip the full update and avoid blink during the + * transition. For example, the video player can change the modesetting from + * 60Hz to 30Hz for playing TV/NTSC content when it goes full screen without + * causing any display blink. This same concept can be applied to a mode + * setting change. + */ +struct drm_display_mode * +amdgpu_dm_get_highest_refresh_rate_mode(struct amdgpu_dm_connector *aconnector, + bool use_probed_modes) +{ + struct drm_display_mode *m, *m_pref = NULL; + u16 current_refresh, highest_refresh; + struct list_head *list_head = use_probed_modes ? + &aconnector->base.probed_modes : + &aconnector->base.modes; + + if (aconnector->base.connector_type == DRM_MODE_CONNECTOR_WRITEBACK) + return NULL; + + if (aconnector->freesync_vid_base.clock != 0) + return &aconnector->freesync_vid_base; + + /* Find the preferred mode */ + list_for_each_entry(m, list_head, head) { + if (m->type & DRM_MODE_TYPE_PREFERRED) { + m_pref = m; + break; + } + } + + if (!m_pref) { + /* Probably an EDID with no preferred mode. Fallback to first entry */ + m_pref = list_first_entry_or_null( + &aconnector->base.modes, struct drm_display_mode, head); + if (!m_pref) { + drm_dbg_driver(aconnector->base.dev, "No preferred mode found in EDID\n"); + return NULL; + } + } + + highest_refresh = drm_mode_vrefresh(m_pref); + + /* + * Find the mode with highest refresh rate with same resolution. + * For some monitors, preferred mode is not the mode with highest + * supported refresh rate. + */ + list_for_each_entry(m, list_head, head) { + current_refresh = drm_mode_vrefresh(m); + + if (m->hdisplay == m_pref->hdisplay && + m->vdisplay == m_pref->vdisplay && + highest_refresh < current_refresh) { + highest_refresh = current_refresh; + m_pref = m; + } + } + + drm_mode_copy(&aconnector->freesync_vid_base, m_pref); + return m_pref; +} + +bool amdgpu_dm_is_freesync_video_mode(const struct drm_display_mode *mode, + struct amdgpu_dm_connector *aconnector) +{ + struct drm_display_mode *high_mode; + int timing_diff; + + high_mode = amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false); + if (!high_mode || !mode) + return false; + + timing_diff = high_mode->vtotal - mode->vtotal; + + if (high_mode->clock == 0 || high_mode->clock != mode->clock || + high_mode->hdisplay != mode->hdisplay || + high_mode->vdisplay != mode->vdisplay || + high_mode->hsync_start != mode->hsync_start || + high_mode->hsync_end != mode->hsync_end || + high_mode->htotal != mode->htotal || + high_mode->hskew != mode->hskew || + high_mode->vscan != mode->vscan || + high_mode->vsync_start - mode->vsync_start != timing_diff || + high_mode->vsync_end - mode->vsync_end != timing_diff) + return false; + else + return true; +} + +#if defined(CONFIG_DRM_AMD_DC_FP) +static void update_dsc_caps(struct amdgpu_dm_connector *aconnector, + struct dc_sink *sink, struct dc_stream_state *stream, + struct dsc_dec_dpcd_caps *dsc_caps) +{ + stream->timing.flags.DSC = 0; + dsc_caps->is_dsc_supported = false; + + if (aconnector->dc_link && (sink->sink_signal == SIGNAL_TYPE_DISPLAY_PORT || + sink->sink_signal == SIGNAL_TYPE_EDP)) { + if (sink->link->dpcd_caps.dongle_type == DISPLAY_DONGLE_NONE) + dc_dsc_parse_dsc_dpcd(aconnector->dc_link->ctx->dc, + aconnector->dc_link->dpcd_caps.dsc_caps.dsc_basic_caps.raw, + aconnector->dc_link->dpcd_caps.dsc_caps.dsc_branch_decoder_caps.raw, + dsc_caps); + else if (sink->link->dpcd_caps.dongle_type == DISPLAY_DONGLE_DP_HDMI_CONVERTER) { + if (aconnector->dc_link->dpcd_caps.dsc_caps.dsc_basic_caps.fields.dsc_support.DSC_PASSTHROUGH_SUPPORT && + !aconnector->dsc_settings.dsc_force_disable_passthrough && + aconnector->dc_link->dpcd_caps.dongle_caps.dp_hdmi_frl_max_link_bw_in_kbps > 0 && + sink->edid_caps.frl_dsc_support && + sink->edid_caps.max_frl_rate > 0 && + sink->edid_caps.frl_dsc_max_frl_rate > 0) + dc_dsc_parse_dsc_edid(aconnector->dc_link->ctx->dc, &sink->edid_caps, dsc_caps); + else + dc_dsc_parse_dsc_dpcd(aconnector->dc_link->ctx->dc, + aconnector->dc_link->dpcd_caps.dsc_caps.dsc_basic_caps.raw, + aconnector->dc_link->dpcd_caps.dsc_caps.dsc_branch_decoder_caps.raw, + dsc_caps); + } + } else if (aconnector->dc_link && sink->sink_signal == SIGNAL_TYPE_HDMI_FRL) { + if (sink->edid_caps.frl_dsc_support && + sink->edid_caps.max_frl_rate > 0 && + sink->edid_caps.frl_dsc_max_frl_rate > 0) + dc_dsc_parse_dsc_edid(aconnector->dc_link->ctx->dc, &sink->edid_caps, dsc_caps); + } +} + +static void apply_dsc_policy_for_edp(struct amdgpu_dm_connector *aconnector, + struct dc_sink *sink, struct dc_stream_state *stream, + struct dsc_dec_dpcd_caps *dsc_caps, + uint32_t max_dsc_target_bpp_limit_override) +{ + const struct dc_link_settings *verified_link_cap = NULL; + u32 link_bw_in_kbps; + u32 edp_min_bpp_x16, edp_max_bpp_x16; + struct dc *dc = sink->ctx->dc; + struct dc_dsc_bw_range bw_range = {0}; + struct dc_dsc_config dsc_cfg = {0}; + struct dc_dsc_config_options dsc_options = {0}; + + dc_dsc_get_default_config_option(dc, &dsc_options); + dsc_options.max_target_bpp_limit_override_x16 = max_dsc_target_bpp_limit_override * 16; + + verified_link_cap = dc_link_get_link_cap(stream->link); + link_bw_in_kbps = dc_link_bandwidth_kbps(stream->link, verified_link_cap); + edp_min_bpp_x16 = 8 * 16; + edp_max_bpp_x16 = 8 * 16; + + if (edp_max_bpp_x16 > dsc_caps->edp_max_bits_per_pixel) + edp_max_bpp_x16 = dsc_caps->edp_max_bits_per_pixel; + + if (edp_max_bpp_x16 < edp_min_bpp_x16) + edp_min_bpp_x16 = edp_max_bpp_x16; + + if (dc_dsc_compute_bandwidth_range(dc->res_pool->dscs[0], + dc->debug.dsc_min_slice_height_override, + edp_min_bpp_x16, edp_max_bpp_x16, + dsc_caps, + &stream->timing, + dc_link_get_highest_encoding_format(aconnector->dc_link), + &bw_range)) { + + if (bw_range.max_kbps < link_bw_in_kbps) { + if (dc_dsc_compute_config(dc->res_pool->dscs[0], + dsc_caps, + &dsc_options, + 0, + &stream->timing, + dc_link_get_highest_encoding_format(aconnector->dc_link), + &dsc_cfg)) { + stream->timing.dsc_cfg = dsc_cfg; + stream->timing.flags.DSC = 1; + stream->timing.dsc_cfg.bits_per_pixel = edp_max_bpp_x16; + } + return; + } + } + + if (dc_dsc_compute_config(dc->res_pool->dscs[0], + dsc_caps, + &dsc_options, + link_bw_in_kbps, + &stream->timing, + dc_link_get_highest_encoding_format(aconnector->dc_link), + &dsc_cfg)) { + stream->timing.dsc_cfg = dsc_cfg; + stream->timing.flags.DSC = 1; + } +} + +static void apply_dsc_policy_for_stream(struct amdgpu_dm_connector *aconnector, + struct dc_sink *sink, struct dc_stream_state *stream, + struct dsc_dec_dpcd_caps *dsc_caps) +{ + struct drm_connector *drm_connector = &aconnector->base; + u32 link_bandwidth_kbps; + struct dc *dc = sink->ctx->dc; + const struct dc_hdmi_frl_link_settings *frl_verified_link_cap = NULL; + u32 converter_bw_in_kbps; + u32 sink_bw_in_kbps; + u32 dsc_sink_bw_in_kbps; + u32 max_supported_bw_in_kbps, timing_bw_in_kbps; + u32 dsc_max_supported_bw_in_kbps; + u32 max_dsc_target_bpp_limit_override = + drm_connector->display_info.max_dsc_bpp; + struct dc_dsc_config_options dsc_options = {0}; + + dc_dsc_get_default_config_option(dc, &dsc_options); + dsc_options.max_target_bpp_limit_override_x16 = max_dsc_target_bpp_limit_override * 16; + + link_bandwidth_kbps = dc_link_bandwidth_kbps(aconnector->dc_link, + dc_link_get_link_cap(aconnector->dc_link)); + + /* Set DSC policy according to dsc_clock_en */ + dc_dsc_policy_set_enable_dsc_when_not_needed( + aconnector->dsc_settings.dsc_force_enable == DSC_CLK_FORCE_ENABLE); + + if (sink->sink_signal == SIGNAL_TYPE_EDP && + !aconnector->dc_link->panel_config.dsc.disable_dsc_edp && + dc->caps.edp_dsc_support && aconnector->dsc_settings.dsc_force_enable != DSC_CLK_FORCE_DISABLE) { + + apply_dsc_policy_for_edp(aconnector, sink, stream, dsc_caps, max_dsc_target_bpp_limit_override); + + } else if (sink->sink_signal == SIGNAL_TYPE_DISPLAY_PORT) { + if (sink->link->dpcd_caps.dongle_type == DISPLAY_DONGLE_NONE) { + if (dc_dsc_compute_config(aconnector->dc_link->ctx->dc->res_pool->dscs[0], + dsc_caps, + &dsc_options, + link_bandwidth_kbps, + &stream->timing, + dc_link_get_highest_encoding_format(aconnector->dc_link), + &stream->timing.dsc_cfg)) { + stream->timing.flags.DSC = 1; + drm_dbg_driver(drm_connector->dev, "%s: SST_DSC [%s] DSC is selected from SST RX\n", + __func__, drm_connector->name); + } + } else if (sink->link->dpcd_caps.dongle_type == DISPLAY_DONGLE_DP_HDMI_CONVERTER) { + timing_bw_in_kbps = dc_bandwidth_in_kbps_from_timing(&stream->timing, + dc_link_get_highest_encoding_format(aconnector->dc_link)); + converter_bw_in_kbps = aconnector->dc_link->dpcd_caps.dongle_caps.dp_hdmi_frl_max_link_bw_in_kbps; + sink_bw_in_kbps = dc_link_bw_kbps_from_raw_frl_link_rate_data(dc, sink->edid_caps.max_frl_rate); + dsc_sink_bw_in_kbps = dc_link_bw_kbps_from_raw_frl_link_rate_data(dc, sink->edid_caps.frl_dsc_max_frl_rate); + + if (dsc_caps->is_frl) { + max_supported_bw_in_kbps = min(link_bandwidth_kbps, converter_bw_in_kbps); + max_supported_bw_in_kbps = min(max_supported_bw_in_kbps, sink_bw_in_kbps); + dsc_max_supported_bw_in_kbps = min(max_supported_bw_in_kbps, dsc_sink_bw_in_kbps); + } else { + max_supported_bw_in_kbps = link_bandwidth_kbps; + dsc_max_supported_bw_in_kbps = link_bandwidth_kbps; + } + + if (timing_bw_in_kbps > max_supported_bw_in_kbps && + max_supported_bw_in_kbps > 0 && + dsc_max_supported_bw_in_kbps > 0) + if (dc_dsc_compute_config(aconnector->dc_link->ctx->dc->res_pool->dscs[0], + dsc_caps, + &dsc_options, + dsc_max_supported_bw_in_kbps, + &stream->timing, + dc_link_get_highest_encoding_format(aconnector->dc_link), + &stream->timing.dsc_cfg)) { + stream->timing.flags.DSC = 1; + drm_dbg_driver(drm_connector->dev, "%s: SST_DSC [%s] DSC is selected from %s\n", + __func__, drm_connector->name, + (dsc_caps->is_frl == 1) ? "HDMI FRL RX" : "DP-HDMI PCON"); + } + } + } else if (aconnector->dc_link && sink->sink_signal == SIGNAL_TYPE_HDMI_FRL) { + struct dc_dsc_policy dsc_policy = {0}; + + frl_verified_link_cap = dc_link_get_frl_link_cap(stream->link); + if (frl_verified_link_cap->frl_link_rate != HDMI_FRL_LINK_RATE_DISABLE && + aconnector->dc_link->frl_flags.force_frl_dsc) { + dc_dsc_policy_set_enable_dsc_when_not_needed(true); + dc_dsc_get_policy_for_timing(&stream->timing, 0, &dsc_policy, dc_link_get_highest_encoding_format(stream->link)); + } + + timing_bw_in_kbps = dc_bandwidth_in_kbps_from_timing(&stream->timing, DC_LINK_ENCODING_HDMI_FRL); + link_bandwidth_kbps = dc_link_frl_bandwidth_kbps(stream->link, frl_verified_link_cap->frl_link_rate); + dsc_sink_bw_in_kbps = dc_link_bw_kbps_from_raw_frl_link_rate_data(dc, sink->edid_caps.frl_dsc_max_frl_rate); + + if ((timing_bw_in_kbps > link_bandwidth_kbps && dsc_sink_bw_in_kbps > 0) || + (dsc_policy.enable_dsc_when_not_needed || dsc_options.force_dsc_when_not_needed)) { + if (dc_dsc_compute_config(aconnector->dc_link->ctx->dc->res_pool->dscs[0], + dsc_caps, + &dsc_options, + dsc_sink_bw_in_kbps, + &stream->timing, + dc_link_get_highest_encoding_format(aconnector->dc_link), + &stream->timing.dsc_cfg)) { + stream->timing.flags.DSC = 1; + drm_dbg_driver(drm_connector->dev, "%s: HDMI_FRL_DSC [%s] DSC is selected from HDMI FRL RX\n", + __func__, drm_connector->name); + } + } + } + + /* Overwrite the stream flag if DSC is enabled through debugfs */ + if (aconnector->dsc_settings.dsc_force_enable == DSC_CLK_FORCE_ENABLE) + stream->timing.flags.DSC = 1; + + if (stream->timing.flags.DSC && aconnector->dsc_settings.dsc_num_slices_h) + stream->timing.dsc_cfg.num_slices_h = aconnector->dsc_settings.dsc_num_slices_h; + + if (stream->timing.flags.DSC && aconnector->dsc_settings.dsc_num_slices_v) + stream->timing.dsc_cfg.num_slices_v = aconnector->dsc_settings.dsc_num_slices_v; + + if (stream->timing.flags.DSC && aconnector->dsc_settings.dsc_bits_per_pixel) + stream->timing.dsc_cfg.bits_per_pixel = aconnector->dsc_settings.dsc_bits_per_pixel; +} +#endif + +static struct dc_stream_state * +create_stream_for_sink(struct drm_connector *connector, + const struct drm_display_mode *drm_mode, + const struct dm_connector_state *dm_state, + const struct dc_stream_state *old_stream, + int requested_bpc) +{ + struct drm_device *dev = connector->dev; + struct amdgpu_dm_connector *aconnector = NULL; + struct drm_display_mode *preferred_mode = NULL; + const struct drm_connector_state *con_state = &dm_state->base; + struct dc_stream_state *stream = NULL; + struct drm_display_mode mode; + struct drm_display_mode saved_mode; + struct drm_display_mode *freesync_mode = NULL; + bool native_mode_found = false; + bool recalculate_timing = false; + bool scale = dm_state->scaling != RMX_OFF; + int mode_refresh; + int preferred_refresh = 0; + enum color_transfer_func tf = TRANSFER_FUNC_UNKNOWN; +#if defined(CONFIG_DRM_AMD_DC_FP) + struct dsc_dec_dpcd_caps dsc_caps = {0}; +#endif + struct dc_link *link = NULL; + struct dc_sink *sink = NULL; + + drm_mode_init(&mode, drm_mode); + memset(&saved_mode, 0, sizeof(saved_mode)); + + if (connector->connector_type != DRM_MODE_CONNECTOR_WRITEBACK) { + aconnector = NULL; + aconnector = to_amdgpu_dm_connector(connector); + link = aconnector->dc_link; + } else { + struct drm_writeback_connector *wbcon = NULL; + struct amdgpu_dm_wb_connector *dm_wbcon = NULL; + + wbcon = drm_connector_to_writeback(connector); + dm_wbcon = to_amdgpu_dm_wb_connector(wbcon); + link = dm_wbcon->link; + } + + if (!aconnector || !aconnector->dc_sink) { + sink = create_fake_sink(dev, link); + if (!sink) + return stream; + + } else { + sink = aconnector->dc_sink; + dc_sink_retain(sink); + } + + stream = dc_create_stream_for_sink(sink); + + if (stream == NULL) { + drm_err(dev, "Failed to create stream for sink!\n"); + goto finish; + } + + /* We leave this NULL for writeback connectors */ + stream->dm_stream_context = aconnector; + + stream->timing.flags.LTE_340MCSC_SCRAMBLE = + connector->display_info.hdmi.scdc.scrambling.low_rates; + + list_for_each_entry(preferred_mode, &connector->modes, head) { + /* Search for preferred mode */ + if (preferred_mode->type & DRM_MODE_TYPE_PREFERRED) { + native_mode_found = true; + break; + } + } + if (!native_mode_found) + preferred_mode = list_first_entry_or_null( + &connector->modes, + struct drm_display_mode, + head); + + mode_refresh = drm_mode_vrefresh(&mode); + + if (preferred_mode == NULL) { + /* + * This may not be an error, the use case is when we have no + * usermode calls to reset and set mode upon hotplug. In this + * case, we call set mode ourselves to restore the previous mode + * and the modelist may not be filled in time. + */ + drm_dbg_driver(dev, "No preferred mode found\n"); + } else if (aconnector) { + recalculate_timing = amdgpu_freesync_vid_mode && + amdgpu_dm_is_freesync_video_mode(&mode, aconnector); + if (recalculate_timing) { + freesync_mode = amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false); + drm_mode_copy(&saved_mode, &mode); + saved_mode.picture_aspect_ratio = mode.picture_aspect_ratio; + drm_mode_copy(&mode, freesync_mode); + mode.picture_aspect_ratio = saved_mode.picture_aspect_ratio; + } else { + decide_crtc_timing_for_drm_display_mode( + &mode, preferred_mode, scale); + + preferred_refresh = drm_mode_vrefresh(preferred_mode); + } + } + + if (recalculate_timing) + drm_mode_set_crtcinfo(&saved_mode, 0); + + /* + * If scaling is enabled and refresh rate didn't change + * we copy the vic and polarities of the old timings + */ + if (!scale || mode_refresh != preferred_refresh) + fill_stream_properties_from_drm_display_mode( + stream, &mode, connector, con_state, NULL, + requested_bpc); + else + fill_stream_properties_from_drm_display_mode( + stream, &mode, connector, con_state, old_stream, + requested_bpc); + + /* The rest isn't needed for writeback connectors */ + if (!aconnector) + goto finish; + + if (aconnector->timing_changed) { + drm_dbg(aconnector->base.dev, + "overriding timing for automated test, bpc %d, changing to %d\n", + stream->timing.display_color_depth, + aconnector->timing_requested->display_color_depth); + stream->timing = *aconnector->timing_requested; + } + +#if defined(CONFIG_DRM_AMD_DC_FP) + /* SST DSC determination policy */ + update_dsc_caps(aconnector, sink, stream, &dsc_caps); + if (aconnector->dsc_settings.dsc_force_enable != DSC_CLK_FORCE_DISABLE && dsc_caps.is_dsc_supported) + apply_dsc_policy_for_stream(aconnector, sink, stream, &dsc_caps); +#endif + + amdgpu_dm_update_stream_scaling_settings(dev, &mode, dm_state, stream); + + amdgpu_dm_fill_audio_info( + &stream->audio_info, + connector, + sink); + + update_stream_signal(stream, sink); + + if (stream->signal == SIGNAL_TYPE_HDMI_TYPE_A || + stream->signal == SIGNAL_TYPE_HDMI_FRL) + mod_build_hf_vsif_infopacket(stream, &stream->vsp_infopacket, false, false); + + if (stream->signal == SIGNAL_TYPE_DISPLAY_PORT || + stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST || + stream->signal == SIGNAL_TYPE_EDP) { + const struct dc_edid_caps *edid_caps; + unsigned int disable_colorimetry = 0; + + if (aconnector->dc_sink) { + edid_caps = &aconnector->dc_sink->edid_caps; + disable_colorimetry = edid_caps->panel_patch.disable_colorimetry; + } + + /* + * should decide stream support vsc sdp colorimetry capability + * before building vsc info packet + */ + stream->use_vsc_sdp_for_colorimetry = stream->link->dpcd_caps.dpcd_rev.raw >= 0x14 && + stream->link->dpcd_caps.dprx_feature.bits.VSC_SDP_COLORIMETRY_SUPPORTED && + !disable_colorimetry; + + if (stream->out_transfer_func.tf == TRANSFER_FUNCTION_GAMMA22) + tf = TRANSFER_FUNC_GAMMA_22; + mod_build_vsc_infopacket(stream, &stream->vsc_infopacket, stream->output_color_space, tf); + aconnector->sr_skip_count = AMDGPU_DM_PSR_ENTRY_DELAY; + + } +finish: + dc_sink_release(sink); + + return stream; +} + +/** + * amdgpu_dm_connector_poll - Poll a connector to see if it's connected to a display + * @aconnector: DM connector to poll (owns @base drm_connector and @dc_link) + * @force: if true, force polling even when DAC load detection was used + * + * Used for connectors that don't support HPD (hotplug detection) to + * periodically check whether the connector is connected to a display. + * + * When connection was determined via DAC load detection, we avoid + * re-running it on normal polls to prevent visible glitches, unless + * @force is set. + * + * Return: The probed connector status (connected/disconnected/unknown). + */ +static enum drm_connector_status +amdgpu_dm_connector_poll(struct amdgpu_dm_connector *aconnector, bool force) +{ + struct drm_connector *connector = &aconnector->base; + struct drm_device *dev = connector->dev; + struct amdgpu_device *adev = drm_to_adev(dev); + struct dc_link *link = aconnector->dc_link; + enum dc_connection_type conn_type = dc_connection_none; + enum drm_connector_status status = connector_status_disconnected; + + /* When we determined the connection using DAC load detection, + * do NOT poll the connector do detect disconnect because + * that would run DAC load detection again which can cause + * visible visual glitches. + * + * Only allow to poll such a connector again when forcing. + */ + if (!force && link->local_sink && link->type == dc_connection_analog_load) + return connector->status; + + mutex_lock(&aconnector->hpd_lock); + + if (dc_link_detect_connection_type(aconnector->dc_link, &conn_type) && + conn_type != dc_connection_none) { + mutex_lock(&adev->dm.dc_lock); + + /* Only call full link detection when a sink isn't created yet, + * ie. just when the display is plugged in, otherwise we risk flickering. + */ + if (link->local_sink || + dc_link_detect(link, DETECT_REASON_HPD)) + status = connector_status_connected; + + mutex_unlock(&adev->dm.dc_lock); + } + + if (connector->status != status) { + if (status == connector_status_disconnected) { + if (link->local_sink) + dc_sink_release(link->local_sink); + + link->local_sink = NULL; + link->dpcd_sink_count = 0; + link->type = dc_connection_none; + } + + amdgpu_dm_update_connector_after_detect(aconnector); + } + + mutex_unlock(&aconnector->hpd_lock); + return status; +} + +/** + * amdgpu_dm_connector_detect() - Detect whether a DRM connector is connected to a display + * + * A connector is considered connected when it has a sink that is not NULL. + * For connectors that support HPD (hotplug detection), the connection is + * handled in the HPD interrupt. + * For connectors that may not support HPD, such as analog connectors, + * DRM will call this function repeatedly to poll them. + * + * Notes: + * 1. This interface is NOT called in context of HPD irq. + * 2. This interface *is called* in context of user-mode ioctl. Which + * makes it a bad place for *any* MST-related activity. + * + * @connector: The DRM connector we are checking. We convert it to + * amdgpu_dm_connector so we can read the DC link and state. + * @force: If true, do a full detect again. This is used even when + * a lighter check would normally be used to avoid flicker. + * + * Return: The connector status (connected, disconnected, or unknown). + * + */ +static enum drm_connector_status +amdgpu_dm_connector_detect(struct drm_connector *connector, bool force) +{ + struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector); + + update_subconnector_property(aconnector); + + if (aconnector->base.force == DRM_FORCE_ON || + aconnector->base.force == DRM_FORCE_ON_DIGITAL) + return connector_status_connected; + else if (aconnector->base.force == DRM_FORCE_OFF) + return connector_status_disconnected; + + /* Poll analog connectors and only when either + * disconnected or connected to an analog display. + */ + if (drm_kms_helper_is_poll_worker() && + dc_connector_supports_analog(aconnector->dc_link->link_id.id) && + (!aconnector->dc_sink || aconnector->dc_sink->edid_caps.analog)) + return amdgpu_dm_connector_poll(aconnector, force); + + return (aconnector->dc_sink ? connector_status_connected : + connector_status_disconnected); +} + +int amdgpu_dm_connector_atomic_set_property(struct drm_connector *connector, + struct drm_connector_state *connector_state, + struct drm_property *property, + uint64_t val) +{ + struct drm_device *dev = connector->dev; + struct amdgpu_device *adev = drm_to_adev(dev); + struct dm_connector_state *dm_old_state = + to_dm_connector_state(connector->state); + struct dm_connector_state *dm_new_state = + to_dm_connector_state(connector_state); + + int ret = -EINVAL; + + if (property == dev->mode_config.scaling_mode_property) { + enum amdgpu_rmx_type rmx_type; + + switch (val) { + case DRM_MODE_SCALE_CENTER: + rmx_type = RMX_CENTER; + break; + case DRM_MODE_SCALE_ASPECT: + rmx_type = RMX_ASPECT; + break; + case DRM_MODE_SCALE_FULLSCREEN: + rmx_type = RMX_FULL; + break; + case DRM_MODE_SCALE_NONE: + default: + rmx_type = RMX_OFF; + break; + } + + if (dm_old_state->scaling == rmx_type) + return 0; + + dm_new_state->scaling = rmx_type; + ret = 0; + } else if (property == adev->mode_info.underscan_hborder_property) { + dm_new_state->underscan_hborder = val; + ret = 0; + } else if (property == adev->mode_info.underscan_vborder_property) { + dm_new_state->underscan_vborder = val; + ret = 0; + } else if (property == adev->mode_info.underscan_property) { + dm_new_state->underscan_enable = val; + ret = 0; + } else if (property == adev->mode_info.abm_level_property) { + switch (val) { + case ABM_SYSFS_CONTROL: + dm_new_state->abm_sysfs_forbidden = false; + break; + case ABM_LEVEL_OFF: + dm_new_state->abm_sysfs_forbidden = true; + dm_new_state->abm_level = ABM_LEVEL_IMMEDIATE_DISABLE; + break; + default: + dm_new_state->abm_sysfs_forbidden = true; + dm_new_state->abm_level = val; + } + ret = 0; + } + + return ret; +} + +int amdgpu_dm_connector_atomic_get_property(struct drm_connector *connector, + const struct drm_connector_state *state, + struct drm_property *property, + uint64_t *val) +{ + struct drm_device *dev = connector->dev; + struct amdgpu_device *adev = drm_to_adev(dev); + struct dm_connector_state *dm_state = + to_dm_connector_state(state); + int ret = -EINVAL; + + if (property == dev->mode_config.scaling_mode_property) { + switch (dm_state->scaling) { + case RMX_CENTER: + *val = DRM_MODE_SCALE_CENTER; + break; + case RMX_ASPECT: + *val = DRM_MODE_SCALE_ASPECT; + break; + case RMX_FULL: + *val = DRM_MODE_SCALE_FULLSCREEN; + break; + case RMX_OFF: + default: + *val = DRM_MODE_SCALE_NONE; + break; + } + ret = 0; + } else if (property == adev->mode_info.underscan_hborder_property) { + *val = dm_state->underscan_hborder; + ret = 0; + } else if (property == adev->mode_info.underscan_vborder_property) { + *val = dm_state->underscan_vborder; + ret = 0; + } else if (property == adev->mode_info.underscan_property) { + *val = dm_state->underscan_enable; + ret = 0; + } else if (property == adev->mode_info.abm_level_property) { + if (!dm_state->abm_sysfs_forbidden) + *val = ABM_SYSFS_CONTROL; + else + *val = (dm_state->abm_level != ABM_LEVEL_IMMEDIATE_DISABLE) ? + dm_state->abm_level : 0; + ret = 0; + } + + return ret; +} + +static void amdgpu_dm_connector_unregister(struct drm_connector *connector) +{ + struct amdgpu_dm_connector *amdgpu_dm_connector = to_amdgpu_dm_connector(connector); + + if (amdgpu_dm_should_create_sysfs(amdgpu_dm_connector)) + sysfs_remove_group(&connector->kdev->kobj, &amdgpu_group); + + cec_notifier_conn_unregister(amdgpu_dm_connector->notifier); + drm_dp_aux_unregister(&amdgpu_dm_connector->dm_dp_aux.aux); +} + +static void amdgpu_dm_connector_destroy(struct drm_connector *connector) +{ + struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector); + struct amdgpu_device *adev = drm_to_adev(connector->dev); + struct amdgpu_display_manager *dm = &adev->dm; + + /* + * Call only if mst_mgr was initialized before since it's not done + * for all connector types. + */ + if (aconnector->mst_mgr.dev) + drm_dp_mst_topology_mgr_destroy(&aconnector->mst_mgr); + + /* Cancel and flush any pending HDMI HPD debounce work */ + if (aconnector->hdmi_hpd_debounce_delay_ms) { + cancel_delayed_work_sync(&aconnector->hdmi_hpd_debounce_work); + if (aconnector->hdmi_prev_sink) { + dc_sink_release(aconnector->hdmi_prev_sink); + aconnector->hdmi_prev_sink = NULL; + } + } + + if (aconnector->bl_idx != -1) { + backlight_device_unregister(dm->backlight_dev[aconnector->bl_idx]); + dm->backlight_dev[aconnector->bl_idx] = NULL; + } + + if (aconnector->dc_em_sink) + dc_sink_release(aconnector->dc_em_sink); + aconnector->dc_em_sink = NULL; + if (aconnector->dc_sink) + dc_sink_release(aconnector->dc_sink); + aconnector->dc_sink = NULL; + + drm_dp_cec_unregister_connector(&aconnector->dm_dp_aux.aux); + drm_connector_unregister(connector); + drm_connector_cleanup(connector); + kfree(aconnector->dm_dp_aux.aux.name); + + kfree(connector); +} + +void amdgpu_dm_connector_funcs_reset(struct drm_connector *connector) +{ + struct dm_connector_state *state = + to_dm_connector_state(connector->state); + + if (connector->state) + __drm_atomic_helper_connector_destroy_state(connector->state); + + kfree(state); + + state = kzalloc_obj(*state); + + if (state) { + state->scaling = RMX_OFF; + state->underscan_enable = false; + state->underscan_hborder = 0; + state->underscan_vborder = 0; + state->base.max_requested_bpc = 8; + state->vcpi_slots = 0; + state->pbn = 0; + + if (connector->connector_type == DRM_MODE_CONNECTOR_eDP) { + if (amdgpu_dm_abm_level <= 0) + state->abm_level = ABM_LEVEL_IMMEDIATE_DISABLE; + else + state->abm_level = amdgpu_dm_abm_level; + } + + __drm_atomic_helper_connector_reset(connector, &state->base); + } +} + +struct drm_connector_state * +amdgpu_dm_connector_atomic_duplicate_state(struct drm_connector *connector) +{ + struct dm_connector_state *state = + to_dm_connector_state(connector->state); + + struct dm_connector_state *new_state = + kmemdup(state, sizeof(*state), GFP_KERNEL); + + if (!new_state) + return NULL; + + __drm_atomic_helper_connector_duplicate_state(connector, &new_state->base); + + new_state->freesync_capable = state->freesync_capable; + new_state->abm_level = state->abm_level; + new_state->scaling = state->scaling; + new_state->underscan_enable = state->underscan_enable; + new_state->underscan_hborder = state->underscan_hborder; + new_state->underscan_vborder = state->underscan_vborder; + new_state->vcpi_slots = state->vcpi_slots; + new_state->pbn = state->pbn; + return &new_state->base; +} + +static int +amdgpu_dm_connector_late_register(struct drm_connector *connector) +{ + struct amdgpu_dm_connector *amdgpu_dm_connector = + to_amdgpu_dm_connector(connector); + int r; + + if (amdgpu_dm_should_create_sysfs(amdgpu_dm_connector)) { + r = sysfs_create_group(&connector->kdev->kobj, + &amdgpu_group); + if (r) + return r; + } + + amdgpu_dm_register_backlight_device(amdgpu_dm_connector); + + if ((connector->connector_type == DRM_MODE_CONNECTOR_DisplayPort) || + (connector->connector_type == DRM_MODE_CONNECTOR_eDP)) { + amdgpu_dm_connector->dm_dp_aux.aux.dev = connector->kdev; + r = drm_dp_aux_register(&amdgpu_dm_connector->dm_dp_aux.aux); + if (r) + return r; + } + +#if defined(CONFIG_DEBUG_FS) + connector_debugfs_init(amdgpu_dm_connector); +#endif + + return 0; +} + +static void amdgpu_dm_connector_funcs_force(struct drm_connector *connector) +{ + struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector); + struct dc_link *dc_link = aconnector->dc_link; + struct dc_sink *dc_em_sink = aconnector->dc_em_sink; + const struct drm_edid *drm_edid; + struct i2c_adapter *ddc; + struct drm_device *dev = connector->dev; + + if (dc_link && dc_link->aux_mode) + ddc = &aconnector->dm_dp_aux.aux.ddc; + else + ddc = &aconnector->i2c->base; + + drm_edid = drm_edid_read_ddc(connector, ddc); + drm_edid_connector_update(connector, drm_edid); + if (!drm_edid) { + drm_err(dev, "No EDID found on connector: %s.\n", connector->name); + return; + } + + aconnector->drm_edid = drm_edid; + /* Update emulated (virtual) sink's EDID */ + if (dc_em_sink && dc_link) { + /* FIXME: Get rid of drm_edid_raw() */ + const struct edid *edid = drm_edid_raw(drm_edid); + + memset(&dc_em_sink->edid_caps, 0, sizeof(struct dc_edid_caps)); + memmove(dc_em_sink->dc_edid.raw_edid, edid, + (edid->extensions + 1) * EDID_LENGTH); + dm_helpers_parse_edid_caps( + dc_link, + &dc_em_sink->dc_edid, + &dc_em_sink->edid_caps); + } +} + +static const struct drm_connector_funcs amdgpu_dm_connector_funcs = { + .reset = amdgpu_dm_connector_funcs_reset, + .detect = amdgpu_dm_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = amdgpu_dm_connector_destroy, + .atomic_duplicate_state = amdgpu_dm_connector_atomic_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, + .atomic_set_property = amdgpu_dm_connector_atomic_set_property, + .atomic_get_property = amdgpu_dm_connector_atomic_get_property, + .late_register = amdgpu_dm_connector_late_register, + .early_unregister = amdgpu_dm_connector_unregister, + .force = amdgpu_dm_connector_funcs_force +}; + +static int get_modes(struct drm_connector *connector) +{ + return amdgpu_dm_connector_get_modes(connector); +} + +static void create_eml_sink(struct amdgpu_dm_connector *aconnector) +{ + struct drm_connector *connector = &aconnector->base; + struct dc_link *dc_link = aconnector->dc_link; + struct dc_sink_init_data init_params = { + .link = aconnector->dc_link, + .sink_signal = SIGNAL_TYPE_VIRTUAL + }; + const struct drm_edid *drm_edid; + const struct edid *edid; + struct i2c_adapter *ddc; + + if (dc_link && dc_link->aux_mode) + ddc = &aconnector->dm_dp_aux.aux.ddc; + else + ddc = &aconnector->i2c->base; + + drm_edid = drm_edid_read_ddc(connector, ddc); + drm_edid_connector_update(connector, drm_edid); + if (!drm_edid) { + drm_err(connector->dev, "No EDID found on connector: %s.\n", connector->name); + return; + } + + if (connector->display_info.is_hdmi) + init_params.sink_signal = SIGNAL_TYPE_HDMI_TYPE_A; + + aconnector->drm_edid = drm_edid; + + /* FIXME: Get rid of drm_edid_raw() */ + edid = drm_edid_raw(drm_edid); + aconnector->dc_em_sink = dc_link_add_remote_sink( + aconnector->dc_link, + (uint8_t *)edid, + (edid->extensions + 1) * EDID_LENGTH, + &init_params); + + if (aconnector->base.force == DRM_FORCE_ON) { + aconnector->dc_sink = aconnector->dc_link->local_sink ? + aconnector->dc_link->local_sink : + aconnector->dc_em_sink; + if (aconnector->dc_sink) + dc_sink_retain(aconnector->dc_sink); + } +} + +static void handle_edid_mgmt(struct amdgpu_dm_connector *aconnector) +{ + struct dc_link *link = (struct dc_link *)aconnector->dc_link; + + /* + * In case of headless boot with force on for DP managed connector + * Those settings have to be != 0 to get initial modeset + */ + if (link->connector_signal == SIGNAL_TYPE_DISPLAY_PORT) { + link->verified_link_cap.lane_count = LANE_COUNT_FOUR; + link->verified_link_cap.link_rate = LINK_RATE_HIGH2; + } + + create_eml_sink(aconnector); +} + +static enum dc_status dm_validate_stream_and_context(struct dc *dc, + struct dc_stream_state *stream) +{ + enum dc_status dc_result = DC_ERROR_UNEXPECTED; + struct dc_plane_state *dc_plane_state = NULL; + struct dc_state *dc_state = NULL; + + if (!stream) + goto cleanup; + + dc_plane_state = dc_create_plane_state(dc); + if (!dc_plane_state) + goto cleanup; + + dc_state = dc_state_create(dc, NULL); + if (!dc_state) + goto cleanup; + + /* populate stream to plane */ + dc_plane_state->src_rect.height = stream->src.height; + dc_plane_state->src_rect.width = stream->src.width; + dc_plane_state->dst_rect.height = stream->src.height; + dc_plane_state->dst_rect.width = stream->src.width; + dc_plane_state->clip_rect.height = stream->src.height; + dc_plane_state->clip_rect.width = stream->src.width; + dc_plane_state->plane_size.surface_pitch = ((stream->src.width + 255) / 256) * 256; + dc_plane_state->plane_size.surface_size.height = stream->src.height; + dc_plane_state->plane_size.surface_size.width = stream->src.width; + dc_plane_state->plane_size.chroma_size.height = stream->src.height; + dc_plane_state->plane_size.chroma_size.width = stream->src.width; + dc_plane_state->format = SURFACE_PIXEL_FORMAT_GRPH_ARGB8888; + dc_plane_state->tiling_info.gfx9.swizzle = DC_SW_UNKNOWN; + dc_plane_state->rotation = ROTATION_ANGLE_0; + dc_plane_state->is_tiling_rotated = false; + dc_plane_state->tiling_info.gfx8.array_mode = DC_ARRAY_LINEAR_GENERAL; + + dc_result = dc_validate_stream(dc, stream); + if (dc_result == DC_OK) + dc_result = dc_validate_plane(dc, dc_plane_state); + + if (dc_result == DC_OK) + dc_result = dc_state_add_stream(dc, dc_state, stream); + + if (dc_result == DC_OK && !dc_state_add_plane( + dc, + stream, + dc_plane_state, + dc_state)) + dc_result = DC_FAIL_ATTACH_SURFACES; + + if (dc_result == DC_OK) + dc_result = dc_validate_global_state(dc, dc_state, DC_VALIDATE_MODE_ONLY); + +cleanup: + if (dc_state) + dc_state_release(dc_state); + + if (dc_plane_state) + dc_plane_state_release(dc_plane_state); + + return dc_result; +} + +struct dc_stream_state * +amdgpu_dm_create_validate_stream_for_sink(struct drm_connector *connector, + const struct drm_display_mode *drm_mode, + const struct dm_connector_state *dm_state, + const struct dc_stream_state *old_stream) +{ + struct amdgpu_dm_connector *aconnector = NULL; + struct amdgpu_device *adev = drm_to_adev(connector->dev); + struct dc_stream_state *stream; + const struct drm_connector_state *drm_state = dm_state ? &dm_state->base : NULL; + int requested_bpc = drm_state ? drm_state->max_requested_bpc : 8; + enum dc_status dc_result = DC_OK; + uint8_t bpc_limit = 6; + + if (!dm_state) + return NULL; + + if (connector->connector_type != DRM_MODE_CONNECTOR_WRITEBACK) + aconnector = to_amdgpu_dm_connector(connector); + + if (aconnector && + (aconnector->dc_link->connector_signal == SIGNAL_TYPE_HDMI_TYPE_A || + aconnector->dc_link->connector_signal == SIGNAL_TYPE_HDMI_FRL || + aconnector->dc_link->dpcd_caps.dongle_type == DISPLAY_DONGLE_DP_HDMI_CONVERTER)) + bpc_limit = 8; + + do { + drm_dbg_kms(connector->dev, "Trying with %d bpc\n", requested_bpc); + stream = create_stream_for_sink(connector, drm_mode, + dm_state, old_stream, + requested_bpc); + if (stream == NULL) { + drm_err(adev_to_drm(adev), "Failed to create stream for sink!\n"); + break; + } + + dc_result = dc_validate_stream(adev->dm.dc, stream); + + if (!aconnector) /* writeback connector */ + return stream; + + if (dc_result == DC_OK && stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST) + dc_result = dm_dp_mst_is_port_support_mode(aconnector, stream); + + if (dc_result == DC_OK) + dc_result = dm_validate_stream_and_context(adev->dm.dc, stream); + + if (dc_result != DC_OK) { + drm_dbg_kms(connector->dev, "Pruned mode %d x %d (clk %d) %s %s -- %s\n", + drm_mode->hdisplay, + drm_mode->vdisplay, + drm_mode->clock, + dc_pixel_encoding_to_str(stream->timing.pixel_encoding), + dc_color_depth_to_str(stream->timing.display_color_depth), + dc_status_to_str(dc_result)); + + dc_stream_release(stream); + stream = NULL; + requested_bpc -= 2; /* lower bpc to retry validation */ + } + + } while (stream == NULL && requested_bpc >= bpc_limit); + + switch (dc_result) { + /* + * If we failed to validate DP bandwidth stream with the requested RGB color depth, + * we try to fallback and configure in order: + * YUV422 (8bpc, 6bpc) + * YUV420 (8bpc, 6bpc) + */ + case DC_FAIL_ENC_VALIDATE: + case DC_EXCEED_DONGLE_CAP: + case DC_NO_DP_LINK_BANDWIDTH: + /* recursively entered twice and already tried both YUV422 and YUV420 */ + if (aconnector->force_yuv422_output && aconnector->force_yuv420_output) + break; + /* first failure; try YUV422 */ + if (!aconnector->force_yuv422_output) { + drm_dbg_kms(connector->dev, "%s:%d Validation failed with %d, retrying w/ YUV422\n", + __func__, __LINE__, dc_result); + aconnector->force_yuv422_output = true; + /* recursively entered and YUV422 failed, try YUV420 */ + } else if (!aconnector->force_yuv420_output) { + drm_dbg_kms(connector->dev, "%s:%d Validation failed with %d, retrying w/ YUV420\n", + __func__, __LINE__, dc_result); + aconnector->force_yuv420_output = true; + } + stream = amdgpu_dm_create_validate_stream_for_sink(connector, drm_mode, + dm_state, old_stream); + aconnector->force_yuv422_output = false; + aconnector->force_yuv420_output = false; + break; + case DC_OK: + break; + default: + drm_dbg_kms(connector->dev, "%s:%d Unhandled validation failure %d\n", + __func__, __LINE__, dc_result); + break; + } + + return stream; +} + +enum drm_mode_status amdgpu_dm_connector_mode_valid(struct drm_connector *connector, + const struct drm_display_mode *mode) +{ + int result = MODE_ERROR; + struct dc_sink *dc_sink; + struct drm_display_mode *test_mode; + /* TODO: Unhardcode stream count */ + struct dc_stream_state *stream; + /* we always have an amdgpu_dm_connector here since we got + * here via the amdgpu_dm_connector_helper_funcs + */ + struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector); + + if ((mode->flags & DRM_MODE_FLAG_INTERLACE) || + (mode->flags & DRM_MODE_FLAG_DBLSCAN)) + return result; + + /* + * Only run this the first time mode_valid is called to initilialize + * EDID mgmt + */ + if (aconnector->base.force != DRM_FORCE_UNSPECIFIED && + !aconnector->dc_em_sink) + handle_edid_mgmt(aconnector); + + dc_sink = to_amdgpu_dm_connector(connector)->dc_sink; + + if (dc_sink == NULL && aconnector->base.force != DRM_FORCE_ON_DIGITAL && + aconnector->base.force != DRM_FORCE_ON) { + drm_err(connector->dev, "dc_sink is NULL!\n"); + goto fail; + } + + test_mode = drm_mode_duplicate(connector->dev, mode); + if (!test_mode) + goto fail; + + drm_mode_set_crtcinfo(test_mode, 0); + + stream = amdgpu_dm_create_validate_stream_for_sink(connector, test_mode, + to_dm_connector_state(connector->state), + NULL); + drm_mode_destroy(connector->dev, test_mode); + if (stream) { + dc_stream_release(stream); + result = MODE_OK; + } + +fail: + /* TODO: error handling*/ + return result; +} + +int amdgpu_dm_fill_hdr_info_packet(const struct drm_connector_state *state, + struct dc_info_packet *out) +{ + struct hdmi_drm_infoframe frame; + unsigned char buf[30]; /* 26 + 4 */ + ssize_t len; + int ret, i; + + memset(out, 0, sizeof(*out)); + + if (!state->hdr_output_metadata) + return 0; + + ret = drm_hdmi_infoframe_set_hdr_metadata(&frame, state); + if (ret) + return ret; + + len = hdmi_drm_infoframe_pack_only(&frame, buf, sizeof(buf)); + if (len < 0) + return (int)len; + + /* Static metadata is a fixed 26 bytes + 4 byte header. */ + if (len != 30) + return -EINVAL; + + /* Prepare the infopacket for DC. */ + switch (state->connector->connector_type) { + case DRM_MODE_CONNECTOR_HDMIA: + out->hb0 = 0x87; /* type */ + out->hb1 = 0x01; /* version */ + out->hb2 = 0x1A; /* length */ + out->sb[0] = buf[3]; /* checksum */ + i = 1; + break; + + case DRM_MODE_CONNECTOR_DisplayPort: + case DRM_MODE_CONNECTOR_eDP: + out->hb0 = 0x00; /* sdp id, zero */ + out->hb1 = 0x87; /* type */ + out->hb2 = 0x1D; /* payload len - 1 */ + out->hb3 = (0x13 << 2); /* sdp version */ + out->sb[0] = 0x01; /* version */ + out->sb[1] = 0x1A; /* length */ + i = 2; + break; + + default: + return -EINVAL; + } + + memcpy(&out->sb[i], &buf[4], 26); + out->valid = true; + + print_hex_dump(KERN_DEBUG, "HDR SB:", DUMP_PREFIX_NONE, 16, 1, out->sb, + sizeof(out->sb), false); + + return 0; +} + +static int +amdgpu_dm_connector_atomic_check(struct drm_connector *conn, + struct drm_atomic_commit *state) +{ + struct drm_connector_state *new_con_state = + drm_atomic_get_new_connector_state(state, conn); + struct drm_connector_state *old_con_state = + drm_atomic_get_old_connector_state(state, conn); + struct drm_crtc *crtc = new_con_state->crtc; + struct drm_crtc_state *new_crtc_state; + struct amdgpu_dm_connector *aconn = to_amdgpu_dm_connector(conn); + int ret; + + if (WARN_ON(unlikely(!old_con_state || !new_con_state))) + return -EINVAL; + + trace_amdgpu_dm_connector_atomic_check(new_con_state); + + if (conn->connector_type == DRM_MODE_CONNECTOR_DisplayPort) { + ret = drm_dp_mst_root_conn_atomic_check(new_con_state, &aconn->mst_mgr); + if (ret < 0) + return ret; + } + + if (!crtc) + return 0; + + if (new_con_state->privacy_screen_sw_state != old_con_state->privacy_screen_sw_state) { + new_crtc_state = drm_atomic_get_crtc_state(state, crtc); + if (IS_ERR(new_crtc_state)) + return PTR_ERR(new_crtc_state); + + new_crtc_state->mode_changed = true; + } + + if (new_con_state->colorspace != old_con_state->colorspace) { + new_crtc_state = drm_atomic_get_crtc_state(state, crtc); + if (IS_ERR(new_crtc_state)) + return PTR_ERR(new_crtc_state); + + new_crtc_state->mode_changed = true; + } + + if (new_con_state->content_type != old_con_state->content_type) { + new_crtc_state = drm_atomic_get_crtc_state(state, crtc); + if (IS_ERR(new_crtc_state)) + return PTR_ERR(new_crtc_state); + + new_crtc_state->mode_changed = true; + } + + if (!drm_connector_atomic_hdr_metadata_equal(old_con_state, new_con_state)) { + struct dc_info_packet hdr_infopacket; + + ret = amdgpu_dm_fill_hdr_info_packet(new_con_state, &hdr_infopacket); + if (ret) + return ret; + + new_crtc_state = drm_atomic_get_crtc_state(state, crtc); + if (IS_ERR(new_crtc_state)) + return PTR_ERR(new_crtc_state); + + /* + * DC considers the stream backends changed if the + * static metadata changes. Forcing the modeset also + * gives a simple way for userspace to switch from + * 8bpc to 10bpc when setting the metadata to enter + * or exit HDR. + * + * Changing the static metadata after it's been + * set is permissible, however. So only force a + * modeset if we're entering or exiting HDR. + */ + new_crtc_state->mode_changed = new_crtc_state->mode_changed || + !old_con_state->hdr_output_metadata || + !new_con_state->hdr_output_metadata; + } + + return 0; +} + +static const struct drm_connector_helper_funcs +amdgpu_dm_connector_helper_funcs = { + /* + * If hotplugging a second bigger display in FB Con mode, bigger resolution + * modes will be filtered by drm_mode_validate_size(), and those modes + * are missing after user start lightdm. So we need to renew modes list. + * in get_modes call back, not just return the modes count + */ + .get_modes = get_modes, + .mode_valid = amdgpu_dm_connector_mode_valid, + .atomic_check = amdgpu_dm_connector_atomic_check, +}; + +int amdgpu_dm_convert_dc_color_depth_into_bpc(enum dc_color_depth display_color_depth) +{ + switch (display_color_depth) { + case COLOR_DEPTH_666: + return 6; + case COLOR_DEPTH_888: + return 8; + case COLOR_DEPTH_101010: + return 10; + case COLOR_DEPTH_121212: + return 12; + case COLOR_DEPTH_141414: + return 14; + case COLOR_DEPTH_161616: + return 16; + default: + break; + } + return 0; +} + +static int to_drm_connector_type(enum signal_type st, uint32_t connector_id) +{ + switch (st) { + case SIGNAL_TYPE_HDMI_TYPE_A: + return DRM_MODE_CONNECTOR_HDMIA; + case SIGNAL_TYPE_EDP: + return DRM_MODE_CONNECTOR_eDP; + case SIGNAL_TYPE_LVDS: + return DRM_MODE_CONNECTOR_LVDS; + case SIGNAL_TYPE_RGB: + return DRM_MODE_CONNECTOR_VGA; + case SIGNAL_TYPE_DISPLAY_PORT: + case SIGNAL_TYPE_DISPLAY_PORT_MST: + /* External DP bridges have a different connector type. */ + if (connector_id == CONNECTOR_ID_VGA) + return DRM_MODE_CONNECTOR_VGA; + else if (connector_id == CONNECTOR_ID_LVDS) + return DRM_MODE_CONNECTOR_LVDS; + + return DRM_MODE_CONNECTOR_DisplayPort; + case SIGNAL_TYPE_DVI_DUAL_LINK: + case SIGNAL_TYPE_DVI_SINGLE_LINK: + if (connector_id == CONNECTOR_ID_SINGLE_LINK_DVII || + connector_id == CONNECTOR_ID_DUAL_LINK_DVII) + return DRM_MODE_CONNECTOR_DVII; + + return DRM_MODE_CONNECTOR_DVID; + case SIGNAL_TYPE_VIRTUAL: + return DRM_MODE_CONNECTOR_VIRTUAL; + + default: + return DRM_MODE_CONNECTOR_Unknown; + } +} + +static struct drm_encoder *amdgpu_dm_connector_to_encoder(struct drm_connector *connector) +{ + struct drm_encoder *encoder; + + /* There is only one encoder per connector */ + drm_connector_for_each_possible_encoder(connector, encoder) + return encoder; + + return NULL; +} + +static void amdgpu_dm_get_native_mode(struct drm_connector *connector) +{ + struct drm_encoder *encoder; + struct amdgpu_encoder *amdgpu_encoder; + + encoder = amdgpu_dm_connector_to_encoder(connector); + + if (encoder == NULL) + return; + + amdgpu_encoder = to_amdgpu_encoder(encoder); + + amdgpu_encoder->native_mode.clock = 0; + + if (!list_empty(&connector->probed_modes)) { + struct drm_display_mode *preferred_mode = NULL; + + list_for_each_entry(preferred_mode, + &connector->probed_modes, + head) { + if (preferred_mode->type & DRM_MODE_TYPE_PREFERRED) + amdgpu_encoder->native_mode = *preferred_mode; + + break; + } + + } +} + +static struct drm_display_mode * +amdgpu_dm_create_common_mode(struct drm_encoder *encoder, + const char *name, + int hdisplay, int vdisplay) +{ + struct drm_device *dev = encoder->dev; + struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); + struct drm_display_mode *mode = NULL; + struct drm_display_mode *native_mode = &amdgpu_encoder->native_mode; + + mode = drm_mode_duplicate(dev, native_mode); + + if (mode == NULL) + return NULL; + + mode->hdisplay = hdisplay; + mode->vdisplay = vdisplay; + mode->type &= ~DRM_MODE_TYPE_PREFERRED; + strscpy(mode->name, name, DRM_DISPLAY_MODE_LEN); + + return mode; + +} + +static const struct amdgpu_dm_mode_size { + char name[DRM_DISPLAY_MODE_LEN]; + int w; + int h; +} common_modes[] = { + { "640x480", 640, 480}, + { "800x600", 800, 600}, + { "1024x768", 1024, 768}, + { "1280x720", 1280, 720}, + { "1280x800", 1280, 800}, + {"1280x1024", 1280, 1024}, + { "1440x900", 1440, 900}, + {"1680x1050", 1680, 1050}, + {"1600x1200", 1600, 1200}, + {"1920x1080", 1920, 1080}, + {"1920x1200", 1920, 1200} +}; + +static void amdgpu_dm_connector_add_common_modes(struct drm_encoder *encoder, + struct drm_connector *connector) +{ + struct amdgpu_encoder *amdgpu_encoder = to_amdgpu_encoder(encoder); + struct drm_display_mode *mode = NULL; + struct drm_display_mode *native_mode = &amdgpu_encoder->native_mode; + struct amdgpu_dm_connector *amdgpu_dm_connector = + to_amdgpu_dm_connector(connector); + int i; + int n; + + if ((connector->connector_type != DRM_MODE_CONNECTOR_eDP) && + (connector->connector_type != DRM_MODE_CONNECTOR_LVDS)) + return; + + n = ARRAY_SIZE(common_modes); + + for (i = 0; i < n; i++) { + struct drm_display_mode *curmode = NULL; + bool mode_existed = false; + + if (common_modes[i].w > native_mode->hdisplay || + common_modes[i].h > native_mode->vdisplay || + (common_modes[i].w == native_mode->hdisplay && + common_modes[i].h == native_mode->vdisplay)) + continue; + + list_for_each_entry(curmode, &connector->probed_modes, head) { + if (common_modes[i].w == curmode->hdisplay && + common_modes[i].h == curmode->vdisplay) { + mode_existed = true; + break; + } + } + + if (mode_existed) + continue; + + mode = amdgpu_dm_create_common_mode(encoder, + common_modes[i].name, common_modes[i].w, + common_modes[i].h); + if (!mode) + continue; + + drm_mode_probed_add(connector, mode); + amdgpu_dm_connector->num_modes++; + } +} + +void amdgpu_set_panel_orientation(struct drm_connector *connector) +{ + struct drm_encoder *encoder; + struct amdgpu_encoder *amdgpu_encoder; + const struct drm_display_mode *native_mode; + + if (connector->connector_type != DRM_MODE_CONNECTOR_eDP && + connector->connector_type != DRM_MODE_CONNECTOR_LVDS) + return; + + mutex_lock(&connector->dev->mode_config.mutex); + amdgpu_dm_connector_get_modes(connector); + mutex_unlock(&connector->dev->mode_config.mutex); + + encoder = amdgpu_dm_connector_to_encoder(connector); + if (!encoder) + return; + + amdgpu_encoder = to_amdgpu_encoder(encoder); + + native_mode = &amdgpu_encoder->native_mode; + if (native_mode->hdisplay == 0 || native_mode->vdisplay == 0) + return; + + drm_connector_set_panel_orientation_with_quirk(connector, + DRM_MODE_PANEL_ORIENTATION_UNKNOWN, + native_mode->hdisplay, + native_mode->vdisplay); +} + +static void amdgpu_dm_connector_ddc_get_modes(struct drm_connector *connector, + const struct drm_edid *drm_edid) +{ + struct amdgpu_dm_connector *amdgpu_dm_connector = + to_amdgpu_dm_connector(connector); + + if (drm_edid) { + /* empty probed_modes */ + INIT_LIST_HEAD(&connector->probed_modes); + amdgpu_dm_connector->num_modes = + drm_edid_connector_add_modes(connector); + + /* sorting the probed modes before calling function + * amdgpu_dm_get_native_mode() since EDID can have + * more than one preferred mode. The modes that are + * later in the probed mode list could be of higher + * and preferred resolution. For example, 3840x2160 + * resolution in base EDID preferred timing and 4096x2160 + * preferred resolution in DID extension block later. + */ + drm_mode_sort(&connector->probed_modes); + amdgpu_dm_get_native_mode(connector); + + /* Freesync capabilities are reset by calling + * drm_edid_connector_add_modes() and need to be + * restored here. + */ + amdgpu_dm_update_freesync_caps(connector, drm_edid, false); + } else { + amdgpu_dm_connector->num_modes = 0; + } +} + +static bool is_duplicate_mode(struct amdgpu_dm_connector *aconnector, + struct drm_display_mode *mode) +{ + struct drm_display_mode *m; + + list_for_each_entry(m, &aconnector->base.probed_modes, head) { + if (drm_mode_equal(m, mode)) + return true; + } + + return false; +} + +static uint add_fs_modes(struct amdgpu_dm_connector *aconnector) +{ + const struct drm_display_mode *m; + struct drm_display_mode *new_mode; + uint i; + u32 new_modes_count = 0; + + /* Standard FPS values + * + * 23.976 - TV/NTSC + * 24 - Cinema + * 25 - TV/PAL + * 29.97 - TV/NTSC + * 30 - TV/NTSC + * 48 - Cinema HFR + * 50 - TV/PAL + * 60 - Commonly used + * 48,72,96,120 - Multiples of 24 + */ + static const u32 common_rates[] = { + 23976, 24000, 25000, 29970, 30000, + 48000, 50000, 60000, 72000, 96000, 120000 + }; + + /* + * Find mode with highest refresh rate with the same resolution + * as the preferred mode. Some monitors report a preferred mode + * with lower resolution than the highest refresh rate supported. + */ + + m = amdgpu_dm_get_highest_refresh_rate_mode(aconnector, true); + if (!m) + return 0; + + for (i = 0; i < ARRAY_SIZE(common_rates); i++) { + u64 target_vtotal, target_vtotal_diff; + u64 num, den; + + if (drm_mode_vrefresh(m) * 1000 < common_rates[i]) + continue; + + if (common_rates[i] < aconnector->min_vfreq * 1000 || + common_rates[i] > aconnector->max_vfreq * 1000) + continue; + + num = (unsigned long long)m->clock * 1000 * 1000; + den = common_rates[i] * (unsigned long long)m->htotal; + target_vtotal = div_u64(num, den); + target_vtotal_diff = target_vtotal - m->vtotal; + + /* Check for illegal modes */ + if (m->vsync_start + target_vtotal_diff < m->vdisplay || + m->vsync_end + target_vtotal_diff < m->vsync_start || + m->vtotal + target_vtotal_diff < m->vsync_end) + continue; + + new_mode = drm_mode_duplicate(aconnector->base.dev, m); + if (!new_mode) + goto out; + + new_mode->vtotal += (u16)target_vtotal_diff; + new_mode->vsync_start += (u16)target_vtotal_diff; + new_mode->vsync_end += (u16)target_vtotal_diff; + new_mode->type &= ~DRM_MODE_TYPE_PREFERRED; + new_mode->type |= DRM_MODE_TYPE_DRIVER; + + if (!is_duplicate_mode(aconnector, new_mode)) { + drm_mode_probed_add(&aconnector->base, new_mode); + new_modes_count += 1; + } else + drm_mode_destroy(aconnector->base.dev, new_mode); + } + out: + return new_modes_count; +} + +static void amdgpu_dm_connector_add_freesync_modes(struct drm_connector *connector, + const struct drm_edid *drm_edid) +{ + struct amdgpu_dm_connector *amdgpu_dm_connector = + to_amdgpu_dm_connector(connector); + + if (!(amdgpu_freesync_vid_mode && drm_edid)) + return; + + if (!amdgpu_dm_connector->dc_sink || !amdgpu_dm_connector->dc_link) + return; + + if (!dc_supports_vrr(amdgpu_dm_connector->dc_sink->ctx->dce_version)) + return; + + if (dc_connector_supports_analog(amdgpu_dm_connector->dc_link->link_id.id) && + amdgpu_dm_connector->dc_sink->edid_caps.analog) + return; + + if (amdgpu_dm_connector->max_vfreq - amdgpu_dm_connector->min_vfreq > 10) + amdgpu_dm_connector->num_modes += + add_fs_modes(amdgpu_dm_connector); +} + +static int amdgpu_dm_connector_get_modes(struct drm_connector *connector) +{ + struct amdgpu_dm_connector *amdgpu_dm_connector = + to_amdgpu_dm_connector(connector); + struct dc_link *dc_link = amdgpu_dm_connector->dc_link; + struct drm_encoder *encoder; + const struct drm_edid *drm_edid = amdgpu_dm_connector->drm_edid; + struct dc_link_settings *verified_link_cap = &dc_link->verified_link_cap; + const struct dc *dc = dc_link->dc; + + encoder = amdgpu_dm_connector_to_encoder(connector); + + if (!drm_edid) { + amdgpu_dm_connector->num_modes = + drm_add_modes_noedid(connector, 640, 480); + if (dc->link_srv->dp_get_encoding_format(verified_link_cap) == DP_128b_132b_ENCODING) + amdgpu_dm_connector->num_modes += + drm_add_modes_noedid(connector, 1920, 1080); + + if (amdgpu_dm_connector->dc_sink && + amdgpu_dm_connector->dc_sink->edid_caps.analog && + dc_connector_supports_analog(dc_link->link_id.id)) { + /* Analog monitor connected by DAC load detection. + * Add common modes. It will be up to the user to select one that works. + */ + for (int i = 0; i < ARRAY_SIZE(common_modes); i++) + amdgpu_dm_connector->num_modes += drm_add_modes_noedid( + connector, common_modes[i].w, common_modes[i].h); + } + } else { + amdgpu_dm_connector_ddc_get_modes(connector, drm_edid); + if (encoder) + amdgpu_dm_connector_add_common_modes(encoder, connector); + amdgpu_dm_connector_add_freesync_modes(connector, drm_edid); + } + amdgpu_dm_fbc_init(connector); + + return amdgpu_dm_connector->num_modes; +} + +static const u32 supported_colorspaces = + BIT(DRM_MODE_COLORIMETRY_BT709_YCC) | + BIT(DRM_MODE_COLORIMETRY_OPRGB) | + BIT(DRM_MODE_COLORIMETRY_BT2020_RGB) | + BIT(DRM_MODE_COLORIMETRY_BT2020_YCC); + +static void hdmi_frl_status_polling_work(struct work_struct *work) +{ + struct amdgpu_display_manager *dm = + container_of(to_delayed_work(work), struct amdgpu_display_manager, + hdmi_frl_status_polling_work); + struct dc *dc = dm->dc; + struct dc_link *dc_link; + bool link_update = false; + + for (int i = 0; i < MAX_LINKS; i++) { + dc_link = dc->links[i]; + + + if (!dc_link || !dc_link->local_sink) + continue; + + if (!dc_is_hdmi_signal(dc_link->connector_signal)) + continue; + + if (dc_link->connector_signal != SIGNAL_TYPE_HDMI_FRL) + continue; + + link_update = dc_link_frl_poll_status_flag(dc_link); + if (link_update) { + mutex_lock(&dm->dc_lock); + dc_link_detect(dc_link, DETECT_REASON_RETRAIN); + mutex_unlock(&dm->dc_lock); + } + } + + queue_delayed_work(dm->hdmi_frl_status_polling_wq, + &dm->hdmi_frl_status_polling_work, + msecs_to_jiffies(dm->hdmi_frl_status_polling_delay_ms)); +} + +void amdgpu_dm_connector_init_helper(struct amdgpu_display_manager *dm, + struct amdgpu_dm_connector *aconnector, + int connector_type, + struct dc_link *link, + int link_index) +{ + struct amdgpu_device *adev = drm_to_adev(dm->ddev); + + /* + * Some of the properties below require access to state, like bpc. + * Allocate some default initial connector state with our reset helper. + */ + if (aconnector->base.funcs->reset) + aconnector->base.funcs->reset(&aconnector->base); + + aconnector->connector_id = link_index; + aconnector->bl_idx = -1; + aconnector->dc_link = link; + aconnector->base.interlace_allowed = false; + aconnector->base.doublescan_allowed = false; + aconnector->base.stereo_allowed = false; + aconnector->base.dpms = DRM_MODE_DPMS_OFF; + aconnector->hpd.hpd = AMDGPU_HPD_NONE; /* not used */ + aconnector->audio_inst = -1; + aconnector->pack_sdp_v1_3 = false; + aconnector->as_type = ADAPTIVE_SYNC_TYPE_NONE; + memset(&aconnector->vsdb_info, 0, sizeof(aconnector->vsdb_info)); + mutex_init(&aconnector->hpd_lock); + mutex_init(&aconnector->handle_mst_msg_ready); + + /* + * If HDMI HPD debounce delay is set, use the minimum between selected + * value and AMDGPU_DM_MAX_HDMI_HPD_DEBOUNCE_MS + */ + if (amdgpu_hdmi_hpd_debounce_delay_ms) { + aconnector->hdmi_hpd_debounce_delay_ms = min(amdgpu_hdmi_hpd_debounce_delay_ms, + AMDGPU_DM_MAX_HDMI_HPD_DEBOUNCE_MS); + INIT_DELAYED_WORK(&aconnector->hdmi_hpd_debounce_work, amdgpu_dm_hdmi_hpd_debounce_work); + aconnector->hdmi_prev_sink = NULL; + } else { + aconnector->hdmi_hpd_debounce_delay_ms = 0; + } + + dm->hdmi_frl_status_polling_delay_ms = 200; + INIT_DELAYED_WORK(&dm->hdmi_frl_status_polling_work, hdmi_frl_status_polling_work); + /* + * configure support HPD hot plug connector_>polled default value is 0 + * which means HPD hot plug not supported + */ + switch (connector_type) { + case DRM_MODE_CONNECTOR_HDMIA: + aconnector->base.polled = DRM_CONNECTOR_POLL_HPD; + aconnector->base.ycbcr_420_allowed = + link->link_enc->features.hdmi_ycbcr420_supported ? true : false; + break; + case DRM_MODE_CONNECTOR_DisplayPort: + aconnector->base.polled = DRM_CONNECTOR_POLL_HPD; + link->link_enc = link_enc_cfg_get_link_enc(link); + ASSERT(link->link_enc); + if (link->link_enc) + aconnector->base.ycbcr_420_allowed = + link->link_enc->features.dp_ycbcr420_supported ? true : false; + break; + case DRM_MODE_CONNECTOR_DVID: + aconnector->base.polled = DRM_CONNECTOR_POLL_HPD; + break; + case DRM_MODE_CONNECTOR_DVII: + case DRM_MODE_CONNECTOR_VGA: + aconnector->base.polled = + DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT; + break; + default: + break; + } + + drm_object_attach_property(&aconnector->base.base, + dm->ddev->mode_config.scaling_mode_property, + DRM_MODE_SCALE_NONE); + + if (connector_type == DRM_MODE_CONNECTOR_HDMIA + || (connector_type == DRM_MODE_CONNECTOR_DisplayPort && !aconnector->mst_root)) + drm_connector_attach_broadcast_rgb_property(&aconnector->base); + + drm_object_attach_property(&aconnector->base.base, + adev->mode_info.underscan_property, + UNDERSCAN_OFF); + drm_object_attach_property(&aconnector->base.base, + adev->mode_info.underscan_hborder_property, + 0); + drm_object_attach_property(&aconnector->base.base, + adev->mode_info.underscan_vborder_property, + 0); + + if (!aconnector->mst_root) + drm_connector_attach_max_bpc_property(&aconnector->base, 8, 16); + + aconnector->base.state->max_bpc = 16; + aconnector->base.state->max_requested_bpc = aconnector->base.state->max_bpc; + + if (connector_type == DRM_MODE_CONNECTOR_HDMIA) { + /* Content Type is currently only implemented for HDMI. */ + drm_connector_attach_content_type_property(&aconnector->base); + } + + if (connector_type == DRM_MODE_CONNECTOR_HDMIA) { + if (!drm_mode_create_hdmi_colorspace_property(&aconnector->base, supported_colorspaces)) + drm_connector_attach_colorspace_property(&aconnector->base); + } else if ((connector_type == DRM_MODE_CONNECTOR_DisplayPort && !aconnector->mst_root) || + connector_type == DRM_MODE_CONNECTOR_eDP) { + if (!drm_mode_create_dp_colorspace_property(&aconnector->base, supported_colorspaces)) + drm_connector_attach_colorspace_property(&aconnector->base); + } + + if (connector_type == DRM_MODE_CONNECTOR_HDMIA || + connector_type == DRM_MODE_CONNECTOR_DisplayPort || + connector_type == DRM_MODE_CONNECTOR_eDP) { + drm_connector_attach_hdr_output_metadata_property(&aconnector->base); + + if (!aconnector->mst_root) + drm_connector_attach_vrr_capable_property(&aconnector->base); + + + if (adev->dm.hdcp_workqueue) + drm_connector_attach_content_protection_property(&aconnector->base, true); + } + + if (connector_type == DRM_MODE_CONNECTOR_eDP) { + struct drm_privacy_screen *privacy_screen; + + drm_connector_attach_panel_type_property(&aconnector->base); + + privacy_screen = drm_privacy_screen_get(adev_to_drm(adev)->dev, NULL); + if (!IS_ERR(privacy_screen)) { + drm_connector_attach_privacy_screen_provider(&aconnector->base, + privacy_screen); + } else if (PTR_ERR(privacy_screen) != -ENODEV) { + drm_warn(adev_to_drm(adev), "Error getting privacy-screen\n"); + } + } +} + +static int amdgpu_dm_i2c_xfer(struct i2c_adapter *i2c_adap, + struct i2c_msg *msgs, int num) +{ + struct amdgpu_i2c_adapter *i2c = i2c_get_adapdata(i2c_adap); + struct ddc_service *ddc_service = i2c->ddc_service; + struct i2c_command cmd; + int i; + int result = -EIO; + + if (!ddc_service->ddc_pin) + return result; + + cmd.payloads = kzalloc_objs(struct i2c_payload, num); + + if (!cmd.payloads) + return result; + + cmd.number_of_payloads = num; + cmd.engine = I2C_COMMAND_ENGINE_DEFAULT; + cmd.speed = 100; + + for (i = 0; i < num; i++) { + cmd.payloads[i].write = !(msgs[i].flags & I2C_M_RD); + cmd.payloads[i].address = msgs[i].addr; + cmd.payloads[i].length = msgs[i].len; + cmd.payloads[i].data = msgs[i].buf; + } + + if (i2c->oem) { + if (dc_submit_i2c_oem( + ddc_service->ctx->dc, + &cmd)) + result = num; + } else { + if (dc_submit_i2c( + ddc_service->ctx->dc, + ddc_service->link->link_index, + &cmd)) + result = num; + } + + kfree(cmd.payloads); + return result; +} + +static u32 amdgpu_dm_i2c_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static const struct i2c_algorithm amdgpu_dm_i2c_algo = { + .master_xfer = amdgpu_dm_i2c_xfer, + .functionality = amdgpu_dm_i2c_func, +}; + +struct amdgpu_i2c_adapter * +amdgpu_dm_create_i2c(struct ddc_service *ddc_service, bool oem) +{ + struct amdgpu_device *adev = ddc_service->ctx->driver_context; + struct amdgpu_i2c_adapter *i2c; + + i2c = kzalloc_obj(struct amdgpu_i2c_adapter); + if (!i2c) + return NULL; + i2c->base.owner = THIS_MODULE; + i2c->base.dev.parent = &adev->pdev->dev; + i2c->base.algo = &amdgpu_dm_i2c_algo; + if (oem) + snprintf(i2c->base.name, sizeof(i2c->base.name), "AMDGPU DM i2c OEM bus"); + else + snprintf(i2c->base.name, sizeof(i2c->base.name), "AMDGPU DM i2c hw bus %d", + ddc_service->link->link_index); + i2c_set_adapdata(&i2c->base, i2c); + i2c->ddc_service = ddc_service; + i2c->oem = oem; + + return i2c; +} + +int amdgpu_dm_initialize_hdmi_connector(struct amdgpu_dm_connector *aconnector) +{ + struct cec_connector_info conn_info; + struct drm_device *ddev = aconnector->base.dev; + struct device *hdmi_dev = ddev->dev; + + if (amdgpu_dc_debug_mask & DC_DISABLE_HDMI_CEC) { + drm_info(ddev, "HDMI-CEC feature masked\n"); + return -EINVAL; + } + + cec_fill_conn_info_from_drm(&conn_info, &aconnector->base); + aconnector->notifier = + cec_notifier_conn_register(hdmi_dev, NULL, &conn_info); + if (!aconnector->notifier) { + drm_err(ddev, "Failed to create cec notifier\n"); + return -ENOMEM; + } + + return 0; +} + +/* + * Note: this function assumes that dc_link_detect() was called for the + * dc_link which will be represented by this aconnector. + */ +int amdgpu_dm_connector_init(struct amdgpu_display_manager *dm, + struct amdgpu_dm_connector *aconnector, + u32 link_index, + struct amdgpu_encoder *aencoder) +{ + int res = 0; + int connector_type; + struct dc *dc = dm->dc; + struct dc_link *link = dc_get_link_at_index(dc, link_index); + struct amdgpu_i2c_adapter *i2c; + + /* Not needed for writeback connector */ + link->priv = aconnector; + + + i2c = amdgpu_dm_create_i2c(link->ddc, false); + if (!i2c) { + drm_err(adev_to_drm(dm->adev), "Failed to create i2c adapter data\n"); + return -ENOMEM; + } + + aconnector->i2c = i2c; + res = devm_i2c_add_adapter(dm->adev->dev, &i2c->base); + + if (res) { + drm_err(adev_to_drm(dm->adev), "Failed to register hw i2c %d\n", link->link_index); + goto out_free; + } + + connector_type = to_drm_connector_type(link->connector_signal, link->link_id.id); + + res = drm_connector_init_with_ddc( + dm->ddev, + &aconnector->base, + &amdgpu_dm_connector_funcs, + connector_type, + &i2c->base); + + if (res) { + drm_err(adev_to_drm(dm->adev), "connector_init failed\n"); + aconnector->connector_id = -1; + goto out_free; + } + + drm_connector_helper_add( + &aconnector->base, + &amdgpu_dm_connector_helper_funcs); + + amdgpu_dm_connector_init_helper( + dm, + aconnector, + connector_type, + link, + link_index); + + drm_connector_attach_encoder( + &aconnector->base, &aencoder->base); + + if (connector_type == DRM_MODE_CONNECTOR_HDMIA || + connector_type == DRM_MODE_CONNECTOR_HDMIB) + amdgpu_dm_initialize_hdmi_connector(aconnector); + + if (dc_is_dp_signal(link->connector_signal)) + amdgpu_dm_initialize_dp_connector(dm, aconnector, link->link_index); + +out_free: + if (res) { + kfree(i2c); + aconnector->i2c = NULL; + } + return res; +} + +static int dm_force_atomic_commit(struct drm_connector *connector) +{ + int ret = 0; + struct drm_device *ddev = connector->dev; + struct drm_atomic_commit *state = drm_atomic_commit_alloc(ddev); + struct amdgpu_crtc *disconnected_acrtc = to_amdgpu_crtc(connector->encoder->crtc); + struct drm_plane *plane = disconnected_acrtc->base.primary; + struct drm_connector_state *conn_state; + struct drm_crtc_state *crtc_state; + struct drm_plane_state *plane_state; + + if (!state) + return -ENOMEM; + + state->acquire_ctx = ddev->mode_config.acquire_ctx; + + /* Construct an atomic state to restore previous display setting */ + + /* + * Attach connectors to drm_atomic_commit + */ + conn_state = drm_atomic_get_connector_state(state, connector); + + /* Check for error in getting connector state */ + if (IS_ERR(conn_state)) { + ret = PTR_ERR(conn_state); + goto out; + } + + /* Attach crtc to drm_atomic_commit*/ + crtc_state = drm_atomic_get_crtc_state(state, &disconnected_acrtc->base); + + /* Check for error in getting crtc state */ + if (IS_ERR(crtc_state)) { + ret = PTR_ERR(crtc_state); + goto out; + } + + /* force a restore */ + crtc_state->mode_changed = true; + + /* Attach plane to drm_atomic_commit */ + plane_state = drm_atomic_get_plane_state(state, plane); + + /* Check for error in getting plane state */ + if (IS_ERR(plane_state)) { + ret = PTR_ERR(plane_state); + goto out; + } + + /* Call commit internally with the state we just constructed */ + ret = drm_atomic_commit(state); + +out: + drm_atomic_commit_put(state); + if (ret) + drm_err(ddev, "Restoring old state failed with %i\n", ret); + + return ret; +} + +/* + * This function handles all cases when set mode does not come upon hotplug. + * This includes when a display is unplugged then plugged back into the + * same port and when running without usermode desktop manager support + */ +void dm_restore_drm_connector_state(struct drm_device *dev, + struct drm_connector *connector) +{ + struct amdgpu_dm_connector *aconnector; + struct amdgpu_crtc *disconnected_acrtc; + struct dm_crtc_state *acrtc_state; + + if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) + return; + + aconnector = to_amdgpu_dm_connector(connector); + + if (!aconnector->dc_sink || !connector->state || !connector->encoder) + return; + + disconnected_acrtc = to_amdgpu_crtc(connector->encoder->crtc); + if (!disconnected_acrtc) + return; + + acrtc_state = to_dm_crtc_state(disconnected_acrtc->base.state); + if (!acrtc_state->stream) + return; + + /* + * If the previous sink is not released and different from the current, + * we deduce we are in a state where we can not rely on usermode call + * to turn on the display, so we do it here + */ + if (acrtc_state->stream->sink != aconnector->dc_sink) + 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) +{ + u8 *edid_ext = NULL; + int i; + int j = 0; + u16 min_vfreq; + u16 max_vfreq; + + if (!edid || !edid->extensions) + return; + + /* Find DisplayID extension */ + for (i = 0; i < edid->extensions; i++) { + edid_ext = (void *)(edid + (i + 1)); + if (edid_ext[0] == DISPLAYID_EXT) + break; + } + + if (i == edid->extensions) + return; + + while (j < EDID_LENGTH) { + /* Get dynamic video timing range from DisplayID if available */ + if (EDID_LENGTH - j > 13 && edid_ext[j] == 0x25 && + (edid_ext[j+1] & 0xFE) == 0 && (edid_ext[j+2] == 9)) { + min_vfreq = edid_ext[j+9]; + if (edid_ext[j+1] & 7) + max_vfreq = edid_ext[j+10] + ((edid_ext[j+11] & 3) << 8); + else + max_vfreq = edid_ext[j+10]; + + if (max_vfreq && min_vfreq) { + connector->display_info.monitor_range.max_vfreq = max_vfreq; + connector->display_info.monitor_range.min_vfreq = min_vfreq; + + return; + } + } + j++; + } +} + +static int get_amd_vsdb(struct amdgpu_dm_connector *aconnector, + struct amdgpu_hdmi_vsdb_info *vsdb_info) +{ + struct drm_connector *connector = &aconnector->base; + + vsdb_info->replay_mode = connector->display_info.amd_vsdb.replay_mode; + vsdb_info->amd_vsdb_version = connector->display_info.amd_vsdb.version; + + 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 + * + * @connector: Connector to query. + * @drm_edid: DRM EDID from monitor + * @do_mccs: Controls whether MCCS (Monitor Control Command Set) over + * DDC (Display Data Channel) transactions are performed. When true, + * the driver queries the monitor to get or update additional FreeSync + * capability information. When false, these transactions are skipped. + * + * Amdgpu supports Freesync in DP and HDMI displays, and it is required to keep + * track of some of the display information in the internal data struct used by + * amdgpu_dm. This function checks which type of connector we need to set the + * FreeSync parameters. + */ +void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, + const struct drm_edid *drm_edid, bool do_mccs) +{ + int i = 0; + struct amdgpu_dm_connector *amdgpu_dm_connector = + to_amdgpu_dm_connector(connector); + struct dm_connector_state *dm_con_state = NULL; + struct dc_sink *sink; + struct amdgpu_device *adev = drm_to_adev(connector->dev); + struct amdgpu_hdmi_vsdb_info vsdb_info = {0}; + const struct edid *edid; + bool freesync_capable = false; + enum adaptive_sync_type as_type = ADAPTIVE_SYNC_TYPE_NONE; + + if (!connector->state) { + drm_err(adev_to_drm(adev), "%s - Connector has no state", __func__); + goto update; + } + + sink = amdgpu_dm_connector->dc_sink ? + amdgpu_dm_connector->dc_sink : + amdgpu_dm_connector->dc_em_sink; + + drm_edid_connector_update(connector, drm_edid); + + if (!drm_edid || !sink) { + dm_con_state = to_dm_connector_state(connector->state); + + amdgpu_dm_connector->min_vfreq = 0; + amdgpu_dm_connector->max_vfreq = 0; + freesync_capable = false; + + goto update; + } + + dm_con_state = to_dm_connector_state(connector->state); + + if (!adev->dm.freesync_module || !dc_supports_vrr(sink->ctx->dce_version)) + goto update; + + /* FIXME: Get rid of drm_edid_raw() */ + edid = drm_edid_raw(drm_edid); + + /* Some eDP panels only have the refresh rate range info in DisplayID */ + if ((connector->display_info.monitor_range.min_vfreq == 0 || + 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; + } + + get_amd_vsdb(amdgpu_dm_connector, &vsdb_info); + + 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 (drm_edid && sink->sink_signal == SIGNAL_TYPE_HDMI_TYPE_A) { + i = parse_hdmi_amd_vsdb(amdgpu_dm_connector, edid, &vsdb_info); + if (i >= 0) { + 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 (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 = parse_hdmi_amd_vsdb(amdgpu_dm_connector, edid, &vsdb_info); + if (i >= 0) { + amdgpu_dm_connector->vsdb_info = vsdb_info; + 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; + + 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; + } + } + } + + /* Handle MCCS */ + if (do_mccs) + dm_helpers_read_mccs_caps(adev->dm.dc->ctx, amdgpu_dm_connector->dc_link, sink); + + if ((sink->sink_signal == SIGNAL_TYPE_HDMI_TYPE_A || + as_type == FREESYNC_TYPE_PCON_IN_WHITELIST) && + (!sink->edid_caps.freesync_vcp_code || + (sink->edid_caps.freesync_vcp_code && !sink->mccs_caps.freesync_supported))) + freesync_capable = false; + + if (do_mccs && sink->mccs_caps.freesync_supported && freesync_capable) + dm_helpers_mccs_vcp_set(adev->dm.dc->ctx, amdgpu_dm_connector->dc_link, sink); + +update: + if (dm_con_state) + dm_con_state->freesync_capable = freesync_capable; + + if (connector->state && amdgpu_dm_connector->dc_link && !freesync_capable && + amdgpu_dm_connector->dc_link->replay_settings.config.replay_supported) { + amdgpu_dm_connector->dc_link->replay_settings.config.replay_supported = false; + amdgpu_dm_connector->dc_link->replay_settings.replay_feature_enabled = false; + } + + if (connector->vrr_capable_property) + drm_connector_set_vrr_capable_property(connector, + freesync_capable); +} diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h new file mode 100644 index 000000000000..db8e5588dbfd --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h @@ -0,0 +1,147 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright 2026 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 __AMDGPU_DM_CONNECTOR_H__ +#define __AMDGPU_DM_CONNECTOR_H__ + +struct amdgpu_device; +struct amdgpu_dm_connector; +struct amdgpu_display_manager; +struct amdgpu_encoder; +struct amdgpu_i2c_adapter; +struct dc_crtc_timing; +struct dc_link; +struct dc_state; +struct dc_stream_state; +struct ddc_service; +struct dm_connector_state; +struct drm_atomic_commit; +struct drm_device; +struct drm_encoder_helper_funcs; +struct drm_connector; +struct drm_connector_state; +struct drm_crtc; +struct drm_device; +struct drm_display_mode; +struct drm_edid; +struct drm_property; + +void amdgpu_dm_connector_funcs_reset(struct drm_connector *connector); + +struct drm_connector_state * +amdgpu_dm_connector_atomic_duplicate_state(struct drm_connector *connector); + +int amdgpu_dm_connector_atomic_set_property(struct drm_connector *connector, + struct drm_connector_state *connector_state, + struct drm_property *property, + uint64_t val); + +int amdgpu_dm_connector_atomic_get_property(struct drm_connector *connector, + const struct drm_connector_state *state, + struct drm_property *property, + uint64_t *val); + +void amdgpu_dm_connector_init_helper(struct amdgpu_display_manager *dm, + struct amdgpu_dm_connector *aconnector, + int connector_type, + struct dc_link *link, + int link_index); + +enum drm_mode_status amdgpu_dm_connector_mode_valid(struct drm_connector *connector, + const struct drm_display_mode *mode); + +void dm_restore_drm_connector_state(struct drm_device *dev, + struct drm_connector *connector); + +void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, + const struct drm_edid *drm_edid, + bool do_mccs); + +void amdgpu_dm_update_connector_after_detect( + struct amdgpu_dm_connector *aconnector); + +void amdgpu_dm_hdmi_cec_set_edid(struct amdgpu_dm_connector *aconnector); +int amdgpu_dm_initialize_hdmi_connector(struct amdgpu_dm_connector *aconnector); + +struct drm_connector * +amdgpu_dm_find_first_crtc_matching_connector(struct drm_atomic_commit *state, + struct drm_crtc *crtc); + +int amdgpu_dm_convert_dc_color_depth_into_bpc(enum dc_color_depth display_color_depth); + +struct dc_stream_state * +amdgpu_dm_create_validate_stream_for_sink(struct drm_connector *connector, + const struct drm_display_mode *drm_mode, + const struct dm_connector_state *dm_state, + const struct dc_stream_state *old_stream); + +int amdgpu_dm_connector_init(struct amdgpu_display_manager *dm, + struct amdgpu_dm_connector *amdgpu_dm_connector, + u32 link_index, + struct amdgpu_encoder *amdgpu_encoder); + +void amdgpu_dm_s3_handle_hdmi_cec(struct drm_device *ddev, bool suspend); + +int amdgpu_dm_detect_mst_link_for_all_connectors(struct drm_device *dev); + +void amdgpu_set_panel_orientation(struct drm_connector *connector); + +enum dc_color_depth +amdgpu_dm_convert_color_depth_from_display_info(const struct drm_connector *connector, + bool is_y420, int requested_bpc); + +void amdgpu_dm_update_stream_scaling_settings(struct drm_device *dev, + const struct drm_display_mode *mode, + const struct dm_connector_state *dm_state, + struct dc_stream_state *stream); + +bool amdgpu_dm_is_freesync_video_mode(const struct drm_display_mode *mode, + struct amdgpu_dm_connector *aconnector); + +int amdgpu_dm_fill_hdr_info_packet(const struct drm_connector_state *state, + struct dc_info_packet *out); + +enum dc_color_space +amdgpu_dm_get_output_color_space(const struct dc_crtc_timing *dc_crtc_timing, + const struct drm_connector_state *connector_state); + +struct drm_display_mode * +amdgpu_dm_get_highest_refresh_rate_mode(struct amdgpu_dm_connector *aconnector, + bool use_probed_modes); + +struct amdgpu_i2c_adapter * +amdgpu_dm_create_i2c(struct ddc_service *ddc_service, bool oem); + +#define DDC_MANUFACTURERNAME_SAMSUNG 0x2D4C + +/* Encoder functions */ +extern const struct drm_encoder_helper_funcs amdgpu_dm_encoder_helper_funcs; +int amdgpu_dm_get_encoder_crtc_mask(struct amdgpu_device *adev); +int amdgpu_dm_encoder_init(struct drm_device *dev, + struct amdgpu_encoder *aencoder, + uint32_t link_index); + +#endif /* __AMDGPU_DM_CONNECTOR_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c index 7db38ad3f848..3bcf3ff30aee 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c @@ -2971,7 +2971,7 @@ static ssize_t hdmi_cec_state_write(struct file *f, const char __user *buf, ret = amdgpu_dm_initialize_hdmi_connector(aconnector); if (ret) return ret; - hdmi_cec_set_edid(aconnector); + amdgpu_dm_hdmi_cec_set_edid(aconnector); } else { if (!aconnector->notifier) return -EINVAL; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c index ff3afeb0ec07..9e1916f8f99b 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c @@ -1784,7 +1784,7 @@ int pre_validate_dsc(struct drm_atomic_commit *state, dm_old_crtc_state = to_dm_crtc_state(state->crtcs[ind].old_state); local_dc_state->streams[i] = - create_validate_stream_for_sink(connector, + amdgpu_dm_create_validate_stream_for_sink(connector, &state->crtcs[ind].new_state->mode, dm_new_conn_state, dm_old_crtc_state->stream); -- cgit v1.2.3 From c1199393ec559071d2afb82399abf4d1a0698c53 Mon Sep 17 00:00:00 2001 From: Rafal Ostrowski Date: Wed, 20 May 2026 10:44:17 +0200 Subject: drm/amd/display: Refactor surface_update_flags to flat struct with helpers [Why] The union surface_update_flags type uses a union with a raw uint32_t member to allow bulk clear/set/test operations on the bitfield. This couples the struct layout to a specific integer width, breaks when the number of flag bits exceeds 32, and scatters raw-access patterns across many call sites. Replacing the union with a plain struct and adding explicit helper functions makes the intent clearer and prepares the code for future flag-set expansion. [How] Rename union surface_update_flags to struct pipe_update_bits and remove the union wrapper, the .bits sub-struct, and the .raw member. Add inline helpers in dc.h: surface_update_flags_clear(), surface_update_flags_set_full(), and surface_update_flags_is_any_set() that operate on the new struct via memset/memcmp. Add stream_update_flags_clear() and stream_update_flags_set_full() in dc_stream.h for the stream update flags union. Update all callers: change the type name, replace .bits.field with .field, replace .raw = 0 with the clear helper, replace .raw = 0xFFFFFFFF with the set_full helper, and replace .raw boolean tests with is_any_set. Reviewed-by: Nicholas Kazlauskas Signed-off-by: Rafal Ostrowski Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 5 +- drivers/gpu/drm/amd/display/dc/core/dc.c | 157 +++++++++++---------- .../gpu/drm/amd/display/dc/core/dc_hw_sequencer.c | 20 +-- drivers/gpu/drm/amd/display/dc/dc.h | 153 ++++++++++++++------ drivers/gpu/drm/amd/display/dc/dc_stream.h | 29 ++++ .../gpu/drm/amd/display/dc/dml/calcs/dcn_calcs.c | 2 +- .../drm/amd/display/dc/hwss/dce110/dce110_hwseq.c | 8 +- .../drm/amd/display/dc/hwss/dce60/dce60_hwseq.c | 8 +- .../drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.c | 62 ++++---- .../drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.c | 56 ++++---- .../drm/amd/display/dc/hwss/dcn201/dcn201_hwseq.c | 2 +- .../drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.c | 4 +- .../drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c | 72 +++++----- .../drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.c | 2 +- 14 files changed, 345 insertions(+), 235 deletions(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') 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 f3833e038e99..68ec8f3264c8 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -2015,8 +2015,7 @@ static int dm_resume(struct amdgpu_ip_block *ip_block) for (i = 0; i < dc_state->stream_count; i++) { dc_state->streams[i]->mode_changed = true; for (j = 0; j < dc_state->stream_status[i].plane_count; j++) { - dc_state->stream_status[i].plane_states[j]->update_flags.raw - = 0xffffffff; + dc_pipe_update_bits_set_full(&dc_state->stream_status[i].plane_states[j]->update_bits); } } @@ -6321,7 +6320,7 @@ static int dm_update_plane_state(struct dc *dc, /* Tell DC to do a full surface update every time there * is a plane change. Inefficient, but works for now. */ - dm_new_plane_state->dc_state->update_flags.bits.full_update = 1; + dm_new_plane_state->dc_state->update_bits.full_update = 1; *lock_and_validation_needed = true; } diff --git a/drivers/gpu/drm/amd/display/dc/core/dc.c b/drivers/gpu/drm/amd/display/dc/core/dc.c index 4220481d3960..0e3c27d526c3 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc.c @@ -2310,7 +2310,7 @@ static enum dc_status dc_commit_state_no_check(struct dc *dc, struct dc_state *c for (i = 0; i < context->stream_count; i++) { uint32_t prev_dsc_changed = context->streams[i]->update_flags.bits.dsc_changed; - context->streams[i]->update_flags.raw = 0xFFFFFFFF; + stream_update_flags_set_full(&context->streams[i]->update_flags); context->streams[i]->update_flags.bits.dsc_changed = prev_dsc_changed; } @@ -2416,7 +2416,7 @@ static enum dc_status dc_commit_state_no_check(struct dc *dc, struct dc_state *c /* Clear update flags that were set earlier to avoid redundant programming */ for (i = 0; i < context->stream_count; i++) { - context->streams[i]->update_flags.raw = 0x0; + stream_update_flags_clear(&context->streams[i]->update_flags); } old_state = dc->current_state; @@ -2764,7 +2764,7 @@ static bool is_surface_in_context( static struct surface_update_descriptor get_plane_info_update_type(const struct dc_surface_update *u) { - union surface_update_flags *update_flags = &u->surface->update_flags; + struct pipe_update_bits *update_bits = &u->surface->update_bits; struct surface_update_descriptor update_type = { UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_NONE }; if (!u->plane_info) @@ -2774,37 +2774,37 @@ static struct surface_update_descriptor get_plane_info_update_type(const struct elevate_update_type(&update_type, UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_STREAM); if (u->plane_info->color_space != u->surface->color_space) { - update_flags->bits.color_space_change = 1; + update_bits->color_space_change = 1; elevate_update_type(&update_type, UPDATE_TYPE_MED, LOCK_DESCRIPTOR_STREAM); } if (u->plane_info->horizontal_mirror != u->surface->horizontal_mirror) { - update_flags->bits.horizontal_mirror_change = 1; + update_bits->horizontal_mirror_change = 1; elevate_update_type(&update_type, UPDATE_TYPE_MED, LOCK_DESCRIPTOR_STREAM); } if (u->plane_info->rotation != u->surface->rotation) { - update_flags->bits.rotation_change = 1; + update_bits->rotation_change = 1; elevate_update_type(&update_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL); } if (u->plane_info->format != u->surface->format) { - update_flags->bits.pixel_format_change = 1; + update_bits->pixel_format_change = 1; elevate_update_type(&update_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL); } if (u->plane_info->stereo_format != u->surface->stereo_format) { - update_flags->bits.stereo_format_change = 1; + update_bits->stereo_format_change = 1; elevate_update_type(&update_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL); } if (u->plane_info->per_pixel_alpha != u->surface->per_pixel_alpha) { - update_flags->bits.per_pixel_alpha_change = 1; + update_bits->per_pixel_alpha_change = 1; elevate_update_type(&update_type, UPDATE_TYPE_MED, LOCK_DESCRIPTOR_STREAM); } if (u->plane_info->global_alpha_value != u->surface->global_alpha_value) { - update_flags->bits.global_alpha_change = 1; + update_bits->global_alpha_change = 1; elevate_update_type(&update_type, UPDATE_TYPE_MED, LOCK_DESCRIPTOR_STREAM); } @@ -2816,7 +2816,7 @@ static struct surface_update_descriptor get_plane_info_update_type(const struct * stutter period calculation. Triggering a full update will * recalculate stutter period. */ - update_flags->bits.dcc_change = 1; + update_bits->dcc_change = 1; elevate_update_type(&update_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL); } @@ -2825,25 +2825,25 @@ static struct surface_update_descriptor get_plane_info_update_type(const struct /* different bytes per element will require full bandwidth * and DML calculation */ - update_flags->bits.bpp_change = 1; + update_bits->bpp_change = 1; elevate_update_type(&update_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL); } if (u->plane_info->plane_size.surface_pitch != u->surface->plane_size.surface_pitch || u->plane_info->plane_size.chroma_pitch != u->surface->plane_size.chroma_pitch) { - update_flags->bits.plane_size_change = 1; + update_bits->plane_size_change = 1; elevate_update_type(&update_type, UPDATE_TYPE_MED, LOCK_DESCRIPTOR_STREAM); } const struct dc_tiling_info *tiling = &u->plane_info->tiling_info; if (memcmp(tiling, &u->surface->tiling_info, sizeof(*tiling)) != 0) { - update_flags->bits.swizzle_change = 1; + update_bits->swizzle_change = 1; if (tiling->flags.avoid_full_update_on_tiling_change) { elevate_update_type(&update_type, UPDATE_TYPE_MED, LOCK_DESCRIPTOR_STREAM); } else { - update_flags->bits.bandwidth_change = 1; + update_bits->bandwidth_change = 1; elevate_update_type(&update_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL); } } @@ -2853,10 +2853,10 @@ static struct surface_update_descriptor get_plane_info_update_type(const struct } static struct surface_update_descriptor get_scaling_info_update_type( - const struct dc_check_config *check_config, - const struct dc_surface_update *u) + const struct dc_check_config *check_config, + const struct dc_surface_update *u) { - union surface_update_flags *update_flags = &u->surface->update_flags; + struct pipe_update_bits *update_bits = &u->surface->update_bits; struct surface_update_descriptor update_type = { UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_NONE }; if (!u->scaling_info) @@ -2873,26 +2873,26 @@ static struct surface_update_descriptor get_scaling_info_update_type( || u->scaling_info->clip_rect.height != u->surface->clip_rect.height || u->scaling_info->scaling_quality.integer_scaling != u->surface->scaling_quality.integer_scaling) { - update_flags->bits.scaling_change = 1; + update_bits->scaling_change = 1; elevate_update_type(&update_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL); if (u->scaling_info->src_rect.width > u->surface->src_rect.width || u->scaling_info->src_rect.height > u->surface->src_rect.height) /* Making src rect bigger requires a bandwidth change */ - update_flags->bits.clock_change = 1; + update_bits->clock_change = 1; if ((u->scaling_info->dst_rect.width < u->surface->dst_rect.width || u->scaling_info->dst_rect.height < u->surface->dst_rect.height) && (u->scaling_info->dst_rect.width < u->surface->src_rect.width || u->scaling_info->dst_rect.height < u->surface->src_rect.height)) /* Making dst rect smaller requires a bandwidth change */ - update_flags->bits.bandwidth_change = 1; + update_bits->bandwidth_change = 1; if (u->scaling_info->src_rect.width > (int)check_config->max_optimizable_video_width && (u->scaling_info->clip_rect.width > u->surface->clip_rect.width || u->scaling_info->clip_rect.height > u->surface->clip_rect.height)) /* Changing clip size of a large surface may result in MPC slice count change */ - update_flags->bits.bandwidth_change = 1; + update_bits->bandwidth_change = 1; } if (u->scaling_info->src_rect.x != u->surface->src_rect.x @@ -2902,7 +2902,7 @@ static struct surface_update_descriptor get_scaling_info_update_type( || u->scaling_info->dst_rect.x != u->surface->dst_rect.x || u->scaling_info->dst_rect.y != u->surface->dst_rect.y) { elevate_update_type(&update_type, UPDATE_TYPE_MED, LOCK_DESCRIPTOR_STREAM); - update_flags->bits.position_change = 1; + update_bits->position_change = 1; } return update_type; @@ -2913,15 +2913,15 @@ static struct surface_update_descriptor det_surface_update( struct dc_surface_update *u) { struct surface_update_descriptor overall_type = { UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_NONE }; - union surface_update_flags *update_flags = &u->surface->update_flags; + struct pipe_update_bits *update_bits = &u->surface->update_bits; if (u->surface->force_full_update) { - update_flags->raw = 0xFFFFFFFF; + dc_pipe_update_bits_set_full(update_bits); elevate_update_type(&overall_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL); return overall_type; } - update_flags->raw = 0; // Reset all flags + dc_pipe_update_bits_clear(update_bits); struct surface_update_descriptor inner_type = get_plane_info_update_type(u); @@ -2931,47 +2931,47 @@ static struct surface_update_descriptor det_surface_update( elevate_update_type(&overall_type, inner_type.update_type, inner_type.lock_descriptor); if (u->flip_addr) { - update_flags->bits.addr_update = 1; + update_bits->addr_update = 1; elevate_update_type(&overall_type, UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_STREAM); if (u->flip_addr->address.tmz_surface != u->surface->address.tmz_surface) { - update_flags->bits.tmz_changed = 1; + update_bits->tmz_changed = 1; elevate_update_type(&overall_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL); } } if (u->in_transfer_func) { - update_flags->bits.in_transfer_func_change = 1; + update_bits->in_transfer_func_change = 1; elevate_update_type(&overall_type, UPDATE_TYPE_MED, LOCK_DESCRIPTOR_STREAM); } if (u->input_csc_color_matrix) { - update_flags->bits.input_csc_change = 1; + update_bits->input_csc_change = 1; elevate_update_type(&overall_type, UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_STREAM); } if (u->cursor_csc_color_matrix) { - update_flags->bits.cursor_csc_color_matrix_change = 1; + update_bits->cursor_csc_color_matrix_change = 1; elevate_update_type(&overall_type, UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_STREAM); } if (u->coeff_reduction_factor) { - update_flags->bits.coeff_reduction_change = 1; + update_bits->coeff_reduction_change = 1; elevate_update_type(&overall_type, UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_STREAM); } if (u->gamut_remap_matrix) { - update_flags->bits.gamut_remap_change = 1; + update_bits->gamut_remap_change = 1; elevate_update_type(&overall_type, UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_STREAM); } if ((u->cm && u->cm->flags.bits.blend_enable) || (u->gamma && dce_use_lut(u->plane_info ? u->plane_info->format : u->surface->format))) { - update_flags->bits.gamma_change = 1; + update_bits->gamma_change = 1; elevate_update_type(&overall_type, UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_STREAM); } if (u->cm && (u->cm->flags.bits.lut3d_enable || u->cm->flags.bits.shaper_enable)) { - update_flags->bits.lut_3d = 1; + update_bits->lut_3d = 1; elevate_update_type(&overall_type, UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_STREAM); } @@ -2989,19 +2989,19 @@ static struct surface_update_descriptor det_surface_update( if (u->hdr_mult.value) if (u->hdr_mult.value != u->surface->hdr_mult.value) { // TODO: Should be fast? - update_flags->bits.hdr_mult = 1; + update_bits->hdr_mult = 1; elevate_update_type(&overall_type, UPDATE_TYPE_MED, LOCK_DESCRIPTOR_STREAM); } if (u->sdr_white_level_nits) if (u->sdr_white_level_nits != u->surface->sdr_white_level_nits) { // TODO: Should be fast? - update_flags->bits.sdr_white_level_nits = 1; + update_bits->sdr_white_level_nits = 1; elevate_update_type(&overall_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL); } if (u->cm_hist_control) { - update_flags->bits.cm_hist_change = 1; + update_bits->cm_hist_change = 1; elevate_update_type(&overall_type, UPDATE_TYPE_FAST, LOCK_DESCRIPTOR_STREAM); } @@ -3016,7 +3016,7 @@ static struct surface_update_descriptor det_surface_update( || u->cm->flags.bits.blend_enable != u->surface->cm.flags.bits.blend_enable || u->cm->flags.bits.lut3d_enable != u->surface->cm.flags.bits.lut3d_enable || u->cm->flags.bits.lut3d_dma_enable != u->surface->cm.flags.bits.lut3d_dma_enable) { - update_flags->bits.mcm_transfer_function_enable_change = 1; + update_bits->mcm_transfer_function_enable_change = 1; elevate_update_type(&overall_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL); } @@ -3026,17 +3026,17 @@ static struct surface_update_descriptor det_surface_update( } } - if (update_flags->bits.lut_3d && + if (update_bits->lut_3d && !u->surface->cm.flags.bits.lut3d_dma_enable) { elevate_update_type(&overall_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL); } if (check_config->enable_legacy_fast_update && - (update_flags->bits.gamma_change || - update_flags->bits.gamut_remap_change || - update_flags->bits.input_csc_change || - update_flags->bits.cm_hist_change || - update_flags->bits.coeff_reduction_change)) { + (update_bits->gamma_change || + update_bits->gamut_remap_change || + update_bits->input_csc_change || + update_bits->cm_hist_change || + update_bits->coeff_reduction_change)) { elevate_update_type(&overall_type, UPDATE_TYPE_FULL, LOCK_DESCRIPTOR_GLOBAL); } return overall_type; @@ -3061,7 +3061,7 @@ static void force_immediate_gsl_plane_flip(struct dc *dc, struct dc_surface_upda if (has_flip_immediate_plane && surface_count > 1) { for (i = 0; i < surface_count; i++) { if (updates[i].surface->flip_immediate) - updates[i].surface->update_flags.bits.addr_update = 1; + updates[i].surface->update_bits.addr_update = 1; } } } @@ -3216,9 +3216,9 @@ struct surface_update_descriptor dc_check_update_surfaces_for_stream( struct dc_stream_update *stream_update) { if (stream_update) - stream_update->stream->update_flags.raw = 0; + stream_update_flags_clear(&stream_update->stream->update_flags); for (int i = 0; i < surface_count; i++) - updates[i].surface->update_flags.raw = 0; + dc_pipe_update_bits_clear(&updates[i].surface->update_bits); return check_update_surfaces_for_stream(check_config, updates, surface_count, stream_update); } @@ -3765,11 +3765,11 @@ static bool update_planes_and_stream_state(struct dc *dc, if (update_type == UPDATE_TYPE_FULL) { if (stream_update) { uint32_t dsc_changed = stream_update->stream->update_flags.bits.dsc_changed; - stream_update->stream->update_flags.raw = 0xFFFFFFFF; + stream_update_flags_set_full(&stream_update->stream->update_flags); stream_update->stream->update_flags.bits.dsc_changed = dsc_changed; } for (i = 0; i < surface_count; i++) - srf_updates[i].surface->update_flags.raw = 0xFFFFFFFF; + dc_pipe_update_bits_set_full(&srf_updates[i].surface->update_bits); } if (update_type >= update_surface_trace_level) @@ -3818,7 +3818,7 @@ static bool update_planes_and_stream_state(struct dc *dc, if (update_type != UPDATE_TYPE_MED) continue; - if (surface->update_flags.bits.position_change) { + if (surface->update_bits.position_change) { for (j = 0; j < dc->res_pool->pipe_count; j++) { struct pipe_ctx *pipe_ctx = &context->res_ctx.pipe_ctx[j]; @@ -4601,14 +4601,23 @@ static void build_dmub_update_dirty_rect( } } -static bool check_address_only_update(union surface_update_flags update_flags) +/** + * dc_check_address_only_update - Check if addr_update is the sole flag set + * + * @update_bits: The pipe update bits to check + * + * Determines whether an update contains only an address change with no other + * pending updates. + * + * Return: %true if addr_update is the sole bit set, %false otherwise. + */ +bool dc_check_address_only_update(struct pipe_update_bits update_bits) { - union surface_update_flags addr_only_update_flags; - addr_only_update_flags.raw = 0; - addr_only_update_flags.bits.addr_update = 1; + struct pipe_update_bits check = update_bits; /* 1. Copy all flags from input */ - return update_flags.bits.addr_update && - !(update_flags.raw & ~addr_only_update_flags.raw); + check.addr_update = 0; /* 2. Zero the addr_update bit in the copy */ + return update_bits.addr_update && /* 3. Check addr_update was set in original */ + !dc_pipe_update_bits_is_any_set(&check); /* 4. Check no other bits remain in the copy */ } /** @@ -4668,7 +4677,7 @@ static void commit_plane_for_stream_offload_fams2_flip(struct dc *dc, continue; /* update pipe context for plane */ - if (pipe_ctx->plane_state->update_flags.bits.addr_update) + if (pipe_ctx->plane_state->update_bits.addr_update) dc->hwss.update_plane_addr(dc, pipe_ctx); } } @@ -4706,8 +4715,8 @@ static void commit_planes_for_stream_fast(struct dc *dc, should_offload_fams2_flip = true; for (i = 0; i < surface_count; i++) { if (srf_updates[i].surface && - srf_updates[i].surface->update_flags.raw && - !check_address_only_update(srf_updates[i].surface->update_flags)) { + dc_pipe_update_bits_is_any_set(&srf_updates[i].surface->update_bits) && + !dc_check_address_only_update(srf_updates[i].surface->update_bits)) { /* more than address update, need to acquire FAMS2 lock */ should_offload_fams2_flip = false; break; @@ -4798,7 +4807,7 @@ static void commit_planes_for_stream_fast(struct dc *dc, * so no need to clear here. */ if (top_pipe_to_program->stream) - top_pipe_to_program->stream->update_flags.raw = 0; + stream_update_flags_clear(&top_pipe_to_program->stream->update_flags); } static void commit_planes_for_stream(struct dc *dc, @@ -5136,7 +5145,7 @@ static void commit_planes_for_stream(struct dc *dc, dc->hwss.program_triplebuffer( dc, pipe_ctx, pipe_ctx->plane_state->triplebuffer_flips); } - if (pipe_ctx->plane_state->update_flags.bits.addr_update) + if (pipe_ctx->plane_state->update_bits.addr_update) dc->hwss.update_plane_addr(dc, pipe_ctx); } } @@ -5227,7 +5236,7 @@ static void commit_planes_for_stream(struct dc *dc, if (pipe_ctx->bottom_pipe || pipe_ctx->next_odm_pipe || !pipe_ctx->stream || !should_update_pipe_for_stream(context, pipe_ctx, stream) || - !pipe_ctx->plane_state->update_flags.bits.addr_update || + !pipe_ctx->plane_state->update_bits.addr_update || pipe_ctx->plane_state->skip_manual_trigger) continue; @@ -5666,7 +5675,7 @@ static bool commit_minimal_transition_state(struct dc *dc, /* force full surface update */ for (i = 0; i < dc->current_state->stream_count; i++) { for (j = 0; j < (unsigned int)dc->current_state->stream_status[i].plane_count; j++) { - dc->current_state->stream_status[i].plane_states[j]->update_flags.raw = 0xFFFFFFFF; + dc_pipe_update_bits_set_full(&dc->current_state->stream_status[i].plane_states[j]->update_bits); } } @@ -6107,17 +6116,17 @@ static bool update_planes_and_stream_v3(struct dc *dc, return true; } -static void clear_update_flags(struct dc_surface_update *srf_updates, +static void clear_update_bits(struct dc_surface_update *srf_updates, int surface_count, struct dc_stream_state *stream) { int i; if (stream) - stream->update_flags.raw = 0; + stream_update_flags_clear(&stream->update_flags); for (i = 0; i < surface_count; i++) if (srf_updates[i].surface) - srf_updates[i].surface->update_flags.raw = 0; + dc_pipe_update_bits_clear(&srf_updates[i].surface->update_bits); } bool dc_update_planes_and_stream(struct dc *dc, @@ -6169,7 +6178,7 @@ void dc_commit_updates_for_stream(struct dc *dc, } if (ret && dc->ctx->dce_version >= DCN_VERSION_3_2) - clear_update_flags(srf_updates, surface_count, stream); + clear_update_bits(srf_updates, surface_count, stream); } uint8_t dc_get_current_stream_count(struct dc *dc) @@ -7919,7 +7928,7 @@ struct dc_update_scratch_space { struct dc_stream_state *stream; struct dc_stream_update *stream_update; bool update_v3; - bool do_clear_update_flags; + bool do_clear_update_bits; enum surface_update_type update_type; struct dc_state *new_context; enum update_v3_flow flow; @@ -7962,8 +7971,8 @@ static bool update_planes_and_stream_cleanup_v2( const struct dc_update_scratch_space *scratch ) { - if (scratch->do_clear_update_flags) - clear_update_flags(scratch->surface_updates, scratch->surface_count, scratch->stream); + if (scratch->do_clear_update_bits) + clear_update_bits(scratch->surface_updates, scratch->surface_count, scratch->stream); return false; } @@ -8217,8 +8226,8 @@ static bool update_planes_and_stream_cleanup_v3( ASSERT(false); } - if (scratch->do_clear_update_flags) - clear_update_flags(scratch->surface_updates, scratch->surface_count, scratch->stream); + if (scratch->do_clear_update_bits) + clear_update_bits(scratch->surface_updates, scratch->surface_count, scratch->stream); return false; } @@ -8241,7 +8250,7 @@ struct dc_update_scratch_space *dc_update_planes_and_stream_init( .stream = stream, .stream_update = stream_update, .update_v3 = version >= DCN_VERSION_4_01 || version == DCN_VERSION_3_2 || version == DCN_VERSION_3_21, - .do_clear_update_flags = version >= DCN_VERSION_1_0, + .do_clear_update_bits = version >= DCN_VERSION_1_0, }; return scratch; diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_hw_sequencer.c b/drivers/gpu/drm/amd/display/dc/core/dc_hw_sequencer.c index 88446817a71f..c7c32c0a6b50 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc_hw_sequencer.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc_hw_sequencer.c @@ -1028,20 +1028,20 @@ void hwss_build_fast_sequence(struct dc *dc, current_mpc_pipe = current_pipe; while (current_mpc_pipe) { if (current_mpc_pipe->plane_state) { - if (dc->hwss.set_flip_control_gsl && current_mpc_pipe->plane_state->update_flags.raw) { + if (dc->hwss.set_flip_control_gsl && dc_pipe_update_bits_is_any_set(¤t_mpc_pipe->plane_state->update_bits)) { block_sequence[*num_steps].params.set_flip_control_gsl_params.hubp = current_mpc_pipe->plane_res.hubp; block_sequence[*num_steps].params.set_flip_control_gsl_params.flip_immediate = current_mpc_pipe->plane_state->flip_immediate; block_sequence[*num_steps].func = HUBP_SET_FLIP_CONTROL_GSL; (*num_steps)++; } - if (dc->hwss.program_triplebuffer && dc->debug.enable_tri_buf && current_mpc_pipe->plane_state->update_flags.raw) { + if (dc->hwss.program_triplebuffer && dc->debug.enable_tri_buf && dc_pipe_update_bits_is_any_set(¤t_mpc_pipe->plane_state->update_bits)) { block_sequence[*num_steps].params.program_triplebuffer_params.dc = dc; block_sequence[*num_steps].params.program_triplebuffer_params.pipe_ctx = current_mpc_pipe; block_sequence[*num_steps].params.program_triplebuffer_params.enableTripleBuffer = current_mpc_pipe->plane_state->triplebuffer_flips; block_sequence[*num_steps].func = HUBP_PROGRAM_TRIPLEBUFFER; (*num_steps)++; } - if (dc->hwss.update_plane_addr && current_mpc_pipe->plane_state->update_flags.bits.addr_update) { + if (dc->hwss.update_plane_addr && current_mpc_pipe->plane_state->update_bits.addr_update) { if (resource_is_pipe_type(current_mpc_pipe, OTG_MASTER) && stream_status->mall_stream_config.type == SUBVP_MAIN) { block_sequence[*num_steps].params.subvp_save_surf_addr.dc_dmub_srv = dc->ctx->dmub_srv; @@ -1057,7 +1057,7 @@ void hwss_build_fast_sequence(struct dc *dc, (*num_steps)++; } - if (hws->funcs.set_input_transfer_func && current_mpc_pipe->plane_state->update_flags.bits.gamma_change) { + if (hws->funcs.set_input_transfer_func && current_mpc_pipe->plane_state->update_bits.gamma_change) { block_sequence[*num_steps].params.set_input_transfer_func_params.dc = dc; block_sequence[*num_steps].params.set_input_transfer_func_params.pipe_ctx = current_mpc_pipe; block_sequence[*num_steps].params.set_input_transfer_func_params.plane_state = current_mpc_pipe->plane_state; @@ -1066,23 +1066,23 @@ void hwss_build_fast_sequence(struct dc *dc, } if (dc->hwss.program_gamut_remap && - (current_mpc_pipe->plane_state->update_flags.bits.gamut_remap_change || + (current_mpc_pipe->plane_state->update_bits.gamut_remap_change || current_mpc_pipe->stream->update_flags.bits.gamut_remap)) { block_sequence[*num_steps].params.program_gamut_remap_params.pipe_ctx = current_mpc_pipe; block_sequence[*num_steps].func = DPP_PROGRAM_GAMUT_REMAP; (*num_steps)++; } - if (current_mpc_pipe->plane_state->update_flags.bits.input_csc_change) { + if (current_mpc_pipe->plane_state->update_bits.input_csc_change) { block_sequence[*num_steps].params.setup_dpp_params.pipe_ctx = current_mpc_pipe; block_sequence[*num_steps].func = DPP_SETUP_DPP; (*num_steps)++; } - if (current_mpc_pipe->plane_state->update_flags.bits.coeff_reduction_change) { + if (current_mpc_pipe->plane_state->update_bits.coeff_reduction_change) { block_sequence[*num_steps].params.program_bias_and_scale_params.pipe_ctx = current_mpc_pipe; block_sequence[*num_steps].func = DPP_PROGRAM_BIAS_AND_SCALE; (*num_steps)++; } - if (current_mpc_pipe->plane_state->update_flags.bits.cm_hist_change) { + if (current_mpc_pipe->plane_state->update_bits.cm_hist_change) { block_sequence[*num_steps].params.control_cm_hist_params.dpp = current_mpc_pipe->plane_res.dpp; block_sequence[*num_steps].params.control_cm_hist_params.cm_hist_control @@ -1095,7 +1095,7 @@ void hwss_build_fast_sequence(struct dc *dc, if (current_mpc_pipe->plane_res.dpp && current_mpc_pipe->plane_res.dpp->funcs->set_cursor_matrix && - current_mpc_pipe->plane_state->update_flags.bits.cursor_csc_color_matrix_change) { + current_mpc_pipe->plane_state->update_bits.cursor_csc_color_matrix_change) { block_sequence[*num_steps].params.dpp_set_cursor_matrix_params.dpp = current_mpc_pipe->plane_res.dpp; block_sequence[*num_steps].params.dpp_set_cursor_matrix_params.color_space = current_mpc_pipe->plane_state->color_space; block_sequence[*num_steps].params.dpp_set_cursor_matrix_params.cursor_csc_color_matrix = ¤t_mpc_pipe->plane_state->cursor_csc_color_matrix; @@ -1176,7 +1176,7 @@ void hwss_build_fast_sequence(struct dc *dc, while (current_mpc_pipe) { if (!current_mpc_pipe->bottom_pipe && !current_mpc_pipe->next_odm_pipe && current_mpc_pipe->stream && current_mpc_pipe->plane_state && - current_mpc_pipe->plane_state->update_flags.bits.addr_update && + current_mpc_pipe->plane_state->update_bits.addr_update && !current_mpc_pipe->plane_state->skip_manual_trigger) { if (dc->hwss.program_cursor_offload_now) { block_sequence[*num_steps].params.program_cursor_update_now_params.dc = dc; diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h index 2a47d7ddf53b..2202c8669bf8 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -1540,47 +1540,120 @@ struct dc_plane_status { struct cm_hist cm_hist; }; -union surface_update_flags { - - struct { - uint32_t addr_update:1; - /* Medium updates */ - uint32_t dcc_change:1; - uint32_t color_space_change:1; - uint32_t horizontal_mirror_change:1; - uint32_t per_pixel_alpha_change:1; - uint32_t global_alpha_change:1; - uint32_t hdr_mult:1; - uint32_t rotation_change:1; - uint32_t swizzle_change:1; - uint32_t scaling_change:1; - uint32_t position_change:1; - uint32_t in_transfer_func_change:1; - uint32_t input_csc_change:1; - uint32_t coeff_reduction_change:1; - uint32_t pixel_format_change:1; - uint32_t plane_size_change:1; - uint32_t gamut_remap_change:1; - uint32_t cursor_csc_color_matrix_change:1; - - /* Full updates */ - uint32_t new_plane:1; - uint32_t bpp_change:1; - uint32_t gamma_change:1; - uint32_t bandwidth_change:1; - uint32_t clock_change:1; - uint32_t stereo_format_change:1; - uint32_t lut_3d:1; - uint32_t tmz_changed:1; - uint32_t mcm_transfer_function_enable_change:1; /* disable or enable MCM transfer func */ - uint32_t full_update:1; - uint32_t sdr_white_level_nits:1; - uint32_t cm_hist_change:1; - } bits; - - uint32_t raw; +struct pipe_update_bits { + uint32_t addr_update:1; + uint32_t dcc_change:1; + uint32_t color_space_change:1; + uint32_t horizontal_mirror_change:1; + uint32_t per_pixel_alpha_change:1; + uint32_t global_alpha_change:1; + uint32_t hdr_mult:1; + uint32_t rotation_change:1; + uint32_t swizzle_change:1; + uint32_t scaling_change:1; + uint32_t position_change:1; + uint32_t in_transfer_func_change:1; + uint32_t input_csc_change:1; + uint32_t coeff_reduction_change:1; + uint32_t pixel_format_change:1; + uint32_t plane_size_change:1; + uint32_t gamut_remap_change:1; + uint32_t cursor_csc_color_matrix_change:1; + uint32_t new_plane:1; + uint32_t bpp_change:1; + uint32_t gamma_change:1; + uint32_t bandwidth_change:1; + uint32_t clock_change:1; + uint32_t stereo_format_change:1; + uint32_t lut_3d:1; + uint32_t tmz_changed:1; + uint32_t mcm_transfer_function_enable_change:1; /* disable or enable MCM transfer func */ + uint32_t full_update:1; + uint32_t sdr_white_level_nits:1; + uint32_t cm_hist_change:1; + /* NOTE: When adding a new field, also update: + * - dc_pipe_update_bits_set_full() + * - dc_pipe_update_bits_is_any_set() + */ }; +static inline void dc_pipe_update_bits_clear(struct pipe_update_bits *flags) +{ + /* memset ensures padding bits are zeroed */ + memset(flags, 0, sizeof(*flags)); +} + +static inline void dc_pipe_update_bits_set_full(struct pipe_update_bits *flags) +{ + dc_pipe_update_bits_clear(flags); + flags->addr_update = 1; + flags->dcc_change = 1; + flags->color_space_change = 1; + flags->horizontal_mirror_change = 1; + flags->per_pixel_alpha_change = 1; + flags->global_alpha_change = 1; + flags->hdr_mult = 1; + flags->rotation_change = 1; + flags->swizzle_change = 1; + flags->scaling_change = 1; + flags->position_change = 1; + flags->in_transfer_func_change = 1; + flags->input_csc_change = 1; + flags->coeff_reduction_change = 1; + flags->pixel_format_change = 1; + flags->plane_size_change = 1; + flags->gamut_remap_change = 1; + flags->cursor_csc_color_matrix_change = 1; + flags->new_plane = 1; + flags->bpp_change = 1; + flags->gamma_change = 1; + flags->bandwidth_change = 1; + flags->clock_change = 1; + flags->stereo_format_change = 1; + flags->lut_3d = 1; + flags->tmz_changed = 1; + flags->mcm_transfer_function_enable_change = 1; + flags->full_update = 1; + flags->sdr_white_level_nits = 1; + flags->cm_hist_change = 1; +} + +static inline bool dc_pipe_update_bits_is_any_set(const struct pipe_update_bits *flags) +{ + return flags->addr_update || + flags->dcc_change || + flags->color_space_change || + flags->horizontal_mirror_change || + flags->per_pixel_alpha_change || + flags->global_alpha_change || + flags->hdr_mult || + flags->rotation_change || + flags->swizzle_change || + flags->scaling_change || + flags->position_change || + flags->in_transfer_func_change || + flags->input_csc_change || + flags->coeff_reduction_change || + flags->pixel_format_change || + flags->plane_size_change || + flags->gamut_remap_change || + flags->cursor_csc_color_matrix_change || + flags->new_plane || + flags->bpp_change || + flags->gamma_change || + flags->bandwidth_change || + flags->clock_change || + flags->stereo_format_change || + flags->lut_3d || + flags->tmz_changed || + flags->mcm_transfer_function_enable_change || + flags->full_update || + flags->sdr_white_level_nits || + flags->cm_hist_change; +} + +bool dc_check_address_only_update(struct pipe_update_bits update_bits); + #define DC_REMOVE_PLANE_POINTERS 1 struct dc_plane_state { @@ -1637,7 +1710,7 @@ struct dc_plane_state { bool horizontal_mirror; unsigned int layer_index; - union surface_update_flags update_flags; + struct pipe_update_bits update_bits; bool flip_int_enabled; bool skip_manual_trigger; diff --git a/drivers/gpu/drm/amd/display/dc/dc_stream.h b/drivers/gpu/drm/amd/display/dc/dc_stream.h index 4154cd059562..8b164edc9c51 100644 --- a/drivers/gpu/drm/amd/display/dc/dc_stream.h +++ b/drivers/gpu/drm/amd/display/dc/dc_stream.h @@ -128,6 +128,35 @@ union stream_update_flags { uint32_t raw; }; +static inline void stream_update_flags_clear(union stream_update_flags *flags) +{ + flags->raw = 0; +} + +static inline void stream_update_flags_set_full(union stream_update_flags *flags) +{ + stream_update_flags_clear(flags); + flags->bits.scaling = 1; + flags->bits.out_tf = 1; + flags->bits.out_csc = 1; + flags->bits.abm_level = 1; + flags->bits.dpms_off = 1; + flags->bits.gamut_remap = 1; + flags->bits.wb_update = 1; + flags->bits.dsc_changed = 1; + flags->bits.mst_bw = 1; + flags->bits.crtc_timing_adjust = 1; + flags->bits.fams_changed = 1; + flags->bits.scaler_sharpener = 1; + flags->bits.sharpening_required = 1; + flags->bits.cursor_attr = 1; + flags->bits.cursor_pos = 1; + flags->bits.periodic_interrupt = 1; + flags->bits.info_frame = 1; + flags->bits.dmdata = 1; + flags->bits.dither = 1; +} + struct test_pattern { enum dp_test_pattern type; enum dp_test_pattern_color_space color_space; diff --git a/drivers/gpu/drm/amd/display/dc/dml/calcs/dcn_calcs.c b/drivers/gpu/drm/amd/display/dc/dml/calcs/dcn_calcs.c index dcca23d53261..2ad4a2635683 100644 --- a/drivers/gpu/drm/amd/display/dc/dml/calcs/dcn_calcs.c +++ b/drivers/gpu/drm/amd/display/dc/dml/calcs/dcn_calcs.c @@ -1237,7 +1237,7 @@ bool dcn_validate_bandwidth( if (pipe->plane_state) { struct pipe_ctx *hsplit_pipe = pipe->bottom_pipe; - pipe->plane_state->update_flags.bits.full_update = 1; + pipe->plane_state->update_bits.full_update = 1; if (v->dpp_per_plane[input_idx] == 2 || ((pipe->stream->view_format == diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c index 042602c50e35..c9691974bf72 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c @@ -3166,12 +3166,12 @@ static void dce110_program_front_end_for_pipe( plane_state->rotation); /* Moved programming gamma from dc to hwss */ - if (pipe_ctx->plane_state->update_flags.bits.full_update || - pipe_ctx->plane_state->update_flags.bits.in_transfer_func_change || - pipe_ctx->plane_state->update_flags.bits.gamma_change) + if (pipe_ctx->plane_state->update_bits.full_update || + pipe_ctx->plane_state->update_bits.in_transfer_func_change || + pipe_ctx->plane_state->update_bits.gamma_change) hws->funcs.set_input_transfer_func(dc, pipe_ctx, pipe_ctx->plane_state); - if (pipe_ctx->plane_state->update_flags.bits.full_update) + if (pipe_ctx->plane_state->update_bits.full_update) hws->funcs.set_output_transfer_func(dc, pipe_ctx, pipe_ctx->stream); DC_LOG_SURFACE( diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dce60/dce60_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dce60/dce60_hwseq.c index a08e9f9eec17..26aa303b8237 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dce60/dce60_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dce60/dce60_hwseq.c @@ -332,12 +332,12 @@ dce60_program_front_end_for_pipe( plane_state->rotation); /* Moved programming gamma from dc to hwss */ - if (pipe_ctx->plane_state->update_flags.bits.full_update || - pipe_ctx->plane_state->update_flags.bits.in_transfer_func_change || - pipe_ctx->plane_state->update_flags.bits.gamma_change) + if (pipe_ctx->plane_state->update_bits.full_update || + pipe_ctx->plane_state->update_bits.in_transfer_func_change || + pipe_ctx->plane_state->update_bits.gamma_change) hws->funcs.set_input_transfer_func(dc, pipe_ctx, pipe_ctx->plane_state); - if (pipe_ctx->plane_state->update_flags.bits.full_update) + if (pipe_ctx->plane_state->update_bits.full_update) hws->funcs.set_output_transfer_func(dc, pipe_ctx, pipe_ctx->stream); DC_LOG_SURFACE( diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.c index 7112b71af977..541cd908b341 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn10/dcn10_hwseq.c @@ -2981,7 +2981,7 @@ void dcn10_update_mpcc(struct dc *dc, struct pipe_ctx *pipe_ctx) mpcc_id = hubp->inst; /* If there is no full update, don't need to touch MPC tree*/ - if (!pipe_ctx->plane_state->update_flags.bits.full_update) { + if (!pipe_ctx->plane_state->update_bits.full_update) { mpc->funcs->update_blending(mpc, &blnd_cfg, mpcc_id); dc->hwss.update_visual_confirm_color(dc, pipe_ctx, mpcc_id); return; @@ -3041,7 +3041,7 @@ static void dcn10_update_dchubp_dpp( /* If request max dpp clk is lower than current dispclk, no need to * divided by 2 */ - if (plane_state->update_flags.bits.full_update) { + if (plane_state->update_bits.full_update) { /* new calculated dispclk, dppclk are stored in * context->bw_ctx.bw.dcn.clk.dispclk_khz / dppclk_khz. current @@ -3096,7 +3096,7 @@ static void dcn10_update_dchubp_dpp( * VTG is within DCHUBBUB which is commond block share by each pipe HUBP. * VTG is 1:1 mapping with OTG. Each pipe HUBP will select which VTG */ - if (plane_state->update_flags.bits.full_update) { + if (plane_state->update_bits.full_update) { hubp->funcs->hubp_vtg_sel(hubp, pipe_ctx->stream_res.tg->inst); hubp->funcs->hubp_setup( @@ -3113,26 +3113,26 @@ static void dcn10_update_dchubp_dpp( size.surface_size = pipe_ctx->plane_res.scl_data.viewport; - if (plane_state->update_flags.bits.full_update || - plane_state->update_flags.bits.bpp_change) + if (plane_state->update_bits.full_update || + plane_state->update_bits.bpp_change) dcn10_update_dpp(dpp, plane_state); - if (plane_state->update_flags.bits.full_update || - plane_state->update_flags.bits.per_pixel_alpha_change || - plane_state->update_flags.bits.global_alpha_change) + if (plane_state->update_bits.full_update || + plane_state->update_bits.per_pixel_alpha_change || + plane_state->update_bits.global_alpha_change) hws->funcs.update_mpcc(dc, pipe_ctx); - if (plane_state->update_flags.bits.full_update || - plane_state->update_flags.bits.per_pixel_alpha_change || - plane_state->update_flags.bits.global_alpha_change || - plane_state->update_flags.bits.scaling_change || - plane_state->update_flags.bits.position_change) { + if (plane_state->update_bits.full_update || + plane_state->update_bits.per_pixel_alpha_change || + plane_state->update_bits.global_alpha_change || + plane_state->update_bits.scaling_change || + plane_state->update_bits.position_change) { update_scaler(pipe_ctx); } - if (plane_state->update_flags.bits.full_update || - plane_state->update_flags.bits.scaling_change || - plane_state->update_flags.bits.position_change) { + if (plane_state->update_bits.full_update || + plane_state->update_bits.scaling_change || + plane_state->update_bits.position_change) { hubp->funcs->mem_program_viewport( hubp, &pipe_ctx->plane_res.scl_data.viewport, @@ -3150,7 +3150,7 @@ static void dcn10_update_dchubp_dpp( dc->hwss.set_cursor_sdr_white_level(pipe_ctx); } - if (plane_state->update_flags.bits.full_update) { + if (plane_state->update_bits.full_update) { /*gamut remap*/ dc->hwss.program_gamut_remap(pipe_ctx); @@ -3161,15 +3161,15 @@ static void dcn10_update_dchubp_dpp( pipe_ctx->stream_res.opp->inst); } - if (plane_state->update_flags.bits.full_update || - plane_state->update_flags.bits.pixel_format_change || - plane_state->update_flags.bits.horizontal_mirror_change || - plane_state->update_flags.bits.rotation_change || - plane_state->update_flags.bits.swizzle_change || - plane_state->update_flags.bits.dcc_change || - plane_state->update_flags.bits.bpp_change || - plane_state->update_flags.bits.scaling_change || - plane_state->update_flags.bits.plane_size_change) { + if (plane_state->update_bits.full_update || + plane_state->update_bits.pixel_format_change || + plane_state->update_bits.horizontal_mirror_change || + plane_state->update_bits.rotation_change || + plane_state->update_bits.swizzle_change || + plane_state->update_bits.dcc_change || + plane_state->update_bits.bpp_change || + plane_state->update_bits.scaling_change || + plane_state->update_bits.plane_size_change) { hubp->funcs->hubp_program_surface_config( hubp, plane_state->format, @@ -3278,16 +3278,16 @@ void dcn10_program_pipe( hws->funcs.blank_pixel_data(dc, pipe_ctx, blank); } - if (pipe_ctx->plane_state->update_flags.bits.full_update) + if (pipe_ctx->plane_state->update_bits.full_update) dcn10_enable_plane(dc, pipe_ctx, context); dcn10_update_dchubp_dpp(dc, pipe_ctx, context); hws->funcs.set_hdr_multiplier(pipe_ctx); - if (pipe_ctx->plane_state->update_flags.bits.full_update || - pipe_ctx->plane_state->update_flags.bits.in_transfer_func_change || - pipe_ctx->plane_state->update_flags.bits.gamma_change) + if (pipe_ctx->plane_state->update_bits.full_update || + pipe_ctx->plane_state->update_bits.in_transfer_func_change || + pipe_ctx->plane_state->update_bits.gamma_change) hws->funcs.set_input_transfer_func(dc, pipe_ctx, pipe_ctx->plane_state); /* dcn10_translate_regamma_to_hw_format takes 750us to finish @@ -3296,7 +3296,7 @@ void dcn10_program_pipe( * Always call this for now since it does memcmp inside before * doing heavy calculation and programming */ - if (pipe_ctx->plane_state->update_flags.bits.full_update) + if (pipe_ctx->plane_state->update_bits.full_update) hws->funcs.set_output_transfer_func(dc, pipe_ctx, pipe_ctx->stream); } diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.c index 50d039b3fb43..95e5b6a6ba0f 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn20/dcn20_hwseq.c @@ -1733,10 +1733,10 @@ void dcn20_update_dchubp_dpp( if (pipe_ctx->update_flags.bits.enable || pipe_ctx->update_flags.bits.plane_changed || - plane_state->update_flags.bits.bpp_change || - plane_state->update_flags.bits.input_csc_change || - plane_state->update_flags.bits.color_space_change || - plane_state->update_flags.bits.coeff_reduction_change) { + plane_state->update_bits.bpp_change || + plane_state->update_bits.input_csc_change || + plane_state->update_bits.color_space_change || + plane_state->update_bits.coeff_reduction_change) { struct dc_bias_and_scale bns_params = plane_state->bias_and_scale; // program the input csc @@ -1760,16 +1760,16 @@ void dcn20_update_dchubp_dpp( if (pipe_ctx->update_flags.bits.mpcc || pipe_ctx->update_flags.bits.plane_changed - || plane_state->update_flags.bits.global_alpha_change - || plane_state->update_flags.bits.per_pixel_alpha_change) { + || plane_state->update_bits.global_alpha_change + || plane_state->update_bits.per_pixel_alpha_change) { // MPCC inst is equal to pipe index in practice hws->funcs.update_mpcc(dc, pipe_ctx); } if (pipe_ctx->update_flags.bits.scaler || - plane_state->update_flags.bits.scaling_change || - plane_state->update_flags.bits.position_change || - plane_state->update_flags.bits.per_pixel_alpha_change || + plane_state->update_bits.scaling_change || + plane_state->update_bits.position_change || + plane_state->update_bits.per_pixel_alpha_change || pipe_ctx->stream->update_flags.bits.scaling) { pipe_ctx->plane_res.scl_data.lb_params.alpha_en = pipe_ctx->plane_state->per_pixel_alpha; ASSERT(pipe_ctx->plane_res.scl_data.lb_params.depth == LB_PIXEL_DEPTH_36BPP); @@ -1779,8 +1779,8 @@ void dcn20_update_dchubp_dpp( } if (pipe_ctx->update_flags.bits.viewport || - (context == dc->current_state && plane_state->update_flags.bits.position_change) || - (context == dc->current_state && plane_state->update_flags.bits.scaling_change) || + (context == dc->current_state && plane_state->update_bits.position_change) || + (context == dc->current_state && plane_state->update_bits.scaling_change) || (context == dc->current_state && pipe_ctx->stream->update_flags.bits.scaling)) { hubp->funcs->mem_program_viewport( @@ -1812,7 +1812,7 @@ void dcn20_update_dchubp_dpp( if (pipe_ctx->update_flags.bits.enable || pipe_ctx->update_flags.bits.opp_changed || pipe_ctx->update_flags.bits.plane_changed || pipe_ctx->stream->update_flags.bits.gamut_remap - || plane_state->update_flags.bits.gamut_remap_change + || plane_state->update_bits.gamut_remap_change || pipe_ctx->stream->update_flags.bits.out_csc) { /* dpp/cm gamut remap*/ dc->hwss.program_gamut_remap(pipe_ctx); @@ -1828,14 +1828,14 @@ void dcn20_update_dchubp_dpp( if (pipe_ctx->update_flags.bits.enable || pipe_ctx->update_flags.bits.plane_changed || pipe_ctx->update_flags.bits.opp_changed || - plane_state->update_flags.bits.pixel_format_change || - plane_state->update_flags.bits.horizontal_mirror_change || - plane_state->update_flags.bits.rotation_change || - plane_state->update_flags.bits.swizzle_change || - plane_state->update_flags.bits.dcc_change || - plane_state->update_flags.bits.bpp_change || - plane_state->update_flags.bits.scaling_change || - plane_state->update_flags.bits.plane_size_change) { + plane_state->update_bits.pixel_format_change || + plane_state->update_bits.horizontal_mirror_change || + plane_state->update_bits.rotation_change || + plane_state->update_bits.swizzle_change || + plane_state->update_bits.dcc_change || + plane_state->update_bits.bpp_change || + plane_state->update_bits.scaling_change || + plane_state->update_bits.plane_size_change) { struct plane_size size = plane_state->plane_size; size.surface_size = pipe_ctx->plane_res.scl_data.viewport; @@ -1853,7 +1853,7 @@ void dcn20_update_dchubp_dpp( if (pipe_ctx->update_flags.bits.enable || pipe_ctx->update_flags.bits.plane_changed || - plane_state->update_flags.bits.addr_update) { + plane_state->update_bits.addr_update) { if (resource_is_pipe_type(pipe_ctx, OTG_MASTER) && pipe_mall_type == SUBVP_MAIN) { union block_sequence_params params; @@ -1969,18 +1969,18 @@ static void dcn20_program_pipe( } if (pipe_ctx->plane_state && (pipe_ctx->update_flags.raw || - pipe_ctx->plane_state->update_flags.raw || + dc_pipe_update_bits_is_any_set(&pipe_ctx->plane_state->update_bits) || pipe_ctx->stream->update_flags.raw)) dcn20_update_dchubp_dpp(dc, pipe_ctx, context); if (pipe_ctx->plane_state && (pipe_ctx->update_flags.bits.enable || - pipe_ctx->plane_state->update_flags.bits.hdr_mult)) + pipe_ctx->plane_state->update_bits.hdr_mult)) hws->funcs.set_hdr_multiplier(pipe_ctx); if (pipe_ctx->plane_state && - (pipe_ctx->plane_state->update_flags.bits.in_transfer_func_change || - pipe_ctx->plane_state->update_flags.bits.gamma_change || - pipe_ctx->plane_state->update_flags.bits.lut_3d || + (pipe_ctx->plane_state->update_bits.in_transfer_func_change || + pipe_ctx->plane_state->update_bits.gamma_change || + pipe_ctx->plane_state->update_bits.lut_3d || pipe_ctx->update_flags.bits.enable)) hws->funcs.set_input_transfer_func(dc, pipe_ctx, pipe_ctx->plane_state); @@ -2186,7 +2186,7 @@ void dcn20_program_front_end_for_ctx( pipe = &context->res_ctx.pipe_ctx[i]; if (!pipe->top_pipe && !pipe->prev_odm_pipe && pipe->stream && pipe->stream->num_wb_info > 0 - && (pipe->update_flags.raw || (pipe->plane_state && pipe->plane_state->update_flags.raw) + && (pipe->update_flags.raw || (pipe->plane_state && dc_pipe_update_bits_is_any_set(&pipe->plane_state->update_bits)) || pipe->stream->update_flags.raw) && hws->funcs.program_all_writeback_pipes_in_tree) hws->funcs.program_all_writeback_pipes_in_tree(dc, pipe->stream, context); @@ -2998,7 +2998,7 @@ void dcn20_update_mpcc(struct dc *dc, struct pipe_ctx *pipe_ctx) mpcc_id = hubp->inst; /* If there is no full update, don't need to touch MPC tree*/ - if (!pipe_ctx->plane_state->update_flags.bits.full_update && + if (!pipe_ctx->plane_state->update_bits.full_update && !pipe_ctx->update_flags.bits.mpcc) { mpc->funcs->update_blending(mpc, &blnd_cfg, mpcc_id); dc->hwss.update_visual_confirm_color(dc, pipe_ctx, mpcc_id); diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn201/dcn201_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn201/dcn201_hwseq.c index ce18d75fd991..7b820bdae55b 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn201/dcn201_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn201/dcn201_hwseq.c @@ -485,7 +485,7 @@ void dcn201_update_mpcc(struct dc *dc, struct pipe_ctx *pipe_ctx) mpcc_id = dpp_id; /* If there is no full update, don't need to touch MPC tree*/ - if (!pipe_ctx->plane_state->update_flags.bits.full_update) { + if (!pipe_ctx->plane_state->update_bits.full_update) { dc->hwss.update_visual_confirm_color(dc, pipe_ctx, mpcc_id); mpc->funcs->update_blending(mpc, &blnd_cfg, mpcc_id); return; diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.c index 34cbd90b2283..1340f673ec3b 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.c @@ -1466,7 +1466,7 @@ void dcn32_update_phantom_vp_position(struct dc *dc, if (pipe->stream && dc_state_get_pipe_subvp_type(context, pipe) == SUBVP_MAIN && dc_state_get_paired_subvp_stream(context, pipe->stream) == phantom_pipe->stream) { - if (pipe->plane_state && pipe->plane_state->update_flags.bits.position_change) { + if (pipe->plane_state && pipe->plane_state->update_bits.position_change) { phantom_plane->src_rect.x = pipe->plane_state->src_rect.x; phantom_plane->src_rect.y = pipe->plane_state->src_rect.y; @@ -1474,7 +1474,7 @@ void dcn32_update_phantom_vp_position(struct dc *dc, phantom_plane->dst_rect.x = pipe->plane_state->dst_rect.x; phantom_plane->dst_rect.y = pipe->plane_state->dst_rect.y; - phantom_pipe->plane_state->update_flags.bits.position_change = 1; + phantom_pipe->plane_state->update_bits.position_change = 1; resource_build_scaling_params(phantom_pipe); return; } diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c index 49efd1f11c9a..9107493cdcda 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c @@ -1421,7 +1421,7 @@ void dcn401_wait_for_dcc_meta_propagation(const struct dc *dc, if (pipe_ctx->plane_state && pipe_ctx->plane_state->dcc.enable && pipe_ctx->plane_state->flip_immediate && - pipe_ctx->plane_state->update_flags.bits.addr_update) { + pipe_ctx->plane_state->update_bits.addr_update) { is_wait_needed = true; break; } @@ -2268,18 +2268,18 @@ void dcn401_program_pipe( } if (pipe_ctx->plane_state && (pipe_ctx->update_flags.raw || - pipe_ctx->plane_state->update_flags.raw || + dc_pipe_update_bits_is_any_set(&pipe_ctx->plane_state->update_bits) || pipe_ctx->stream->update_flags.raw)) dc->hwss.update_dchubp_dpp(dc, pipe_ctx, context); if (pipe_ctx->plane_state && (pipe_ctx->update_flags.bits.enable || - pipe_ctx->plane_state->update_flags.bits.hdr_mult)) + pipe_ctx->plane_state->update_bits.hdr_mult)) hws->funcs.set_hdr_multiplier(pipe_ctx); if (pipe_ctx->plane_state && - (pipe_ctx->plane_state->update_flags.bits.in_transfer_func_change || - pipe_ctx->plane_state->update_flags.bits.gamma_change || - pipe_ctx->plane_state->update_flags.bits.lut_3d || + (pipe_ctx->plane_state->update_bits.in_transfer_func_change || + pipe_ctx->plane_state->update_bits.gamma_change || + pipe_ctx->plane_state->update_bits.lut_3d || pipe_ctx->update_flags.bits.enable)) hws->funcs.set_input_transfer_func(dc, pipe_ctx, pipe_ctx->plane_state); @@ -2338,7 +2338,7 @@ void dcn401_program_pipe( pipe_ctx->stream_res.test_pattern_params.offset); } if (pipe_ctx->plane_state - && pipe_ctx->plane_state->update_flags.bits.cm_hist_change + && pipe_ctx->plane_state->update_bits.cm_hist_change && hws->funcs.program_cm_hist) hws->funcs.program_cm_hist(dc, pipe_ctx, pipe_ctx->plane_state); } @@ -2419,7 +2419,7 @@ void dcn401_program_pipe_sequence( } if (pipe_ctx->plane_state && (pipe_ctx->update_flags.raw || - pipe_ctx->plane_state->update_flags.raw || + dc_pipe_update_bits_is_any_set(&pipe_ctx->plane_state->update_bits) || pipe_ctx->stream->update_flags.raw)) { if (dc->hwss.update_dchubp_dpp_sequence) @@ -2427,15 +2427,15 @@ void dcn401_program_pipe_sequence( } if (pipe_ctx->plane_state && (pipe_ctx->update_flags.bits.enable || - pipe_ctx->plane_state->update_flags.bits.hdr_mult)) { + pipe_ctx->plane_state->update_bits.hdr_mult)) { hws->funcs.set_hdr_multiplier_sequence(pipe_ctx, seq_state); } if (pipe_ctx->plane_state && - (pipe_ctx->plane_state->update_flags.bits.in_transfer_func_change || - pipe_ctx->plane_state->update_flags.bits.gamma_change || - pipe_ctx->plane_state->update_flags.bits.lut_3d || + (pipe_ctx->plane_state->update_bits.in_transfer_func_change || + pipe_ctx->plane_state->update_bits.gamma_change || + pipe_ctx->plane_state->update_bits.lut_3d || pipe_ctx->update_flags.bits.enable)) { hwss_add_dpp_set_input_transfer_func(seq_state, dc, pipe_ctx, pipe_ctx->plane_state); @@ -2493,7 +2493,7 @@ void dcn401_program_pipe_sequence( } if (pipe_ctx->plane_state - && pipe_ctx->plane_state->update_flags.bits.cm_hist_change + && pipe_ctx->plane_state->update_bits.cm_hist_change && hws->funcs.program_cm_hist) { hwss_add_dpp_program_cm_hist(seq_state, pipe_ctx->plane_res.dpp, @@ -2647,7 +2647,7 @@ void dcn401_program_front_end_for_ctx( pipe = &context->res_ctx.pipe_ctx[i]; if (!pipe->top_pipe && !pipe->prev_odm_pipe && pipe->stream && pipe->stream->num_wb_info > 0 - && (pipe->update_flags.raw || (pipe->plane_state && pipe->plane_state->update_flags.raw) + && (pipe->update_flags.raw || (pipe->plane_state && dc_pipe_update_bits_is_any_set(&pipe->plane_state->update_bits)) || pipe->stream->update_flags.raw) && hws->funcs.program_all_writeback_pipes_in_tree) hws->funcs.program_all_writeback_pipes_in_tree(dc, pipe->stream, context); @@ -3733,10 +3733,10 @@ void dcn401_update_dchubp_dpp_sequence(struct dc *dc, /* Step 7: DPP setup - input CSC and format setup */ if (pipe_ctx->update_flags.bits.enable || pipe_ctx->update_flags.bits.plane_changed || - plane_state->update_flags.bits.bpp_change || - plane_state->update_flags.bits.input_csc_change || - plane_state->update_flags.bits.color_space_change || - plane_state->update_flags.bits.coeff_reduction_change) { + plane_state->update_bits.bpp_change || + plane_state->update_bits.input_csc_change || + plane_state->update_bits.color_space_change || + plane_state->update_bits.coeff_reduction_change) { hwss_add_dpp_setup_dpp(seq_state, pipe_ctx); /* Step 8: DPP cursor matrix setup */ @@ -3753,8 +3753,8 @@ void dcn401_update_dchubp_dpp_sequence(struct dc *dc, /* Step 10: MPCC updates */ if (pipe_ctx->update_flags.bits.mpcc || pipe_ctx->update_flags.bits.plane_changed || - plane_state->update_flags.bits.global_alpha_change || - plane_state->update_flags.bits.per_pixel_alpha_change) { + plane_state->update_bits.global_alpha_change || + plane_state->update_bits.per_pixel_alpha_change) { /* Check if update_mpcc_sequence is implemented and prefer it over single MPC_UPDATE_MPCC step */ if (hws->funcs.update_mpcc_sequence) @@ -3763,9 +3763,9 @@ void dcn401_update_dchubp_dpp_sequence(struct dc *dc, /* Step 11: DPP scaler setup */ if (pipe_ctx->update_flags.bits.scaler || - plane_state->update_flags.bits.scaling_change || - plane_state->update_flags.bits.position_change || - plane_state->update_flags.bits.per_pixel_alpha_change || + plane_state->update_bits.scaling_change || + plane_state->update_bits.position_change || + plane_state->update_bits.per_pixel_alpha_change || pipe_ctx->stream->update_flags.bits.scaling) { pipe_ctx->plane_res.scl_data.lb_params.alpha_en = pipe_ctx->plane_state->per_pixel_alpha; ASSERT(pipe_ctx->plane_res.scl_data.lb_params.depth == LB_PIXEL_DEPTH_36BPP); @@ -3774,8 +3774,8 @@ void dcn401_update_dchubp_dpp_sequence(struct dc *dc, /* Step 12: HUBP viewport programming */ if (pipe_ctx->update_flags.bits.viewport || - (context == dc->current_state && plane_state->update_flags.bits.position_change) || - (context == dc->current_state && plane_state->update_flags.bits.scaling_change) || + (context == dc->current_state && plane_state->update_bits.position_change) || + (context == dc->current_state && plane_state->update_bits.scaling_change) || (context == dc->current_state && pipe_ctx->stream->update_flags.bits.scaling)) { hwss_add_hubp_mem_program_viewport(seq_state, hubp, &pipe_ctx->plane_res.scl_data.viewport, &pipe_ctx->plane_res.scl_data.viewport_c); @@ -3807,7 +3807,7 @@ void dcn401_update_dchubp_dpp_sequence(struct dc *dc, if (pipe_ctx->update_flags.bits.enable || pipe_ctx->update_flags.bits.opp_changed || pipe_ctx->update_flags.bits.plane_changed || pipe_ctx->stream->update_flags.bits.gamut_remap || - plane_state->update_flags.bits.gamut_remap_change || + plane_state->update_bits.gamut_remap_change || pipe_ctx->stream->update_flags.bits.out_csc) { /* Gamut remap */ @@ -3822,14 +3822,14 @@ void dcn401_update_dchubp_dpp_sequence(struct dc *dc, if (pipe_ctx->update_flags.bits.enable || pipe_ctx->update_flags.bits.plane_changed || pipe_ctx->update_flags.bits.opp_changed || - plane_state->update_flags.bits.pixel_format_change || - plane_state->update_flags.bits.horizontal_mirror_change || - plane_state->update_flags.bits.rotation_change || - plane_state->update_flags.bits.swizzle_change || - plane_state->update_flags.bits.dcc_change || - plane_state->update_flags.bits.bpp_change || - plane_state->update_flags.bits.scaling_change || - plane_state->update_flags.bits.plane_size_change) { + plane_state->update_bits.pixel_format_change || + plane_state->update_bits.horizontal_mirror_change || + plane_state->update_bits.rotation_change || + plane_state->update_bits.swizzle_change || + plane_state->update_bits.dcc_change || + plane_state->update_bits.bpp_change || + plane_state->update_bits.scaling_change || + plane_state->update_bits.plane_size_change) { struct plane_size size = plane_state->plane_size; size.surface_size = pipe_ctx->plane_res.scl_data.viewport; @@ -3843,7 +3843,7 @@ void dcn401_update_dchubp_dpp_sequence(struct dc *dc, /* Step 19: Update plane address (with SubVP support) */ if (pipe_ctx->update_flags.bits.enable || pipe_ctx->update_flags.bits.plane_changed || - plane_state->update_flags.bits.addr_update) { + plane_state->update_bits.addr_update) { /* SubVP save surface address if needed */ if (resource_is_pipe_type(pipe_ctx, OTG_MASTER) && pipe_mall_type == SUBVP_MAIN) { @@ -3916,7 +3916,7 @@ void dcn401_update_mpcc_sequence(struct dc *dc, mpcc_id = hubp->inst; /* Step 1: Update blending if no full update needed */ - if (!pipe_ctx->plane_state->update_flags.bits.full_update && + if (!pipe_ctx->plane_state->update_bits.full_update && !pipe_ctx->update_flags.bits.mpcc) { /* Update blending configuration */ diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.c index 9cf8b379cb34..f415473517d4 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn42/dcn42_hwseq.c @@ -355,7 +355,7 @@ void dcn42_update_mpcc(struct dc *dc, struct pipe_ctx *pipe_ctx) mpcc_id = hubp->inst; /* If there is no full update, don't need to touch MPC tree*/ - if (!pipe_ctx->plane_state->update_flags.bits.full_update && + if (!pipe_ctx->plane_state->update_bits.full_update && !pipe_ctx->update_flags.bits.mpcc) { mpc->funcs->update_blending(mpc, &blnd_cfg, mpcc_id); dc->hwss.update_visual_confirm_color(dc, pipe_ctx, mpcc_id); -- cgit v1.2.3 From fcf4919cd87333f2e67c149cd3eacb1cd0835137 Mon Sep 17 00:00:00 2001 From: Fangzhi Zuo Date: Wed, 3 Jun 2026 13:39:13 -0400 Subject: drm/amd/display: Add Support for HDMI Compliance Automation Add support to get DUT trained at FRL link rate when working with Teledyne M41h compliance automation. Reviewed-by: Alex Hung Signed-off-by: Fangzhi Zuo Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 3 + .../amd/display/amdgpu_dm/amdgpu_dm_connector.c | 5 ++ .../drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c | 67 +++++++++++++++++++++- .../drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 6 ++ 4 files changed, 80 insertions(+), 1 deletion(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h index c0144d14b793..eedca412eca0 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h @@ -877,6 +877,9 @@ struct amdgpu_dm_connector { unsigned int hdmi_hpd_debounce_delay_ms; struct delayed_work hdmi_hpd_debounce_work; struct dc_sink *hdmi_prev_sink; + + /* HDMI compliance automation */ + bool hdmi_comp_auto; }; static inline void amdgpu_dm_set_mst_status(uint8_t *status, 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 f239ce767bff..df09627f4c04 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 @@ -573,6 +573,11 @@ void amdgpu_dm_update_connector_after_detect( amdgpu_dm_update_freesync_caps(connector, aconnector->drm_edid, true); amdgpu_dm_update_connector_ext_caps(aconnector); dm_set_panel_type(aconnector); + + if (aconnector->hdmi_comp_auto) { + if (sink->sink_signal != SIGNAL_TYPE_HDMI_FRL) + sink->sink_signal = SIGNAL_TYPE_HDMI_FRL; + } } else { hdmi_cec_unset_edid(aconnector); drm_dp_cec_unset_edid(&aconnector->dm_dp_aux.aux); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c index 3bcf3ff30aee..2d455359fdb4 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c @@ -2982,6 +2982,64 @@ static ssize_t hdmi_cec_state_write(struct file *f, const char __user *buf, return size; } +/** + * hdmi_automation_enable - Enable/Disable HDMI automation feature + * @f: file structure. + * @buf: userspace buffer. set to '1' to enable; '0' to disable automation feature. + * @size: size of buffer from userpsace. + * @pos: unused. + * + * Return size on success, error code on failure + */ +static ssize_t hdmi_automation_enable(struct file *f, const char __user *buf, + size_t size, loff_t *pos) +{ + struct amdgpu_dm_connector *aconnector = file_inode(f)->i_private; + char *wr_buf = NULL; + const uint32_t wr_buf_size = 40; + int max_param_num = 1; + uint8_t param_nums = 0; + long param[2]; + bool hdmi_comp_auto; + + if (size == 0) + return -EINVAL; + + wr_buf = kcalloc(wr_buf_size, sizeof(char), GFP_KERNEL); + if (!wr_buf) + return -ENOSPC; + + if (parse_write_buffer_into_params(wr_buf, wr_buf_size, + (long *)param, buf, + max_param_num, + ¶m_nums)) { + kfree(wr_buf); + return -EINVAL; + } + + if (param_nums <= 0) { + kfree(wr_buf); + DRM_DEBUG_DRIVER("user data not be read\n"); + return -EINVAL; + } + + switch (param[0]) { + case 0: + hdmi_comp_auto = false; + break; + case 1: + default: + hdmi_comp_auto = true; + break; + } + + /* Persist setting across sink re-detection/hotplug. */ + aconnector->hdmi_comp_auto = hdmi_comp_auto; + + kfree(wr_buf); + return size; +} + DEFINE_SHOW_ATTRIBUTE(dp_dsc_fec_support); DEFINE_SHOW_ATTRIBUTE(dmub_fw_state); DEFINE_SHOW_ATTRIBUTE(dmub_tracebuffer); @@ -3099,6 +3157,12 @@ static const struct file_operations dp_mst_link_settings_debugfs_fops = { .llseek = default_llseek }; +static const struct file_operations hdmi_automation_debugfs_fops = { + .owner = THIS_MODULE, + .write = hdmi_automation_enable, + .llseek = default_llseek +}; + static const struct { char *name; const struct file_operations *fops; @@ -3131,7 +3195,8 @@ static const struct { const struct file_operations *fops; } hdmi_debugfs_entries[] = { {"hdcp_sink_capability", &hdcp_sink_capability_fops}, - {"hdmi_cec_state", &hdmi_cec_state_fops} + {"hdmi_cec_state", &hdmi_cec_state_fops}, + {"hdmi_automation", &hdmi_automation_debugfs_fops} }; /* diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c index c6f94eb71ffa..eef031022be2 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c @@ -193,6 +193,12 @@ enum dc_edid_status dm_helpers_parse_edid_caps( __func__, connector->name, edid_caps->frl_dsc_10bpc, edid_caps->frl_dsc_12bpc, \ edid_caps->frl_dsc_all_bpp, edid_caps->frl_dsc_native_420, edid_caps->frl_dsc_max_slices, \ edid_caps->frl_dsc_max_frl_rate, edid_caps->frl_dsc_total_chunk_kbytes); + if (aconnector->hdmi_comp_auto) { + edid_caps->panel_patch.hdmi_comp_auto = true; + link->ctx->dc->debug.force_frl_max = true; + link->ctx->dc->debug.force_frl_dsc = true; + drm_dbg_driver(connector->dev, "%s: HDMI_FRL [%s] hdmi_comp_auto --> enabled\n", __func__, connector->name); + } } apply_edid_quirks(link, edid_buf, edid_caps); -- cgit v1.2.3 From c26b643aa31b7f4b9ac2d9de856fc263904490d9 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Thu, 30 Apr 2026 15:46:34 -0600 Subject: drm/amd/display: Add KUnit tests for amdgpu_dm Add KUnit tests for pure helper functions in amdgpu_dm.c. Tests cover: - dm_plane_layer_index_cmp(): equal, ascending, and descending layer_index ordering - fill_plane_color_attributes(): RGB plus BT601/BT709/BT2020 full- and limited-range YCbCr, and invalid encoding - modereset_required(): active vs inactive stream states with and without a mode change - dm_get_oriented_plane_size(): 0/90/180/270 degree rotations - dm_get_plane_scale(): identity, rotated identity, and division-by-zero guard - is_scaling_state_different(): identical state, scaling mode change, and underscan enable/border changes - is_timing_unchanged_for_freesync(): NULL args, identical modes, VRR vtotal/vsync shift, and pixel clock change - set_freesync_fixed_config(): fixed refresh-rate computation - is_dc_timing_adjust_needed(): pending hw adjust, VRR active-fixed, VRR active-state toggle, and steady state - set_multisync_trigger_params(): disabled trigger and rising/falling edge selection by vsync polarity - set_master_stream(): highest refresh-rate selection and the default-to-first-stream case Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 42 +- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 21 + .../display/amdgpu_dm/amdgpu_dm_kunit_helpers.h | 1 + .../gpu/drm/amd/display/amdgpu_dm/tests/Makefile | 1 + .../amd/display/amdgpu_dm/tests/amdgpu_dm_test.c | 929 +++++++++++++++++++++ 5 files changed, 979 insertions(+), 15 deletions(-) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') 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 68ec8f3264c8..d23d9d85e567 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -70,6 +70,7 @@ #include "amdgpu_dm_audio.h" #include "amdgpu_dm_dmub.h" #include "amdgpu_dm_connector.h" +#include "amdgpu_dm_kunit_helpers.h" #include "ivsrcid/ivsrcid_vislands30.h" @@ -146,7 +147,7 @@ static void dm_enable_per_frame_crtc_master_sync(struct dc_state *context); static int amdgpu_dm_atomic_check(struct drm_device *dev, struct drm_atomic_commit *state); -static bool +STATIC_IFN_KUNIT bool is_timing_unchanged_for_freesync(struct drm_crtc_state *old_crtc_state, struct drm_crtc_state *new_crtc_state); @@ -247,8 +248,8 @@ static int dm_soft_reset(struct amdgpu_ip_block *ip_block) return 0; } -static inline bool is_dc_timing_adjust_needed(struct dm_crtc_state *old_state, - struct dm_crtc_state *new_state) +STATIC_IFN_KUNIT bool is_dc_timing_adjust_needed(struct dm_crtc_state *old_state, + struct dm_crtc_state *new_state) { if (new_state->stream->adjust.timing_adjust_pending) return true; @@ -259,13 +260,14 @@ static inline bool is_dc_timing_adjust_needed(struct dm_crtc_state *old_state, else return false; } +EXPORT_IF_KUNIT(is_dc_timing_adjust_needed); /* * DC will program planes with their z-order determined by their ordering * in the dc_surface_updates array. This comparator is used to sort them * by descending zpos. */ -static int dm_plane_layer_index_cmp(const void *a, const void *b) +STATIC_IFN_KUNIT int dm_plane_layer_index_cmp(const void *a, const void *b) { const struct dc_surface_update *sa = (struct dc_surface_update *)a; const struct dc_surface_update *sb = (struct dc_surface_update *)b; @@ -273,6 +275,7 @@ static int dm_plane_layer_index_cmp(const void *a, const void *b) /* Sort by descending dc_plane layer_index (i.e. normalized_zpos) */ return sb->surface->layer_index - sa->surface->layer_index; } +EXPORT_IF_KUNIT(dm_plane_layer_index_cmp); /** * update_planes_and_stream_adapter() - Send planes to be updated in DC @@ -2980,12 +2983,13 @@ static int dm_early_init(struct amdgpu_ip_block *ip_block) return dm_init_microcode(adev); } -static bool modereset_required(struct drm_crtc_state *crtc_state) +STATIC_IFN_KUNIT bool modereset_required(struct drm_crtc_state *crtc_state) { return !crtc_state->active && drm_atomic_crtc_needs_modeset(crtc_state); } +EXPORT_IF_KUNIT(modereset_required); -static int +STATIC_IFN_KUNIT int fill_plane_color_attributes(const struct drm_plane_state *plane_state, const enum surface_pixel_format format, enum dc_color_space *color_space) @@ -3032,6 +3036,7 @@ fill_plane_color_attributes(const struct drm_plane_state *plane_state, return 0; } +EXPORT_IF_KUNIT(fill_plane_color_attributes); static int fill_dc_plane_info_and_addr(struct amdgpu_device *adev, @@ -3600,7 +3605,7 @@ static void dm_update_pflip_irq_state(struct amdgpu_device *adev, amdgpu_irq_update(adev, &adev->pageflip_irq, irq_type); } -static bool +STATIC_IFN_KUNIT bool is_scaling_state_different(const struct dm_connector_state *dm_state, const struct dm_connector_state *old_dm_state) { @@ -3617,6 +3622,7 @@ is_scaling_state_different(const struct dm_connector_state *dm_state, return true; return false; } +EXPORT_IF_KUNIT(is_scaling_state_different); static bool is_content_protection_different(struct drm_crtc_state *new_crtc_state, struct drm_crtc_state *old_crtc_state, @@ -5079,7 +5085,7 @@ static int amdgpu_dm_atomic_setup_commit(struct drm_atomic_commit *state) return 0; } -static void set_multisync_trigger_params( +STATIC_IFN_KUNIT void set_multisync_trigger_params( struct dc_stream_state *stream) { struct dc_stream_state *master = NULL; @@ -5092,9 +5098,10 @@ static void set_multisync_trigger_params( stream->triggered_crtc_reset.delay = TRIGGER_DELAY_NEXT_PIXEL; } } +EXPORT_IF_KUNIT(set_multisync_trigger_params); -static void set_master_stream(struct dc_stream_state *stream_set[], - int stream_count) +STATIC_IFN_KUNIT void set_master_stream(struct dc_stream_state *stream_set[], + int stream_count) { int j, highest_rfr = 0, master_stream = 0; @@ -5115,6 +5122,7 @@ static void set_master_stream(struct dc_stream_state *stream_set[], stream_set[j]->triggered_crtc_reset.event_source = stream_set[master_stream]; } } +EXPORT_IF_KUNIT(set_master_stream); static void dm_enable_per_frame_crtc_master_sync(struct dc_state *context) { @@ -5560,7 +5568,7 @@ static void reset_freesync_config_for_crtc( sizeof(new_crtc_state->vrr_infopacket)); } -static bool +STATIC_IFN_KUNIT bool is_timing_unchanged_for_freesync(struct drm_crtc_state *old_crtc_state, struct drm_crtc_state *new_crtc_state) { @@ -5589,8 +5597,9 @@ is_timing_unchanged_for_freesync(struct drm_crtc_state *old_crtc_state, return false; } +EXPORT_IF_KUNIT(is_timing_unchanged_for_freesync); -static void set_freesync_fixed_config(struct dm_crtc_state *dm_new_crtc_state) +STATIC_IFN_KUNIT void set_freesync_fixed_config(struct dm_crtc_state *dm_new_crtc_state) { u64 num, den, res; struct drm_crtc_state *new_crtc_state = &dm_new_crtc_state->base; @@ -5604,6 +5613,7 @@ static void set_freesync_fixed_config(struct dm_crtc_state *dm_new_crtc_state) res = div_u64(num, den); dm_new_crtc_state->freesync_config.fixed_refresh_in_uhz = res; } +EXPORT_IF_KUNIT(set_freesync_fixed_config); static int dm_update_crtc_state(struct amdgpu_display_manager *dm, struct drm_atomic_commit *state, @@ -6339,8 +6349,8 @@ out: return ret; } -static void dm_get_oriented_plane_size(struct drm_plane_state *plane_state, - int *src_w, int *src_h) +STATIC_IFN_KUNIT void dm_get_oriented_plane_size(struct drm_plane_state *plane_state, + int *src_w, int *src_h) { switch (plane_state->rotation & DRM_MODE_ROTATE_MASK) { case DRM_MODE_ROTATE_90: @@ -6356,8 +6366,9 @@ static void dm_get_oriented_plane_size(struct drm_plane_state *plane_state, break; } } +EXPORT_IF_KUNIT(dm_get_oriented_plane_size); -static void +STATIC_IFN_KUNIT void dm_get_plane_scale(struct drm_plane_state *plane_state, int *out_plane_scale_w, int *out_plane_scale_h) { @@ -6367,6 +6378,7 @@ dm_get_plane_scale(struct drm_plane_state *plane_state, *out_plane_scale_w = plane_src_w ? plane_state->crtc_w * 1000 / plane_src_w : 0; *out_plane_scale_h = plane_src_h ? plane_state->crtc_h * 1000 / plane_src_h : 0; } +EXPORT_IF_KUNIT(dm_get_plane_scale); /* * The normalized_zpos value cannot be used by this iterator directly. It's only diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h index eedca412eca0..2ace3abe15e5 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h @@ -1126,4 +1126,25 @@ void amdgpu_dm_emulated_link_detect(struct dc_link *link); void amdgpu_dm_apply_delay_after_dpcd_poweroff(struct amdgpu_device *adev, struct dc_sink *sink); +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +int dm_plane_layer_index_cmp(const void *a, const void *b); +int fill_plane_color_attributes(const struct drm_plane_state *plane_state, + const enum surface_pixel_format format, + enum dc_color_space *color_space); +bool modereset_required(struct drm_crtc_state *crtc_state); +void dm_get_oriented_plane_size(struct drm_plane_state *plane_state, + int *src_w, int *src_h); +void dm_get_plane_scale(struct drm_plane_state *plane_state, + int *out_plane_scale_w, int *out_plane_scale_h); +bool is_scaling_state_different(const struct dm_connector_state *dm_state, + const struct dm_connector_state *old_dm_state); +bool is_timing_unchanged_for_freesync(struct drm_crtc_state *old_crtc_state, + struct drm_crtc_state *new_crtc_state); +void set_freesync_fixed_config(struct dm_crtc_state *dm_new_crtc_state); +bool is_dc_timing_adjust_needed(struct dm_crtc_state *old_state, + struct dm_crtc_state *new_state); +void set_multisync_trigger_params(struct dc_stream_state *stream); +void set_master_stream(struct dc_stream_state *stream_set[], int stream_count); +#endif + #endif /* __AMDGPU_DM_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_kunit_helpers.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_kunit_helpers.h index 4b2864375105..1f910a6a00c0 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_kunit_helpers.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_kunit_helpers.h @@ -10,6 +10,7 @@ #define STATIC_IFN_KUNIT #define INLINE_IFN_KUNIT inline #define EXPORT_IF_KUNIT(symbol) EXPORT_SYMBOL(symbol) + #else #define STATIC_IFN_KUNIT static #define INLINE_IFN_KUNIT diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile index 4d2eb301c2af..4365d4024f70 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile @@ -20,3 +20,4 @@ obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_ism_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_wb_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_mst_types_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_pp_smu_test.o +obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_test.o diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c new file mode 100644 index 000000000000..31194ab42f04 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c @@ -0,0 +1,929 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit tests for amdgpu_dm.c + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include + +#include "dc.h" +#include "amdgpu_mode.h" +#include "amdgpu_dm.h" + +/* Tests for dm_plane_layer_index_cmp() */ + +/** + * dm_test_plane_layer_index_cmp_equal - Test Plane layer index cmp equal + * @test: The KUnit test context + */ +static void dm_test_plane_layer_index_cmp_equal(struct kunit *test) +{ + struct dc_plane_state *plane_a; + struct dc_plane_state *plane_b; + struct dc_surface_update sa, sb; + + plane_a = kunit_kzalloc(test, sizeof(*plane_a), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_a); + plane_b = kunit_kzalloc(test, sizeof(*plane_b), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_b); + + plane_a->layer_index = 5; + plane_b->layer_index = 5; + sa.surface = plane_a; + sb.surface = plane_b; + + KUNIT_EXPECT_EQ(test, dm_plane_layer_index_cmp(&sa, &sb), 0); +} + +/** + * dm_test_plane_layer_index_cmp_descending - Test Plane layer index cmp descending + * @test: The KUnit test context + */ +static void dm_test_plane_layer_index_cmp_descending(struct kunit *test) +{ + struct dc_plane_state *plane_a; + struct dc_plane_state *plane_b; + struct dc_surface_update sa, sb; + + plane_a = kunit_kzalloc(test, sizeof(*plane_a), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_a); + plane_b = kunit_kzalloc(test, sizeof(*plane_b), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_b); + + plane_a->layer_index = 3; + plane_b->layer_index = 7; + sa.surface = plane_a; + sb.surface = plane_b; + + /* b has higher index, so cmp(a,b) = b - a > 0 (b sorts first) */ + KUNIT_EXPECT_GT(test, dm_plane_layer_index_cmp(&sa, &sb), 0); +} + +/** + * dm_test_plane_layer_index_cmp_ascending - Test Plane layer index cmp ascending + * @test: The KUnit test context + */ +static void dm_test_plane_layer_index_cmp_ascending(struct kunit *test) +{ + struct dc_plane_state *plane_a; + struct dc_plane_state *plane_b; + struct dc_surface_update sa, sb; + + plane_a = kunit_kzalloc(test, sizeof(*plane_a), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_a); + plane_b = kunit_kzalloc(test, sizeof(*plane_b), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_b); + + plane_a->layer_index = 9; + plane_b->layer_index = 2; + sa.surface = plane_a; + sb.surface = plane_b; + + /* a has higher index, so cmp(a,b) = b - a < 0 (a sorts first) */ + KUNIT_EXPECT_LT(test, dm_plane_layer_index_cmp(&sa, &sb), 0); +} + +/* Tests for fill_plane_color_attributes() */ + +/** + * dm_test_fill_color_attr_rgb_format - Test Fill color attr rgb format + * @test: The KUnit test context + */ +static void dm_test_fill_color_attr_rgb_format(struct kunit *test) +{ + struct drm_plane_state plane_state = { 0 }; + enum dc_color_space color_space = COLOR_SPACE_UNKNOWN; + int ret; + + /* RGB format: should return 0 and set SRGB regardless of encoding */ + plane_state.color_encoding = DRM_COLOR_YCBCR_BT709; + plane_state.color_range = DRM_COLOR_YCBCR_FULL_RANGE; + + ret = fill_plane_color_attributes(&plane_state, + SURFACE_PIXEL_FORMAT_GRPH_ARGB8888, + &color_space); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, (int)color_space, (int)COLOR_SPACE_SRGB); +} + +/** + * dm_test_fill_color_attr_bt601_full - Test Fill color attr bt601 full + * @test: The KUnit test context + */ +static void dm_test_fill_color_attr_bt601_full(struct kunit *test) +{ + struct drm_plane_state plane_state = { 0 }; + enum dc_color_space color_space = COLOR_SPACE_UNKNOWN; + int ret; + + plane_state.color_encoding = DRM_COLOR_YCBCR_BT601; + plane_state.color_range = DRM_COLOR_YCBCR_FULL_RANGE; + + ret = fill_plane_color_attributes(&plane_state, + SURFACE_PIXEL_FORMAT_VIDEO_420_YCbCr, + &color_space); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, (int)color_space, (int)COLOR_SPACE_YCBCR601); +} + +/** + * dm_test_fill_color_attr_bt601_limited - Test Fill color attr bt601 limited + * @test: The KUnit test context + */ +static void dm_test_fill_color_attr_bt601_limited(struct kunit *test) +{ + struct drm_plane_state plane_state = { 0 }; + enum dc_color_space color_space = COLOR_SPACE_UNKNOWN; + int ret; + + plane_state.color_encoding = DRM_COLOR_YCBCR_BT601; + plane_state.color_range = DRM_COLOR_YCBCR_LIMITED_RANGE; + + ret = fill_plane_color_attributes(&plane_state, + SURFACE_PIXEL_FORMAT_VIDEO_420_YCbCr, + &color_space); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, (int)color_space, + (int)COLOR_SPACE_YCBCR601_LIMITED); +} + +/** + * dm_test_fill_color_attr_bt709_full - Test Fill color attr bt709 full + * @test: The KUnit test context + */ +static void dm_test_fill_color_attr_bt709_full(struct kunit *test) +{ + struct drm_plane_state plane_state = { 0 }; + enum dc_color_space color_space = COLOR_SPACE_UNKNOWN; + int ret; + + plane_state.color_encoding = DRM_COLOR_YCBCR_BT709; + plane_state.color_range = DRM_COLOR_YCBCR_FULL_RANGE; + + ret = fill_plane_color_attributes(&plane_state, + SURFACE_PIXEL_FORMAT_VIDEO_420_YCbCr, + &color_space); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, (int)color_space, (int)COLOR_SPACE_YCBCR709); +} + +/** + * dm_test_fill_color_attr_bt709_limited - Test Fill color attr bt709 limited + * @test: The KUnit test context + */ +static void dm_test_fill_color_attr_bt709_limited(struct kunit *test) +{ + struct drm_plane_state plane_state = { 0 }; + enum dc_color_space color_space = COLOR_SPACE_UNKNOWN; + int ret; + + plane_state.color_encoding = DRM_COLOR_YCBCR_BT709; + plane_state.color_range = DRM_COLOR_YCBCR_LIMITED_RANGE; + + ret = fill_plane_color_attributes(&plane_state, + SURFACE_PIXEL_FORMAT_VIDEO_420_YCbCr, + &color_space); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, (int)color_space, + (int)COLOR_SPACE_YCBCR709_LIMITED); +} + +/** + * dm_test_fill_color_attr_bt2020_full - Test Fill color attr bt2020 full + * @test: The KUnit test context + */ +static void dm_test_fill_color_attr_bt2020_full(struct kunit *test) +{ + struct drm_plane_state plane_state = { 0 }; + enum dc_color_space color_space = COLOR_SPACE_UNKNOWN; + int ret; + + plane_state.color_encoding = DRM_COLOR_YCBCR_BT2020; + plane_state.color_range = DRM_COLOR_YCBCR_FULL_RANGE; + + ret = fill_plane_color_attributes(&plane_state, + SURFACE_PIXEL_FORMAT_VIDEO_420_YCbCr, + &color_space); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, (int)color_space, + (int)COLOR_SPACE_2020_YCBCR_FULL); +} + +/** + * dm_test_fill_color_attr_bt2020_limited - Test Fill color attr bt2020 limited + * @test: The KUnit test context + */ +static void dm_test_fill_color_attr_bt2020_limited(struct kunit *test) +{ + struct drm_plane_state plane_state = { 0 }; + enum dc_color_space color_space = COLOR_SPACE_UNKNOWN; + int ret; + + plane_state.color_encoding = DRM_COLOR_YCBCR_BT2020; + plane_state.color_range = DRM_COLOR_YCBCR_LIMITED_RANGE; + + ret = fill_plane_color_attributes(&plane_state, + SURFACE_PIXEL_FORMAT_VIDEO_420_YCbCr, + &color_space); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, (int)color_space, + (int)COLOR_SPACE_2020_YCBCR_LIMITED); +} + +/** + * dm_test_fill_color_attr_invalid_encoding - Test Fill color attr invalid encoding + * @test: The KUnit test context + */ +static void dm_test_fill_color_attr_invalid_encoding(struct kunit *test) +{ + struct drm_plane_state plane_state = { 0 }; + enum dc_color_space color_space = COLOR_SPACE_UNKNOWN; + int ret; + + plane_state.color_encoding = 99; + plane_state.color_range = DRM_COLOR_YCBCR_FULL_RANGE; + + ret = fill_plane_color_attributes(&plane_state, + SURFACE_PIXEL_FORMAT_VIDEO_420_YCbCr, + &color_space); + KUNIT_EXPECT_EQ(test, ret, -EINVAL); +} + +/* Tests for modereset_required() */ + +/** + * dm_test_modereset_required_when_inactive_and_modeset - Test Modereset required when inactive and modeset + * @test: The KUnit test context + */ +static void dm_test_modereset_required_when_inactive_and_modeset(struct kunit *test) +{ + struct drm_crtc_state crtc_state = { 0 }; + + crtc_state.active = false; + crtc_state.mode_changed = true; + + KUNIT_EXPECT_TRUE(test, modereset_required(&crtc_state)); +} + +/** + * dm_test_modereset_not_required_when_active_and_modeset - Test Modereset not required when active and modeset + * @test: The KUnit test context + */ +static void dm_test_modereset_not_required_when_active_and_modeset(struct kunit *test) +{ + struct drm_crtc_state crtc_state = { 0 }; + + crtc_state.active = true; + crtc_state.mode_changed = true; + + KUNIT_EXPECT_FALSE(test, modereset_required(&crtc_state)); +} + +/** + * dm_test_modereset_not_required_when_inactive_without_modeset - Test Modereset not required when inactive without modeset + * @test: The KUnit test context + */ +static void dm_test_modereset_not_required_when_inactive_without_modeset(struct kunit *test) +{ + struct drm_crtc_state crtc_state = { 0 }; + + crtc_state.active = false; + crtc_state.mode_changed = false; + + KUNIT_EXPECT_FALSE(test, modereset_required(&crtc_state)); +} + +/* Tests for dm_get_oriented_plane_size() */ + +/** + * dm_test_oriented_plane_size_rotate_0 - Test Oriented plane size rotate 0 + * @test: The KUnit test context + */ +static void dm_test_oriented_plane_size_rotate_0(struct kunit *test) +{ + struct drm_plane_state plane_state = { 0 }; + int src_w = 0; + int src_h = 0; + + plane_state.rotation = DRM_MODE_ROTATE_0; + plane_state.src_w = 1920 << 16; + plane_state.src_h = 1080 << 16; + + dm_get_oriented_plane_size(&plane_state, &src_w, &src_h); + + KUNIT_EXPECT_EQ(test, src_w, 1920); + KUNIT_EXPECT_EQ(test, src_h, 1080); +} + +/** + * dm_test_oriented_plane_size_rotate_90 - Test Oriented plane size rotate 90 + * @test: The KUnit test context + */ +static void dm_test_oriented_plane_size_rotate_90(struct kunit *test) +{ + struct drm_plane_state plane_state = { 0 }; + int src_w = 0; + int src_h = 0; + + plane_state.rotation = DRM_MODE_ROTATE_90; + plane_state.src_w = 1920 << 16; + plane_state.src_h = 1080 << 16; + + dm_get_oriented_plane_size(&plane_state, &src_w, &src_h); + + KUNIT_EXPECT_EQ(test, src_w, 1080); + KUNIT_EXPECT_EQ(test, src_h, 1920); +} + +/** + * dm_test_oriented_plane_size_rotate_180 - Test Oriented plane size rotate 180 + * @test: The KUnit test context + */ +static void dm_test_oriented_plane_size_rotate_180(struct kunit *test) +{ + struct drm_plane_state plane_state = { 0 }; + int src_w = 0; + int src_h = 0; + + plane_state.rotation = DRM_MODE_ROTATE_180; + plane_state.src_w = 1920 << 16; + plane_state.src_h = 1080 << 16; + + dm_get_oriented_plane_size(&plane_state, &src_w, &src_h); + + KUNIT_EXPECT_EQ(test, src_w, 1920); + KUNIT_EXPECT_EQ(test, src_h, 1080); +} + +/** + * dm_test_oriented_plane_size_rotate_270 - Test Oriented plane size rotate 270 + * @test: The KUnit test context + */ +static void dm_test_oriented_plane_size_rotate_270(struct kunit *test) +{ + struct drm_plane_state plane_state = { 0 }; + int src_w = 0; + int src_h = 0; + + plane_state.rotation = DRM_MODE_ROTATE_270; + plane_state.src_w = 1920 << 16; + plane_state.src_h = 1080 << 16; + + dm_get_oriented_plane_size(&plane_state, &src_w, &src_h); + + KUNIT_EXPECT_EQ(test, src_w, 1080); + KUNIT_EXPECT_EQ(test, src_h, 1920); +} + +/* Tests for dm_get_plane_scale() */ + +/** + * dm_test_get_plane_scale_identity - Test Get plane scale identity + * @test: The KUnit test context + */ +static void dm_test_get_plane_scale_identity(struct kunit *test) +{ + struct drm_plane_state plane_state = { 0 }; + int scale_w = 0; + int scale_h = 0; + + plane_state.rotation = DRM_MODE_ROTATE_0; + plane_state.src_w = 1920 << 16; + plane_state.src_h = 1080 << 16; + plane_state.crtc_w = 1920; + plane_state.crtc_h = 1080; + + dm_get_plane_scale(&plane_state, &scale_w, &scale_h); + + KUNIT_EXPECT_EQ(test, scale_w, 1000); + KUNIT_EXPECT_EQ(test, scale_h, 1000); +} + +/** + * dm_test_get_plane_scale_rotate_90_identity - Test Get plane scale rotate 90 identity + * @test: The KUnit test context + */ +static void dm_test_get_plane_scale_rotate_90_identity(struct kunit *test) +{ + struct drm_plane_state plane_state = { 0 }; + int scale_w = 0; + int scale_h = 0; + + plane_state.rotation = DRM_MODE_ROTATE_90; + plane_state.src_w = 1920 << 16; + plane_state.src_h = 1080 << 16; + plane_state.crtc_w = 1080; + plane_state.crtc_h = 1920; + + dm_get_plane_scale(&plane_state, &scale_w, &scale_h); + + KUNIT_EXPECT_EQ(test, scale_w, 1000); + KUNIT_EXPECT_EQ(test, scale_h, 1000); +} + +/** + * dm_test_get_plane_scale_zero_src_width - Test Get plane scale zero src width + * @test: The KUnit test context + */ +static void dm_test_get_plane_scale_zero_src_width(struct kunit *test) +{ + struct drm_plane_state plane_state = { 0 }; + int scale_w = 0; + int scale_h = 0; + + plane_state.rotation = DRM_MODE_ROTATE_0; + plane_state.src_w = 0; + plane_state.src_h = 1080 << 16; + plane_state.crtc_w = 100; + plane_state.crtc_h = 200; + + dm_get_plane_scale(&plane_state, &scale_w, &scale_h); + + KUNIT_EXPECT_EQ(test, scale_w, 0); + KUNIT_EXPECT_EQ(test, scale_h, 185); +} + +/* Tests for is_scaling_state_different() */ + +/** + * dm_test_scaling_state_same - Test identical scaling states compare equal + * @test: The KUnit test context + */ +static void dm_test_scaling_state_same(struct kunit *test) +{ + struct dm_connector_state a = { 0 }; + struct dm_connector_state b = { 0 }; + + a.scaling = RMX_FULL; + a.underscan_enable = false; + b = a; + + KUNIT_EXPECT_FALSE(test, is_scaling_state_different(&a, &b)); +} + +/** + * dm_test_scaling_state_scaling_changed - Test differing scaling mode is detected + * @test: The KUnit test context + */ +static void dm_test_scaling_state_scaling_changed(struct kunit *test) +{ + struct dm_connector_state a = { 0 }; + struct dm_connector_state b = { 0 }; + + a.scaling = RMX_FULL; + b.scaling = RMX_CENTER; + + KUNIT_EXPECT_TRUE(test, is_scaling_state_different(&a, &b)); +} + +/** + * dm_test_scaling_state_underscan_enabled - Test enabling underscan with borders differs + * @test: The KUnit test context + */ +static void dm_test_scaling_state_underscan_enabled(struct kunit *test) +{ + struct dm_connector_state old_state = { 0 }; + struct dm_connector_state new_state = { 0 }; + + /* new enables underscan with non-zero borders, old has it disabled */ + new_state.underscan_enable = true; + new_state.underscan_hborder = 16; + new_state.underscan_vborder = 16; + old_state.underscan_enable = false; + + KUNIT_EXPECT_TRUE(test, is_scaling_state_different(&new_state, &old_state)); +} + +/** + * dm_test_scaling_state_underscan_border_changed - Test changed underscan borders differ + * @test: The KUnit test context + */ +static void dm_test_scaling_state_underscan_border_changed(struct kunit *test) +{ + struct dm_connector_state a = { 0 }; + struct dm_connector_state b = { 0 }; + + a.underscan_enable = true; + a.underscan_hborder = 16; + a.underscan_vborder = 16; + b = a; + b.underscan_hborder = 32; + + KUNIT_EXPECT_TRUE(test, is_scaling_state_different(&a, &b)); +} + +/* Tests for is_timing_unchanged_for_freesync() */ + +/** + * dm_test_timing_unchanged_null_args - Test NULL crtc states return false + * @test: The KUnit test context + */ +static void dm_test_timing_unchanged_null_args(struct kunit *test) +{ + struct drm_crtc_state crtc_state = { 0 }; + + KUNIT_EXPECT_FALSE(test, + is_timing_unchanged_for_freesync(NULL, &crtc_state)); + KUNIT_EXPECT_FALSE(test, + is_timing_unchanged_for_freesync(&crtc_state, NULL)); +} + +/** + * dm_test_timing_unchanged_identical_modes - Test identical modes are not "unchanged" + * @test: The KUnit test context + * + * The helper only returns true when vtotal/vsync shift (vrr) while the rest + * of the timing stays fixed, so identical modes must return false. + */ +static void dm_test_timing_unchanged_identical_modes(struct kunit *test) +{ + struct drm_crtc_state old_state = { 0 }; + struct drm_crtc_state new_state = { 0 }; + + old_state.mode.clock = 148500; + old_state.mode.hdisplay = 1920; + old_state.mode.vdisplay = 1080; + old_state.mode.htotal = 2200; + old_state.mode.vtotal = 1125; + new_state.mode = old_state.mode; + + KUNIT_EXPECT_FALSE(test, + is_timing_unchanged_for_freesync(&old_state, &new_state)); +} + +/** + * dm_test_timing_unchanged_vrr_shift - Test vrr-style vtotal/vsync shift is detected + * @test: The KUnit test context + */ +static void dm_test_timing_unchanged_vrr_shift(struct kunit *test) +{ + struct drm_crtc_state old_state = { 0 }; + struct drm_crtc_state new_state = { 0 }; + + old_state.mode.clock = 148500; + old_state.mode.hdisplay = 1920; + old_state.mode.vdisplay = 1080; + old_state.mode.htotal = 2200; + old_state.mode.vtotal = 1125; + old_state.mode.hsync_start = 2008; + old_state.mode.vsync_start = 1084; + old_state.mode.hsync_end = 2052; + old_state.mode.vsync_end = 1089; + + /* Same horizontal timing, vertical totals/sync shifted by 125 lines */ + new_state.mode = old_state.mode; + new_state.mode.vtotal = 1250; + new_state.mode.vsync_start = 1209; + new_state.mode.vsync_end = 1214; + + KUNIT_EXPECT_TRUE(test, + is_timing_unchanged_for_freesync(&old_state, &new_state)); +} + +/** + * dm_test_timing_unchanged_clock_changed - Test pixel clock change returns false + * @test: The KUnit test context + */ +static void dm_test_timing_unchanged_clock_changed(struct kunit *test) +{ + struct drm_crtc_state old_state = { 0 }; + struct drm_crtc_state new_state = { 0 }; + + old_state.mode.clock = 148500; + old_state.mode.htotal = 2200; + old_state.mode.vtotal = 1125; + old_state.mode.vsync_start = 1084; + old_state.mode.vsync_end = 1089; + + new_state.mode = old_state.mode; + new_state.mode.clock = 297000; + new_state.mode.vtotal = 1250; + new_state.mode.vsync_start = 1209; + new_state.mode.vsync_end = 1214; + + KUNIT_EXPECT_FALSE(test, + is_timing_unchanged_for_freesync(&old_state, &new_state)); +} + +/* Tests for set_freesync_fixed_config() */ + +/** + * dm_test_set_freesync_fixed_config_60hz - Test fixed refresh computed for 1080p60 + * @test: The KUnit test context + */ +static void dm_test_set_freesync_fixed_config_60hz(struct kunit *test) +{ + struct dm_crtc_state dm_crtc_state = { 0 }; + + dm_crtc_state.base.mode.clock = 148500; + dm_crtc_state.base.mode.htotal = 2200; + dm_crtc_state.base.mode.vtotal = 1125; + + set_freesync_fixed_config(&dm_crtc_state); + + KUNIT_EXPECT_EQ(test, (int)dm_crtc_state.freesync_config.state, + (int)VRR_STATE_ACTIVE_FIXED); + /* 148500 kHz / (2200 * 1125) = 60 Hz = 60000000 uHz */ + KUNIT_EXPECT_EQ(test, dm_crtc_state.freesync_config.fixed_refresh_in_uhz, + 60000000U); +} + +/* Tests for is_dc_timing_adjust_needed() */ + +/** + * dm_test_dc_timing_adjust_pending - Test a pending hw timing adjust forces true + * @test: The KUnit test context + */ +static void dm_test_dc_timing_adjust_pending(struct kunit *test) +{ + struct dm_crtc_state *old_state, *new_state; + struct dc_stream_state *stream; + + old_state = kunit_kzalloc(test, sizeof(*old_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_state); + new_state = kunit_kzalloc(test, sizeof(*new_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_state); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); + + new_state->stream = stream; + stream->adjust.timing_adjust_pending = 1; + + KUNIT_EXPECT_TRUE(test, is_dc_timing_adjust_needed(old_state, new_state)); +} + +/** + * dm_test_dc_timing_adjust_active_fixed - Test VRR active-fixed forces true + * @test: The KUnit test context + */ +static void dm_test_dc_timing_adjust_active_fixed(struct kunit *test) +{ + struct dm_crtc_state *old_state, *new_state; + struct dc_stream_state *stream; + + old_state = kunit_kzalloc(test, sizeof(*old_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_state); + new_state = kunit_kzalloc(test, sizeof(*new_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_state); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); + + new_state->stream = stream; + new_state->freesync_config.state = VRR_STATE_ACTIVE_FIXED; + + KUNIT_EXPECT_TRUE(test, is_dc_timing_adjust_needed(old_state, new_state)); +} + +/** + * dm_test_dc_timing_adjust_vrr_toggle - Test a change in vrr active state forces true + * @test: The KUnit test context + */ +static void dm_test_dc_timing_adjust_vrr_toggle(struct kunit *test) +{ + struct dm_crtc_state *old_state, *new_state; + struct dc_stream_state *stream; + + old_state = kunit_kzalloc(test, sizeof(*old_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_state); + new_state = kunit_kzalloc(test, sizeof(*new_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_state); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); + + new_state->stream = stream; + old_state->freesync_config.state = VRR_STATE_ACTIVE_VARIABLE; + new_state->freesync_config.state = VRR_STATE_INACTIVE; + + KUNIT_EXPECT_TRUE(test, is_dc_timing_adjust_needed(old_state, new_state)); +} + +/** + * dm_test_dc_timing_adjust_not_needed - Test steady-state timing needs no adjust + * @test: The KUnit test context + */ +static void dm_test_dc_timing_adjust_not_needed(struct kunit *test) +{ + struct dm_crtc_state *old_state, *new_state; + struct dc_stream_state *stream; + + old_state = kunit_kzalloc(test, sizeof(*old_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, old_state); + new_state = kunit_kzalloc(test, sizeof(*new_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, new_state); + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); + + new_state->stream = stream; + old_state->freesync_config.state = VRR_STATE_INACTIVE; + new_state->freesync_config.state = VRR_STATE_INACTIVE; + + KUNIT_EXPECT_FALSE(test, is_dc_timing_adjust_needed(old_state, new_state)); +} + +/* Tests for set_multisync_trigger_params() */ + +/** + * dm_test_multisync_trigger_disabled - Test disabled reset leaves params untouched + * @test: The KUnit test context + */ +static void dm_test_multisync_trigger_disabled(struct kunit *test) +{ + struct dc_stream_state *stream; + + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); + + stream->triggered_crtc_reset.enabled = false; + stream->triggered_crtc_reset.event = CRTC_EVENT_VSYNC_FALLING; + stream->triggered_crtc_reset.delay = TRIGGER_DELAY_NEXT_LINE; + + set_multisync_trigger_params(stream); + + /* Nothing should change when the reset trigger is disabled */ + KUNIT_EXPECT_EQ(test, (int)stream->triggered_crtc_reset.event, + (int)CRTC_EVENT_VSYNC_FALLING); + KUNIT_EXPECT_EQ(test, (int)stream->triggered_crtc_reset.delay, + (int)TRIGGER_DELAY_NEXT_LINE); +} + +/** + * dm_test_multisync_trigger_rising - Test positive vsync polarity selects rising edge + * @test: The KUnit test context + */ +static void dm_test_multisync_trigger_rising(struct kunit *test) +{ + struct dc_stream_state *stream; + struct dc_stream_state *master; + + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); + master = kunit_kzalloc(test, sizeof(*master), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, master); + + master->timing.flags.VSYNC_POSITIVE_POLARITY = 1; + stream->triggered_crtc_reset.enabled = true; + stream->triggered_crtc_reset.event_source = master; + + set_multisync_trigger_params(stream); + + KUNIT_EXPECT_EQ(test, (int)stream->triggered_crtc_reset.event, + (int)CRTC_EVENT_VSYNC_RISING); + KUNIT_EXPECT_EQ(test, (int)stream->triggered_crtc_reset.delay, + (int)TRIGGER_DELAY_NEXT_PIXEL); +} + +/** + * dm_test_multisync_trigger_falling - Test negative vsync polarity selects falling edge + * @test: The KUnit test context + */ +static void dm_test_multisync_trigger_falling(struct kunit *test) +{ + struct dc_stream_state *stream; + struct dc_stream_state *master; + + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream); + master = kunit_kzalloc(test, sizeof(*master), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, master); + + master->timing.flags.VSYNC_POSITIVE_POLARITY = 0; + stream->triggered_crtc_reset.enabled = true; + stream->triggered_crtc_reset.event_source = master; + + set_multisync_trigger_params(stream); + + KUNIT_EXPECT_EQ(test, (int)stream->triggered_crtc_reset.event, + (int)CRTC_EVENT_VSYNC_FALLING); + KUNIT_EXPECT_EQ(test, (int)stream->triggered_crtc_reset.delay, + (int)TRIGGER_DELAY_NEXT_PIXEL); +} + +/* Tests for set_master_stream() */ + +/** + * dm_test_master_stream_highest_refresh - Test highest refresh-rate stream becomes master + * @test: The KUnit test context + */ +static void dm_test_master_stream_highest_refresh(struct kunit *test) +{ + struct dc_stream_state *stream0, *stream1; + struct dc_stream_state *stream_set[2]; + + stream0 = kunit_kzalloc(test, sizeof(*stream0), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream0); + stream1 = kunit_kzalloc(test, sizeof(*stream1), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream1); + stream_set[0] = stream0; + stream_set[1] = stream1; + + /* stream0: 60Hz, stream1: 120Hz -> stream1 is master */ + stream0->triggered_crtc_reset.enabled = true; + stream0->timing.pix_clk_100hz = 1485000; + stream0->timing.h_total = 2200; + stream0->timing.v_total = 1125; + + stream1->triggered_crtc_reset.enabled = true; + stream1->timing.pix_clk_100hz = 2970000; + stream1->timing.h_total = 2200; + stream1->timing.v_total = 1125; + + set_master_stream(stream_set, 2); + + KUNIT_EXPECT_PTR_EQ(test, stream0->triggered_crtc_reset.event_source, + stream1); + KUNIT_EXPECT_PTR_EQ(test, stream1->triggered_crtc_reset.event_source, + stream1); +} + +/** + * dm_test_master_stream_defaults_to_first - Test default master when none triggered + * @test: The KUnit test context + * + * When no stream has the reset trigger enabled, master_stream stays 0 and all + * streams point at the first stream as their event source. + */ +static void dm_test_master_stream_defaults_to_first(struct kunit *test) +{ + struct dc_stream_state *stream0, *stream1; + struct dc_stream_state *stream_set[2]; + + stream0 = kunit_kzalloc(test, sizeof(*stream0), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream0); + stream1 = kunit_kzalloc(test, sizeof(*stream1), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream1); + stream_set[0] = stream0; + stream_set[1] = stream1; + + set_master_stream(stream_set, 2); + + KUNIT_EXPECT_PTR_EQ(test, stream0->triggered_crtc_reset.event_source, + stream0); + KUNIT_EXPECT_PTR_EQ(test, stream1->triggered_crtc_reset.event_source, + stream0); +} + +static struct kunit_case amdgpu_dm_tests[] = { + /* dm_plane_layer_index_cmp */ + KUNIT_CASE(dm_test_plane_layer_index_cmp_equal), + KUNIT_CASE(dm_test_plane_layer_index_cmp_descending), + KUNIT_CASE(dm_test_plane_layer_index_cmp_ascending), + /* fill_plane_color_attributes */ + KUNIT_CASE(dm_test_fill_color_attr_rgb_format), + KUNIT_CASE(dm_test_fill_color_attr_bt601_full), + KUNIT_CASE(dm_test_fill_color_attr_bt601_limited), + KUNIT_CASE(dm_test_fill_color_attr_bt709_full), + KUNIT_CASE(dm_test_fill_color_attr_bt709_limited), + KUNIT_CASE(dm_test_fill_color_attr_bt2020_full), + KUNIT_CASE(dm_test_fill_color_attr_bt2020_limited), + KUNIT_CASE(dm_test_fill_color_attr_invalid_encoding), + /* modereset_required */ + KUNIT_CASE(dm_test_modereset_required_when_inactive_and_modeset), + KUNIT_CASE(dm_test_modereset_not_required_when_active_and_modeset), + KUNIT_CASE(dm_test_modereset_not_required_when_inactive_without_modeset), + /* dm_get_oriented_plane_size */ + KUNIT_CASE(dm_test_oriented_plane_size_rotate_0), + KUNIT_CASE(dm_test_oriented_plane_size_rotate_90), + KUNIT_CASE(dm_test_oriented_plane_size_rotate_180), + KUNIT_CASE(dm_test_oriented_plane_size_rotate_270), + /* dm_get_plane_scale */ + KUNIT_CASE(dm_test_get_plane_scale_identity), + KUNIT_CASE(dm_test_get_plane_scale_rotate_90_identity), + KUNIT_CASE(dm_test_get_plane_scale_zero_src_width), + /* is_scaling_state_different */ + KUNIT_CASE(dm_test_scaling_state_same), + KUNIT_CASE(dm_test_scaling_state_scaling_changed), + KUNIT_CASE(dm_test_scaling_state_underscan_enabled), + KUNIT_CASE(dm_test_scaling_state_underscan_border_changed), + /* is_timing_unchanged_for_freesync */ + KUNIT_CASE(dm_test_timing_unchanged_null_args), + KUNIT_CASE(dm_test_timing_unchanged_identical_modes), + KUNIT_CASE(dm_test_timing_unchanged_vrr_shift), + KUNIT_CASE(dm_test_timing_unchanged_clock_changed), + /* set_freesync_fixed_config */ + KUNIT_CASE(dm_test_set_freesync_fixed_config_60hz), + /* is_dc_timing_adjust_needed */ + KUNIT_CASE(dm_test_dc_timing_adjust_pending), + KUNIT_CASE(dm_test_dc_timing_adjust_active_fixed), + KUNIT_CASE(dm_test_dc_timing_adjust_vrr_toggle), + KUNIT_CASE(dm_test_dc_timing_adjust_not_needed), + /* set_multisync_trigger_params */ + KUNIT_CASE(dm_test_multisync_trigger_disabled), + KUNIT_CASE(dm_test_multisync_trigger_rising), + KUNIT_CASE(dm_test_multisync_trigger_falling), + /* set_master_stream */ + KUNIT_CASE(dm_test_master_stream_highest_refresh), + KUNIT_CASE(dm_test_master_stream_defaults_to_first), + {} +}; + +static struct kunit_suite amdgpu_dm_test_suite = { + .name = "amdgpu_dm", + .test_cases = amdgpu_dm_tests, +}; + +kunit_test_suite(amdgpu_dm_test_suite); + +MODULE_AUTHOR("AMD"); +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm"); +MODULE_LICENSE("Dual MIT/GPL"); -- cgit v1.2.3 From 179b22085ef0177fa3c78afa2858100d59f5a991 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Wed, 29 Apr 2026 20:57:54 -0600 Subject: drm/amd/display: Add KUnit tests for amdgpu_dm_backlight Add KUnit tests for the backlight helpers in amdgpu_dm_backlight.c. Tests cover: - amdgpu_dm_update_backlight_caps(): short-circuit on populated caps and default value assignment - get_brightness_range(): NULL, PWM-only, and AUX backlight paths - convert_brightness_to_user(): minimum clamp, maximum passthrough, and mid-range rescaling - convert_brightness_from_user(): linear rescaling, AUX path, and custom-curve mapping - convert_custom_brightness(): exact match, below-first, interpolation, above-last, single data point, zero lower luminance, and the debug-mask and no-data-point guards - amdgpu_dm_update_connector_ext_caps(): negative bl_idx and non-eDP early returns, OLED defaults, luminance range copy, and the amdgpu_backlight force-AUX/force-PWM overrides - amdgpu_dm_should_create_sysfs(): forced ABM, non-eDP, missing backlight index, and AUX vs PWM backlight - amdgpu_dm_setup_backlight_device(): non-eDP/LVDS skip, disconnected link skip, eDP-count limit, and the successful eDP setup path Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_backlight.c | 71 +- .../amd/display/amdgpu_dm/amdgpu_dm_backlight.h | 18 + .../gpu/drm/amd/display/amdgpu_dm/tests/Makefile | 2 + .../amdgpu_dm/tests/amdgpu_dm_backlight_test.c | 1128 ++++++++++++++++++++ 4 files changed, 1210 insertions(+), 9 deletions(-) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c index 3770e8dafdbf..f101aed75bb3 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c @@ -47,6 +47,7 @@ #include "amdgpu_dm_trace.h" #include "amd_shared.h" +#include "amdgpu_dm_kunit_helpers.h" #define AMDGPU_DM_DEFAULT_MIN_BACKLIGHT 12 #define AMDGPU_DM_DEFAULT_MAX_BACKLIGHT 255 @@ -92,9 +93,11 @@ void amdgpu_dm_update_backlight_caps(struct amdgpu_display_manager *dm, caps->caps_valid = true; #endif } +EXPORT_IF_KUNIT(amdgpu_dm_update_backlight_caps); -static int get_brightness_range(const struct amdgpu_dm_backlight_caps *caps, - unsigned int *min, unsigned int *max) +STATIC_IFN_KUNIT +int get_brightness_range(const struct amdgpu_dm_backlight_caps *caps, + unsigned int *min, unsigned int *max) { if (!caps) return 0; @@ -110,6 +113,7 @@ static int get_brightness_range(const struct amdgpu_dm_backlight_caps *caps, } return 1; } +EXPORT_IF_KUNIT(get_brightness_range); /* Rescale from [min..max] to [0..AMDGPU_MAX_BL_LEVEL] */ static inline u32 scale_input_to_fw(int min, int max, u64 input) @@ -123,9 +127,10 @@ static inline u32 scale_fw_to_input(int min, int max, u64 input) return min + DIV_ROUND_CLOSEST_ULL(input * (max - min), AMDGPU_MAX_BL_LEVEL); } -static void convert_custom_brightness(const struct amdgpu_dm_backlight_caps *caps, - unsigned int min, unsigned int max, - uint32_t *user_brightness) +STATIC_IFN_KUNIT +void convert_custom_brightness(const struct amdgpu_dm_backlight_caps *caps, + unsigned int min, unsigned int max, + uint32_t *user_brightness) { u32 brightness = scale_input_to_fw(min, max, *user_brightness); u8 lower_signal, upper_signal, upper_lum, lower_lum, lum; @@ -187,8 +192,11 @@ scale: DIV_ROUND_CLOSEST(lum * brightness, 101)); } -static u32 convert_brightness_from_user(const struct amdgpu_dm_backlight_caps *caps, - uint32_t brightness) +EXPORT_IF_KUNIT(convert_custom_brightness); + +STATIC_IFN_KUNIT +u32 convert_brightness_from_user(const struct amdgpu_dm_backlight_caps *caps, + uint32_t brightness) { unsigned int min, max; @@ -201,8 +209,11 @@ static u32 convert_brightness_from_user(const struct amdgpu_dm_backlight_caps *c return min + DIV_ROUND_CLOSEST_ULL((u64)(max - min) * brightness, max); } -static u32 convert_brightness_to_user(const struct amdgpu_dm_backlight_caps *caps, - uint32_t brightness) +EXPORT_IF_KUNIT(convert_brightness_from_user); + +STATIC_IFN_KUNIT +u32 convert_brightness_to_user(const struct amdgpu_dm_backlight_caps *caps, + uint32_t brightness) { unsigned int min, max; @@ -215,6 +226,7 @@ static u32 convert_brightness_to_user(const struct amdgpu_dm_backlight_caps *cap return DIV_ROUND_CLOSEST_ULL((u64)max * (brightness - min), max - min); } +EXPORT_IF_KUNIT(convert_brightness_to_user); static struct dc_stream_state *dm_find_stream_with_link( struct amdgpu_display_manager *dm, @@ -529,6 +541,7 @@ void amdgpu_dm_update_connector_ext_caps(struct amdgpu_dm_connector *aconnector) } } } +EXPORT_IF_KUNIT(amdgpu_dm_update_connector_ext_caps); void amdgpu_dm_setup_backlight_device(struct amdgpu_display_manager *dm, struct amdgpu_dm_connector *aconnector) @@ -561,6 +574,7 @@ void amdgpu_dm_setup_backlight_device(struct amdgpu_display_manager *dm, dm->adev->mode_info.abm_level_property, ABM_SYSFS_CONTROL); } +EXPORT_IF_KUNIT(amdgpu_dm_setup_backlight_device); /** * DOC: panel power savings @@ -658,3 +672,42 @@ amdgpu_dm_should_create_sysfs(struct amdgpu_dm_connector *amdgpu_dm_connector) return true; } +EXPORT_IF_KUNIT(amdgpu_dm_should_create_sysfs); + +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +uint amdgpu_dm_get_dc_debug_mask(void) +{ + return amdgpu_dc_debug_mask; +} +EXPORT_IF_KUNIT(amdgpu_dm_get_dc_debug_mask); + +void amdgpu_dm_set_dc_debug_mask(uint val) +{ + amdgpu_dc_debug_mask = val; +} +EXPORT_IF_KUNIT(amdgpu_dm_set_dc_debug_mask); + +int amdgpu_dm_get_abm_level_param(void) +{ + return amdgpu_dm_abm_level; +} +EXPORT_IF_KUNIT(amdgpu_dm_get_abm_level_param); + +void amdgpu_dm_set_abm_level_param(int val) +{ + amdgpu_dm_abm_level = val; +} +EXPORT_IF_KUNIT(amdgpu_dm_set_abm_level_param); + +int amdgpu_dm_get_backlight_param(void) +{ + return amdgpu_backlight; +} +EXPORT_IF_KUNIT(amdgpu_dm_get_backlight_param); + +void amdgpu_dm_set_backlight_param(int val) +{ + amdgpu_backlight = val; +} +EXPORT_IF_KUNIT(amdgpu_dm_set_backlight_param); +#endif diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h index acff23f9feef..5234da6ae484 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h @@ -41,4 +41,22 @@ bool amdgpu_dm_should_create_sysfs(struct amdgpu_dm_connector *aconnector); extern const struct attribute_group amdgpu_group; +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +int get_brightness_range(const struct amdgpu_dm_backlight_caps *caps, + unsigned int *min, unsigned int *max); +void convert_custom_brightness(const struct amdgpu_dm_backlight_caps *caps, + unsigned int min, unsigned int max, + uint32_t *user_brightness); +u32 convert_brightness_from_user(const struct amdgpu_dm_backlight_caps *caps, + uint32_t brightness); +u32 convert_brightness_to_user(const struct amdgpu_dm_backlight_caps *caps, + uint32_t brightness); +uint amdgpu_dm_get_dc_debug_mask(void); +void amdgpu_dm_set_dc_debug_mask(uint val); +int amdgpu_dm_get_abm_level_param(void); +void amdgpu_dm_set_abm_level_param(int val); +int amdgpu_dm_get_backlight_param(void); +void amdgpu_dm_set_backlight_param(int val); +#endif + #endif /* __AMDGPU_DM_BACKLIGHT_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile index 4365d4024f70..ddd9fce66232 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile @@ -8,12 +8,14 @@ ccflags-y += -I$(src)/../../include ccflags-y += -I$(src)/../../modules/inc ccflags-y += -I$(src)/../../dc ccflags-y += -I$(src)/../../../amdgpu +ccflags-y += -I$(src)/../../../amdkfd ccflags-y += -I$(src)/../../../include obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_crc_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_hdcp_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_color_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_colorop_test.o +obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_backlight_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_psr_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_replay_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_ism_test.o diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c new file mode 100644 index 000000000000..2f4293cfd478 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c @@ -0,0 +1,1128 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit tests for amdgpu_dm_backlight.c + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include + +#include "dc.h" +#include "amdgpu.h" +#include "amdgpu_mode.h" +#include "amdgpu_dm.h" +#include "amdgpu_dm_backlight.h" +#include "amd_shared.h" + +struct dm_backlight_connector_fixture { + struct amdgpu_device *adev; + struct amdgpu_dm_connector *aconnector; + struct dc_link *link; +}; + +static struct amdgpu_display_manager *alloc_test_dm(struct kunit *test) +{ + struct amdgpu_display_manager *dm; + + dm = kunit_kzalloc(test, sizeof(*dm), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm); + + return dm; +} + +static void setup_test_connector(struct kunit *test, + struct dm_backlight_connector_fixture *fixture, + int bl_idx, enum signal_type signal) +{ + fixture->adev = kunit_kzalloc(test, sizeof(*fixture->adev), GFP_KERNEL); + fixture->aconnector = kunit_kzalloc(test, sizeof(*fixture->aconnector), GFP_KERNEL); + fixture->link = kunit_kzalloc(test, sizeof(*fixture->link), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fixture->adev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fixture->aconnector); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fixture->link); + + fixture->aconnector->bl_idx = bl_idx; + fixture->aconnector->dc_link = fixture->link; + fixture->aconnector->base.dev = &fixture->adev->ddev; + fixture->link->connector_signal = signal; +} + +/* Tests for amdgpu_dm_update_backlight_caps() */ + +/** + * dm_test_backlight_caps_valid_short_circuit - Test Backlight caps valid short circuit + * @test: The KUnit test context + */ +static void dm_test_backlight_caps_valid_short_circuit(struct kunit *test) +{ + struct amdgpu_display_manager *dm = alloc_test_dm(test); + struct amdgpu_dm_backlight_caps *caps = &dm->backlight_caps[0]; + + caps->caps_valid = true; + caps->aux_support = false; + caps->min_input_signal = 42; + caps->max_input_signal = 199; + + amdgpu_dm_update_backlight_caps(dm, 0); + + KUNIT_EXPECT_TRUE(test, caps->caps_valid); + KUNIT_EXPECT_EQ(test, caps->min_input_signal, 42); + KUNIT_EXPECT_EQ(test, caps->max_input_signal, 199); +} + +#if !defined(CONFIG_ACPI) + +/** + * dm_test_backlight_caps_aux_support_noop - Test Backlight caps aux support noop + * @test: The KUnit test context + */ +static void dm_test_backlight_caps_aux_support_noop(struct kunit *test) +{ + struct amdgpu_display_manager *dm = alloc_test_dm(test); + struct amdgpu_dm_backlight_caps *caps = &dm->backlight_caps[0]; + + caps->caps_valid = false; + caps->aux_support = true; + caps->min_input_signal = 11; + caps->max_input_signal = 222; + + amdgpu_dm_update_backlight_caps(dm, 0); + + KUNIT_EXPECT_FALSE(test, caps->caps_valid); + KUNIT_EXPECT_EQ(test, caps->min_input_signal, 11); + KUNIT_EXPECT_EQ(test, caps->max_input_signal, 222); +} + +/** + * dm_test_backlight_caps_non_aux_sets_defaults - Test Backlight caps non aux sets defaults + * @test: The KUnit test context + */ +static void dm_test_backlight_caps_non_aux_sets_defaults(struct kunit *test) +{ + struct amdgpu_display_manager *dm = alloc_test_dm(test); + struct amdgpu_dm_backlight_caps *caps = &dm->backlight_caps[0]; + + caps->caps_valid = false; + caps->aux_support = false; + caps->min_input_signal = 0; + caps->max_input_signal = 0; + + amdgpu_dm_update_backlight_caps(dm, 0); + + KUNIT_EXPECT_TRUE(test, caps->caps_valid); + KUNIT_EXPECT_EQ(test, caps->min_input_signal, 12); + KUNIT_EXPECT_EQ(test, caps->max_input_signal, 255); +} +#endif + +/* Tests for get_brightness_range() */ + +/** + * dm_test_brightness_range_null_caps - Test Brightness range null caps + * @test: The KUnit test context + */ +static void dm_test_brightness_range_null_caps(struct kunit *test) +{ + unsigned int min = 99, max = 99; + + KUNIT_EXPECT_EQ(test, get_brightness_range(NULL, &min, &max), 0); + /* min/max should remain untouched */ + KUNIT_EXPECT_EQ(test, min, 99U); + KUNIT_EXPECT_EQ(test, max, 99U); +} + +/** + * dm_test_brightness_range_pwm - Test Brightness range pwm + * @test: The KUnit test context + */ +static void dm_test_brightness_range_pwm(struct kunit *test) +{ + struct amdgpu_dm_backlight_caps caps = {}; + unsigned int min, max; + + caps.aux_support = false; + caps.min_input_signal = 12; + caps.max_input_signal = 255; + + KUNIT_EXPECT_EQ(test, get_brightness_range(&caps, &min, &max), 1); + /* 0x101 * 12 = 3084, 0x101 * 255 = 65535 */ + KUNIT_EXPECT_EQ(test, min, 0x101U * 12); + KUNIT_EXPECT_EQ(test, max, 0x101U * 255); +} + +/** + * dm_test_brightness_range_aux - Test Brightness range aux + * @test: The KUnit test context + */ +static void dm_test_brightness_range_aux(struct kunit *test) +{ + struct amdgpu_dm_backlight_caps caps = {}; + unsigned int min, max; + + caps.aux_support = true; + caps.aux_min_input_signal = 1; + caps.aux_max_input_signal = 512; + + KUNIT_EXPECT_EQ(test, get_brightness_range(&caps, &min, &max), 1); + /* millinits: 1000 * value */ + KUNIT_EXPECT_EQ(test, min, 1000U); + KUNIT_EXPECT_EQ(test, max, 512000U); +} + +/* Tests for convert_brightness_to_user() */ + +/** + * dm_test_brightness_to_user_null_caps - Test Brightness to user null caps + * @test: The KUnit test context + */ +static void dm_test_brightness_to_user_null_caps(struct kunit *test) +{ + /* + * With NULL caps, get_brightness_range fails → passthrough. + * We simulate this by passing a zeroed caps struct where + * max_input_signal=0 makes max=0 and the function hits + * get_brightness_range returning 0 since caps is NULL. + */ + KUNIT_EXPECT_EQ(test, convert_brightness_to_user(NULL, 42), 42U); +} + +/** + * dm_test_brightness_to_user_below_min - Test Brightness to user below min + * @test: The KUnit test context + */ +static void dm_test_brightness_to_user_below_min(struct kunit *test) +{ + struct amdgpu_dm_backlight_caps caps = {}; + + caps.aux_support = false; + caps.min_input_signal = 12; + caps.max_input_signal = 255; + + /* brightness < min (0x101*12 = 3084), should return 0 */ + KUNIT_EXPECT_EQ(test, convert_brightness_to_user(&caps, 100), 0U); +} + +/** + * dm_test_brightness_to_user_at_max - Test Brightness to user at max + * @test: The KUnit test context + */ +static void dm_test_brightness_to_user_at_max(struct kunit *test) +{ + struct amdgpu_dm_backlight_caps caps = {}; + unsigned int min, max; + + caps.aux_support = false; + caps.min_input_signal = 12; + caps.max_input_signal = 255; + + get_brightness_range(&caps, &min, &max); + + /* At max → should return max */ + KUNIT_EXPECT_EQ(test, convert_brightness_to_user(&caps, max), max); +} + +/** + * dm_test_brightness_to_user_at_min - Test Brightness to user at min + * @test: The KUnit test context + */ +static void dm_test_brightness_to_user_at_min(struct kunit *test) +{ + struct amdgpu_dm_backlight_caps caps = {}; + unsigned int min, max; + + caps.aux_support = false; + caps.min_input_signal = 12; + caps.max_input_signal = 255; + + get_brightness_range(&caps, &min, &max); + + /* At min → should return 0 */ + KUNIT_EXPECT_EQ(test, convert_brightness_to_user(&caps, min), 0U); +} + +/** + * dm_test_brightness_to_user_midpoint_pwm - Test Brightness to user midpoint pwm + * @test: The KUnit test context + */ +static void dm_test_brightness_to_user_midpoint_pwm(struct kunit *test) +{ + struct amdgpu_dm_backlight_caps caps = {}; + unsigned int min, max, mid_hw, result; + u64 expected; + + caps.aux_support = false; + caps.min_input_signal = 12; + caps.max_input_signal = 255; + + get_brightness_range(&caps, &min, &max); + + /* midpoint of hw range */ + mid_hw = min + (max - min) / 2; + /* expected = DIV_ROUND_CLOSEST_ULL((u64)max * (mid_hw - min), max - min) */ + expected = DIV_ROUND_CLOSEST_ULL((u64)max * (mid_hw - min), max - min); + result = convert_brightness_to_user(&caps, mid_hw); + + KUNIT_EXPECT_EQ(test, result, (u32)expected); +} + +/* Tests for convert_brightness_from_user() — no custom curve */ + +/** + * dm_test_brightness_from_user_null_caps - Test Brightness from user null caps + * @test: The KUnit test context + */ +static void dm_test_brightness_from_user_null_caps(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, convert_brightness_from_user(NULL, 100), 100U); +} + +/** + * dm_test_brightness_from_user_zero - Test Brightness from user zero + * @test: The KUnit test context + */ +static void dm_test_brightness_from_user_zero(struct kunit *test) +{ + struct amdgpu_dm_backlight_caps caps = {}; + unsigned int min, max; + + caps.aux_support = false; + caps.min_input_signal = 12; + caps.max_input_signal = 255; + /* no custom curve */ + caps.data_points = 0; + + get_brightness_range(&caps, &min, &max); + + /* brightness=0 → min + 0 = min */ + KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, 0), (u32)min); +} + +/** + * dm_test_brightness_from_user_max - Test Brightness from user max + * @test: The KUnit test context + */ +static void dm_test_brightness_from_user_max(struct kunit *test) +{ + struct amdgpu_dm_backlight_caps caps = {}; + unsigned int min, max; + + caps.aux_support = false; + caps.min_input_signal = 12; + caps.max_input_signal = 255; + caps.data_points = 0; + + get_brightness_range(&caps, &min, &max); + + /* + * brightness=max → min + DIV_ROUND_CLOSEST((max-min)*max, max) + * = min + (max - min) = max + */ + KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, max), (u32)max); +} + +/** + * dm_test_brightness_from_user_aux - Test Brightness from user aux + * @test: The KUnit test context + */ +static void dm_test_brightness_from_user_aux(struct kunit *test) +{ + struct amdgpu_dm_backlight_caps caps = {}; + unsigned int min, max; + + caps.aux_support = true; + caps.aux_min_input_signal = 1; + caps.aux_max_input_signal = 512; + caps.data_points = 0; + + get_brightness_range(&caps, &min, &max); + + /* brightness=0 → min */ + KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, 0), (u32)min); + /* brightness=max → max */ + KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, max), (u32)max); +} + +/* Tests for convert_custom_brightness() */ + +/** + * dm_test_custom_brightness_no_data_points - Test Custom brightness no data points + * @test: The KUnit test context + */ +static void dm_test_custom_brightness_no_data_points(struct kunit *test) +{ + struct amdgpu_dm_backlight_caps caps = {}; + uint32_t brightness = 128; + uint32_t saved = brightness; + + caps.data_points = 0; + + convert_custom_brightness(&caps, 3084, 65535, &brightness); + + /* No data points → no-op */ + KUNIT_EXPECT_EQ(test, brightness, saved); +} + +/** + * dm_test_custom_brightness_debug_mask_disables - Test Custom brightness debug mask disables + * @test: The KUnit test context + */ +static void dm_test_custom_brightness_debug_mask_disables(struct kunit *test) +{ + struct amdgpu_dm_backlight_caps caps = {}; + uint32_t brightness = 128; + uint32_t saved = brightness; + uint saved_mask = amdgpu_dm_get_dc_debug_mask(); + + caps.data_points = 3; + caps.luminance_data[0].input_signal = 50; + caps.luminance_data[0].luminance = 10; + + /* Set the disable flag */ + amdgpu_dm_set_dc_debug_mask(amdgpu_dm_get_dc_debug_mask() | DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE); + + convert_custom_brightness(&caps, 3084, 65535, &brightness); + + /* Should be no-op due to debug mask */ + KUNIT_EXPECT_EQ(test, brightness, saved); + + amdgpu_dm_set_dc_debug_mask(saved_mask); +} + +/** + * dm_test_custom_brightness_exact_match - Test Custom brightness exact match + * @test: The KUnit test context + */ +static void dm_test_custom_brightness_exact_match(struct kunit *test) +{ + struct amdgpu_dm_backlight_caps caps = {}; + uint32_t brightness; + unsigned int min, max; + uint saved_mask = amdgpu_dm_get_dc_debug_mask(); + + amdgpu_dm_set_dc_debug_mask(amdgpu_dm_get_dc_debug_mask() & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE); + + caps.aux_support = false; + caps.min_input_signal = 0; + caps.max_input_signal = 255; + caps.data_points = 3; + caps.luminance_data[0].input_signal = 50; + caps.luminance_data[0].luminance = 20; + caps.luminance_data[1].input_signal = 128; + caps.luminance_data[1].luminance = 50; + caps.luminance_data[2].input_signal = 200; + caps.luminance_data[2].luminance = 90; + + get_brightness_range(&caps, &min, &max); + + /* + * Set brightness so that scale_input_to_fw yields exactly 128. + * scale_input_to_fw(min, max, x) = DIV_ROUND_CLOSEST(x * 255, max - min) + * With min=0, max=0x101*255=65535: + * We need x such that DIV_ROUND_CLOSEST(x * 255, 65535) = 128 + * → x = 128 * 65535 / 255 = 32896 + */ + brightness = 32896; + + convert_custom_brightness(&caps, min, max, &brightness); + + /* + * Exact match: lum=50, brightness_scaled=128 + * result = scale_fw_to_input(min, max, DIV_ROUND_CLOSEST(50*128, 101)) + * = scale_fw_to_input(0, 65535, DIV_ROUND_CLOSEST(6400, 101)) + * = scale_fw_to_input(0, 65535, 63) + * = 0 + DIV_ROUND_CLOSEST(63 * 65535, 255) = 16191 (approx) + */ + KUNIT_EXPECT_TRUE(test, brightness != 32896); + KUNIT_EXPECT_TRUE(test, brightness < 32896); + + amdgpu_dm_set_dc_debug_mask(saved_mask); +} + +/** + * dm_test_custom_brightness_below_first - Test Custom brightness below first + * @test: The KUnit test context + */ +static void dm_test_custom_brightness_below_first(struct kunit *test) +{ + struct amdgpu_dm_backlight_caps caps = {}; + uint32_t brightness; + unsigned int min, max; + uint saved_mask = amdgpu_dm_get_dc_debug_mask(); + + amdgpu_dm_set_dc_debug_mask(amdgpu_dm_get_dc_debug_mask() & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE); + + caps.aux_support = false; + caps.min_input_signal = 0; + caps.max_input_signal = 255; + caps.data_points = 2; + caps.luminance_data[0].input_signal = 100; + caps.luminance_data[0].luminance = 40; + caps.luminance_data[1].input_signal = 200; + caps.luminance_data[1].luminance = 80; + + get_brightness_range(&caps, &min, &max); + + /* + * Set brightness low enough that scaled value < 100. + * scale_input_to_fw(0, 65535, x) = DIV_ROUND_CLOSEST(x*255, 65535) + * For result=50: x = 50*65535/255 = 12850 + */ + brightness = 12850; + + convert_custom_brightness(&caps, min, max, &brightness); + + /* + * Below first data point: lum = DIV_ROUND_CLOSEST(40 * 50, 100) = 20 + * Then: scale_fw_to_input(0, 65535, DIV_ROUND_CLOSEST(20 * 50, 101)) + * = scale_fw_to_input(0, 65535, DIV_ROUND_CLOSEST(1000, 101)) + * = scale_fw_to_input(0, 65535, 10) + * The output should be significantly less than input. + */ + KUNIT_EXPECT_TRUE(test, brightness < 12850); + + amdgpu_dm_set_dc_debug_mask(saved_mask); +} + +/** + * dm_test_custom_brightness_interpolation - Test Custom brightness interpolation + * @test: The KUnit test context + */ +static void dm_test_custom_brightness_interpolation(struct kunit *test) +{ + struct amdgpu_dm_backlight_caps caps = {}; + uint32_t brightness; + unsigned int min, max; + uint saved_mask = amdgpu_dm_get_dc_debug_mask(); + + amdgpu_dm_set_dc_debug_mask(amdgpu_dm_get_dc_debug_mask() & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE); + + caps.aux_support = false; + caps.min_input_signal = 0; + caps.max_input_signal = 255; + caps.data_points = 2; + caps.luminance_data[0].input_signal = 50; + caps.luminance_data[0].luminance = 20; + caps.luminance_data[1].input_signal = 200; + caps.luminance_data[1].luminance = 80; + + get_brightness_range(&caps, &min, &max); + + /* + * Choose a value between data points 50 and 200. + * scale_input_to_fw(0, 65535, x) = 125 when x = 125*65535/255 = 32125 + */ + brightness = 32125; + + convert_custom_brightness(&caps, min, max, &brightness); + + /* + * The function should interpolate between data points and produce + * a remapped value different from the input. + */ + KUNIT_EXPECT_TRUE(test, brightness != 32125); + + amdgpu_dm_set_dc_debug_mask(saved_mask); +} + +/** + * dm_test_custom_brightness_above_last - Test Custom brightness above last data point + * @test: The KUnit test context + */ +static void dm_test_custom_brightness_above_last(struct kunit *test) +{ + struct amdgpu_dm_backlight_caps caps = {}; + uint32_t brightness; + unsigned int min, max; + uint saved_mask = amdgpu_dm_get_dc_debug_mask(); + + amdgpu_dm_set_dc_debug_mask(amdgpu_dm_get_dc_debug_mask() & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE); + + caps.aux_support = false; + caps.min_input_signal = 0; + caps.max_input_signal = 255; + caps.data_points = 2; + caps.luminance_data[0].input_signal = 50; + caps.luminance_data[0].luminance = 20; + caps.luminance_data[1].input_signal = 150; + caps.luminance_data[1].luminance = 60; + + get_brightness_range(&caps, &min, &max); + + /* + * Choose brightness above the last data point (150). + * scale_input_to_fw(0, 65535, x) = 220 when x = 220*65535/255 = 56533 + * After binary search, left >= data_points, clamped → right==left, + * so lum = upper_lum = 60. + */ + brightness = 56533; + + convert_custom_brightness(&caps, min, max, &brightness); + + /* Output should differ from input (remapped via curve) */ + KUNIT_EXPECT_TRUE(test, brightness != 56533); + KUNIT_EXPECT_TRUE(test, brightness < 56533); + + amdgpu_dm_set_dc_debug_mask(saved_mask); +} + +/** + * dm_test_custom_brightness_single_data_point - Test Custom brightness with single data point + * @test: The KUnit test context + */ +static void dm_test_custom_brightness_single_data_point(struct kunit *test) +{ + struct amdgpu_dm_backlight_caps caps = {}; + uint32_t brightness; + unsigned int min, max; + uint saved_mask = amdgpu_dm_get_dc_debug_mask(); + + amdgpu_dm_set_dc_debug_mask(amdgpu_dm_get_dc_debug_mask() & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE); + + caps.aux_support = false; + caps.min_input_signal = 0; + caps.max_input_signal = 255; + caps.data_points = 1; + caps.luminance_data[0].input_signal = 128; + caps.luminance_data[0].luminance = 50; + + get_brightness_range(&caps, &min, &max); + + /* + * Brightness below the single data point triggers the + * "below first" path: lum = DIV_ROUND_CLOSEST(50 * scaled, 128). + * scale_input_to_fw(0, 65535, x) = 64 when x = 64*65535/255 = 16448 + */ + brightness = 16448; + + convert_custom_brightness(&caps, min, max, &brightness); + + KUNIT_EXPECT_TRUE(test, brightness < 16448); + + amdgpu_dm_set_dc_debug_mask(saved_mask); +} + +/** + * dm_test_custom_brightness_lower_lum_zero - Test Custom brightness with zero lower luminance + * @test: The KUnit test context + */ +static void dm_test_custom_brightness_lower_lum_zero(struct kunit *test) +{ + struct amdgpu_dm_backlight_caps caps = {}; + uint32_t brightness; + unsigned int min, max; + uint saved_mask = amdgpu_dm_get_dc_debug_mask(); + + amdgpu_dm_set_dc_debug_mask(amdgpu_dm_get_dc_debug_mask() & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE); + + caps.aux_support = false; + caps.min_input_signal = 0; + caps.max_input_signal = 255; + caps.data_points = 2; + caps.luminance_data[0].input_signal = 50; + caps.luminance_data[0].luminance = 0; /* zero lower luminance */ + caps.luminance_data[1].input_signal = 200; + caps.luminance_data[1].luminance = 80; + + get_brightness_range(&caps, &min, &max); + + /* + * Choose brightness between data points to trigger interpolation. + * scale_input_to_fw(0, 65535, x) = 125 when x = 125*65535/255 = 32125 + * With lower_lum == 0, code takes shortcut: lum = upper_lum = 80. + */ + brightness = 32125; + + convert_custom_brightness(&caps, min, max, &brightness); + + /* Should remap; result should differ from input */ + KUNIT_EXPECT_TRUE(test, brightness != 32125); + + amdgpu_dm_set_dc_debug_mask(saved_mask); +} + +/** + * dm_test_brightness_to_user_above_max - Test Brightness to user above max + * @test: The KUnit test context + */ +static void dm_test_brightness_to_user_above_max(struct kunit *test) +{ + struct amdgpu_dm_backlight_caps caps = {}; + unsigned int min, max, result; + + caps.aux_support = false; + caps.min_input_signal = 12; + caps.max_input_signal = 255; + + get_brightness_range(&caps, &min, &max); + + /* brightness above max → result > max (linear extrapolation) */ + result = convert_brightness_to_user(&caps, max + 1000); + + KUNIT_EXPECT_GT(test, result, max); +} + +/** + * dm_test_brightness_from_user_midrange - Test Brightness from user mid-range value + * @test: The KUnit test context + */ +static void dm_test_brightness_from_user_midrange(struct kunit *test) +{ + struct amdgpu_dm_backlight_caps caps = {}; + unsigned int min, max; + u32 result; + + caps.aux_support = false; + caps.min_input_signal = 12; + caps.max_input_signal = 255; + caps.data_points = 0; + + get_brightness_range(&caps, &min, &max); + + /* Mid-range brightness should map to between min and max */ + result = convert_brightness_from_user(&caps, max / 2); + + KUNIT_EXPECT_GE(test, result, min); + KUNIT_EXPECT_LE(test, result, max); +} + +/** + * dm_test_brightness_from_user_with_curve - Test Brightness from user with custom curve active + * @test: The KUnit test context + */ +static void dm_test_brightness_from_user_with_curve(struct kunit *test) +{ + struct amdgpu_dm_backlight_caps caps = {}; + unsigned int min, max; + u32 with_curve, without_curve; + uint saved_mask = amdgpu_dm_get_dc_debug_mask(); + + amdgpu_dm_set_dc_debug_mask(amdgpu_dm_get_dc_debug_mask() & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE); + + caps.aux_support = false; + caps.min_input_signal = 0; + caps.max_input_signal = 255; + caps.data_points = 2; + caps.luminance_data[0].input_signal = 50; + caps.luminance_data[0].luminance = 20; + caps.luminance_data[1].input_signal = 200; + caps.luminance_data[1].luminance = 80; + + get_brightness_range(&caps, &min, &max); + + with_curve = convert_brightness_from_user(&caps, max / 2); + + /* Now disable the curve and compare */ + amdgpu_dm_set_dc_debug_mask(amdgpu_dm_get_dc_debug_mask() | DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE); + without_curve = convert_brightness_from_user(&caps, max / 2); + + /* Custom curve should produce a different mapping */ + KUNIT_EXPECT_NE(test, with_curve, without_curve); + + amdgpu_dm_set_dc_debug_mask(saved_mask); +} + +/** + * dm_test_brightness_range_zero_signals - Test Brightness range with zero min and max signals + * @test: The KUnit test context + */ +static void dm_test_brightness_range_zero_signals(struct kunit *test) +{ + struct amdgpu_dm_backlight_caps caps = {}; + unsigned int min = 99, max = 99; + + caps.aux_support = false; + caps.min_input_signal = 0; + caps.max_input_signal = 0; + + /* Both signals zero → min=max=0 */ + KUNIT_EXPECT_EQ(test, get_brightness_range(&caps, &min, &max), 1); + KUNIT_EXPECT_EQ(test, min, 0U); + KUNIT_EXPECT_EQ(test, max, 0U); +} + +/* Tests for amdgpu_dm_update_connector_ext_caps() */ + +/** + * dm_test_update_connector_ext_caps_negative_bl_idx - Test negative backlight index early return + * @test: The KUnit test context + */ +static void dm_test_update_connector_ext_caps_negative_bl_idx(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconnector); + + aconnector->bl_idx = -1; + + amdgpu_dm_update_connector_ext_caps(aconnector); + + KUNIT_SUCCEED(test); +} + +/** + * dm_test_update_connector_ext_caps_non_edp - Test non-eDP connector early return + * @test: The KUnit test context + */ +static void dm_test_update_connector_ext_caps_non_edp(struct kunit *test) +{ + struct dm_backlight_connector_fixture fixture = {}; + + setup_test_connector(test, &fixture, 0, SIGNAL_TYPE_HDMI_TYPE_A); + fixture.adev->dm.backlight_caps[0].aux_support = true; + + amdgpu_dm_update_connector_ext_caps(fixture.aconnector); + + KUNIT_EXPECT_TRUE(test, fixture.adev->dm.backlight_caps[0].aux_support); + KUNIT_EXPECT_PTR_EQ(test, fixture.adev->dm.backlight_caps[0].ext_caps, NULL); +} + +/** + * dm_test_update_connector_ext_caps_oled_defaults - Test OLED eDP defaults to AUX backlight + * @test: The KUnit test context + */ +static void dm_test_update_connector_ext_caps_oled_defaults(struct kunit *test) +{ + struct dm_backlight_connector_fixture fixture = {}; + int saved_backlight = amdgpu_dm_get_backlight_param(); + + amdgpu_dm_set_backlight_param(-1); + setup_test_connector(test, &fixture, 0, SIGNAL_TYPE_EDP); + fixture.link->dpcd_sink_ext_caps.bits.oled = 1; + + amdgpu_dm_update_connector_ext_caps(fixture.aconnector); + + KUNIT_EXPECT_PTR_EQ(test, fixture.adev->dm.backlight_caps[0].ext_caps, + &fixture.link->dpcd_sink_ext_caps); + KUNIT_EXPECT_TRUE(test, fixture.adev->dm.backlight_caps[0].aux_support); + KUNIT_EXPECT_EQ(test, fixture.link->backlight_control_type, + BACKLIGHT_CONTROL_AMD_AUX); + KUNIT_EXPECT_EQ(test, fixture.adev->dm.backlight_caps[0].aux_max_input_signal, 512); + KUNIT_EXPECT_EQ(test, fixture.adev->dm.backlight_caps[0].aux_min_input_signal, 1); + + amdgpu_dm_set_backlight_param(saved_backlight); +} + +/** + * dm_test_update_connector_ext_caps_luminance_values - Test luminance range copy + * @test: The KUnit test context + */ +static void dm_test_update_connector_ext_caps_luminance_values(struct kunit *test) +{ + struct dm_backlight_connector_fixture fixture = {}; + int saved_backlight = amdgpu_dm_get_backlight_param(); + + amdgpu_dm_set_backlight_param(-1); + setup_test_connector(test, &fixture, 0, SIGNAL_TYPE_EDP); + fixture.aconnector->base.display_info.luminance_range.min_luminance = 2; + fixture.aconnector->base.display_info.luminance_range.max_luminance = 400; + + amdgpu_dm_update_connector_ext_caps(fixture.aconnector); + + KUNIT_EXPECT_FALSE(test, fixture.adev->dm.backlight_caps[0].aux_support); + KUNIT_EXPECT_EQ(test, fixture.adev->dm.backlight_caps[0].aux_max_input_signal, 400); + KUNIT_EXPECT_EQ(test, fixture.adev->dm.backlight_caps[0].aux_min_input_signal, 2); + + amdgpu_dm_set_backlight_param(saved_backlight); +} + +/** + * dm_test_update_connector_ext_caps_force_aux - Test module parameter forces AUX backlight + * @test: The KUnit test context + */ +static void dm_test_update_connector_ext_caps_force_aux(struct kunit *test) +{ + struct dm_backlight_connector_fixture fixture = {}; + int saved_backlight = amdgpu_dm_get_backlight_param(); + + amdgpu_dm_set_backlight_param(1); + setup_test_connector(test, &fixture, 0, SIGNAL_TYPE_EDP); + + amdgpu_dm_update_connector_ext_caps(fixture.aconnector); + + KUNIT_EXPECT_TRUE(test, fixture.adev->dm.backlight_caps[0].aux_support); + KUNIT_EXPECT_EQ(test, fixture.link->backlight_control_type, + BACKLIGHT_CONTROL_AMD_AUX); + + amdgpu_dm_set_backlight_param(saved_backlight); +} + +/** + * dm_test_update_connector_ext_caps_force_pwm - Test module parameter forces PWM backlight + * @test: The KUnit test context + */ +static void dm_test_update_connector_ext_caps_force_pwm(struct kunit *test) +{ + struct dm_backlight_connector_fixture fixture = {}; + int saved_backlight = amdgpu_dm_get_backlight_param(); + + amdgpu_dm_set_backlight_param(0); + setup_test_connector(test, &fixture, 0, SIGNAL_TYPE_EDP); + fixture.link->dpcd_sink_ext_caps.bits.oled = 1; + + amdgpu_dm_update_connector_ext_caps(fixture.aconnector); + + KUNIT_EXPECT_FALSE(test, fixture.adev->dm.backlight_caps[0].aux_support); + KUNIT_EXPECT_NE(test, fixture.link->backlight_control_type, + BACKLIGHT_CONTROL_AMD_AUX); + + amdgpu_dm_set_backlight_param(saved_backlight); +} + +/* Tests for amdgpu_dm_should_create_sysfs() */ + +/** + * dm_test_should_create_sysfs_abm_forced - Test forced ABM disables sysfs + * @test: The KUnit test context + */ +static void dm_test_should_create_sysfs_abm_forced(struct kunit *test) +{ + struct dm_backlight_connector_fixture fixture = {}; + int saved_abm_level = amdgpu_dm_get_abm_level_param(); + + amdgpu_dm_set_abm_level_param(1); + setup_test_connector(test, &fixture, 0, SIGNAL_TYPE_EDP); + fixture.aconnector->base.connector_type = DRM_MODE_CONNECTOR_eDP; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_should_create_sysfs(fixture.aconnector)); + + amdgpu_dm_set_abm_level_param(saved_abm_level); +} + +/** + * dm_test_should_create_sysfs_non_edp - Test non-eDP connector disables sysfs + * @test: The KUnit test context + */ +static void dm_test_should_create_sysfs_non_edp(struct kunit *test) +{ + struct dm_backlight_connector_fixture fixture = {}; + int saved_abm_level = amdgpu_dm_get_abm_level_param(); + + amdgpu_dm_set_abm_level_param(-1); + setup_test_connector(test, &fixture, 0, SIGNAL_TYPE_HDMI_TYPE_A); + fixture.aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_should_create_sysfs(fixture.aconnector)); + + amdgpu_dm_set_abm_level_param(saved_abm_level); +} + +/** + * dm_test_should_create_sysfs_no_backlight_index - Test eDP without backlight index enables sysfs + * @test: The KUnit test context + */ +static void dm_test_should_create_sysfs_no_backlight_index(struct kunit *test) +{ + struct dm_backlight_connector_fixture fixture = {}; + int saved_abm_level = amdgpu_dm_get_abm_level_param(); + + amdgpu_dm_set_abm_level_param(-1); + setup_test_connector(test, &fixture, -1, SIGNAL_TYPE_EDP); + fixture.aconnector->base.connector_type = DRM_MODE_CONNECTOR_eDP; + + KUNIT_EXPECT_TRUE(test, amdgpu_dm_should_create_sysfs(fixture.aconnector)); + + amdgpu_dm_set_abm_level_param(saved_abm_level); +} + +/** + * dm_test_should_create_sysfs_aux_backlight - Test AUX backlight disables sysfs + * @test: The KUnit test context + */ +static void dm_test_should_create_sysfs_aux_backlight(struct kunit *test) +{ + struct dm_backlight_connector_fixture fixture = {}; + int saved_abm_level = amdgpu_dm_get_abm_level_param(); + + amdgpu_dm_set_abm_level_param(-1); + setup_test_connector(test, &fixture, 0, SIGNAL_TYPE_EDP); + fixture.aconnector->base.connector_type = DRM_MODE_CONNECTOR_eDP; + fixture.adev->dm.backlight_caps[0].aux_support = true; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_should_create_sysfs(fixture.aconnector)); + + amdgpu_dm_set_abm_level_param(saved_abm_level); +} + +/** + * dm_test_should_create_sysfs_pwm_backlight - Test PWM backlight enables sysfs + * @test: The KUnit test context + */ +static void dm_test_should_create_sysfs_pwm_backlight(struct kunit *test) +{ + struct dm_backlight_connector_fixture fixture = {}; + int saved_abm_level = amdgpu_dm_get_abm_level_param(); + + amdgpu_dm_set_abm_level_param(-1); + setup_test_connector(test, &fixture, 0, SIGNAL_TYPE_EDP); + fixture.aconnector->base.connector_type = DRM_MODE_CONNECTOR_eDP; + fixture.adev->dm.backlight_caps[0].aux_support = false; + + KUNIT_EXPECT_TRUE(test, amdgpu_dm_should_create_sysfs(fixture.aconnector)); + + amdgpu_dm_set_abm_level_param(saved_abm_level); +} + +/* Tests for amdgpu_dm_setup_backlight_device() */ + +/** + * dm_test_setup_backlight_device_non_edp - Test non-eDP/LVDS link is skipped + * @test: The KUnit test context + */ +static void dm_test_setup_backlight_device_non_edp(struct kunit *test) +{ + struct dm_backlight_connector_fixture fixture = {}; + struct amdgpu_display_manager *dm; + + setup_test_connector(test, &fixture, -1, SIGNAL_TYPE_HDMI_TYPE_A); + fixture.link->type = dc_connection_single; + dm = &fixture.adev->dm; + dm->adev = fixture.adev; + dm->num_of_edps = 0; + + amdgpu_dm_setup_backlight_device(dm, fixture.aconnector); + + /* Non-eDP/LVDS signal → no backlight setup */ + KUNIT_EXPECT_EQ(test, dm->num_of_edps, 0); + KUNIT_EXPECT_EQ(test, fixture.aconnector->bl_idx, -1); +} + +/** + * dm_test_setup_backlight_device_connection_none - Test disconnected link is skipped + * @test: The KUnit test context + */ +static void dm_test_setup_backlight_device_connection_none(struct kunit *test) +{ + struct dm_backlight_connector_fixture fixture = {}; + struct amdgpu_display_manager *dm; + + setup_test_connector(test, &fixture, -1, SIGNAL_TYPE_EDP); + fixture.link->type = dc_connection_none; + dm = &fixture.adev->dm; + dm->adev = fixture.adev; + dm->num_of_edps = 0; + + amdgpu_dm_setup_backlight_device(dm, fixture.aconnector); + + /* Disconnected link → no backlight setup */ + KUNIT_EXPECT_EQ(test, dm->num_of_edps, 0); + KUNIT_EXPECT_EQ(test, fixture.aconnector->bl_idx, -1); +} + +/** + * dm_test_setup_backlight_device_max_edps - Test setup is skipped when at eDP limit + * @test: The KUnit test context + */ +static void dm_test_setup_backlight_device_max_edps(struct kunit *test) +{ + struct dm_backlight_connector_fixture fixture = {}; + struct amdgpu_display_manager *dm; + + setup_test_connector(test, &fixture, -1, SIGNAL_TYPE_EDP); + fixture.link->type = dc_connection_single; + dm = &fixture.adev->dm; + dm->adev = fixture.adev; + dm->num_of_edps = AMDGPU_DM_MAX_NUM_EDP; + + amdgpu_dm_setup_backlight_device(dm, fixture.aconnector); + + /* Already at the eDP limit → no additional setup */ + KUNIT_EXPECT_EQ(test, dm->num_of_edps, AMDGPU_DM_MAX_NUM_EDP); + KUNIT_EXPECT_EQ(test, fixture.aconnector->bl_idx, -1); +} + +/** + * dm_test_setup_backlight_device_oled_success - Test successful eDP backlight setup + * @test: The KUnit test context + */ +static void dm_test_setup_backlight_device_oled_success(struct kunit *test) +{ + struct dm_backlight_connector_fixture fixture = {}; + struct amdgpu_display_manager *dm; + int saved_backlight = amdgpu_dm_get_backlight_param(); + + amdgpu_dm_set_backlight_param(-1); + setup_test_connector(test, &fixture, -1, SIGNAL_TYPE_EDP); + fixture.link->type = dc_connection_single; + /* OLED panel avoids the ABM property attach path */ + fixture.link->dpcd_sink_ext_caps.bits.oled = 1; + dm = &fixture.adev->dm; + dm->adev = fixture.adev; + dm->num_of_edps = 0; + + amdgpu_dm_setup_backlight_device(dm, fixture.aconnector); + + KUNIT_EXPECT_EQ(test, dm->num_of_edps, 1); + KUNIT_EXPECT_EQ(test, fixture.aconnector->bl_idx, 0); + KUNIT_EXPECT_PTR_EQ(test, (void *)dm->backlight_link[0], + (void *)fixture.link); + KUNIT_EXPECT_TRUE(test, dm->backlight_caps[0].aux_support); + + amdgpu_dm_set_backlight_param(saved_backlight); +} + +static struct kunit_case dm_backlight_test_cases[] = { + KUNIT_CASE(dm_test_backlight_caps_valid_short_circuit), +#if !defined(CONFIG_ACPI) + KUNIT_CASE(dm_test_backlight_caps_aux_support_noop), + KUNIT_CASE(dm_test_backlight_caps_non_aux_sets_defaults), +#endif + /* get_brightness_range */ + KUNIT_CASE(dm_test_brightness_range_null_caps), + KUNIT_CASE(dm_test_brightness_range_pwm), + KUNIT_CASE(dm_test_brightness_range_aux), + /* convert_brightness_to_user */ + KUNIT_CASE(dm_test_brightness_to_user_null_caps), + KUNIT_CASE(dm_test_brightness_to_user_below_min), + KUNIT_CASE(dm_test_brightness_to_user_at_max), + KUNIT_CASE(dm_test_brightness_to_user_at_min), + KUNIT_CASE(dm_test_brightness_to_user_midpoint_pwm), + /* convert_brightness_from_user */ + KUNIT_CASE(dm_test_brightness_from_user_null_caps), + KUNIT_CASE(dm_test_brightness_from_user_zero), + KUNIT_CASE(dm_test_brightness_from_user_max), + KUNIT_CASE(dm_test_brightness_from_user_aux), + /* convert_custom_brightness */ + KUNIT_CASE(dm_test_custom_brightness_no_data_points), + KUNIT_CASE(dm_test_custom_brightness_debug_mask_disables), + KUNIT_CASE(dm_test_custom_brightness_exact_match), + KUNIT_CASE(dm_test_custom_brightness_below_first), + KUNIT_CASE(dm_test_custom_brightness_interpolation), + KUNIT_CASE(dm_test_custom_brightness_above_last), + KUNIT_CASE(dm_test_custom_brightness_single_data_point), + KUNIT_CASE(dm_test_custom_brightness_lower_lum_zero), + KUNIT_CASE(dm_test_brightness_to_user_above_max), + KUNIT_CASE(dm_test_brightness_from_user_midrange), + KUNIT_CASE(dm_test_brightness_from_user_with_curve), + KUNIT_CASE(dm_test_brightness_range_zero_signals), + /* amdgpu_dm_update_connector_ext_caps */ + KUNIT_CASE(dm_test_update_connector_ext_caps_negative_bl_idx), + KUNIT_CASE(dm_test_update_connector_ext_caps_non_edp), + KUNIT_CASE(dm_test_update_connector_ext_caps_oled_defaults), + KUNIT_CASE(dm_test_update_connector_ext_caps_luminance_values), + KUNIT_CASE(dm_test_update_connector_ext_caps_force_aux), + KUNIT_CASE(dm_test_update_connector_ext_caps_force_pwm), + /* amdgpu_dm_should_create_sysfs */ + KUNIT_CASE(dm_test_should_create_sysfs_abm_forced), + KUNIT_CASE(dm_test_should_create_sysfs_non_edp), + KUNIT_CASE(dm_test_should_create_sysfs_no_backlight_index), + KUNIT_CASE(dm_test_should_create_sysfs_aux_backlight), + KUNIT_CASE(dm_test_should_create_sysfs_pwm_backlight), + /* amdgpu_dm_setup_backlight_device */ + KUNIT_CASE(dm_test_setup_backlight_device_non_edp), + KUNIT_CASE(dm_test_setup_backlight_device_connection_none), + KUNIT_CASE(dm_test_setup_backlight_device_max_edps), + KUNIT_CASE(dm_test_setup_backlight_device_oled_success), + {} +}; + +static struct kunit_suite dm_backlight_test_suite = { + .name = "amdgpu_dm_backlight", + .test_cases = dm_backlight_test_cases, +}; + +kunit_test_suite(dm_backlight_test_suite); + +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_backlight"); +MODULE_AUTHOR("AMD"); -- cgit v1.2.3 From c71a6dc1cf01cb2c03c9cbb00bfb761cd65a4543 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Wed, 29 Apr 2026 21:17:43 -0600 Subject: drm/amd/display: Add KUnit tests for amdgpu_dm_audio Add KUnit tests for amdgpu_dm_audio.c. Tests cover: - amdgpu_dm_audio_init(): early exit when audio is disabled - amdgpu_dm_audio_fini(): early exit when audio is not enabled - fill_audio_info(): manufacturer and product ID propagation, display name copy, speaker allocation flags, CEA revision gating of audio mode copying (including the zero-mode case), and latency field propagation - amdgpu_dm_audio_component_bind()/unbind(): component ops, device, and audio_component pointer are wired up on bind and cleared on unbind - amdgpu_dm_audio_eld_notify(): callback is forwarded with the correct port and audio pointer, and the no-op guard paths for a missing component, audio_ops, or pin_eld_notify callback Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- .../drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c | 27 +- .../drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h | 12 + .../gpu/drm/amd/display/amdgpu_dm/tests/Makefile | 1 + .../display/amdgpu_dm/tests/amdgpu_dm_audio_test.c | 490 +++++++++++++++++++++ 4 files changed, 527 insertions(+), 3 deletions(-) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_audio_test.c (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c index a15b7c0c9075..13c9a9d145ba 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.c @@ -26,6 +26,7 @@ #include "amdgpu.h" #include "amdgpu_dm.h" #include "amdgpu_dm_audio.h" +#include "amdgpu_dm_kunit_helpers.h" #include "dc.h" #include @@ -83,7 +84,7 @@ static const struct drm_audio_component_ops amdgpu_dm_audio_component_ops = { .get_eld = amdgpu_dm_audio_component_get_eld, }; -static int amdgpu_dm_audio_component_bind(struct device *kdev, +STATIC_IFN_KUNIT int amdgpu_dm_audio_component_bind(struct device *kdev, struct device *hda_kdev, void *data) { struct drm_device *dev = dev_get_drvdata(kdev); @@ -96,8 +97,9 @@ static int amdgpu_dm_audio_component_bind(struct device *kdev, return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_audio_component_bind); -static void amdgpu_dm_audio_component_unbind(struct device *kdev, +STATIC_IFN_KUNIT void amdgpu_dm_audio_component_unbind(struct device *kdev, struct device *hda_kdev, void *data) { struct amdgpu_device *adev = drm_to_adev(dev_get_drvdata(kdev)); @@ -107,6 +109,7 @@ static void amdgpu_dm_audio_component_unbind(struct device *kdev, acomp->dev = NULL; adev->dm.audio_component = NULL; } +EXPORT_IF_KUNIT(amdgpu_dm_audio_component_unbind); static const struct component_ops amdgpu_dm_audio_component_bind_ops = { .bind = amdgpu_dm_audio_component_bind, @@ -144,6 +147,7 @@ int amdgpu_dm_audio_init(struct amdgpu_device *adev) return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_audio_init); void amdgpu_dm_audio_fini(struct amdgpu_device *adev) { @@ -162,8 +166,9 @@ void amdgpu_dm_audio_fini(struct amdgpu_device *adev) adev->mode_info.audio.enabled = false; } +EXPORT_IF_KUNIT(amdgpu_dm_audio_fini); -static void amdgpu_dm_audio_eld_notify(struct amdgpu_device *adev, int pin) +STATIC_IFN_KUNIT void amdgpu_dm_audio_eld_notify(struct amdgpu_device *adev, int pin) { struct drm_audio_component *acomp = adev->dm.audio_component; @@ -174,6 +179,7 @@ static void amdgpu_dm_audio_eld_notify(struct amdgpu_device *adev, int pin) pin, -1); } } +EXPORT_IF_KUNIT(amdgpu_dm_audio_eld_notify); void amdgpu_dm_fill_audio_info(struct audio_info *audio_info, const struct drm_connector *drm_connector, @@ -219,6 +225,7 @@ void amdgpu_dm_fill_audio_info(struct audio_info *audio_info, /* TODO: For DP, video and audio latency should be calculated from DPCD caps */ } +EXPORT_IF_KUNIT(amdgpu_dm_fill_audio_info); void amdgpu_dm_commit_audio(struct drm_device *dev, struct drm_atomic_commit *state) @@ -300,3 +307,17 @@ notify: amdgpu_dm_audio_eld_notify(adev, inst); } } + +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +int amdgpu_dm_audio_get_param(void) +{ + return amdgpu_audio; +} +EXPORT_IF_KUNIT(amdgpu_dm_audio_get_param); + +void amdgpu_dm_audio_set_param(int val) +{ + amdgpu_audio = val; +} +EXPORT_IF_KUNIT(amdgpu_dm_audio_set_param); +#endif diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h index 58cce1f79ffd..7acfc5ef69b3 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_audio.h @@ -41,4 +41,16 @@ void amdgpu_dm_fill_audio_info(struct audio_info *audio_info, const struct drm_connector *drm_connector, const struct dc_sink *dc_sink); +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +struct device; + +int amdgpu_dm_audio_component_bind(struct device *kdev, + struct device *hda_kdev, void *data); +void amdgpu_dm_audio_component_unbind(struct device *kdev, + struct device *hda_kdev, void *data); +void amdgpu_dm_audio_eld_notify(struct amdgpu_device *adev, int pin); +int amdgpu_dm_audio_get_param(void); +void amdgpu_dm_audio_set_param(int val); +#endif + #endif /* __AMDGPU_DM_AUDIO_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile index ddd9fce66232..5bb43b3bc439 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile @@ -13,6 +13,7 @@ ccflags-y += -I$(src)/../../../include obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_crc_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_hdcp_test.o +obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_audio_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_color_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_colorop_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_backlight_test.o diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_audio_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_audio_test.c new file mode 100644 index 000000000000..79ff5d9b3fa5 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_audio_test.c @@ -0,0 +1,490 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit tests for amdgpu_dm_audio.c + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include + +#include + +#include "dc.h" +#include "amdgpu.h" +#include "amdgpu_mode.h" +#include "amdgpu_dm.h" +#include "amdgpu_dm_audio.h" + +/* Tests for amdgpu_dm_audio_init() */ + +/** + * dm_test_audio_init_disabled - Test audio init exits when audio is disabled + * @test: The KUnit test context + */ +static void dm_test_audio_init_disabled(struct kunit *test) +{ + struct amdgpu_device *adev; + int saved_audio = amdgpu_dm_audio_get_param(); + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + amdgpu_dm_audio_set_param(0); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_audio_init(adev), 0); + KUNIT_EXPECT_FALSE(test, adev->mode_info.audio.enabled); + KUNIT_EXPECT_FALSE(test, adev->dm.audio_registered); + + amdgpu_dm_audio_set_param(saved_audio); +} + +/* Tests for amdgpu_dm_audio_fini() */ + +/** + * dm_test_audio_fini_without_enabled_audio - Test fini exits when audio is not enabled + * @test: The KUnit test context + */ +static void dm_test_audio_fini_without_enabled_audio(struct kunit *test) +{ + struct amdgpu_device *adev; + int saved_audio = amdgpu_dm_audio_get_param(); + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + amdgpu_dm_audio_set_param(1); + adev->mode_info.audio.enabled = false; + adev->dm.audio_registered = true; + + amdgpu_dm_audio_fini(adev); + + KUNIT_EXPECT_FALSE(test, adev->mode_info.audio.enabled); + KUNIT_EXPECT_TRUE(test, adev->dm.audio_registered); + + amdgpu_dm_audio_set_param(saved_audio); +} + +/* Tests for amdgpu_dm_fill_audio_info() */ + +/** + * dm_test_fill_audio_info_ids_name_flags - Test Fill audio info ids name flags + * @test: The KUnit test context + */ +static void dm_test_fill_audio_info_ids_name_flags(struct kunit *test) +{ + struct audio_info *audio_info; + struct drm_connector *connector; + struct dc_sink *dc_sink; + const char *name = "DM-AUDIO-PANEL"; + + audio_info = kunit_kzalloc(test, sizeof(*audio_info), GFP_KERNEL); + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + dc_sink = kunit_kzalloc(test, sizeof(*dc_sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, audio_info); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc_sink); + + dc_sink->edid_caps.manufacturer_id = 0x1234; + dc_sink->edid_caps.product_id = 0xABCD; + dc_sink->edid_caps.speaker_flags = 0x5; + strscpy(dc_sink->edid_caps.display_name, name, + AUDIO_INFO_DISPLAY_NAME_SIZE_IN_CHARS); + + connector->display_info.cea_rev = 1; + + amdgpu_dm_fill_audio_info(audio_info, connector, dc_sink); + + KUNIT_EXPECT_EQ(test, audio_info->manufacture_id, 0x1234U); + KUNIT_EXPECT_EQ(test, audio_info->product_id, 0xABCDU); + KUNIT_EXPECT_EQ(test, audio_info->flags.all, 0x5U); + KUNIT_EXPECT_STREQ(test, audio_info->display_name, name); +} + +/** + * dm_test_fill_audio_info_cea_lt_3_skips_modes - Test Fill audio info cea lt 3 skips modes + * @test: The KUnit test context + */ +static void dm_test_fill_audio_info_cea_lt_3_skips_modes(struct kunit *test) +{ + struct audio_info *audio_info; + struct drm_connector *connector; + struct dc_sink *dc_sink; + + audio_info = kunit_kzalloc(test, sizeof(*audio_info), GFP_KERNEL); + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + dc_sink = kunit_kzalloc(test, sizeof(*dc_sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, audio_info); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc_sink); + + connector->display_info.cea_rev = 2; + dc_sink->edid_caps.audio_mode_count = 2; + dc_sink->edid_caps.audio_modes[0].format_code = 1; + dc_sink->edid_caps.audio_modes[0].channel_count = 2; + dc_sink->edid_caps.audio_modes[0].sample_rate = 0x07; + dc_sink->edid_caps.audio_modes[0].sample_size = 16; + + amdgpu_dm_fill_audio_info(audio_info, connector, dc_sink); + + KUNIT_EXPECT_EQ(test, audio_info->mode_count, 0U); +} + +/** + * dm_test_fill_audio_info_cea_ge_3_copies_modes - Test Fill audio info cea ge 3 copies modes + * @test: The KUnit test context + */ +static void dm_test_fill_audio_info_cea_ge_3_copies_modes(struct kunit *test) +{ + struct audio_info *audio_info; + struct drm_connector *connector; + struct dc_sink *dc_sink; + + audio_info = kunit_kzalloc(test, sizeof(*audio_info), GFP_KERNEL); + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + dc_sink = kunit_kzalloc(test, sizeof(*dc_sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, audio_info); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc_sink); + + connector->display_info.cea_rev = 3; + dc_sink->edid_caps.audio_mode_count = 2; + + dc_sink->edid_caps.audio_modes[0].format_code = 1; + dc_sink->edid_caps.audio_modes[0].channel_count = 2; + dc_sink->edid_caps.audio_modes[0].sample_rate = 0x07; + dc_sink->edid_caps.audio_modes[0].sample_size = 16; + + dc_sink->edid_caps.audio_modes[1].format_code = 11; + dc_sink->edid_caps.audio_modes[1].channel_count = 6; + dc_sink->edid_caps.audio_modes[1].sample_rate = 0x1F; + dc_sink->edid_caps.audio_modes[1].sample_size = 24; + + amdgpu_dm_fill_audio_info(audio_info, connector, dc_sink); + + KUNIT_EXPECT_EQ(test, audio_info->mode_count, 2U); + + KUNIT_EXPECT_EQ(test, (int)audio_info->modes[0].format_code, 1); + KUNIT_EXPECT_EQ(test, audio_info->modes[0].channel_count, 2); + KUNIT_EXPECT_EQ(test, audio_info->modes[0].sample_rates.all, 0x07U); + KUNIT_EXPECT_EQ(test, audio_info->modes[0].sample_size, 16); + + KUNIT_EXPECT_EQ(test, (int)audio_info->modes[1].format_code, 11); + KUNIT_EXPECT_EQ(test, audio_info->modes[1].channel_count, 6); + KUNIT_EXPECT_EQ(test, audio_info->modes[1].sample_rates.all, 0x1FU); + KUNIT_EXPECT_EQ(test, audio_info->modes[1].sample_size, 24); +} + +/** + * dm_test_fill_audio_info_latency_present - Test Fill audio info latency present + * @test: The KUnit test context + */ +static void dm_test_fill_audio_info_latency_present(struct kunit *test) +{ + struct audio_info *audio_info; + struct drm_connector *connector; + struct dc_sink *dc_sink; + + audio_info = kunit_kzalloc(test, sizeof(*audio_info), GFP_KERNEL); + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + dc_sink = kunit_kzalloc(test, sizeof(*dc_sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, audio_info); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc_sink); + + connector->display_info.cea_rev = 3; + connector->latency_present[0] = true; + connector->video_latency[0] = 11; + connector->audio_latency[0] = 22; + + amdgpu_dm_fill_audio_info(audio_info, connector, dc_sink); + + KUNIT_EXPECT_EQ(test, audio_info->video_latency, 11U); + KUNIT_EXPECT_EQ(test, audio_info->audio_latency, 22U); +} + +/** + * dm_test_fill_audio_info_latency_absent_keeps_zero - Test Fill audio info latency absent keeps zero + * @test: The KUnit test context + */ +static void dm_test_fill_audio_info_latency_absent_keeps_zero(struct kunit *test) +{ + struct audio_info *audio_info; + struct drm_connector *connector; + struct dc_sink *dc_sink; + + audio_info = kunit_kzalloc(test, sizeof(*audio_info), GFP_KERNEL); + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + dc_sink = kunit_kzalloc(test, sizeof(*dc_sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, audio_info); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc_sink); + + connector->display_info.cea_rev = 3; + connector->latency_present[0] = false; + connector->video_latency[0] = 99; + connector->audio_latency[0] = 88; + + amdgpu_dm_fill_audio_info(audio_info, connector, dc_sink); + + KUNIT_EXPECT_EQ(test, audio_info->video_latency, 0U); + KUNIT_EXPECT_EQ(test, audio_info->audio_latency, 0U); +} + +/** + * dm_test_fill_audio_info_cea_ge_3_zero_modes - Test cea >= 3 with zero modes + * @test: The KUnit test context + * + * When cea_rev >= 3 but the sink reports no audio modes, mode_count must be + * copied as 0 and no mode entries should be populated. + */ +static void dm_test_fill_audio_info_cea_ge_3_zero_modes(struct kunit *test) +{ + struct audio_info *audio_info; + struct drm_connector *connector; + struct dc_sink *dc_sink; + + audio_info = kunit_kzalloc(test, sizeof(*audio_info), GFP_KERNEL); + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + dc_sink = kunit_kzalloc(test, sizeof(*dc_sink), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, audio_info); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc_sink); + + connector->display_info.cea_rev = 3; + dc_sink->edid_caps.audio_mode_count = 0; + + amdgpu_dm_fill_audio_info(audio_info, connector, dc_sink); + + KUNIT_EXPECT_EQ(test, audio_info->mode_count, 0U); + KUNIT_EXPECT_EQ(test, (int)audio_info->modes[0].format_code, 0); +} + +/* Tests for amdgpu_dm_audio_component_bind()/unbind() */ + +/** + * dm_test_audio_component_bind_sets_fields - Test bind wires up audio component + * @test: The KUnit test context + * + * Binding must publish the DRM audio component ops, record the kernel device, + * and store the component pointer in the display manager. + */ +static void dm_test_audio_component_bind_sets_fields(struct kunit *test) +{ + struct amdgpu_device *adev; + struct device *kdev; + struct drm_audio_component *acomp; + int ret; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + kdev = kunit_kzalloc(test, sizeof(*kdev), GFP_KERNEL); + acomp = kunit_kzalloc(test, sizeof(*acomp), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, kdev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acomp); + + dev_set_drvdata(kdev, &adev->ddev); + + ret = amdgpu_dm_audio_component_bind(kdev, NULL, acomp); + + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_NOT_NULL(test, acomp->ops); + KUNIT_EXPECT_PTR_EQ(test, acomp->dev, kdev); + KUNIT_EXPECT_PTR_EQ(test, adev->dm.audio_component, acomp); +} + +/** + * dm_test_audio_component_unbind_clears_fields - Test unbind tears down component + * @test: The KUnit test context + * + * Unbinding must clear the component ops, the kernel device, and the display + * manager's stored component pointer. + */ +static void dm_test_audio_component_unbind_clears_fields(struct kunit *test) +{ + struct amdgpu_device *adev; + struct device *kdev; + struct drm_audio_component *acomp; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + kdev = kunit_kzalloc(test, sizeof(*kdev), GFP_KERNEL); + acomp = kunit_kzalloc(test, sizeof(*acomp), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, kdev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acomp); + + dev_set_drvdata(kdev, &adev->ddev); + + /* Pretend a prior bind already happened. */ + acomp->dev = kdev; + adev->dm.audio_component = acomp; + + amdgpu_dm_audio_component_unbind(kdev, NULL, acomp); + + KUNIT_EXPECT_NULL(test, acomp->ops); + KUNIT_EXPECT_NULL(test, acomp->dev); + KUNIT_EXPECT_NULL(test, adev->dm.audio_component); +} + +/* Tests for amdgpu_dm_audio_eld_notify() */ + +static int dm_test_eld_notify_count; +static int dm_test_eld_notify_port; +static void *dm_test_eld_notify_ptr; + +static void dm_test_pin_eld_notify(void *audio_ptr, int port, int pipe) +{ + dm_test_eld_notify_count++; + dm_test_eld_notify_port = port; + dm_test_eld_notify_ptr = audio_ptr; +} + +/** + * dm_test_eld_notify_invokes_callback - Test ELD notify forwards to hda driver + * @test: The KUnit test context + * + * When a component with a pin_eld_notify callback is registered, the notify + * helper must invoke it with the audio pointer and the requested pin. + */ +static void dm_test_eld_notify_invokes_callback(struct kunit *test) +{ + struct amdgpu_device *adev; + struct drm_audio_component *acomp; + struct drm_audio_component_audio_ops *audio_ops; + int marker = 0; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + acomp = kunit_kzalloc(test, sizeof(*acomp), GFP_KERNEL); + audio_ops = kunit_kzalloc(test, sizeof(*audio_ops), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acomp); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, audio_ops); + + audio_ops->audio_ptr = ▮ + audio_ops->pin_eld_notify = dm_test_pin_eld_notify; + acomp->audio_ops = audio_ops; + adev->dm.audio_component = acomp; + + dm_test_eld_notify_count = 0; + dm_test_eld_notify_port = -100; + dm_test_eld_notify_ptr = NULL; + + amdgpu_dm_audio_eld_notify(adev, 7); + + KUNIT_EXPECT_EQ(test, dm_test_eld_notify_count, 1); + KUNIT_EXPECT_EQ(test, dm_test_eld_notify_port, 7); + KUNIT_EXPECT_PTR_EQ(test, dm_test_eld_notify_ptr, (void *)&marker); +} + +/** + * dm_test_eld_notify_no_component - Test ELD notify is a no-op without component + * @test: The KUnit test context + * + * With no registered audio component, the notify helper must return without + * invoking any callback. + */ +static void dm_test_eld_notify_no_component(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + adev->dm.audio_component = NULL; + + dm_test_eld_notify_count = 0; + + amdgpu_dm_audio_eld_notify(adev, 3); + + KUNIT_EXPECT_EQ(test, dm_test_eld_notify_count, 0); +} + +/** + * dm_test_eld_notify_null_audio_ops - Test ELD notify is a no-op without audio_ops + * @test: The KUnit test context + * + * A component without audio_ops must not trigger any callback. + */ +static void dm_test_eld_notify_null_audio_ops(struct kunit *test) +{ + struct amdgpu_device *adev; + struct drm_audio_component *acomp; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + acomp = kunit_kzalloc(test, sizeof(*acomp), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acomp); + + acomp->audio_ops = NULL; + adev->dm.audio_component = acomp; + + dm_test_eld_notify_count = 0; + + amdgpu_dm_audio_eld_notify(adev, 3); + + KUNIT_EXPECT_EQ(test, dm_test_eld_notify_count, 0); +} + +/** + * dm_test_eld_notify_null_callback - Test ELD notify is a no-op without callback + * @test: The KUnit test context + * + * audio_ops present but with a NULL pin_eld_notify must not crash or call + * anything. + */ +static void dm_test_eld_notify_null_callback(struct kunit *test) +{ + struct amdgpu_device *adev; + struct drm_audio_component *acomp; + struct drm_audio_component_audio_ops *audio_ops; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + acomp = kunit_kzalloc(test, sizeof(*acomp), GFP_KERNEL); + audio_ops = kunit_kzalloc(test, sizeof(*audio_ops), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acomp); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, audio_ops); + + audio_ops->pin_eld_notify = NULL; + acomp->audio_ops = audio_ops; + adev->dm.audio_component = acomp; + + dm_test_eld_notify_count = 0; + + amdgpu_dm_audio_eld_notify(adev, 3); + + KUNIT_EXPECT_EQ(test, dm_test_eld_notify_count, 0); +} + +static struct kunit_case dm_audio_test_cases[] = { + /* amdgpu_dm_audio_init */ + KUNIT_CASE(dm_test_audio_init_disabled), + /* amdgpu_dm_audio_fini */ + KUNIT_CASE(dm_test_audio_fini_without_enabled_audio), + /* amdgpu_dm_fill_audio_info */ + KUNIT_CASE(dm_test_fill_audio_info_ids_name_flags), + KUNIT_CASE(dm_test_fill_audio_info_cea_lt_3_skips_modes), + KUNIT_CASE(dm_test_fill_audio_info_cea_ge_3_copies_modes), + KUNIT_CASE(dm_test_fill_audio_info_cea_ge_3_zero_modes), + KUNIT_CASE(dm_test_fill_audio_info_latency_present), + KUNIT_CASE(dm_test_fill_audio_info_latency_absent_keeps_zero), + /* amdgpu_dm_audio_component_bind/unbind */ + KUNIT_CASE(dm_test_audio_component_bind_sets_fields), + KUNIT_CASE(dm_test_audio_component_unbind_clears_fields), + /* amdgpu_dm_audio_eld_notify */ + KUNIT_CASE(dm_test_eld_notify_invokes_callback), + KUNIT_CASE(dm_test_eld_notify_no_component), + KUNIT_CASE(dm_test_eld_notify_null_audio_ops), + KUNIT_CASE(dm_test_eld_notify_null_callback), + {} +}; + +static struct kunit_suite dm_audio_test_suite = { + .name = "amdgpu_dm_audio", + .test_cases = dm_audio_test_cases, +}; + +kunit_test_suite(dm_audio_test_suite); + +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_audio"); +MODULE_AUTHOR("AMD"); -- cgit v1.2.3 From 829e9b68eaf2f5b4fd3cd9c24e7e154fcd637c53 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Wed, 29 Apr 2026 21:28:36 -0600 Subject: drm/amd/display: Add KUnit tests for amdgpu_dm_dmub Add KUnit tests for amdgpu_dm_dmub.c covering the following functions: - dm_register_dmub_notify_callback(): NULL callback rejection, out-of-range type, valid registration with offload flag - dm_dmub_aux_setconfig_callback(): copy and complete on AUX reply, non-AUX skip, NULL dm_notify, SET_CONFIG reply - dm_dmub_aux_fused_io_callback(): copy reply and complete, max ddc_line boundary - dm_get_default_ips_mode(): IPS mode per DCN version (3.5, 3.5.1, 3.6, 4.2), disabled for older ASICs, default enabled for unhandled newer ASICs - dm_dmub_hw_init(): early returns for no dmub_srv, no fb_info, no firmware - dm_dmub_hw_resume(): no-op when dmub_srv is NULL - dm_dmub_sw_init(): returns 0 for unsupported ASIC - dm_init_microcode(): returns 0 for unsupported ASIC Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c | 9 + .../gpu/drm/amd/display/amdgpu_dm/tests/Makefile | 1 + .../display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c | 600 +++++++++++++++++++++ 3 files changed, 610 insertions(+) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c index 739e685f1c3c..b4c3371f5757 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c @@ -38,6 +38,7 @@ #include "amdgpu_ucode.h" #include "amdgpu_dm.h" #include "amdgpu_dm_dmub.h" +#include "amdgpu_dm_kunit_helpers.h" #include #include @@ -80,6 +81,7 @@ void dm_dmub_aux_setconfig_callback(struct amdgpu_device *adev, if (notify->type == DMUB_NOTIFICATION_AUX_REPLY) complete(&adev->dm.dmub_aux_transfer_done); } +EXPORT_IF_KUNIT(dm_dmub_aux_setconfig_callback); void dm_dmub_aux_fused_io_callback(struct amdgpu_device *adev, struct dmub_notification *notify) @@ -103,6 +105,7 @@ void dm_dmub_aux_fused_io_callback(struct amdgpu_device *adev, memcpy(sync->reply_data, req, sizeof(*req)); complete(&sync->replied); } +EXPORT_IF_KUNIT(dm_dmub_aux_fused_io_callback); /** * dm_register_dmub_notify_callback - Sets callback for DMUB notify @@ -129,6 +132,7 @@ bool dm_register_dmub_notify_callback(struct amdgpu_device *adev, return true; } +EXPORT_IF_KUNIT(dm_register_dmub_notify_callback); int dm_dmub_hw_init(struct amdgpu_device *adev) { @@ -318,6 +322,7 @@ int dm_dmub_hw_init(struct amdgpu_device *adev) return 0; } +EXPORT_IF_KUNIT(dm_dmub_hw_init); void dm_dmub_hw_resume(struct amdgpu_device *adev) { @@ -347,6 +352,7 @@ void dm_dmub_hw_resume(struct amdgpu_device *adev) drm_err(adev_to_drm(adev), "DMUB interface failed to initialize: status=%d\n", r); } } +EXPORT_IF_KUNIT(dm_dmub_hw_resume); static enum dmub_status dm_dmub_send_vbios_gpint_command(struct amdgpu_device *adev, @@ -460,6 +466,7 @@ enum dmub_ips_disable_type dm_get_default_ips_mode( return ret; } +EXPORT_IF_KUNIT(dm_get_default_ips_mode); static uint32_t amdgpu_dm_dmub_reg_read(void *ctx, uint32_t address) { @@ -677,6 +684,7 @@ int dm_dmub_sw_init(struct amdgpu_device *adev) return 0; } +EXPORT_IF_KUNIT(dm_dmub_sw_init); int dm_init_microcode(struct amdgpu_device *adev) { @@ -749,6 +757,7 @@ int dm_init_microcode(struct amdgpu_device *adev) "%s", fw_name_dmub); return r; } +EXPORT_IF_KUNIT(dm_init_microcode); int amdgpu_dm_process_dmub_aux_transfer_sync( struct dc_context *ctx, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile index 5bb43b3bc439..4bd8d1fa0fee 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile @@ -17,6 +17,7 @@ obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_audio_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_color_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_colorop_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_backlight_test.o +obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_dmub_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_psr_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_replay_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_ism_test.o diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c new file mode 100644 index 000000000000..b82dd301a896 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c @@ -0,0 +1,600 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit tests for amdgpu_dm_dmub.c + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include + +#include "dc.h" +#include "dc/inc/core_types.h" +#include "amdgpu_mode.h" +#include "amdgpu_dm.h" +#include "dmub/dmub_srv.h" +#include "amdgpu_dm_dmub.h" + +/* Tests for dm_register_dmub_notify_callback() */ + +static void dummy_callback(struct amdgpu_device *adev, + struct dmub_notification *notify) +{ +} + +/** + * dm_test_register_dmub_notify_callback_null_callback - Test null callback is rejected + * @test: The KUnit test context + */ +static void dm_test_register_dmub_notify_callback_null_callback(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + KUNIT_EXPECT_FALSE(test, dm_register_dmub_notify_callback(adev, + DMUB_NOTIFICATION_AUX_REPLY, NULL, false)); +} + +/** + * dm_test_register_dmub_notify_callback_type_out_of_range - Test out-of-range type is rejected + * @test: The KUnit test context + */ +static void dm_test_register_dmub_notify_callback_type_out_of_range(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + KUNIT_EXPECT_FALSE(test, dm_register_dmub_notify_callback(adev, + AMDGPU_DMUB_NOTIFICATION_MAX, dummy_callback, false)); +} + +/** + * dm_test_register_dmub_notify_callback_valid - Test Register dmub notify callback valid + * @test: The KUnit test context + */ +static void dm_test_register_dmub_notify_callback_valid(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + KUNIT_EXPECT_TRUE(test, dm_register_dmub_notify_callback(adev, + DMUB_NOTIFICATION_AUX_REPLY, dummy_callback, true)); + + KUNIT_EXPECT_TRUE(test, + adev->dm.dmub_callback[DMUB_NOTIFICATION_AUX_REPLY] == dummy_callback); + KUNIT_EXPECT_TRUE(test, + adev->dm.dmub_thread_offload[DMUB_NOTIFICATION_AUX_REPLY]); +} + +/** + * dm_test_register_dmub_notify_callback_offload_false - Test registration with offload disabled + * @test: The KUnit test context + */ +static void dm_test_register_dmub_notify_callback_offload_false(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + KUNIT_EXPECT_TRUE(test, dm_register_dmub_notify_callback(adev, + DMUB_NOTIFICATION_HPD, dummy_callback, false)); + + KUNIT_EXPECT_TRUE(test, + adev->dm.dmub_callback[DMUB_NOTIFICATION_HPD] == dummy_callback); + KUNIT_EXPECT_FALSE(test, + adev->dm.dmub_thread_offload[DMUB_NOTIFICATION_HPD]); +} + +/* Tests for dm_dmub_aux_setconfig_callback() */ + +/** + * dm_test_dmub_aux_setconfig_callback_copies_and_completes - Test copy and complete on AUX reply + * @test: The KUnit test context + */ +static void dm_test_dmub_aux_setconfig_callback_copies_and_completes(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dmub_notification *dm_notify; + struct dmub_notification notify = {}; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + dm_notify = kunit_kzalloc(test, sizeof(*dm_notify), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm_notify); + + init_completion(&adev->dm.dmub_aux_transfer_done); + adev->dm.dmub_notify = dm_notify; + + notify.type = DMUB_NOTIFICATION_AUX_REPLY; + notify.result = AUX_RET_SUCCESS; + notify.aux_reply.command = 0xA5; + notify.aux_reply.length = 3; + notify.aux_reply.data[0] = 0x11; + notify.aux_reply.data[1] = 0x22; + notify.aux_reply.data[2] = 0x33; + + dm_dmub_aux_setconfig_callback(adev, ¬ify); + + KUNIT_EXPECT_EQ(test, dm_notify->type, notify.type); + KUNIT_EXPECT_EQ(test, dm_notify->result, notify.result); + KUNIT_EXPECT_EQ(test, dm_notify->aux_reply.command, notify.aux_reply.command); + KUNIT_EXPECT_EQ(test, dm_notify->aux_reply.length, notify.aux_reply.length); + KUNIT_EXPECT_EQ(test, dm_notify->aux_reply.data[0], notify.aux_reply.data[0]); + KUNIT_EXPECT_EQ(test, dm_notify->aux_reply.data[1], notify.aux_reply.data[1]); + KUNIT_EXPECT_EQ(test, dm_notify->aux_reply.data[2], notify.aux_reply.data[2]); + KUNIT_EXPECT_TRUE(test, completion_done(&adev->dm.dmub_aux_transfer_done)); +} + +/** + * dm_test_dmub_aux_setconfig_callback_non_aux_no_complete - Test non-AUX type skips completion + * @test: The KUnit test context + */ +static void dm_test_dmub_aux_setconfig_callback_non_aux_no_complete(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dmub_notification *dm_notify; + struct dmub_notification notify = {}; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + dm_notify = kunit_kzalloc(test, sizeof(*dm_notify), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm_notify); + + init_completion(&adev->dm.dmub_aux_transfer_done); + adev->dm.dmub_notify = dm_notify; + + notify.type = DMUB_NOTIFICATION_HPD; + notify.result = AUX_RET_ERROR_TIMEOUT; + + dm_dmub_aux_setconfig_callback(adev, ¬ify); + + KUNIT_EXPECT_EQ(test, dm_notify->type, notify.type); + KUNIT_EXPECT_FALSE(test, completion_done(&adev->dm.dmub_aux_transfer_done)); +} + +/** + * dm_test_dmub_aux_setconfig_callback_aux_with_null_dm_notify - Test AUX with NULL dm_notify + * @test: The KUnit test context + */ +static void dm_test_dmub_aux_setconfig_callback_aux_with_null_dm_notify(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dmub_notification notify = {}; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + init_completion(&adev->dm.dmub_aux_transfer_done); + adev->dm.dmub_notify = NULL; + + notify.type = DMUB_NOTIFICATION_AUX_REPLY; + + dm_dmub_aux_setconfig_callback(adev, ¬ify); + + KUNIT_EXPECT_TRUE(test, completion_done(&adev->dm.dmub_aux_transfer_done)); +} + +/** + * dm_test_dmub_aux_setconfig_callback_set_config_reply - Test SET_CONFIG reply copies status + * @test: The KUnit test context + */ +static void dm_test_dmub_aux_setconfig_callback_set_config_reply(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dmub_notification *dm_notify; + struct dmub_notification notify = {}; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + dm_notify = kunit_kzalloc(test, sizeof(*dm_notify), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm_notify); + + init_completion(&adev->dm.dmub_aux_transfer_done); + adev->dm.dmub_notify = dm_notify; + + notify.type = DMUB_NOTIFICATION_SET_CONFIG_REPLY; + notify.sc_status = SET_CONFIG_RX_TIMEOUT; + + dm_dmub_aux_setconfig_callback(adev, ¬ify); + + KUNIT_EXPECT_EQ(test, dm_notify->type, notify.type); + KUNIT_EXPECT_EQ(test, dm_notify->sc_status, notify.sc_status); + KUNIT_EXPECT_FALSE(test, completion_done(&adev->dm.dmub_aux_transfer_done)); +} + +/* Tests for dm_dmub_aux_fused_io_callback() */ + +/** + * dm_test_dmub_aux_fused_io_callback_copies_reply_and_completes - Test copy and complete + * @test: The KUnit test context + */ +static void dm_test_dmub_aux_fused_io_callback_copies_reply_and_completes(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dmub_notification notify = {}; + struct dmub_cmd_fused_request *reply; + u32 reply_ddc_line; + u32 notify_ddc_line; + u32 reply_address; + u32 notify_address; + u32 reply_length; + u32 notify_length; + uint8_t ddc_line = 2; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + init_completion(&adev->dm.fused_io[ddc_line].replied); + + notify.fused_request.identifier = 0x34; + notify.fused_request.status = FUSED_REQUEST_STATUS_SUCCESS; + notify.fused_request.u.aux.ddc_line = ddc_line; + notify.fused_request.u.aux.address = 0x50; + notify.fused_request.u.aux.length = 4; + + dm_dmub_aux_fused_io_callback(adev, ¬ify); + + KUNIT_EXPECT_TRUE(test, completion_done(&adev->dm.fused_io[ddc_line].replied)); + + reply = (struct dmub_cmd_fused_request *)adev->dm.fused_io[ddc_line].reply_data; + reply_ddc_line = reply->u.aux.ddc_line; + notify_ddc_line = notify.fused_request.u.aux.ddc_line; + reply_address = reply->u.aux.address; + notify_address = notify.fused_request.u.aux.address; + reply_length = reply->u.aux.length; + notify_length = notify.fused_request.u.aux.length; + + KUNIT_EXPECT_EQ(test, reply->identifier, notify.fused_request.identifier); + KUNIT_EXPECT_EQ(test, reply->status, notify.fused_request.status); + KUNIT_EXPECT_EQ(test, reply_ddc_line, notify_ddc_line); + KUNIT_EXPECT_EQ(test, reply_address, notify_address); + KUNIT_EXPECT_EQ(test, reply_length, notify_length); +} + +/** + * dm_test_dmub_aux_fused_io_callback_max_ddc_line - Test Dmub aux fused io callback max ddc line + * @test: The KUnit test context + */ +static void dm_test_dmub_aux_fused_io_callback_max_ddc_line(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dmub_notification notify = {}; + struct dmub_cmd_fused_request *reply; + u32 reply_ddc_line; + u32 notify_ddc_line; + uint8_t ddc_line; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + ddc_line = ARRAY_SIZE(adev->dm.fused_io) - 1; + init_completion(&adev->dm.fused_io[ddc_line].replied); + + notify.fused_request.identifier = 0x56; + notify.fused_request.status = FUSED_REQUEST_STATUS_SUCCESS; + notify.fused_request.u.aux.ddc_line = ddc_line; + notify.fused_request.u.aux.address = 0x50; + notify.fused_request.u.aux.length = 1; + + dm_dmub_aux_fused_io_callback(adev, ¬ify); + + KUNIT_EXPECT_TRUE(test, completion_done(&adev->dm.fused_io[ddc_line].replied)); + + reply = (struct dmub_cmd_fused_request *)adev->dm.fused_io[ddc_line].reply_data; + reply_ddc_line = reply->u.aux.ddc_line; + notify_ddc_line = notify.fused_request.u.aux.ddc_line; + + KUNIT_EXPECT_EQ(test, reply->identifier, notify.fused_request.identifier); + KUNIT_EXPECT_EQ(test, reply_ddc_line, notify_ddc_line); +} + +/* Tests for dm_get_default_ips_mode() */ + +/** + * dm_test_get_default_ips_mode_dcn35 - Test Get default ips mode dcn35 + * @test: The KUnit test context + */ +static void dm_test_get_default_ips_mode_dcn35(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 5, 0); + + KUNIT_EXPECT_EQ(test, dm_get_default_ips_mode(adev), + DMUB_IPS_RCG_IN_ACTIVE_IPS2_IN_OFF); +} + +/** + * dm_test_get_default_ips_mode_dcn351 - Test Get default ips mode dcn351 + * @test: The KUnit test context + */ +static void dm_test_get_default_ips_mode_dcn351(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 5, 1); + + KUNIT_EXPECT_EQ(test, dm_get_default_ips_mode(adev), + DMUB_IPS_RCG_IN_ACTIVE_IPS2_IN_OFF); +} + +/** + * dm_test_get_default_ips_mode_dcn36 - Test Get default ips mode dcn36 + * @test: The KUnit test context + */ +static void dm_test_get_default_ips_mode_dcn36(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 6, 0); + + KUNIT_EXPECT_EQ(test, dm_get_default_ips_mode(adev), + DMUB_IPS_RCG_IN_ACTIVE_IPS2_IN_OFF); +} + +/** + * dm_test_get_default_ips_mode_dcn42 - Test Get default ips mode dcn42 + * @test: The KUnit test context + */ +static void dm_test_get_default_ips_mode_dcn42(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + adev->ip_versions[DCE_HWIP][0] = IP_VERSION(4, 2, 0); + + KUNIT_EXPECT_EQ(test, dm_get_default_ips_mode(adev), + DMUB_IPS_DISABLE_ALL); +} + +/** + * dm_test_get_default_ips_mode_older_than_dcn35 - Test Get default ips mode older than dcn35 + * @test: The KUnit test context + */ +static void dm_test_get_default_ips_mode_older_than_dcn35(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + adev->ip_versions[DCE_HWIP][0] = IP_VERSION(3, 2, 0); + + KUNIT_EXPECT_EQ(test, dm_get_default_ips_mode(adev), + DMUB_IPS_DISABLE_ALL); +} + +/** + * dm_test_get_default_ips_mode_newer_default - Test Get default ips mode newer default + * @test: The KUnit test context + */ +static void dm_test_get_default_ips_mode_newer_default(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + /* DCN 4.0.1 is >= 3.5 but has no explicit case, returns ENABLE */ + adev->ip_versions[DCE_HWIP][0] = IP_VERSION(4, 0, 1); + + KUNIT_EXPECT_EQ(test, dm_get_default_ips_mode(adev), + DMUB_IPS_ENABLE); +} + +/* Tests for dm_dmub_hw_init() */ + +/* + * Build an amdgpu_device with the minimal dc/res_pool pointers that + * dm_dmub_hw_init() and dm_dmub_hw_resume() dereference before their + * early-return checks. + */ +static struct amdgpu_device *dm_test_alloc_adev_with_dc(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct resource_pool *res_pool; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + + res_pool = kunit_kzalloc(test, sizeof(*res_pool), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, res_pool); + + dc->res_pool = res_pool; + adev->dm.dc = dc; + + return adev; +} + +/** + * dm_test_dmub_hw_init_no_dmub_srv - Test hw init returns 0 when DMUB unsupported + * @test: The KUnit test context + * + * When adev->dm.dmub_srv is NULL the ASIC does not support DMUB and + * dm_dmub_hw_init() should return 0 without touching the hardware. + */ +static void dm_test_dmub_hw_init_no_dmub_srv(struct kunit *test) +{ + struct amdgpu_device *adev = dm_test_alloc_adev_with_dc(test); + + adev->dm.dmub_srv = NULL; + + KUNIT_EXPECT_EQ(test, dm_dmub_hw_init(adev), 0); +} + +/** + * dm_test_dmub_hw_init_no_fb_info - Test hw init fails without framebuffer info + * @test: The KUnit test context + * + * With a DMUB service present but no framebuffer info, dm_dmub_hw_init() + * should return -EINVAL. + */ +static void dm_test_dmub_hw_init_no_fb_info(struct kunit *test) +{ + struct amdgpu_device *adev = dm_test_alloc_adev_with_dc(test); + struct dmub_srv *dmub_srv; + + dmub_srv = kunit_kzalloc(test, sizeof(*dmub_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dmub_srv); + + adev->dm.dmub_srv = dmub_srv; + adev->dm.dmub_fb_info = NULL; + + KUNIT_EXPECT_EQ(test, dm_dmub_hw_init(adev), -EINVAL); +} + +/** + * dm_test_dmub_hw_init_no_firmware - Test hw init fails without firmware + * @test: The KUnit test context + * + * With a DMUB service and framebuffer info present but no firmware, + * dm_dmub_hw_init() should return -EINVAL. + */ +static void dm_test_dmub_hw_init_no_firmware(struct kunit *test) +{ + struct amdgpu_device *adev = dm_test_alloc_adev_with_dc(test); + struct dmub_srv *dmub_srv; + struct dmub_srv_fb_info *fb_info; + + dmub_srv = kunit_kzalloc(test, sizeof(*dmub_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dmub_srv); + + fb_info = kunit_kzalloc(test, sizeof(*fb_info), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fb_info); + + adev->dm.dmub_srv = dmub_srv; + adev->dm.dmub_fb_info = fb_info; + adev->dm.dmub_fw = NULL; + + KUNIT_EXPECT_EQ(test, dm_dmub_hw_init(adev), -EINVAL); +} + +/* Tests for dm_dmub_hw_resume() */ + +/** + * dm_test_dmub_hw_resume_no_dmub_srv - Test hw resume is a no-op when DMUB unsupported + * @test: The KUnit test context + * + * When adev->dm.dmub_srv is NULL, dm_dmub_hw_resume() should return early + * without dereferencing the (absent) DMUB service. + */ +static void dm_test_dmub_hw_resume_no_dmub_srv(struct kunit *test) +{ + struct amdgpu_device *adev = dm_test_alloc_adev_with_dc(test); + + adev->dm.dmub_srv = NULL; + + /* Must not crash. */ + dm_dmub_hw_resume(adev); +} + +/* Tests for dm_dmub_sw_init() */ + +/** + * dm_test_dmub_sw_init_unsupported_asic - Test sw init returns 0 for unsupported ASIC + * @test: The KUnit test context + * + * For an IP version with no DMUB support, dm_dmub_sw_init() should return 0 + * before attempting to access the firmware. + */ +static void dm_test_dmub_sw_init_unsupported_asic(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + adev->ip_versions[DCE_HWIP][0] = IP_VERSION(1, 0, 0); + + KUNIT_EXPECT_EQ(test, dm_dmub_sw_init(adev), 0); +} + +/* Tests for dm_init_microcode() */ + +/** + * dm_test_init_microcode_unsupported_asic - Test microcode init returns 0 for unsupported ASIC + * @test: The KUnit test context + * + * For an IP version with no DMUB support, dm_init_microcode() should return 0 + * without requesting any firmware. + */ +static void dm_test_init_microcode_unsupported_asic(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + adev->ip_versions[DCE_HWIP][0] = IP_VERSION(1, 0, 0); + + KUNIT_EXPECT_EQ(test, dm_init_microcode(adev), 0); +} + +static struct kunit_case amdgpu_dm_dmub_tests[] = { + /* dm_register_dmub_notify_callback() */ + KUNIT_CASE(dm_test_register_dmub_notify_callback_null_callback), + KUNIT_CASE(dm_test_register_dmub_notify_callback_type_out_of_range), + KUNIT_CASE(dm_test_register_dmub_notify_callback_valid), + KUNIT_CASE(dm_test_register_dmub_notify_callback_offload_false), + /* dm_dmub_aux_setconfig_callback() */ + KUNIT_CASE(dm_test_dmub_aux_setconfig_callback_copies_and_completes), + KUNIT_CASE(dm_test_dmub_aux_setconfig_callback_non_aux_no_complete), + KUNIT_CASE(dm_test_dmub_aux_setconfig_callback_aux_with_null_dm_notify), + KUNIT_CASE(dm_test_dmub_aux_setconfig_callback_set_config_reply), + /* dm_dmub_aux_fused_io_callback() */ + KUNIT_CASE(dm_test_dmub_aux_fused_io_callback_copies_reply_and_completes), + KUNIT_CASE(dm_test_dmub_aux_fused_io_callback_max_ddc_line), + /* dm_get_default_ips_mode() */ + KUNIT_CASE(dm_test_get_default_ips_mode_dcn35), + KUNIT_CASE(dm_test_get_default_ips_mode_dcn351), + KUNIT_CASE(dm_test_get_default_ips_mode_dcn36), + KUNIT_CASE(dm_test_get_default_ips_mode_dcn42), + KUNIT_CASE(dm_test_get_default_ips_mode_older_than_dcn35), + KUNIT_CASE(dm_test_get_default_ips_mode_newer_default), + /* dm_dmub_hw_init() */ + KUNIT_CASE(dm_test_dmub_hw_init_no_dmub_srv), + KUNIT_CASE(dm_test_dmub_hw_init_no_fb_info), + KUNIT_CASE(dm_test_dmub_hw_init_no_firmware), + /* dm_dmub_hw_resume() */ + KUNIT_CASE(dm_test_dmub_hw_resume_no_dmub_srv), + /* dm_dmub_sw_init() */ + KUNIT_CASE(dm_test_dmub_sw_init_unsupported_asic), + /* dm_init_microcode() */ + KUNIT_CASE(dm_test_init_microcode_unsupported_asic), + {} +}; + +static struct kunit_suite amdgpu_dm_dmub_test_suite = { + .name = "amdgpu_dm_dmub", + .test_cases = amdgpu_dm_dmub_tests, +}; + +kunit_test_suite(amdgpu_dm_dmub_test_suite); + +MODULE_AUTHOR("AMD"); +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_dmub"); +MODULE_LICENSE("Dual MIT/GPL"); -- cgit v1.2.3 From a895eb57a55f7a8ad42d5bf14165549fb844394d Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Wed, 29 Apr 2026 22:16:11 -0600 Subject: drm/amd/display: Add KUnit tests for amdgpu_dm_connector Add KUnit tests for helper functions in amdgpu_dm_connector.c, including both pure helper tests and DRM mock-based tests. Tests cover: - get_subconnector_type(): all dongle types and unknown default - get_output_content_type(): all content type mappings and unknown default - adjust_colour_depth_from_display_info(): depth reduction from 12bpc to 10bpc, 16bpc no-fallback, YCbCr420 clock halving, and no-fit rejection - get_output_color_space(): RGB full/limited, YCbCr default 709/601, BT601/709 with Y_ONLY, OPRGB, BT2020 RGB/YCC paths - convert_dc_color_depth_into_bpc(): all depths and undefined default - convert_color_depth_from_display_info(): non-Y420 bpc values, Y420 default/10/12/16bpc, requested odd bpc rounding, unsupported bpc, and requested_bpc capping - to_drm_connector_type(): HDMI, eDP, LVDS, RGB, DP/MST, DVI single and dual link DVII/DVID, virtual, and unknown - is_duplicate_mode(): empty list, match, no-match, and same-size different-clock cases - amdgpu_dm_get_encoder_crtc_mask(): 1-6 CRTCs and default - get_aspect_ratio(): all HDMI picture aspect ratios - decide_crtc_timing_for_drm_display_mode(): scale enabled, matching mode, no copy, and no crtc_clock cases - amdgpu_dm_connector_funcs_reset(): default fields, eDP ABM level set, and eDP ABM disabled - amdgpu_dm_connector_atomic_duplicate_state(): field copy verification - amdgpu_dm_fill_hdr_info_packet(): null metadata early return and output zeroing - amdgpu_dm_connector_atomic_set_property(): scaling center/aspect/ fullscreen/none/unchanged, underscan hborder/vborder/enable, abm sysfs control/level off/level value, and unknown property -EINVAL - amdgpu_dm_connector_atomic_get_property(): scaling center/aspect/ full/off, underscan borders, abm sysfs allowed/level/disabled, and unknown property -EINVAL - amdgpu_dm_get_highest_refresh_rate_mode(): null writeback, cached base mode, and preferred mode selection - amdgpu_dm_is_freesync_video_mode(): null mode, match, and no-match cases Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_connector.c | 33 +- .../amd/display/amdgpu_dm/amdgpu_dm_connector.h | 15 + .../gpu/drm/amd/display/amdgpu_dm/tests/Makefile | 1 + .../amdgpu_dm/tests/amdgpu_dm_connector_test.c | 2142 ++++++++++++++++++++ 4 files changed, 2184 insertions(+), 7 deletions(-) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') 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 df09627f4c04..27f8fb2e8c12 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 @@ -42,6 +42,7 @@ #include "amdgpu_display.h" #include "amdgpu_dm.h" #include "amdgpu_dm_connector.h" +#include "amdgpu_dm_kunit_helpers.h" #include "amdgpu_dm_plane.h" #include "amdgpu_dm_crtc.h" #include "amdgpu_dm_wb.h" @@ -188,6 +189,7 @@ int amdgpu_dm_get_encoder_crtc_mask(struct amdgpu_device *adev) return 0x3f; } } +EXPORT_IF_KUNIT(amdgpu_dm_get_encoder_crtc_mask); int amdgpu_dm_encoder_init(struct drm_device *dev, struct amdgpu_encoder *aencoder, @@ -213,7 +215,7 @@ int amdgpu_dm_encoder_init(struct drm_device *dev, return res; } -static enum drm_mode_subconnector get_subconnector_type(struct dc_link *link) +STATIC_IFN_KUNIT enum drm_mode_subconnector get_subconnector_type(struct dc_link *link) { switch (link->dpcd_caps.dongle_type) { case DISPLAY_DONGLE_NONE: @@ -231,6 +233,7 @@ static enum drm_mode_subconnector get_subconnector_type(struct dc_link *link) return DRM_MODE_SUBCONNECTOR_Unknown; } } +EXPORT_IF_KUNIT(get_subconnector_type); static void update_subconnector_property(struct amdgpu_dm_connector *aconnector) { @@ -662,13 +665,15 @@ amdgpu_dm_convert_color_depth_from_display_info(const struct drm_connector *conn return COLOR_DEPTH_UNDEFINED; } } +EXPORT_IF_KUNIT(amdgpu_dm_convert_color_depth_from_display_info); -static enum dc_aspect_ratio +STATIC_IFN_KUNIT enum dc_aspect_ratio get_aspect_ratio(const struct drm_display_mode *mode_in) { /* 1-1 mapping, since both enums follow the HDMI spec. */ return (enum dc_aspect_ratio) mode_in->picture_aspect_ratio; } +EXPORT_IF_KUNIT(get_aspect_ratio); enum dc_color_space amdgpu_dm_get_output_color_space(const struct dc_crtc_timing *dc_crtc_timing, @@ -728,8 +733,9 @@ amdgpu_dm_get_output_color_space(const struct dc_crtc_timing *dc_crtc_timing, return color_space; } +EXPORT_IF_KUNIT(amdgpu_dm_get_output_color_space); -static enum display_content_type +STATIC_IFN_KUNIT enum display_content_type get_output_content_type(const struct drm_connector_state *connector_state) { switch (connector_state->content_type) { @@ -746,8 +752,9 @@ get_output_content_type(const struct drm_connector_state *connector_state) return DISPLAY_CONTENT_TYPE_GAME; } } +EXPORT_IF_KUNIT(get_output_content_type); -static bool adjust_colour_depth_from_display_info( +STATIC_IFN_KUNIT bool adjust_colour_depth_from_display_info( struct dc_crtc_timing *timing_out, const struct drm_display_info *info) { @@ -783,6 +790,7 @@ static bool adjust_colour_depth_from_display_info( } while (--depth > COLOR_DEPTH_666); return false; } +EXPORT_IF_KUNIT(adjust_colour_depth_from_display_info); static void fill_stream_properties_from_drm_display_mode( struct dc_stream_state *stream, @@ -932,7 +940,7 @@ copy_crtc_timing_for_drm_display_mode(const struct drm_display_mode *src_mode, dst_mode->crtc_vtotal = src_mode->crtc_vtotal; } -static void +STATIC_IFN_KUNIT void decide_crtc_timing_for_drm_display_mode(struct drm_display_mode *drm_mode, const struct drm_display_mode *native_mode, bool scale_enabled) @@ -947,6 +955,7 @@ decide_crtc_timing_for_drm_display_mode(struct drm_display_mode *drm_mode, /* no scaling nor amdgpu inserted, no need to patch */ } } +EXPORT_IF_KUNIT(decide_crtc_timing_for_drm_display_mode); static struct dc_sink * create_fake_sink(struct drm_device *dev, struct dc_link *link) @@ -1052,6 +1061,7 @@ amdgpu_dm_get_highest_refresh_rate_mode(struct amdgpu_dm_connector *aconnector, drm_mode_copy(&aconnector->freesync_vid_base, m_pref); return m_pref; } +EXPORT_IF_KUNIT(amdgpu_dm_get_highest_refresh_rate_mode); bool amdgpu_dm_is_freesync_video_mode(const struct drm_display_mode *mode, struct amdgpu_dm_connector *aconnector) @@ -1079,6 +1089,7 @@ bool amdgpu_dm_is_freesync_video_mode(const struct drm_display_mode *mode, else return true; } +EXPORT_IF_KUNIT(amdgpu_dm_is_freesync_video_mode); #if defined(CONFIG_DRM_AMD_DC_FP) static void update_dsc_caps(struct amdgpu_dm_connector *aconnector, @@ -1667,6 +1678,7 @@ int amdgpu_dm_connector_atomic_set_property(struct drm_connector *connector, return ret; } +EXPORT_IF_KUNIT(amdgpu_dm_connector_atomic_set_property); int amdgpu_dm_connector_atomic_get_property(struct drm_connector *connector, const struct drm_connector_state *state, @@ -1716,6 +1728,7 @@ int amdgpu_dm_connector_atomic_get_property(struct drm_connector *connector, return ret; } +EXPORT_IF_KUNIT(amdgpu_dm_connector_atomic_get_property); static void amdgpu_dm_connector_unregister(struct drm_connector *connector) { @@ -1801,6 +1814,7 @@ void amdgpu_dm_connector_funcs_reset(struct drm_connector *connector) __drm_atomic_helper_connector_reset(connector, &state->base); } } +EXPORT_IF_KUNIT(amdgpu_dm_connector_funcs_reset); struct drm_connector_state * amdgpu_dm_connector_atomic_duplicate_state(struct drm_connector *connector) @@ -1826,6 +1840,7 @@ amdgpu_dm_connector_atomic_duplicate_state(struct drm_connector *connector) new_state->pbn = state->pbn; return &new_state->base; } +EXPORT_IF_KUNIT(amdgpu_dm_connector_atomic_duplicate_state); static int amdgpu_dm_connector_late_register(struct drm_connector *connector) @@ -2253,6 +2268,7 @@ int amdgpu_dm_fill_hdr_info_packet(const struct drm_connector_state *state, return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_fill_hdr_info_packet); static int amdgpu_dm_connector_atomic_check(struct drm_connector *conn, @@ -2368,8 +2384,9 @@ int amdgpu_dm_convert_dc_color_depth_into_bpc(enum dc_color_depth display_color_ } return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_convert_dc_color_depth_into_bpc); -static int to_drm_connector_type(enum signal_type st, uint32_t connector_id) +STATIC_IFN_KUNIT int to_drm_connector_type(enum signal_type st, uint32_t connector_id) { switch (st) { case SIGNAL_TYPE_HDMI_TYPE_A: @@ -2403,6 +2420,7 @@ static int to_drm_connector_type(enum signal_type st, uint32_t connector_id) return DRM_MODE_CONNECTOR_Unknown; } } +EXPORT_IF_KUNIT(to_drm_connector_type); static struct drm_encoder *amdgpu_dm_connector_to_encoder(struct drm_connector *connector) { @@ -2598,7 +2616,7 @@ static void amdgpu_dm_connector_ddc_get_modes(struct drm_connector *connector, } } -static bool is_duplicate_mode(struct amdgpu_dm_connector *aconnector, +STATIC_IFN_KUNIT bool is_duplicate_mode(struct amdgpu_dm_connector *aconnector, struct drm_display_mode *mode) { struct drm_display_mode *m; @@ -2610,6 +2628,7 @@ static bool is_duplicate_mode(struct amdgpu_dm_connector *aconnector, return false; } +EXPORT_IF_KUNIT(is_duplicate_mode); static uint add_fs_modes(struct amdgpu_dm_connector *aconnector) { diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h index db8e5588dbfd..c5b8b13f8f06 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.h @@ -34,6 +34,7 @@ struct amdgpu_encoder; struct amdgpu_i2c_adapter; struct dc_crtc_timing; struct dc_link; +enum signal_type; struct dc_state; struct dc_stream_state; struct ddc_service; @@ -144,4 +145,18 @@ int amdgpu_dm_encoder_init(struct drm_device *dev, struct amdgpu_encoder *aencoder, uint32_t link_index); +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +enum drm_mode_subconnector get_subconnector_type(struct dc_link *link); +enum display_content_type +get_output_content_type(const struct drm_connector_state *connector_state); +bool adjust_colour_depth_from_display_info(struct dc_crtc_timing *timing_out, + const struct drm_display_info *info); + +int to_drm_connector_type(enum signal_type st, uint32_t connector_id); +bool is_duplicate_mode(struct amdgpu_dm_connector *aconnector, struct drm_display_mode *mode); +enum dc_aspect_ratio get_aspect_ratio(const struct drm_display_mode *mode_in); +void decide_crtc_timing_for_drm_display_mode(struct drm_display_mode *drm_mode, + const struct drm_display_mode *native_mode, + bool scale_enabled); +#endif #endif /* __AMDGPU_DM_CONNECTOR_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile index 4bd8d1fa0fee..422eef0bfe49 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile @@ -16,6 +16,7 @@ obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_hdcp_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_audio_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_color_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_colorop_test.o +obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_connector_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_backlight_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_dmub_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_psr_test.o diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c new file mode 100644 index 000000000000..34e40d2a9d2c --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c @@ -0,0 +1,2142 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit tests for amdgpu_dm_connector.c + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include +#include +#include +#include +#include +#include + +#include "dc.h" +#include "amdgpu.h" +#include "amdgpu_mode.h" +#include "amdgpu_display.h" +#include "amdgpu_dm.h" +#include "amdgpu_dm_connector.h" +#include "amdgpu_dm_backlight.h" +#include "include/grph_object_id.h" + +/* Tests for get_subconnector_type() */ + +/** + * dm_test_subconnector_type_none - Test Subconnector type none + * @test: The KUnit test context + */ +static void dm_test_subconnector_type_none(struct kunit *test) +{ + struct dc_link link = {}; + + link.dpcd_caps.dongle_type = DISPLAY_DONGLE_NONE; + KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(&link), (int)DRM_MODE_SUBCONNECTOR_Native); +} + +/** + * dm_test_subconnector_type_vga - Test Subconnector type vga + * @test: The KUnit test context + */ +static void dm_test_subconnector_type_vga(struct kunit *test) +{ + struct dc_link link = {}; + + link.dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_VGA_CONVERTER; + KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(&link), (int)DRM_MODE_SUBCONNECTOR_VGA); +} + +/** + * dm_test_subconnector_type_dvi_converter - Test Subconnector type dvi converter + * @test: The KUnit test context + */ +static void dm_test_subconnector_type_dvi_converter(struct kunit *test) +{ + struct dc_link link = {}; + + link.dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_DVI_CONVERTER; + KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(&link), (int)DRM_MODE_SUBCONNECTOR_DVID); +} + +/** + * dm_test_subconnector_type_dvi_dongle - Test Subconnector type dvi dongle + * @test: The KUnit test context + */ +static void dm_test_subconnector_type_dvi_dongle(struct kunit *test) +{ + struct dc_link link = {}; + + link.dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_DVI_DONGLE; + KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(&link), (int)DRM_MODE_SUBCONNECTOR_DVID); +} + +/** + * dm_test_subconnector_type_hdmi_converter - Test Subconnector type hdmi converter + * @test: The KUnit test context + */ +static void dm_test_subconnector_type_hdmi_converter(struct kunit *test) +{ + struct dc_link link = {}; + + link.dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_HDMI_CONVERTER; + KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(&link), (int)DRM_MODE_SUBCONNECTOR_HDMIA); +} + +/** + * dm_test_subconnector_type_hdmi_dongle - Test Subconnector type hdmi dongle + * @test: The KUnit test context + */ +static void dm_test_subconnector_type_hdmi_dongle(struct kunit *test) +{ + struct dc_link link = {}; + + link.dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_HDMI_DONGLE; + KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(&link), (int)DRM_MODE_SUBCONNECTOR_HDMIA); +} + +/** + * dm_test_subconnector_type_mismatched - Test Subconnector type mismatched + * @test: The KUnit test context + */ +static void dm_test_subconnector_type_mismatched(struct kunit *test) +{ + struct dc_link link = {}; + + link.dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_HDMI_MISMATCHED_DONGLE; + KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(&link), (int)DRM_MODE_SUBCONNECTOR_Unknown); +} + +/** + * dm_test_subconnector_type_default_unknown - Test Subconnector type default unknown + * @test: The KUnit test context + */ +static void dm_test_subconnector_type_default_unknown(struct kunit *test) +{ + struct dc_link link = {}; + + link.dpcd_caps.dongle_type = (typeof(link.dpcd_caps.dongle_type))0x7f; + KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(&link), (int)DRM_MODE_SUBCONNECTOR_Unknown); +} + +/* Tests for get_output_content_type() */ + +/** + * dm_test_content_type_no_data - Test Content type no data + * @test: The KUnit test context + */ +static void dm_test_content_type_no_data(struct kunit *test) +{ + struct drm_connector_state state = {}; + + state.content_type = DRM_MODE_CONTENT_TYPE_NO_DATA; + KUNIT_EXPECT_EQ(test, (int)get_output_content_type(&state), (int)DISPLAY_CONTENT_TYPE_NO_DATA); +} + +/** + * dm_test_content_type_graphics - Test Content type graphics + * @test: The KUnit test context + */ +static void dm_test_content_type_graphics(struct kunit *test) +{ + struct drm_connector_state state = {}; + + state.content_type = DRM_MODE_CONTENT_TYPE_GRAPHICS; + KUNIT_EXPECT_EQ(test, (int)get_output_content_type(&state), (int)DISPLAY_CONTENT_TYPE_GRAPHICS); +} + +/** + * dm_test_content_type_photo - Test Content type photo + * @test: The KUnit test context + */ +static void dm_test_content_type_photo(struct kunit *test) +{ + struct drm_connector_state state = {}; + + state.content_type = DRM_MODE_CONTENT_TYPE_PHOTO; + KUNIT_EXPECT_EQ(test, (int)get_output_content_type(&state), (int)DISPLAY_CONTENT_TYPE_PHOTO); +} + +/** + * dm_test_content_type_cinema - Test Content type cinema + * @test: The KUnit test context + */ +static void dm_test_content_type_cinema(struct kunit *test) +{ + struct drm_connector_state state = {}; + + state.content_type = DRM_MODE_CONTENT_TYPE_CINEMA; + KUNIT_EXPECT_EQ(test, (int)get_output_content_type(&state), (int)DISPLAY_CONTENT_TYPE_CINEMA); +} + +/** + * dm_test_content_type_game - Test Content type game + * @test: The KUnit test context + */ +static void dm_test_content_type_game(struct kunit *test) +{ + struct drm_connector_state state = {}; + + state.content_type = DRM_MODE_CONTENT_TYPE_GAME; + KUNIT_EXPECT_EQ(test, (int)get_output_content_type(&state), (int)DISPLAY_CONTENT_TYPE_GAME); +} + +/** + * dm_test_content_type_unknown_defaults_no_data - Test unknown content type defaults to no data + * @test: The KUnit test context + */ +static void dm_test_content_type_unknown_defaults_no_data(struct kunit *test) +{ + struct drm_connector_state state = {}; + + state.content_type = 0x7f; + KUNIT_EXPECT_EQ(test, (int)get_output_content_type(&state), + (int)DISPLAY_CONTENT_TYPE_NO_DATA); +} + +/* Tests for adjust_colour_depth_from_display_info() */ + +/** + * dm_test_adjust_colour_depth_fits_at_888 - Test Adjust colour depth fits at 888 + * @test: The KUnit test context + */ +static void dm_test_adjust_colour_depth_fits_at_888(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_display_info info = {}; + + /* 1080p @ 148500 KHz = 1485000 in 100Hz units */ + timing.pix_clk_100hz = 1485000; + timing.display_color_depth = COLOR_DEPTH_888; + timing.pixel_encoding = PIXEL_ENCODING_RGB; + info.max_tmds_clock = 150000; /* 150 MHz */ + + KUNIT_EXPECT_TRUE(test, adjust_colour_depth_from_display_info(&timing, &info)); + KUNIT_EXPECT_EQ(test, (int)timing.display_color_depth, (int)COLOR_DEPTH_888); +} + +/** + * dm_test_adjust_colour_depth_reduces_to_888 - Test Adjust colour depth reduces to 888 + * @test: The KUnit test context + */ +static void dm_test_adjust_colour_depth_reduces_to_888(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_display_info info = {}; + + /* Request 10bpc but TMDS limit only allows 8bpc */ + timing.pix_clk_100hz = 1485000; + timing.display_color_depth = COLOR_DEPTH_101010; + timing.pixel_encoding = PIXEL_ENCODING_RGB; + /* 10bpc would need 148500*30/24 = 185625 KHz, exceeds limit */ + info.max_tmds_clock = 160000; + + KUNIT_EXPECT_TRUE(test, adjust_colour_depth_from_display_info(&timing, &info)); + KUNIT_EXPECT_EQ(test, (int)timing.display_color_depth, (int)COLOR_DEPTH_888); +} + +/** + * dm_test_adjust_colour_depth_10bpc_passes - Test Adjust colour depth 10bpc passes + * @test: The KUnit test context + */ +static void dm_test_adjust_colour_depth_10bpc_passes(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_display_info info = {}; + + timing.pix_clk_100hz = 1485000; + timing.display_color_depth = COLOR_DEPTH_101010; + timing.pixel_encoding = PIXEL_ENCODING_RGB; + /* 10bpc needs 185625 KHz, allow it */ + info.max_tmds_clock = 200000; + + KUNIT_EXPECT_TRUE(test, adjust_colour_depth_from_display_info(&timing, &info)); + KUNIT_EXPECT_EQ(test, (int)timing.display_color_depth, (int)COLOR_DEPTH_101010); +} + +/** + * dm_test_adjust_colour_depth_420_halves_clk - Test Adjust colour depth 420 halves clk + * @test: The KUnit test context + */ +static void dm_test_adjust_colour_depth_420_halves_clk(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_display_info info = {}; + + /* 4K @ 594000 KHz = 5940000 in 100Hz units */ + timing.pix_clk_100hz = 5940000; + timing.display_color_depth = COLOR_DEPTH_101010; + timing.pixel_encoding = PIXEL_ENCODING_YCBCR420; + /* With 420: effective = 594000/2 = 297000, 10bpc = 297000*30/24 = 371250 */ + info.max_tmds_clock = 400000; + + KUNIT_EXPECT_TRUE(test, adjust_colour_depth_from_display_info(&timing, &info)); + KUNIT_EXPECT_EQ(test, (int)timing.display_color_depth, (int)COLOR_DEPTH_101010); +} + +/** + * dm_test_adjust_colour_depth_reduces_12bpc_to_10bpc - Test Adjust colour + * depth reduces 12bpc to 10bpc + * @test: The KUnit test context + */ +static void dm_test_adjust_colour_depth_reduces_12bpc_to_10bpc(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_display_info info = {}; + + timing.pix_clk_100hz = 1485000; + timing.display_color_depth = COLOR_DEPTH_121212; + timing.pixel_encoding = PIXEL_ENCODING_RGB; + info.max_tmds_clock = 190000; + + KUNIT_EXPECT_TRUE(test, adjust_colour_depth_from_display_info(&timing, &info)); + KUNIT_EXPECT_EQ(test, (int)timing.display_color_depth, (int)COLOR_DEPTH_101010); +} + +/** + * dm_test_adjust_colour_depth_16bpc_no_fallback - Test Adjust colour depth + * 16bpc cannot fall back + * @test: The KUnit test context + */ +static void dm_test_adjust_colour_depth_16bpc_no_fallback(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_display_info info = {}; + + /* 16bpc that exceeds limit cannot reduce because the next enum + * value (COLOR_DEPTH_141414) is not a valid HDMI depth. + */ + timing.pix_clk_100hz = 1485000; + timing.display_color_depth = COLOR_DEPTH_161616; + timing.pixel_encoding = PIXEL_ENCODING_RGB; + info.max_tmds_clock = 230000; + + KUNIT_EXPECT_FALSE(test, adjust_colour_depth_from_display_info(&timing, &info)); +} + +/** + * dm_test_adjust_colour_depth_none_fits - Test Adjust colour depth none fits + * @test: The KUnit test context + */ +static void dm_test_adjust_colour_depth_none_fits(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_display_info info = {}; + + /* Even 8bpc doesn't fit */ + timing.pix_clk_100hz = 1485000; + timing.display_color_depth = COLOR_DEPTH_888; + timing.pixel_encoding = PIXEL_ENCODING_RGB; + info.max_tmds_clock = 100000; /* Too low */ + + KUNIT_EXPECT_FALSE(test, adjust_colour_depth_from_display_info(&timing, &info)); +} + +/** + * dm_test_adjust_colour_depth_invalid_depth - Test Adjust colour depth invalid depth + * @test: The KUnit test context + */ +static void dm_test_adjust_colour_depth_invalid_depth(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_display_info info = {}; + + timing.pix_clk_100hz = 1485000; + timing.display_color_depth = COLOR_DEPTH_141414; + timing.pixel_encoding = PIXEL_ENCODING_RGB; + info.max_tmds_clock = 400000; + + KUNIT_EXPECT_FALSE(test, adjust_colour_depth_from_display_info(&timing, &info)); + KUNIT_EXPECT_EQ(test, (int)timing.display_color_depth, (int)COLOR_DEPTH_141414); +} + +/* Tests for amdgpu_dm_get_output_color_space() */ + +/** + * dm_test_output_color_space_default_rgb_full - Test Output color space default rgb full + * @test: The KUnit test context + */ +static void dm_test_output_color_space_default_rgb_full(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + + timing.pixel_encoding = PIXEL_ENCODING_RGB; + state.colorspace = DRM_MODE_COLORIMETRY_DEFAULT; + state.hdmi.broadcast_rgb = DRM_HDMI_BROADCAST_RGB_AUTO; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_SRGB); +} + +/** + * dm_test_output_color_space_default_rgb_limited - Test Output color space default rgb limited + * @test: The KUnit test context + */ +static void dm_test_output_color_space_default_rgb_limited(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + + timing.pixel_encoding = PIXEL_ENCODING_RGB; + state.colorspace = DRM_MODE_COLORIMETRY_DEFAULT; + state.hdmi.broadcast_rgb = DRM_HDMI_BROADCAST_RGB_LIMITED; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_SRGB_LIMITED); +} + +/** + * dm_test_output_color_space_default_ycbcr709 - Test Output color space default ycbcr709 + * @test: The KUnit test context + */ +static void dm_test_output_color_space_default_ycbcr709(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + + timing.pixel_encoding = PIXEL_ENCODING_YCBCR444; + timing.pix_clk_100hz = 300000; + timing.flags.Y_ONLY = 0; + state.colorspace = DRM_MODE_COLORIMETRY_DEFAULT; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_YCBCR709); +} + +/** + * dm_test_output_color_space_default_ycbcr601_limited - Test Output color space + * default ycbcr601 limited + * @test: The KUnit test context + */ +static void dm_test_output_color_space_default_ycbcr601_limited(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + + timing.pixel_encoding = PIXEL_ENCODING_YCBCR444; + timing.pix_clk_100hz = 270300; + timing.flags.Y_ONLY = 1; + state.colorspace = DRM_MODE_COLORIMETRY_DEFAULT; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_YCBCR601_LIMITED); +} + +/** + * dm_test_output_color_space_bt601_y_only - Test Output color space bt601 y only + * @test: The KUnit test context + */ +static void dm_test_output_color_space_bt601_y_only(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + + timing.flags.Y_ONLY = 1; + state.colorspace = DRM_MODE_COLORIMETRY_BT601_YCC; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_YCBCR601_LIMITED); +} + +/** + * dm_test_output_color_space_bt601 - Test Output color space bt601 + * @test: The KUnit test context + */ +static void dm_test_output_color_space_bt601(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + + timing.flags.Y_ONLY = 0; + state.colorspace = DRM_MODE_COLORIMETRY_BT601_YCC; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_YCBCR601); +} + +/** + * dm_test_output_color_space_bt709 - Test Output color space bt709 + * @test: The KUnit test context + */ +static void dm_test_output_color_space_bt709(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + + timing.flags.Y_ONLY = 0; + state.colorspace = DRM_MODE_COLORIMETRY_BT709_YCC; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_YCBCR709); +} + +/** + * dm_test_output_color_space_bt709_y_only - Test Output color space bt709 y only + * @test: The KUnit test context + */ +static void dm_test_output_color_space_bt709_y_only(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + + timing.flags.Y_ONLY = 1; + state.colorspace = DRM_MODE_COLORIMETRY_BT709_YCC; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_YCBCR709_LIMITED); +} + +/** + * dm_test_output_color_space_oprgb - Test Output color space oprgb + * @test: The KUnit test context + */ +static void dm_test_output_color_space_oprgb(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + + state.colorspace = DRM_MODE_COLORIMETRY_OPRGB; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_ADOBERGB); +} + +/** + * dm_test_output_color_space_bt2020_rgb - Test Output color space bt2020 rgb + * @test: The KUnit test context + */ +static void dm_test_output_color_space_bt2020_rgb(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + + timing.pixel_encoding = PIXEL_ENCODING_RGB; + state.colorspace = DRM_MODE_COLORIMETRY_BT2020_RGB; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_2020_RGB_FULLRANGE); +} + +/** + * dm_test_output_color_space_bt2020_ycc - Test Output color space bt2020 ycc + * @test: The KUnit test context + */ +static void dm_test_output_color_space_bt2020_ycc(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + + timing.pixel_encoding = PIXEL_ENCODING_YCBCR422; + state.colorspace = DRM_MODE_COLORIMETRY_BT2020_YCC; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_2020_YCBCR_LIMITED); +} + +/* Tests for amdgpu_dm_convert_dc_color_depth_into_bpc() */ + +/** + * dm_test_convert_color_depth_bpc_mappings - Test Convert color depth bpc mappings + * @test: The KUnit test context + */ +static void dm_test_convert_color_depth_bpc_mappings(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, amdgpu_dm_convert_dc_color_depth_into_bpc(COLOR_DEPTH_666), 6); + KUNIT_EXPECT_EQ(test, amdgpu_dm_convert_dc_color_depth_into_bpc(COLOR_DEPTH_888), 8); + KUNIT_EXPECT_EQ(test, amdgpu_dm_convert_dc_color_depth_into_bpc(COLOR_DEPTH_101010), 10); + KUNIT_EXPECT_EQ(test, amdgpu_dm_convert_dc_color_depth_into_bpc(COLOR_DEPTH_121212), 12); + KUNIT_EXPECT_EQ(test, amdgpu_dm_convert_dc_color_depth_into_bpc(COLOR_DEPTH_141414), 14); + KUNIT_EXPECT_EQ(test, amdgpu_dm_convert_dc_color_depth_into_bpc(COLOR_DEPTH_161616), 16); +} + +/** + * dm_test_convert_color_depth_bpc_unknown - Test Convert color depth bpc unknown + * @test: The KUnit test context + */ +static void dm_test_convert_color_depth_bpc_unknown(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, amdgpu_dm_convert_dc_color_depth_into_bpc(COLOR_DEPTH_UNDEFINED), 0); +} + +/* Tests for amdgpu_dm_convert_color_depth_from_display_info() */ + +/** + * dm_test_color_depth_from_info_bpc8 - Test Color depth from info bpc8 + * @test: The KUnit test context + */ +static void dm_test_color_depth_from_info_bpc8(struct kunit *test) +{ + struct drm_connector *connector; + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector); + + connector->display_info.bpc = 8; + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_convert_color_depth_from_display_info(connector, false, 0), + (int)COLOR_DEPTH_888); +} + +/** + * dm_test_color_depth_from_info_bpc10 - Test Color depth from info bpc10 + * @test: The KUnit test context + */ +static void dm_test_color_depth_from_info_bpc10(struct kunit *test) +{ + struct drm_connector *connector; + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector); + + connector->display_info.bpc = 10; + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_convert_color_depth_from_display_info(connector, false, 0), + (int)COLOR_DEPTH_101010); +} + +/** + * dm_test_color_depth_from_info_zero_bpc_defaults_888 - Test Color depth from + * info zero bpc defaults 888 + * @test: The KUnit test context + */ +static void dm_test_color_depth_from_info_zero_bpc_defaults_888(struct kunit *test) +{ + struct drm_connector *connector; + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector); + + connector->display_info.bpc = 0; + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_convert_color_depth_from_display_info(connector, false, 0), + (int)COLOR_DEPTH_888); +} + +/** + * dm_test_color_depth_from_info_requested_bpc_caps - Test Color depth from info requested bpc caps + * @test: The KUnit test context + */ +static void dm_test_color_depth_from_info_requested_bpc_caps(struct kunit *test) +{ + struct drm_connector *connector; + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector); + + /* Display supports 12bpc but user requests max 10 */ + connector->display_info.bpc = 12; + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_convert_color_depth_from_display_info(connector, false, 10), + (int)COLOR_DEPTH_101010); +} + +/** + * dm_test_color_depth_from_info_y420_default - Test Color depth from info y420 default + * @test: The KUnit test context + */ +static void dm_test_color_depth_from_info_y420_default(struct kunit *test) +{ + struct drm_connector *connector; + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector); + + /* No Y420 DC modes set → 8bpc */ + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_convert_color_depth_from_display_info(connector, true, 0), + (int)COLOR_DEPTH_888); +} + +/** + * dm_test_color_depth_from_info_y420_10bpc - Test Color depth from info y420 10bpc + * @test: The KUnit test context + */ +static void dm_test_color_depth_from_info_y420_10bpc(struct kunit *test) +{ + struct drm_connector *connector; + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector); + + connector->display_info.hdmi.y420_dc_modes = DRM_EDID_YCBCR420_DC_30; + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_convert_color_depth_from_display_info(connector, true, 0), + (int)COLOR_DEPTH_101010); +} + +/** + * dm_test_color_depth_from_info_y420_12bpc - Test Color depth from info y420 12bpc + * @test: The KUnit test context + */ +static void dm_test_color_depth_from_info_y420_12bpc(struct kunit *test) +{ + struct drm_connector *connector; + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector); + + connector->display_info.hdmi.y420_dc_modes = DRM_EDID_YCBCR420_DC_36; + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_convert_color_depth_from_display_info(connector, true, 0), + (int)COLOR_DEPTH_121212); +} + +/** + * dm_test_color_depth_from_info_y420_16bpc - Test Color depth from info y420 16bpc + * @test: The KUnit test context + */ +static void dm_test_color_depth_from_info_y420_16bpc(struct kunit *test) +{ + struct drm_connector *connector; + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector); + + connector->display_info.hdmi.y420_dc_modes = DRM_EDID_YCBCR420_DC_48; + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_convert_color_depth_from_display_info(connector, true, 0), + (int)COLOR_DEPTH_161616); +} + +/** + * dm_test_color_depth_from_info_requested_odd_bpc - Test Color depth from info requested odd bpc + * @test: The KUnit test context + */ +static void dm_test_color_depth_from_info_requested_odd_bpc(struct kunit *test) +{ + struct drm_connector *connector; + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector); + + connector->display_info.bpc = 12; + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_convert_color_depth_from_display_info(connector, false, 11), + (int)COLOR_DEPTH_101010); +} + +/** + * dm_test_color_depth_from_info_unsupported_bpc - Test Color depth from info unsupported bpc + * @test: The KUnit test context + */ +static void dm_test_color_depth_from_info_unsupported_bpc(struct kunit *test) +{ + struct drm_connector *connector; + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, connector); + + connector->display_info.bpc = 9; + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_convert_color_depth_from_display_info(connector, false, 0), + (int)COLOR_DEPTH_UNDEFINED); +} + +/* Tests for to_drm_connector_type() */ + +/** + * dm_test_to_connector_type_hdmi - Test To connector type hdmi + * @test: The KUnit test context + */ +static void dm_test_to_connector_type_hdmi(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, to_drm_connector_type(SIGNAL_TYPE_HDMI_TYPE_A, 0), + DRM_MODE_CONNECTOR_HDMIA); +} + +/** + * dm_test_to_connector_type_edp - Test To connector type edp + * @test: The KUnit test context + */ +static void dm_test_to_connector_type_edp(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, to_drm_connector_type(SIGNAL_TYPE_EDP, 0), + DRM_MODE_CONNECTOR_eDP); +} + +/** + * dm_test_to_connector_type_lvds - Test To connector type lvds + * @test: The KUnit test context + */ +static void dm_test_to_connector_type_lvds(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, to_drm_connector_type(SIGNAL_TYPE_LVDS, 0), + DRM_MODE_CONNECTOR_LVDS); +} + +/** + * dm_test_to_connector_type_rgb - Test To connector type rgb + * @test: The KUnit test context + */ +static void dm_test_to_connector_type_rgb(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, to_drm_connector_type(SIGNAL_TYPE_RGB, 0), + DRM_MODE_CONNECTOR_VGA); +} + +/** + * dm_test_to_connector_type_dp - Test To connector type dp + * @test: The KUnit test context + */ +static void dm_test_to_connector_type_dp(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, to_drm_connector_type(SIGNAL_TYPE_DISPLAY_PORT, 0), + DRM_MODE_CONNECTOR_DisplayPort); +} + +/** + * dm_test_to_connector_type_dp_mst - Test To connector type dp mst + * @test: The KUnit test context + */ +static void dm_test_to_connector_type_dp_mst(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, to_drm_connector_type(SIGNAL_TYPE_DISPLAY_PORT_MST, 0), + DRM_MODE_CONNECTOR_DisplayPort); +} + +/** + * dm_test_to_connector_type_dvi_dvii - Test To connector type dvi dvii + * @test: The KUnit test context + */ +static void dm_test_to_connector_type_dvi_dvii(struct kunit *test) +{ + int type = to_drm_connector_type(SIGNAL_TYPE_DVI_SINGLE_LINK, CONNECTOR_ID_SINGLE_LINK_DVII); + + KUNIT_EXPECT_EQ(test, type, DRM_MODE_CONNECTOR_DVII); +} + +/** + * dm_test_to_connector_type_dual_link_dvii - Test To connector type dual link dvii + * @test: The KUnit test context + */ +static void dm_test_to_connector_type_dual_link_dvii(struct kunit *test) +{ + int type = to_drm_connector_type(SIGNAL_TYPE_DVI_DUAL_LINK, CONNECTOR_ID_DUAL_LINK_DVII); + + KUNIT_EXPECT_EQ(test, type, DRM_MODE_CONNECTOR_DVII); +} + +/** + * dm_test_to_connector_type_dvi_dvid - Test To connector type dvi dvid + * @test: The KUnit test context + */ +static void dm_test_to_connector_type_dvi_dvid(struct kunit *test) +{ + int type = to_drm_connector_type(SIGNAL_TYPE_DVI_SINGLE_LINK, CONNECTOR_ID_SINGLE_LINK_DVID); + + KUNIT_EXPECT_EQ(test, type, DRM_MODE_CONNECTOR_DVID); +} + +/** + * dm_test_to_connector_type_virtual - Test To connector type virtual + * @test: The KUnit test context + */ +static void dm_test_to_connector_type_virtual(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, to_drm_connector_type(SIGNAL_TYPE_VIRTUAL, 0), + DRM_MODE_CONNECTOR_VIRTUAL); +} + +/** + * dm_test_to_connector_type_unknown - Test To connector type unknown + * @test: The KUnit test context + */ +static void dm_test_to_connector_type_unknown(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, to_drm_connector_type(SIGNAL_TYPE_NONE, 0), + DRM_MODE_CONNECTOR_Unknown); +} + +/* Tests for is_duplicate_mode() */ + +/** + * dm_test_is_duplicate_mode_empty_list - Test Is duplicate mode empty list + * @test: The KUnit test context + */ +static void dm_test_is_duplicate_mode_empty_list(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct drm_display_mode mode = {}; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconnector); + + INIT_LIST_HEAD(&aconnector->base.probed_modes); + mode.hdisplay = 1920; + mode.vdisplay = 1080; + + KUNIT_EXPECT_FALSE(test, is_duplicate_mode(aconnector, &mode)); +} + +/** + * dm_test_is_duplicate_mode_match - Test Is duplicate mode match + * @test: The KUnit test context + */ +static void dm_test_is_duplicate_mode_match(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct drm_display_mode existing = {}; + struct drm_display_mode candidate = {}; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconnector); + + INIT_LIST_HEAD(&aconnector->base.probed_modes); + existing.hdisplay = 1920; + existing.vdisplay = 1080; + existing.clock = 148500; + list_add_tail(&existing.head, &aconnector->base.probed_modes); + + candidate.hdisplay = 1920; + candidate.vdisplay = 1080; + candidate.clock = 148500; + + KUNIT_EXPECT_TRUE(test, is_duplicate_mode(aconnector, &candidate)); +} + +/** + * dm_test_is_duplicate_mode_no_match - Test Is duplicate mode no match + * @test: The KUnit test context + */ +static void dm_test_is_duplicate_mode_no_match(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct drm_display_mode existing = {}; + struct drm_display_mode candidate = {}; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconnector); + + INIT_LIST_HEAD(&aconnector->base.probed_modes); + existing.hdisplay = 1920; + existing.vdisplay = 1080; + existing.clock = 148500; + list_add_tail(&existing.head, &aconnector->base.probed_modes); + + candidate.hdisplay = 2560; + candidate.vdisplay = 1440; + candidate.clock = 241500; + + KUNIT_EXPECT_FALSE(test, is_duplicate_mode(aconnector, &candidate)); +} + +/** + * dm_test_is_duplicate_mode_same_size_different_clock - Test Is duplicate mode + * same size different clock + * @test: The KUnit test context + */ +static void dm_test_is_duplicate_mode_same_size_different_clock(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct drm_display_mode existing = {}; + struct drm_display_mode candidate = {}; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, aconnector); + + INIT_LIST_HEAD(&aconnector->base.probed_modes); + existing.hdisplay = 1920; + existing.vdisplay = 1080; + existing.clock = 148500; + list_add_tail(&existing.head, &aconnector->base.probed_modes); + + candidate.hdisplay = 1920; + candidate.vdisplay = 1080; + candidate.clock = 74250; + + KUNIT_EXPECT_FALSE(test, is_duplicate_mode(aconnector, &candidate)); +} + +/* Tests for amdgpu_dm_get_encoder_crtc_mask() */ + +/** + * dm_test_encoder_crtc_mask_1 - Test Encoder crtc mask 1 + * @test: The KUnit test context + */ +static void dm_test_encoder_crtc_mask_1(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + adev->mode_info.num_crtc = 1; + KUNIT_EXPECT_EQ(test, amdgpu_dm_get_encoder_crtc_mask(adev), 0x1); +} + +/** + * dm_test_encoder_crtc_mask_2 - Test Encoder crtc mask 2 + * @test: The KUnit test context + */ +static void dm_test_encoder_crtc_mask_2(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + adev->mode_info.num_crtc = 2; + KUNIT_EXPECT_EQ(test, amdgpu_dm_get_encoder_crtc_mask(adev), 0x3); +} + +/** + * dm_test_encoder_crtc_mask_3 - Test Encoder crtc mask 3 + * @test: The KUnit test context + */ +static void dm_test_encoder_crtc_mask_3(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + adev->mode_info.num_crtc = 3; + KUNIT_EXPECT_EQ(test, amdgpu_dm_get_encoder_crtc_mask(adev), 0x7); +} + +/** + * dm_test_encoder_crtc_mask_4 - Test Encoder crtc mask 4 + * @test: The KUnit test context + */ +static void dm_test_encoder_crtc_mask_4(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + adev->mode_info.num_crtc = 4; + KUNIT_EXPECT_EQ(test, amdgpu_dm_get_encoder_crtc_mask(adev), 0xf); +} + +/** + * dm_test_encoder_crtc_mask_5 - Test Encoder crtc mask 5 + * @test: The KUnit test context + */ +static void dm_test_encoder_crtc_mask_5(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + adev->mode_info.num_crtc = 5; + KUNIT_EXPECT_EQ(test, amdgpu_dm_get_encoder_crtc_mask(adev), 0x1f); +} + +/** + * dm_test_encoder_crtc_mask_6 - Test Encoder crtc mask 6 + * @test: The KUnit test context + */ +static void dm_test_encoder_crtc_mask_6(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + adev->mode_info.num_crtc = 6; + KUNIT_EXPECT_EQ(test, amdgpu_dm_get_encoder_crtc_mask(adev), 0x3f); +} + +/** + * dm_test_encoder_crtc_mask_default - Test Encoder crtc mask default + * @test: The KUnit test context + */ +static void dm_test_encoder_crtc_mask_default(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + /* Values > 6 use the default case */ + adev->mode_info.num_crtc = 8; + KUNIT_EXPECT_EQ(test, amdgpu_dm_get_encoder_crtc_mask(adev), 0x3f); +} + +/* Tests for get_aspect_ratio() */ + +/** + * dm_test_aspect_ratio_no_data - Test Aspect ratio no data + * @test: The KUnit test context + */ +static void dm_test_aspect_ratio_no_data(struct kunit *test) +{ + struct drm_display_mode mode = {}; + + mode.picture_aspect_ratio = HDMI_PICTURE_ASPECT_NONE; + KUNIT_EXPECT_EQ(test, (int)get_aspect_ratio(&mode), (int)ASPECT_RATIO_NO_DATA); +} + +/** + * dm_test_aspect_ratio_4_3 - Test Aspect ratio 4 3 + * @test: The KUnit test context + */ +static void dm_test_aspect_ratio_4_3(struct kunit *test) +{ + struct drm_display_mode mode = {}; + + mode.picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3; + KUNIT_EXPECT_EQ(test, (int)get_aspect_ratio(&mode), (int)ASPECT_RATIO_4_3); +} + +/** + * dm_test_aspect_ratio_16_9 - Test Aspect ratio 16 9 + * @test: The KUnit test context + */ +static void dm_test_aspect_ratio_16_9(struct kunit *test) +{ + struct drm_display_mode mode = {}; + + mode.picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9; + KUNIT_EXPECT_EQ(test, (int)get_aspect_ratio(&mode), (int)ASPECT_RATIO_16_9); +} + +/** + * dm_test_aspect_ratio_64_27 - Test Aspect ratio 64 27 + * @test: The KUnit test context + */ +static void dm_test_aspect_ratio_64_27(struct kunit *test) +{ + struct drm_display_mode mode = {}; + + mode.picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27; + KUNIT_EXPECT_EQ(test, (int)get_aspect_ratio(&mode), (int)ASPECT_RATIO_64_27); +} + +/** + * dm_test_aspect_ratio_256_135 - Test Aspect ratio 256 135 + * @test: The KUnit test context + */ +static void dm_test_aspect_ratio_256_135(struct kunit *test) +{ + struct drm_display_mode mode = {}; + + mode.picture_aspect_ratio = HDMI_PICTURE_ASPECT_256_135; + KUNIT_EXPECT_EQ(test, (int)get_aspect_ratio(&mode), (int)ASPECT_RATIO_256_135); +} + +/* Tests for decide_crtc_timing_for_drm_display_mode() */ + +/** + * dm_test_decide_crtc_timing_scale_enabled - Test Decide crtc timing scale enabled + * @test: The KUnit test context + */ +static void dm_test_decide_crtc_timing_scale_enabled(struct kunit *test) +{ + struct drm_display_mode drm_mode = {}; + struct drm_display_mode native_mode = {}; + + native_mode.crtc_clock = 148500; + native_mode.crtc_hdisplay = 1920; + native_mode.crtc_vdisplay = 1080; + native_mode.crtc_htotal = 2200; + native_mode.crtc_vtotal = 1125; + native_mode.crtc_hsync_start = 2008; + native_mode.crtc_hsync_end = 2052; + native_mode.crtc_vsync_start = 1084; + native_mode.crtc_vsync_end = 1089; + + /* Different clock/htotal/vtotal, but scale_enabled forces copy */ + drm_mode.clock = 74250; + drm_mode.htotal = 1650; + drm_mode.vtotal = 750; + + decide_crtc_timing_for_drm_display_mode(&drm_mode, &native_mode, true); + + KUNIT_EXPECT_EQ(test, drm_mode.crtc_clock, 148500); + KUNIT_EXPECT_EQ(test, drm_mode.crtc_hdisplay, 1920); + KUNIT_EXPECT_EQ(test, drm_mode.crtc_vdisplay, 1080); + KUNIT_EXPECT_EQ(test, drm_mode.crtc_htotal, 2200); + KUNIT_EXPECT_EQ(test, drm_mode.crtc_vtotal, 1125); +} + +/** + * dm_test_decide_crtc_timing_matching_mode - Test Decide crtc timing matching mode + * @test: The KUnit test context + */ +static void dm_test_decide_crtc_timing_matching_mode(struct kunit *test) +{ + struct drm_display_mode drm_mode = {}; + struct drm_display_mode native_mode = {}; + + native_mode.clock = 148500; + native_mode.htotal = 2200; + native_mode.vtotal = 1125; + native_mode.crtc_clock = 148500; + native_mode.crtc_hdisplay = 1920; + native_mode.crtc_vdisplay = 1080; + native_mode.crtc_htotal = 2200; + native_mode.crtc_vtotal = 1125; + + /* Matching clock/htotal/vtotal triggers copy */ + drm_mode.clock = 148500; + drm_mode.htotal = 2200; + drm_mode.vtotal = 1125; + + decide_crtc_timing_for_drm_display_mode(&drm_mode, &native_mode, false); + + KUNIT_EXPECT_EQ(test, drm_mode.crtc_clock, 148500); + KUNIT_EXPECT_EQ(test, drm_mode.crtc_hdisplay, 1920); + KUNIT_EXPECT_EQ(test, drm_mode.crtc_vtotal, 1125); +} + +/** + * dm_test_decide_crtc_timing_no_copy - Test Decide crtc timing no copy + * @test: The KUnit test context + */ +static void dm_test_decide_crtc_timing_no_copy(struct kunit *test) +{ + struct drm_display_mode drm_mode = {}; + struct drm_display_mode native_mode = {}; + + native_mode.clock = 148500; + native_mode.htotal = 2200; + native_mode.vtotal = 1125; + native_mode.crtc_clock = 148500; + native_mode.crtc_hdisplay = 1920; + + /* Different timings, no scaling → no copy */ + drm_mode.clock = 74250; + drm_mode.htotal = 1650; + drm_mode.vtotal = 750; + + decide_crtc_timing_for_drm_display_mode(&drm_mode, &native_mode, false); + + KUNIT_EXPECT_EQ(test, drm_mode.crtc_clock, 0); + KUNIT_EXPECT_EQ(test, drm_mode.crtc_hdisplay, 0); +} + +/** + * dm_test_decide_crtc_timing_no_crtc_clock - Test Decide crtc timing no crtc clock + * @test: The KUnit test context + */ +static void dm_test_decide_crtc_timing_no_crtc_clock(struct kunit *test) +{ + struct drm_display_mode drm_mode = {}; + struct drm_display_mode native_mode = {}; + + /* Matching timings but native crtc_clock is 0 → no copy */ + native_mode.clock = 148500; + native_mode.htotal = 2200; + native_mode.vtotal = 1125; + native_mode.crtc_clock = 0; + native_mode.crtc_hdisplay = 1920; + + drm_mode.clock = 148500; + drm_mode.htotal = 2200; + drm_mode.vtotal = 1125; + + decide_crtc_timing_for_drm_display_mode(&drm_mode, &native_mode, false); + + KUNIT_EXPECT_EQ(test, drm_mode.crtc_clock, 0); + KUNIT_EXPECT_EQ(test, drm_mode.crtc_hdisplay, 0); +} + +/* Tests for amdgpu_dm_connector_funcs_reset() */ + +static const struct drm_connector_funcs dm_test_connector_funcs = { + .reset = amdgpu_dm_connector_funcs_reset, + .atomic_duplicate_state = amdgpu_dm_connector_atomic_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +/** + * dm_test_funcs_reset_sets_defaults - Test funcs_reset sets defaults + * @test: The KUnit test context + */ +static void dm_test_funcs_reset_sets_defaults(struct kunit *test) +{ + struct device *dev; + struct drm_device *drm; + struct drm_connector *connector; + struct dm_connector_state *dm_state; + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + drm = __drm_kunit_helper_alloc_drm_device(test, dev, + sizeof(*drm), 0, + DRIVER_MODESET); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, connector); + + drmm_connector_init(drm, connector, &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort, NULL); + + amdgpu_dm_connector_funcs_reset(connector); + + KUNIT_ASSERT_NOT_NULL(test, connector->state); + dm_state = to_dm_connector_state(connector->state); + KUNIT_EXPECT_EQ(test, (int)dm_state->scaling, (int)RMX_OFF); + KUNIT_EXPECT_FALSE(test, dm_state->underscan_enable); + KUNIT_EXPECT_EQ(test, (int)dm_state->underscan_hborder, 0); + KUNIT_EXPECT_EQ(test, (int)dm_state->underscan_vborder, 0); + KUNIT_EXPECT_EQ(test, (int)dm_state->base.max_requested_bpc, 8); + KUNIT_EXPECT_EQ(test, dm_state->vcpi_slots, 0); + KUNIT_EXPECT_EQ(test, (int)dm_state->pbn, 0); +} + +/** + * dm_test_funcs_reset_edp_abm_level - Test funcs_reset eDP sets ABM + * @test: The KUnit test context + */ +static void dm_test_funcs_reset_edp_abm_level(struct kunit *test) +{ + struct device *dev; + struct drm_device *drm; + struct drm_connector *connector; + struct dm_connector_state *dm_state; + int saved_abm_level = amdgpu_dm_get_abm_level_param(); + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + drm = __drm_kunit_helper_alloc_drm_device(test, dev, + sizeof(*drm), 0, + DRIVER_MODESET); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, connector); + + drmm_connector_init(drm, connector, &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_eDP, NULL); + + /* Test with abm_level > 0 */ + amdgpu_dm_set_abm_level_param(3); + amdgpu_dm_connector_funcs_reset(connector); + + KUNIT_ASSERT_NOT_NULL(test, connector->state); + dm_state = to_dm_connector_state(connector->state); + KUNIT_EXPECT_EQ(test, (int)dm_state->abm_level, 3); + + amdgpu_dm_set_abm_level_param(saved_abm_level); +} + +/** + * dm_test_funcs_reset_edp_abm_disabled - Test funcs_reset eDP ABM + * disabled + * @test: The KUnit test context + */ +static void dm_test_funcs_reset_edp_abm_disabled(struct kunit *test) +{ + struct device *dev; + struct drm_device *drm; + struct drm_connector *connector; + struct dm_connector_state *dm_state; + int saved_abm_level = amdgpu_dm_get_abm_level_param(); + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + drm = __drm_kunit_helper_alloc_drm_device(test, dev, + sizeof(*drm), 0, + DRIVER_MODESET); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, connector); + + drmm_connector_init(drm, connector, &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_eDP, NULL); + + /* Test with abm_level <= 0 → immediate disable */ + amdgpu_dm_set_abm_level_param(-1); + amdgpu_dm_connector_funcs_reset(connector); + + KUNIT_ASSERT_NOT_NULL(test, connector->state); + dm_state = to_dm_connector_state(connector->state); + KUNIT_EXPECT_EQ(test, (int)dm_state->abm_level, + (int)ABM_LEVEL_IMMEDIATE_DISABLE); + + amdgpu_dm_set_abm_level_param(saved_abm_level); +} + +/* Tests for amdgpu_dm_connector_atomic_duplicate_state() */ + +/** + * dm_test_atomic_dup_state_copies_fields - Test atomic_duplicate copies + * fields + * @test: The KUnit test context + */ +static void dm_test_atomic_dup_state_copies_fields(struct kunit *test) +{ + struct device *dev; + struct drm_device *drm; + struct drm_connector *connector; + struct dm_connector_state *dm_state; + struct dm_connector_state *new_dm_state; + struct drm_connector_state *new_state; + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + drm = __drm_kunit_helper_alloc_drm_device(test, dev, + sizeof(*drm), 0, + DRIVER_MODESET); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + + connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, connector); + + drmm_connector_init(drm, connector, &dm_test_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA, NULL); + + amdgpu_dm_connector_funcs_reset(connector); + KUNIT_ASSERT_NOT_NULL(test, connector->state); + + /* Modify original state fields */ + dm_state = to_dm_connector_state(connector->state); + dm_state->scaling = RMX_CENTER; + dm_state->underscan_enable = true; + dm_state->underscan_hborder = 10; + dm_state->underscan_vborder = 20; + dm_state->freesync_capable = true; + dm_state->abm_level = 2; + dm_state->vcpi_slots = 4; + dm_state->pbn = 1234; + + /* Duplicate */ + new_state = amdgpu_dm_connector_atomic_duplicate_state(connector); + KUNIT_ASSERT_NOT_NULL(test, new_state); + new_dm_state = to_dm_connector_state(new_state); + + /* Verify all fields copied */ + KUNIT_EXPECT_EQ(test, (int)new_dm_state->scaling, (int)RMX_CENTER); + KUNIT_EXPECT_TRUE(test, new_dm_state->underscan_enable); + KUNIT_EXPECT_EQ(test, (int)new_dm_state->underscan_hborder, 10); + KUNIT_EXPECT_EQ(test, (int)new_dm_state->underscan_vborder, 20); + KUNIT_EXPECT_TRUE(test, new_dm_state->freesync_capable); + KUNIT_EXPECT_EQ(test, (int)new_dm_state->abm_level, 2); + KUNIT_EXPECT_EQ(test, new_dm_state->vcpi_slots, 4); + KUNIT_EXPECT_EQ(test, (int)new_dm_state->pbn, 1234); + + kfree(new_dm_state); +} + +/* Tests for amdgpu_dm_fill_hdr_info_packet() */ + +/** + * dm_test_fill_hdr_null_metadata - Test fill_hdr returns 0 with no + * metadata + * @test: The KUnit test context + */ +static void dm_test_fill_hdr_null_metadata(struct kunit *test) +{ + struct drm_connector_state state = {}; + struct dc_info_packet out = {}; + + /* No hdr_output_metadata → early return 0, out stays zeroed */ + state.hdr_output_metadata = NULL; + KUNIT_EXPECT_EQ(test, amdgpu_dm_fill_hdr_info_packet(&state, &out), 0); + KUNIT_EXPECT_FALSE(test, out.valid); +} + +/** + * dm_test_fill_hdr_zeroes_output - Test fill_hdr zeroes output with no + * metadata + * @test: The KUnit test context + */ +static void dm_test_fill_hdr_zeroes_output(struct kunit *test) +{ + struct drm_connector_state state = {}; + struct dc_info_packet out; + + /* Pre-fill out with nonzero to verify memset(0) */ + memset(&out, 0xAA, sizeof(out)); + + state.hdr_output_metadata = NULL; + KUNIT_EXPECT_EQ(test, amdgpu_dm_fill_hdr_info_packet(&state, &out), 0); + KUNIT_EXPECT_FALSE(test, out.valid); + KUNIT_EXPECT_EQ(test, (int)out.hb0, 0); + KUNIT_EXPECT_EQ(test, (int)out.hb1, 0); + KUNIT_EXPECT_EQ(test, (int)out.hb2, 0); + KUNIT_EXPECT_EQ(test, (int)out.hb3, 0); +} + +/* Tests for amdgpu_dm_connector_atomic_set_property() */ + +/* + * Build a connector wired to a kunit-allocated amdgpu_device so that + * drm_to_adev() resolves correctly, together with old/new dm states and + * the set of properties used by the get/set property handlers. + */ +struct dm_test_prop_ctx { + struct amdgpu_device *adev; + struct drm_connector *connector; + struct dm_connector_state *old_state; + struct dm_connector_state *new_state; + struct drm_property *scaling_prop; + struct drm_property *hborder_prop; + struct drm_property *vborder_prop; + struct drm_property *underscan_prop; + struct drm_property *abm_prop; +}; + +static struct dm_test_prop_ctx *dm_test_prop_ctx_alloc(struct kunit *test) +{ + struct dm_test_prop_ctx *ctx; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + ctx->adev = kunit_kzalloc(test, sizeof(*ctx->adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->adev); + ctx->connector = kunit_kzalloc(test, sizeof(*ctx->connector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->connector); + ctx->old_state = kunit_kzalloc(test, sizeof(*ctx->old_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->old_state); + ctx->new_state = kunit_kzalloc(test, sizeof(*ctx->new_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->new_state); + ctx->scaling_prop = kunit_kzalloc(test, sizeof(*ctx->scaling_prop), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->scaling_prop); + ctx->hborder_prop = kunit_kzalloc(test, sizeof(*ctx->hborder_prop), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->hborder_prop); + ctx->vborder_prop = kunit_kzalloc(test, sizeof(*ctx->vborder_prop), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->vborder_prop); + ctx->underscan_prop = kunit_kzalloc(test, sizeof(*ctx->underscan_prop), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->underscan_prop); + ctx->abm_prop = kunit_kzalloc(test, sizeof(*ctx->abm_prop), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->abm_prop); + + ctx->connector->dev = &ctx->adev->ddev; + ctx->connector->state = &ctx->old_state->base; + + ctx->adev->ddev.mode_config.scaling_mode_property = ctx->scaling_prop; + ctx->adev->mode_info.underscan_hborder_property = ctx->hborder_prop; + ctx->adev->mode_info.underscan_vborder_property = ctx->vborder_prop; + ctx->adev->mode_info.underscan_property = ctx->underscan_prop; + ctx->adev->mode_info.abm_level_property = ctx->abm_prop; + + return ctx; +} + +/** + * dm_test_set_property_scaling_center - Test set scaling property to center + * @test: The KUnit test context + */ +static void dm_test_set_property_scaling_center(struct kunit *test) +{ + struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_set_property( + ctx->connector, &ctx->new_state->base, + ctx->scaling_prop, DRM_MODE_SCALE_CENTER), 0); + KUNIT_EXPECT_EQ(test, (int)ctx->new_state->scaling, (int)RMX_CENTER); +} + +/** + * dm_test_set_property_scaling_aspect - Test set scaling property to aspect + * @test: The KUnit test context + */ +static void dm_test_set_property_scaling_aspect(struct kunit *test) +{ + struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_set_property( + ctx->connector, &ctx->new_state->base, + ctx->scaling_prop, DRM_MODE_SCALE_ASPECT), 0); + KUNIT_EXPECT_EQ(test, (int)ctx->new_state->scaling, (int)RMX_ASPECT); +} + +/** + * dm_test_set_property_scaling_fullscreen - Test set scaling property to full + * @test: The KUnit test context + */ +static void dm_test_set_property_scaling_fullscreen(struct kunit *test) +{ + struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_set_property( + ctx->connector, &ctx->new_state->base, + ctx->scaling_prop, DRM_MODE_SCALE_FULLSCREEN), 0); + KUNIT_EXPECT_EQ(test, (int)ctx->new_state->scaling, (int)RMX_FULL); +} + +/** + * dm_test_set_property_scaling_none - Test set scaling property to none + * @test: The KUnit test context + */ +static void dm_test_set_property_scaling_none(struct kunit *test) +{ + struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test); + + /* old scaling is RMX_CENTER so RMX_OFF is a real change */ + ctx->old_state->scaling = RMX_CENTER; + KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_set_property( + ctx->connector, &ctx->new_state->base, + ctx->scaling_prop, DRM_MODE_SCALE_NONE), 0); + KUNIT_EXPECT_EQ(test, (int)ctx->new_state->scaling, (int)RMX_OFF); +} + +/** + * dm_test_set_property_scaling_unchanged - Test set scaling property unchanged + * @test: The KUnit test context + */ +static void dm_test_set_property_scaling_unchanged(struct kunit *test) +{ + struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test); + + /* old already RMX_OFF, requesting NONE/OFF returns 0 without write */ + ctx->old_state->scaling = RMX_OFF; + ctx->new_state->scaling = RMX_CENTER; + KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_set_property( + ctx->connector, &ctx->new_state->base, + ctx->scaling_prop, DRM_MODE_SCALE_NONE), 0); + /* new_state untouched because of early return */ + KUNIT_EXPECT_EQ(test, (int)ctx->new_state->scaling, (int)RMX_CENTER); +} + +/** + * dm_test_set_property_underscan_hborder - Test set underscan hborder + * @test: The KUnit test context + */ +static void dm_test_set_property_underscan_hborder(struct kunit *test) +{ + struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_set_property( + ctx->connector, &ctx->new_state->base, + ctx->hborder_prop, 42), 0); + KUNIT_EXPECT_EQ(test, (int)ctx->new_state->underscan_hborder, 42); +} + +/** + * dm_test_set_property_underscan_vborder - Test set underscan vborder + * @test: The KUnit test context + */ +static void dm_test_set_property_underscan_vborder(struct kunit *test) +{ + struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_set_property( + ctx->connector, &ctx->new_state->base, + ctx->vborder_prop, 24), 0); + KUNIT_EXPECT_EQ(test, (int)ctx->new_state->underscan_vborder, 24); +} + +/** + * dm_test_set_property_underscan_enable - Test set underscan enable + * @test: The KUnit test context + */ +static void dm_test_set_property_underscan_enable(struct kunit *test) +{ + struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_set_property( + ctx->connector, &ctx->new_state->base, + ctx->underscan_prop, 1), 0); + KUNIT_EXPECT_TRUE(test, ctx->new_state->underscan_enable); +} + +/** + * dm_test_set_property_abm_sysfs_control - Test set abm sysfs control + * @test: The KUnit test context + */ +static void dm_test_set_property_abm_sysfs_control(struct kunit *test) +{ + struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test); + + ctx->new_state->abm_sysfs_forbidden = true; + KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_set_property( + ctx->connector, &ctx->new_state->base, + ctx->abm_prop, ABM_SYSFS_CONTROL), 0); + KUNIT_EXPECT_FALSE(test, ctx->new_state->abm_sysfs_forbidden); +} + +/** + * dm_test_set_property_abm_level_off - Test set abm level off + * @test: The KUnit test context + */ +static void dm_test_set_property_abm_level_off(struct kunit *test) +{ + struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_set_property( + ctx->connector, &ctx->new_state->base, + ctx->abm_prop, ABM_LEVEL_OFF), 0); + KUNIT_EXPECT_TRUE(test, ctx->new_state->abm_sysfs_forbidden); + KUNIT_EXPECT_EQ(test, (int)ctx->new_state->abm_level, + (int)ABM_LEVEL_IMMEDIATE_DISABLE); +} + +/** + * dm_test_set_property_abm_level_value - Test set abm level to a value + * @test: The KUnit test context + */ +static void dm_test_set_property_abm_level_value(struct kunit *test) +{ + struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_set_property( + ctx->connector, &ctx->new_state->base, + ctx->abm_prop, 3), 0); + KUNIT_EXPECT_TRUE(test, ctx->new_state->abm_sysfs_forbidden); + KUNIT_EXPECT_EQ(test, (int)ctx->new_state->abm_level, 3); +} + +/** + * dm_test_set_property_unknown - Test set unknown property returns -EINVAL + * @test: The KUnit test context + */ +static void dm_test_set_property_unknown(struct kunit *test) +{ + struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test); + struct drm_property *other; + + other = kunit_kzalloc(test, sizeof(*other), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, other); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_set_property( + ctx->connector, &ctx->new_state->base, + other, 0), -EINVAL); +} + +/* Tests for amdgpu_dm_connector_atomic_get_property() */ + +/** + * dm_test_get_property_scaling_center - Test get scaling property center + * @test: The KUnit test context + */ +static void dm_test_get_property_scaling_center(struct kunit *test) +{ + struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test); + uint64_t val = 0; + + ctx->new_state->scaling = RMX_CENTER; + KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_get_property( + ctx->connector, &ctx->new_state->base, + ctx->scaling_prop, &val), 0); + KUNIT_EXPECT_EQ(test, (int)val, (int)DRM_MODE_SCALE_CENTER); +} + +/** + * dm_test_get_property_scaling_aspect - Test get scaling property aspect + * @test: The KUnit test context + */ +static void dm_test_get_property_scaling_aspect(struct kunit *test) +{ + struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test); + uint64_t val = 0; + + ctx->new_state->scaling = RMX_ASPECT; + KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_get_property( + ctx->connector, &ctx->new_state->base, + ctx->scaling_prop, &val), 0); + KUNIT_EXPECT_EQ(test, (int)val, (int)DRM_MODE_SCALE_ASPECT); +} + +/** + * dm_test_get_property_scaling_full - Test get scaling property fullscreen + * @test: The KUnit test context + */ +static void dm_test_get_property_scaling_full(struct kunit *test) +{ + struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test); + uint64_t val = 0; + + ctx->new_state->scaling = RMX_FULL; + KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_get_property( + ctx->connector, &ctx->new_state->base, + ctx->scaling_prop, &val), 0); + KUNIT_EXPECT_EQ(test, (int)val, (int)DRM_MODE_SCALE_FULLSCREEN); +} + +/** + * dm_test_get_property_scaling_off - Test get scaling property off/none + * @test: The KUnit test context + */ +static void dm_test_get_property_scaling_off(struct kunit *test) +{ + struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test); + uint64_t val = 0; + + ctx->new_state->scaling = RMX_OFF; + KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_get_property( + ctx->connector, &ctx->new_state->base, + ctx->scaling_prop, &val), 0); + KUNIT_EXPECT_EQ(test, (int)val, (int)DRM_MODE_SCALE_NONE); +} + +/** + * dm_test_get_property_underscan_borders - Test get underscan borders/enable + * @test: The KUnit test context + */ +static void dm_test_get_property_underscan_borders(struct kunit *test) +{ + struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test); + uint64_t val = 0; + + ctx->new_state->underscan_hborder = 12; + ctx->new_state->underscan_vborder = 34; + ctx->new_state->underscan_enable = true; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_get_property( + ctx->connector, &ctx->new_state->base, + ctx->hborder_prop, &val), 0); + KUNIT_EXPECT_EQ(test, (int)val, 12); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_get_property( + ctx->connector, &ctx->new_state->base, + ctx->vborder_prop, &val), 0); + KUNIT_EXPECT_EQ(test, (int)val, 34); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_get_property( + ctx->connector, &ctx->new_state->base, + ctx->underscan_prop, &val), 0); + KUNIT_EXPECT_EQ(test, (int)val, 1); +} + +/** + * dm_test_get_property_abm_sysfs_allowed - Test get abm returns sysfs control + * @test: The KUnit test context + */ +static void dm_test_get_property_abm_sysfs_allowed(struct kunit *test) +{ + struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test); + uint64_t val = 0; + + ctx->new_state->abm_sysfs_forbidden = false; + KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_get_property( + ctx->connector, &ctx->new_state->base, + ctx->abm_prop, &val), 0); + KUNIT_EXPECT_EQ(test, (int)val, (int)ABM_SYSFS_CONTROL); +} + +/** + * dm_test_get_property_abm_level - Test get abm returns level when forbidden + * @test: The KUnit test context + */ +static void dm_test_get_property_abm_level(struct kunit *test) +{ + struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test); + uint64_t val = 0; + + ctx->new_state->abm_sysfs_forbidden = true; + ctx->new_state->abm_level = 2; + KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_get_property( + ctx->connector, &ctx->new_state->base, + ctx->abm_prop, &val), 0); + KUNIT_EXPECT_EQ(test, (int)val, 2); +} + +/** + * dm_test_get_property_abm_disabled_zero - Test get abm returns 0 when disabled + * @test: The KUnit test context + */ +static void dm_test_get_property_abm_disabled_zero(struct kunit *test) +{ + struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test); + uint64_t val = 0xdead; + + ctx->new_state->abm_sysfs_forbidden = true; + ctx->new_state->abm_level = ABM_LEVEL_IMMEDIATE_DISABLE; + KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_get_property( + ctx->connector, &ctx->new_state->base, + ctx->abm_prop, &val), 0); + KUNIT_EXPECT_EQ(test, (int)val, 0); +} + +/** + * dm_test_get_property_unknown - Test get unknown property returns -EINVAL + * @test: The KUnit test context + */ +static void dm_test_get_property_unknown(struct kunit *test) +{ + struct dm_test_prop_ctx *ctx = dm_test_prop_ctx_alloc(test); + struct drm_property *other; + uint64_t val = 0; + + other = kunit_kzalloc(test, sizeof(*other), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, other); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_connector_atomic_get_property( + ctx->connector, &ctx->new_state->base, + other, &val), -EINVAL); +} + +/* Tests for amdgpu_dm_get_highest_refresh_rate_mode() */ + +/** + * dm_test_highest_refresh_writeback_null - Test writeback connector returns NULL + * @test: The KUnit test context + */ +static void dm_test_highest_refresh_writeback_null(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + aconnector->base.connector_type = DRM_MODE_CONNECTOR_WRITEBACK; + KUNIT_EXPECT_NULL(test, amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false)); +} + +/** + * dm_test_highest_refresh_cached_base - Test cached freesync_vid_base is returned + * @test: The KUnit test context + */ +static void dm_test_highest_refresh_cached_base(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA; + aconnector->freesync_vid_base.clock = 148500; + + KUNIT_EXPECT_PTR_EQ(test, amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false), + &aconnector->freesync_vid_base); +} + +/** + * dm_test_highest_refresh_preferred_mode - Test preferred mode is selected + * @test: The KUnit test context + */ +static void dm_test_highest_refresh_preferred_mode(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct drm_display_mode *mode; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + mode = kunit_kzalloc(test, sizeof(*mode), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, mode); + + aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA; + INIT_LIST_HEAD(&aconnector->base.modes); + + mode->type = DRM_MODE_TYPE_PREFERRED; + mode->clock = 148500; + mode->hdisplay = 1920; + mode->vdisplay = 1080; + mode->htotal = 2200; + mode->vtotal = 1125; + list_add_tail(&mode->head, &aconnector->base.modes); + + KUNIT_EXPECT_PTR_EQ(test, amdgpu_dm_get_highest_refresh_rate_mode(aconnector, false), + mode); +} + +/* Tests for amdgpu_dm_is_freesync_video_mode() */ + +/** + * dm_test_is_freesync_video_mode_null_mode - Test NULL mode returns false + * @test: The KUnit test context + */ +static void dm_test_is_freesync_video_mode_null_mode(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA; + aconnector->freesync_vid_base.clock = 148500; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_is_freesync_video_mode(NULL, aconnector)); +} + +/** + * dm_test_is_freesync_video_mode_match - Test matching mode returns true + * @test: The KUnit test context + */ +static void dm_test_is_freesync_video_mode_match(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct drm_display_mode candidate = {}; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + /* Cached high mode acts as reference */ + aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA; + aconnector->freesync_vid_base.clock = 148500; + aconnector->freesync_vid_base.hdisplay = 1920; + aconnector->freesync_vid_base.vdisplay = 1080; + aconnector->freesync_vid_base.hsync_start = 2008; + aconnector->freesync_vid_base.hsync_end = 2052; + aconnector->freesync_vid_base.htotal = 2200; + aconnector->freesync_vid_base.vsync_start = 1084; + aconnector->freesync_vid_base.vsync_end = 1089; + aconnector->freesync_vid_base.vtotal = 1125; + + candidate.clock = 148500; + candidate.hdisplay = 1920; + candidate.vdisplay = 1080; + candidate.hsync_start = 2008; + candidate.hsync_end = 2052; + candidate.htotal = 2200; + candidate.vsync_start = 1084; + candidate.vsync_end = 1089; + candidate.vtotal = 1125; + + KUNIT_EXPECT_TRUE(test, amdgpu_dm_is_freesync_video_mode(&candidate, aconnector)); +} + +/** + * dm_test_is_freesync_video_mode_no_match - Test mismatched mode returns false + * @test: The KUnit test context + */ +static void dm_test_is_freesync_video_mode_no_match(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct drm_display_mode candidate = {}; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + aconnector->base.connector_type = DRM_MODE_CONNECTOR_HDMIA; + aconnector->freesync_vid_base.clock = 148500; + aconnector->freesync_vid_base.hdisplay = 1920; + aconnector->freesync_vid_base.vdisplay = 1080; + aconnector->freesync_vid_base.htotal = 2200; + aconnector->freesync_vid_base.vtotal = 1125; + + /* Different resolution → not a freesync video mode */ + candidate.clock = 148500; + candidate.hdisplay = 1280; + candidate.vdisplay = 720; + candidate.htotal = 1650; + candidate.vtotal = 750; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_is_freesync_video_mode(&candidate, aconnector)); +} + +static struct kunit_case amdgpu_dm_connector_tests[] = { + /* get_subconnector_type */ + KUNIT_CASE(dm_test_subconnector_type_none), + KUNIT_CASE(dm_test_subconnector_type_vga), + KUNIT_CASE(dm_test_subconnector_type_dvi_converter), + KUNIT_CASE(dm_test_subconnector_type_dvi_dongle), + KUNIT_CASE(dm_test_subconnector_type_hdmi_converter), + KUNIT_CASE(dm_test_subconnector_type_hdmi_dongle), + KUNIT_CASE(dm_test_subconnector_type_mismatched), + KUNIT_CASE(dm_test_subconnector_type_default_unknown), + /* get_output_content_type */ + KUNIT_CASE(dm_test_content_type_no_data), + KUNIT_CASE(dm_test_content_type_graphics), + KUNIT_CASE(dm_test_content_type_photo), + KUNIT_CASE(dm_test_content_type_cinema), + KUNIT_CASE(dm_test_content_type_game), + KUNIT_CASE(dm_test_content_type_unknown_defaults_no_data), + /* adjust_colour_depth_from_display_info */ + KUNIT_CASE(dm_test_adjust_colour_depth_fits_at_888), + KUNIT_CASE(dm_test_adjust_colour_depth_reduces_to_888), + KUNIT_CASE(dm_test_adjust_colour_depth_10bpc_passes), + KUNIT_CASE(dm_test_adjust_colour_depth_420_halves_clk), + KUNIT_CASE(dm_test_adjust_colour_depth_reduces_12bpc_to_10bpc), + KUNIT_CASE(dm_test_adjust_colour_depth_16bpc_no_fallback), + KUNIT_CASE(dm_test_adjust_colour_depth_none_fits), + KUNIT_CASE(dm_test_adjust_colour_depth_invalid_depth), + /* amdgpu_dm_get_output_color_space */ + KUNIT_CASE(dm_test_output_color_space_default_rgb_full), + KUNIT_CASE(dm_test_output_color_space_default_rgb_limited), + KUNIT_CASE(dm_test_output_color_space_default_ycbcr709), + KUNIT_CASE(dm_test_output_color_space_default_ycbcr601_limited), + KUNIT_CASE(dm_test_output_color_space_bt601_y_only), + KUNIT_CASE(dm_test_output_color_space_bt601), + KUNIT_CASE(dm_test_output_color_space_bt709), + KUNIT_CASE(dm_test_output_color_space_bt709_y_only), + KUNIT_CASE(dm_test_output_color_space_oprgb), + KUNIT_CASE(dm_test_output_color_space_bt2020_rgb), + KUNIT_CASE(dm_test_output_color_space_bt2020_ycc), + /* Tests for amdgpu_dm_convert_dc_color_depth_into_bpc */ + KUNIT_CASE(dm_test_convert_color_depth_bpc_mappings), + KUNIT_CASE(dm_test_convert_color_depth_bpc_unknown), + /* amdgpu_dm_convert_color_depth_from_display_info */ + KUNIT_CASE(dm_test_color_depth_from_info_bpc8), + KUNIT_CASE(dm_test_color_depth_from_info_bpc10), + KUNIT_CASE(dm_test_color_depth_from_info_zero_bpc_defaults_888), + KUNIT_CASE(dm_test_color_depth_from_info_requested_bpc_caps), + KUNIT_CASE(dm_test_color_depth_from_info_y420_default), + KUNIT_CASE(dm_test_color_depth_from_info_y420_10bpc), + KUNIT_CASE(dm_test_color_depth_from_info_y420_12bpc), + KUNIT_CASE(dm_test_color_depth_from_info_y420_16bpc), + KUNIT_CASE(dm_test_color_depth_from_info_requested_odd_bpc), + KUNIT_CASE(dm_test_color_depth_from_info_unsupported_bpc), + /* to_drm_connector_type */ + KUNIT_CASE(dm_test_to_connector_type_hdmi), + KUNIT_CASE(dm_test_to_connector_type_edp), + KUNIT_CASE(dm_test_to_connector_type_lvds), + KUNIT_CASE(dm_test_to_connector_type_rgb), + KUNIT_CASE(dm_test_to_connector_type_dp), + KUNIT_CASE(dm_test_to_connector_type_dp_mst), + KUNIT_CASE(dm_test_to_connector_type_dvi_dvii), + KUNIT_CASE(dm_test_to_connector_type_dual_link_dvii), + KUNIT_CASE(dm_test_to_connector_type_dvi_dvid), + KUNIT_CASE(dm_test_to_connector_type_virtual), + KUNIT_CASE(dm_test_to_connector_type_unknown), + /* is_duplicate_mode */ + KUNIT_CASE(dm_test_is_duplicate_mode_empty_list), + KUNIT_CASE(dm_test_is_duplicate_mode_match), + KUNIT_CASE(dm_test_is_duplicate_mode_no_match), + KUNIT_CASE(dm_test_is_duplicate_mode_same_size_different_clock), + /* amdgpu_dm_get_encoder_crtc_mask */ + KUNIT_CASE(dm_test_encoder_crtc_mask_1), + KUNIT_CASE(dm_test_encoder_crtc_mask_2), + KUNIT_CASE(dm_test_encoder_crtc_mask_3), + KUNIT_CASE(dm_test_encoder_crtc_mask_4), + KUNIT_CASE(dm_test_encoder_crtc_mask_5), + KUNIT_CASE(dm_test_encoder_crtc_mask_6), + KUNIT_CASE(dm_test_encoder_crtc_mask_default), + /* get_aspect_ratio */ + KUNIT_CASE(dm_test_aspect_ratio_no_data), + KUNIT_CASE(dm_test_aspect_ratio_4_3), + KUNIT_CASE(dm_test_aspect_ratio_16_9), + KUNIT_CASE(dm_test_aspect_ratio_64_27), + KUNIT_CASE(dm_test_aspect_ratio_256_135), + /* decide_crtc_timing_for_drm_display_mode */ + KUNIT_CASE(dm_test_decide_crtc_timing_scale_enabled), + KUNIT_CASE(dm_test_decide_crtc_timing_matching_mode), + KUNIT_CASE(dm_test_decide_crtc_timing_no_copy), + KUNIT_CASE(dm_test_decide_crtc_timing_no_crtc_clock), + /* amdgpu_dm_connector_funcs_reset */ + KUNIT_CASE(dm_test_funcs_reset_sets_defaults), + KUNIT_CASE(dm_test_funcs_reset_edp_abm_level), + KUNIT_CASE(dm_test_funcs_reset_edp_abm_disabled), + /* amdgpu_dm_connector_atomic_duplicate_state */ + KUNIT_CASE(dm_test_atomic_dup_state_copies_fields), + /* amdgpu_dm_fill_hdr_info_packet */ + KUNIT_CASE(dm_test_fill_hdr_null_metadata), + KUNIT_CASE(dm_test_fill_hdr_zeroes_output), + /* amdgpu_dm_connector_atomic_set_property */ + KUNIT_CASE(dm_test_set_property_scaling_center), + KUNIT_CASE(dm_test_set_property_scaling_aspect), + KUNIT_CASE(dm_test_set_property_scaling_fullscreen), + KUNIT_CASE(dm_test_set_property_scaling_none), + KUNIT_CASE(dm_test_set_property_scaling_unchanged), + KUNIT_CASE(dm_test_set_property_underscan_hborder), + KUNIT_CASE(dm_test_set_property_underscan_vborder), + KUNIT_CASE(dm_test_set_property_underscan_enable), + KUNIT_CASE(dm_test_set_property_abm_sysfs_control), + KUNIT_CASE(dm_test_set_property_abm_level_off), + KUNIT_CASE(dm_test_set_property_abm_level_value), + KUNIT_CASE(dm_test_set_property_unknown), + /* amdgpu_dm_connector_atomic_get_property */ + KUNIT_CASE(dm_test_get_property_scaling_center), + KUNIT_CASE(dm_test_get_property_scaling_aspect), + KUNIT_CASE(dm_test_get_property_scaling_full), + KUNIT_CASE(dm_test_get_property_scaling_off), + KUNIT_CASE(dm_test_get_property_underscan_borders), + KUNIT_CASE(dm_test_get_property_abm_sysfs_allowed), + KUNIT_CASE(dm_test_get_property_abm_level), + KUNIT_CASE(dm_test_get_property_abm_disabled_zero), + KUNIT_CASE(dm_test_get_property_unknown), + /* amdgpu_dm_get_highest_refresh_rate_mode */ + KUNIT_CASE(dm_test_highest_refresh_writeback_null), + KUNIT_CASE(dm_test_highest_refresh_cached_base), + KUNIT_CASE(dm_test_highest_refresh_preferred_mode), + /* amdgpu_dm_is_freesync_video_mode */ + KUNIT_CASE(dm_test_is_freesync_video_mode_null_mode), + KUNIT_CASE(dm_test_is_freesync_video_mode_match), + KUNIT_CASE(dm_test_is_freesync_video_mode_no_match), + {} +}; + +static struct kunit_suite amdgpu_dm_connector_test_suite = { + .name = "amdgpu_dm_connector", + .test_cases = amdgpu_dm_connector_tests, +}; + +kunit_test_suite(amdgpu_dm_connector_test_suite); + +MODULE_AUTHOR("AMD"); +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_connector"); +MODULE_LICENSE("Dual MIT/GPL"); -- cgit v1.2.3 From 55d6cc7dae145e52b7cb804344d12b1e113d4876 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Thu, 30 Apr 2026 15:00:51 -0600 Subject: drm/amd/display: Add KUnit tests for amdgpu_dm_irq Add KUnit tests for helper functions, IRQ table management paths, and DRM mock-backed CRTC lookup in amdgpu_dm_irq.c. Tests cover: - amdgpu_dm_hpd_to_dal_irq_source(): all HPD types 1-6, AMDGPU_HPD_NONE, and out-of-range values - are_sinks_equal(): NULL inputs, signal mismatch, EDID length mismatch, EDID data mismatch, identical sinks, zero-length EDID, full-length identical EDID, and a single trailing-byte difference - dmub_notification_type_str(): notification type mappings that are always built, plus the unknown/default case - amdgpu_dm_irq_init(): low/high handler list initialization - amdgpu_dm_irq_register_interrupt(): NULL input rejection, invalid context/source rejection, low/high handler insertion, multiple handlers on one source, and the same handler registered in both low and high contexts - amdgpu_dm_irq_unregister_interrupt(): invalid source and NULL handler rejection, removal of registered low/high handlers, and the handler-not-found path - amdgpu_dm_irq_fini(): cleanup of registered low/high handlers and the empty-table case - amdgpu_dm_get_crtc_by_otg_inst(): DRM mock CRTC list match, no-match, and empty-list paths Assisted-by: Copilot:Claude-Opus-4 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c | 15 +- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h | 8 + .../gpu/drm/amd/display/amdgpu_dm/tests/Makefile | 1 + .../display/amdgpu_dm/tests/amdgpu_dm_irq_test.c | 934 +++++++++++++++++++++ 4 files changed, 955 insertions(+), 3 deletions(-) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c index 36c0177f5eb0..0759c1d92b61 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c @@ -33,6 +33,7 @@ #include "amdgpu_display.h" #include "amdgpu_dm.h" #include "amdgpu_dm_irq.h" +#include "amdgpu_dm_kunit_helpers.h" #include "amdgpu_dm_crtc.h" #include "amdgpu_dm_hdcp.h" #include "amdgpu_dm_mst_types.h" @@ -372,6 +373,7 @@ void *amdgpu_dm_irq_register_interrupt(struct amdgpu_device *adev, return handler_data; } +EXPORT_IF_KUNIT(amdgpu_dm_irq_register_interrupt); /** * amdgpu_dm_irq_unregister_interrupt() - Remove a handler from the DM IRQ table @@ -416,6 +418,7 @@ void amdgpu_dm_irq_unregister_interrupt(struct amdgpu_device *adev, ih, irq_source); } } +EXPORT_IF_KUNIT(amdgpu_dm_irq_unregister_interrupt); /** * amdgpu_dm_irq_init() - Initialize DM IRQ management @@ -450,6 +453,7 @@ int amdgpu_dm_irq_init(struct amdgpu_device *adev) return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_irq_init); /** * amdgpu_dm_irq_fini() - Tear down DM IRQ management @@ -488,6 +492,7 @@ void amdgpu_dm_irq_fini(struct amdgpu_device *adev) /* Deallocate handlers from the table. */ unregister_all_irq_handlers(adev); } +EXPORT_IF_KUNIT(amdgpu_dm_irq_fini); void amdgpu_dm_irq_suspend(struct amdgpu_device *adev) { @@ -690,7 +695,7 @@ static int amdgpu_dm_irq_handler(struct amdgpu_device *adev, return 0; } -static enum dc_irq_source amdgpu_dm_hpd_to_dal_irq_source(unsigned int type) +STATIC_IFN_KUNIT enum dc_irq_source amdgpu_dm_hpd_to_dal_irq_source(unsigned int type) { switch (type) { case AMDGPU_HPD_1: @@ -709,6 +714,7 @@ static enum dc_irq_source amdgpu_dm_hpd_to_dal_irq_source(unsigned int type) return DC_IRQ_SOURCE_INVALID; } } +EXPORT_IF_KUNIT(amdgpu_dm_hpd_to_dal_irq_source); static int amdgpu_dm_set_hpd_irq_state(struct amdgpu_device *adev, struct amdgpu_irq_src *source, @@ -1192,7 +1198,7 @@ void amdgpu_dm_hpd_rx_irq_work_suspend(struct amdgpu_display_manager *dm) } } -static bool are_sinks_equal(const struct dc_sink *sink1, const struct dc_sink *sink2) +STATIC_IFN_KUNIT bool are_sinks_equal(const struct dc_sink *sink1, const struct dc_sink *sink2) { if (!sink1 || !sink2) return false; @@ -1207,6 +1213,7 @@ static bool are_sinks_equal(const struct dc_sink *sink1, const struct dc_sink *s return false; return true; } +EXPORT_IF_KUNIT(are_sinks_equal); /** @@ -1692,6 +1699,7 @@ amdgpu_dm_get_crtc_by_otg_inst(struct amdgpu_device *adev, return NULL; } +EXPORT_IF_KUNIT(amdgpu_dm_get_crtc_by_otg_inst); /** * dm_pflip_high_irq() - Handle pageflip interrupt @@ -2067,7 +2075,7 @@ static void dm_handle_hpd_work(struct work_struct *work) } -static const char *dmub_notification_type_str(enum dmub_notification_type e) +STATIC_IFN_KUNIT const char *dmub_notification_type_str(enum dmub_notification_type e) { switch (e) { case DMUB_NOTIFICATION_NO_DATA: @@ -2090,6 +2098,7 @@ static const char *dmub_notification_type_str(enum dmub_notification_type e) return ""; } } +EXPORT_IF_KUNIT(dmub_notification_type_str); #define DMUB_TRACE_MAX_READ 64 /** diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h index ba6968f5626f..bccb5d354a9f 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h @@ -30,8 +30,10 @@ struct amdgpu_device; struct amdgpu_crtc; struct amdgpu_display_manager; +struct dc_sink; struct hpd_rx_irq_offload_work_queue; struct work_struct; +enum dmub_notification_type; /* * Display Manager IRQ-related interfaces (for use by DAL). @@ -120,4 +122,10 @@ int amdgpu_dm_dce110_register_irq_handlers(struct amdgpu_device *adev); int amdgpu_dm_dcn10_register_irq_handlers(struct amdgpu_device *adev); int amdgpu_dm_register_outbox_irq_handlers(struct amdgpu_device *adev); +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +enum dc_irq_source amdgpu_dm_hpd_to_dal_irq_source(unsigned int type); +bool are_sinks_equal(const struct dc_sink *sink1, const struct dc_sink *sink2); +const char *dmub_notification_type_str(enum dmub_notification_type e); +#endif + #endif /* __AMDGPU_DM_IRQ_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile index 422eef0bfe49..583604914753 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile @@ -22,6 +22,7 @@ obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_dmub_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_psr_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_replay_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_ism_test.o +obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_irq_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_wb_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_mst_types_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_pp_smu_test.o diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c new file mode 100644 index 000000000000..525caa0b1f6a --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c @@ -0,0 +1,934 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit tests for amdgpu_dm_irq.c + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include +#include + +#include "dc.h" +#include "amdgpu.h" +#include "amdgpu_mode.h" +#include "amdgpu_dm.h" +#include "amdgpu_dm_irq.h" +#include "dmub/dmub_srv.h" + +static void dm_test_irq_handler(void *arg) +{ +} + +static void dm_test_irq_handler_alt(void *arg) +{ +} + +static void dm_test_crtc_list_del(void *data) +{ + struct amdgpu_crtc *acrtc = data; + + list_del_init(&acrtc->base.head); +} + +/* Tests for amdgpu_dm_hpd_to_dal_irq_source() */ + +/** + * dm_test_hpd_to_dal_irq_source_hpd1 - Test Hpd to dal irq source hpd1 + * @test: The KUnit test context + */ +static void dm_test_hpd_to_dal_irq_source_hpd1(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_hpd_to_dal_irq_source(AMDGPU_HPD_1), + (int)DC_IRQ_SOURCE_HPD1); +} + +/** + * dm_test_hpd_to_dal_irq_source_hpd2 - Test Hpd to dal irq source hpd2 + * @test: The KUnit test context + */ +static void dm_test_hpd_to_dal_irq_source_hpd2(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_hpd_to_dal_irq_source(AMDGPU_HPD_2), + (int)DC_IRQ_SOURCE_HPD2); +} + +/** + * dm_test_hpd_to_dal_irq_source_hpd3 - Test Hpd to dal irq source hpd3 + * @test: The KUnit test context + */ +static void dm_test_hpd_to_dal_irq_source_hpd3(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_hpd_to_dal_irq_source(AMDGPU_HPD_3), + (int)DC_IRQ_SOURCE_HPD3); +} + +/** + * dm_test_hpd_to_dal_irq_source_hpd4 - Test Hpd to dal irq source hpd4 + * @test: The KUnit test context + */ +static void dm_test_hpd_to_dal_irq_source_hpd4(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_hpd_to_dal_irq_source(AMDGPU_HPD_4), + (int)DC_IRQ_SOURCE_HPD4); +} + +/** + * dm_test_hpd_to_dal_irq_source_hpd5 - Test Hpd to dal irq source hpd5 + * @test: The KUnit test context + */ +static void dm_test_hpd_to_dal_irq_source_hpd5(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_hpd_to_dal_irq_source(AMDGPU_HPD_5), + (int)DC_IRQ_SOURCE_HPD5); +} + +/** + * dm_test_hpd_to_dal_irq_source_hpd6 - Test Hpd to dal irq source hpd6 + * @test: The KUnit test context + */ +static void dm_test_hpd_to_dal_irq_source_hpd6(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_hpd_to_dal_irq_source(AMDGPU_HPD_6), + (int)DC_IRQ_SOURCE_HPD6); +} + +/** + * dm_test_hpd_to_dal_irq_source_invalid - Test Hpd to dal irq source invalid + * @test: The KUnit test context + */ +static void dm_test_hpd_to_dal_irq_source_invalid(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_hpd_to_dal_irq_source(AMDGPU_HPD_NONE), + (int)DC_IRQ_SOURCE_INVALID); +} + +/** + * dm_test_hpd_to_dal_irq_source_out_of_range - Test Hpd to dal irq source out of range + * @test: The KUnit test context + */ +static void dm_test_hpd_to_dal_irq_source_out_of_range(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_hpd_to_dal_irq_source(99), + (int)DC_IRQ_SOURCE_INVALID); +} + +/* Tests for are_sinks_equal() */ + +/** + * dm_test_are_sinks_equal_both_null - Test Are sinks equal both null + * @test: The KUnit test context + */ +static void dm_test_are_sinks_equal_both_null(struct kunit *test) +{ + KUNIT_EXPECT_FALSE(test, are_sinks_equal(NULL, NULL)); +} + +/** + * dm_test_are_sinks_equal_first_null - Test Are sinks equal first null + * @test: The KUnit test context + */ +static void dm_test_are_sinks_equal_first_null(struct kunit *test) +{ + struct dc_sink *sink2; + + sink2 = kunit_kzalloc(test, sizeof(*sink2), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink2); + + KUNIT_EXPECT_FALSE(test, are_sinks_equal(NULL, sink2)); +} + +/** + * dm_test_are_sinks_equal_second_null - Test Are sinks equal second null + * @test: The KUnit test context + */ +static void dm_test_are_sinks_equal_second_null(struct kunit *test) +{ + struct dc_sink *sink1; + + sink1 = kunit_kzalloc(test, sizeof(*sink1), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink1); + + KUNIT_EXPECT_FALSE(test, are_sinks_equal(sink1, NULL)); +} + +/** + * dm_test_are_sinks_equal_different_signal - Test Are sinks equal different signal + * @test: The KUnit test context + */ +static void dm_test_are_sinks_equal_different_signal(struct kunit *test) +{ + struct dc_sink *sink1, *sink2; + + sink1 = kunit_kzalloc(test, sizeof(*sink1), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink1); + sink2 = kunit_kzalloc(test, sizeof(*sink2), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink2); + + sink1->sink_signal = SIGNAL_TYPE_HDMI_TYPE_A; + sink2->sink_signal = SIGNAL_TYPE_DISPLAY_PORT; + + KUNIT_EXPECT_FALSE(test, are_sinks_equal(sink1, sink2)); +} + +/** + * dm_test_are_sinks_equal_different_edid_length - Test Are sinks equal different edid length + * @test: The KUnit test context + */ +static void dm_test_are_sinks_equal_different_edid_length(struct kunit *test) +{ + struct dc_sink *sink1, *sink2; + + sink1 = kunit_kzalloc(test, sizeof(*sink1), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink1); + sink2 = kunit_kzalloc(test, sizeof(*sink2), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink2); + + sink1->sink_signal = SIGNAL_TYPE_HDMI_TYPE_A; + sink2->sink_signal = SIGNAL_TYPE_HDMI_TYPE_A; + sink1->dc_edid.length = 128; + sink2->dc_edid.length = 256; + + KUNIT_EXPECT_FALSE(test, are_sinks_equal(sink1, sink2)); +} + +/** + * dm_test_are_sinks_equal_different_edid_data - Test Are sinks equal different edid data + * @test: The KUnit test context + */ +static void dm_test_are_sinks_equal_different_edid_data(struct kunit *test) +{ + struct dc_sink *sink1, *sink2; + + sink1 = kunit_kzalloc(test, sizeof(*sink1), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink1); + sink2 = kunit_kzalloc(test, sizeof(*sink2), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink2); + + sink1->sink_signal = SIGNAL_TYPE_HDMI_TYPE_A; + sink2->sink_signal = SIGNAL_TYPE_HDMI_TYPE_A; + sink1->dc_edid.length = 4; + sink2->dc_edid.length = 4; + memset(sink1->dc_edid.raw_edid, 0xAA, 4); + memset(sink2->dc_edid.raw_edid, 0xBB, 4); + + KUNIT_EXPECT_FALSE(test, are_sinks_equal(sink1, sink2)); +} + +/** + * dm_test_are_sinks_equal_identical - Test Are sinks equal identical + * @test: The KUnit test context + */ +static void dm_test_are_sinks_equal_identical(struct kunit *test) +{ + struct dc_sink *sink1, *sink2; + + sink1 = kunit_kzalloc(test, sizeof(*sink1), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink1); + sink2 = kunit_kzalloc(test, sizeof(*sink2), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink2); + + sink1->sink_signal = SIGNAL_TYPE_HDMI_TYPE_A; + sink2->sink_signal = SIGNAL_TYPE_HDMI_TYPE_A; + sink1->dc_edid.length = 4; + sink2->dc_edid.length = 4; + memset(sink1->dc_edid.raw_edid, 0xAA, 4); + memset(sink2->dc_edid.raw_edid, 0xAA, 4); + + KUNIT_EXPECT_TRUE(test, are_sinks_equal(sink1, sink2)); +} + +/** + * dm_test_are_sinks_equal_zero_length - Test Are sinks equal zero length + * @test: The KUnit test context + */ +static void dm_test_are_sinks_equal_zero_length(struct kunit *test) +{ + struct dc_sink *sink1, *sink2; + + sink1 = kunit_kzalloc(test, sizeof(*sink1), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink1); + sink2 = kunit_kzalloc(test, sizeof(*sink2), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink2); + + sink1->sink_signal = SIGNAL_TYPE_DISPLAY_PORT; + sink2->sink_signal = SIGNAL_TYPE_DISPLAY_PORT; + sink1->dc_edid.length = 0; + sink2->dc_edid.length = 0; + + KUNIT_EXPECT_TRUE(test, are_sinks_equal(sink1, sink2)); +} + +/** + * dm_test_are_sinks_equal_full_edid_identical - Test Are sinks equal full edid identical + * @test: The KUnit test context + */ +static void dm_test_are_sinks_equal_full_edid_identical(struct kunit *test) +{ + struct dc_sink *sink1, *sink2; + + sink1 = kunit_kzalloc(test, sizeof(*sink1), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink1); + sink2 = kunit_kzalloc(test, sizeof(*sink2), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink2); + + sink1->sink_signal = SIGNAL_TYPE_HDMI_TYPE_A; + sink2->sink_signal = SIGNAL_TYPE_HDMI_TYPE_A; + sink1->dc_edid.length = 128; + sink2->dc_edid.length = 128; + memset(sink1->dc_edid.raw_edid, 0x5A, 128); + memset(sink2->dc_edid.raw_edid, 0x5A, 128); + + KUNIT_EXPECT_TRUE(test, are_sinks_equal(sink1, sink2)); +} + +/** + * dm_test_are_sinks_equal_full_edid_last_byte_differs - Test Are sinks equal last byte differs + * @test: The KUnit test context + */ +static void dm_test_are_sinks_equal_full_edid_last_byte_differs(struct kunit *test) +{ + struct dc_sink *sink1, *sink2; + + sink1 = kunit_kzalloc(test, sizeof(*sink1), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink1); + sink2 = kunit_kzalloc(test, sizeof(*sink2), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, sink2); + + sink1->sink_signal = SIGNAL_TYPE_HDMI_TYPE_A; + sink2->sink_signal = SIGNAL_TYPE_HDMI_TYPE_A; + sink1->dc_edid.length = 128; + sink2->dc_edid.length = 128; + memset(sink1->dc_edid.raw_edid, 0x5A, 128); + memset(sink2->dc_edid.raw_edid, 0x5A, 128); + sink2->dc_edid.raw_edid[127] = 0x5B; + + KUNIT_EXPECT_FALSE(test, are_sinks_equal(sink1, sink2)); +} + +/* Tests for dmub_notification_type_str() */ + +/** + * dm_test_notification_str_no_data - Test Notification str no data + * @test: The KUnit test context + */ +static void dm_test_notification_str_no_data(struct kunit *test) +{ + KUNIT_EXPECT_STREQ(test, dmub_notification_type_str(DMUB_NOTIFICATION_NO_DATA), "NO_DATA"); +} + +/** + * dm_test_notification_str_aux_reply - Test Notification str aux reply + * @test: The KUnit test context + */ +static void dm_test_notification_str_aux_reply(struct kunit *test) +{ + KUNIT_EXPECT_STREQ(test, dmub_notification_type_str(DMUB_NOTIFICATION_AUX_REPLY), "AUX_REPLY"); +} + +/** + * dm_test_notification_str_hpd - Test Notification str hpd + * @test: The KUnit test context + */ +static void dm_test_notification_str_hpd(struct kunit *test) +{ + KUNIT_EXPECT_STREQ(test, dmub_notification_type_str(DMUB_NOTIFICATION_HPD), "HPD"); +} + +/** + * dm_test_notification_str_hpd_irq - Test Notification str hpd irq + * @test: The KUnit test context + */ +static void dm_test_notification_str_hpd_irq(struct kunit *test) +{ + KUNIT_EXPECT_STREQ(test, dmub_notification_type_str(DMUB_NOTIFICATION_HPD_IRQ), "HPD_IRQ"); +} + +/** + * dm_test_notification_str_set_config - Test Notification str set config + * @test: The KUnit test context + */ +static void dm_test_notification_str_set_config(struct kunit *test) +{ + KUNIT_EXPECT_STREQ(test, dmub_notification_type_str(DMUB_NOTIFICATION_SET_CONFIG_REPLY), + "SET_CONFIG_REPLY"); +} + +/** + * dm_test_notification_str_dpia - Test Notification str dpia + * @test: The KUnit test context + */ +static void dm_test_notification_str_dpia(struct kunit *test) +{ + KUNIT_EXPECT_STREQ(test, dmub_notification_type_str(DMUB_NOTIFICATION_DPIA_NOTIFICATION), + "DPIA_NOTIFICATION"); +} + +/** + * dm_test_notification_str_hpd_sense - Test Notification str hpd sense + * @test: The KUnit test context + */ +static void dm_test_notification_str_hpd_sense(struct kunit *test) +{ + KUNIT_EXPECT_STREQ(test, dmub_notification_type_str(DMUB_NOTIFICATION_HPD_SENSE_NOTIFY), + "HPD_SENSE_NOTIFY"); +} + +/** + * dm_test_notification_str_fused_io - Test Notification str fused io + * @test: The KUnit test context + */ +static void dm_test_notification_str_fused_io(struct kunit *test) +{ + KUNIT_EXPECT_STREQ(test, dmub_notification_type_str(DMUB_NOTIFICATION_FUSED_IO), + "FUSED_IO"); +} + +/** + * dm_test_notification_str_unknown - Test Notification str unknown + * @test: The KUnit test context + */ +static void dm_test_notification_str_unknown(struct kunit *test) +{ + KUNIT_EXPECT_STREQ(test, dmub_notification_type_str(DMUB_NOTIFICATION_MAX), ""); +} + +/* Tests for amdgpu_dm_irq_init() */ + +/** + * dm_test_irq_init_initializes_lists - Test irq init initializes list heads + * @test: The KUnit test context + */ +static void dm_test_irq_init_initializes_lists(struct kunit *test) +{ + struct amdgpu_device *adev; + int src; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + for (src = 0; src < DAL_IRQ_SOURCES_NUMBER; src++) { + KUNIT_EXPECT_TRUE(test, + list_empty(&adev->dm.irq_handler_list_low_tab[src])); + KUNIT_EXPECT_TRUE(test, + list_empty(&adev->dm.irq_handler_list_high_tab[src])); + } +} + +/* Tests for amdgpu_dm_irq_register_interrupt() */ + +/** + * dm_test_irq_register_rejects_null_params - Test register rejects null params + * @test: The KUnit test context + */ +static void dm_test_irq_register_rejects_null_params(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_interrupt_params int_params = { 0 }; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; + int_params.irq_source = DC_IRQ_SOURCE_HPD1; + + KUNIT_EXPECT_NULL(test, + amdgpu_dm_irq_register_interrupt(adev, NULL, + dm_test_irq_handler, NULL)); + KUNIT_EXPECT_NULL(test, + amdgpu_dm_irq_register_interrupt(adev, &int_params, NULL, NULL)); +} + +/** + * dm_test_irq_register_rejects_invalid_context - Test register rejects context + * @test: The KUnit test context + */ +static void dm_test_irq_register_rejects_invalid_context(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_interrupt_params int_params = { 0 }; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + int_params.int_context = INTERRUPT_CONTEXT_NUMBER; + int_params.irq_source = DC_IRQ_SOURCE_HPD1; + + KUNIT_EXPECT_NULL(test, + amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler, NULL)); +} + +/** + * dm_test_irq_register_rejects_invalid_source - Test register rejects source + * @test: The KUnit test context + */ +static void dm_test_irq_register_rejects_invalid_source(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_interrupt_params int_params = { 0 }; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; + int_params.irq_source = DC_IRQ_SOURCE_INVALID; + + KUNIT_EXPECT_NULL(test, + amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler, NULL)); +} + +/** + * dm_test_irq_register_adds_low_context_handler - Test register adds low handler + * @test: The KUnit test context + */ +static void dm_test_irq_register_adds_low_context_handler(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_interrupt_params int_params = { 0 }; + void *handler; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; + int_params.irq_source = DC_IRQ_SOURCE_HPD1; + + handler = amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler, adev); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler); + KUNIT_EXPECT_FALSE(test, + list_empty(&adev->dm.irq_handler_list_low_tab[DC_IRQ_SOURCE_HPD1])); + KUNIT_EXPECT_TRUE(test, + list_empty(&adev->dm.irq_handler_list_high_tab[DC_IRQ_SOURCE_HPD1])); + + amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_HPD1, + dm_test_irq_handler); + KUNIT_EXPECT_TRUE(test, + list_empty(&adev->dm.irq_handler_list_low_tab[DC_IRQ_SOURCE_HPD1])); +} + +/** + * dm_test_irq_register_adds_high_context_handler - Test register adds high handler + * @test: The KUnit test context + */ +static void dm_test_irq_register_adds_high_context_handler(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_interrupt_params int_params = { 0 }; + void *handler; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; + int_params.irq_source = DC_IRQ_SOURCE_HPD2; + + handler = amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler, adev); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler); + KUNIT_EXPECT_FALSE(test, + list_empty(&adev->dm.irq_handler_list_high_tab[DC_IRQ_SOURCE_HPD2])); + KUNIT_EXPECT_TRUE(test, + list_empty(&adev->dm.irq_handler_list_low_tab[DC_IRQ_SOURCE_HPD2])); + + amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_HPD2, + dm_test_irq_handler); + KUNIT_EXPECT_TRUE(test, + list_empty(&adev->dm.irq_handler_list_high_tab[DC_IRQ_SOURCE_HPD2])); +} + +/** + * dm_test_irq_register_multiple_handlers - Test register keeps multiple handlers + * @test: The KUnit test context + */ +static void dm_test_irq_register_multiple_handlers(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_interrupt_params int_params = { 0 }; + struct list_head *hnd_list; + void *handler1, *handler2; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; + int_params.irq_source = DC_IRQ_SOURCE_HPD1; + + handler1 = amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler, adev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler1); + handler2 = amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler_alt, adev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler2); + + hnd_list = &adev->dm.irq_handler_list_low_tab[DC_IRQ_SOURCE_HPD1]; + KUNIT_EXPECT_EQ(test, list_count_nodes(hnd_list), 2); + + amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_HPD1, + dm_test_irq_handler); + amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_HPD1, + dm_test_irq_handler_alt); + KUNIT_EXPECT_TRUE(test, list_empty(hnd_list)); +} + +/** + * dm_test_irq_register_separate_contexts - Test register same source in two contexts + * @test: The KUnit test context + */ +static void dm_test_irq_register_separate_contexts(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_interrupt_params int_params = { 0 }; + void *handler; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + int_params.irq_source = DC_IRQ_SOURCE_HPD5; + + int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; + handler = amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler, adev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler); + + int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; + handler = amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler, adev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler); + + KUNIT_EXPECT_FALSE(test, + list_empty(&adev->dm.irq_handler_list_low_tab[DC_IRQ_SOURCE_HPD5])); + KUNIT_EXPECT_FALSE(test, + list_empty(&adev->dm.irq_handler_list_high_tab[DC_IRQ_SOURCE_HPD5])); + + /* + * A single unregister call stops at the first context where the handler + * is found (low context), leaving the high context handler in place. + */ + amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_HPD5, + dm_test_irq_handler); + + KUNIT_EXPECT_TRUE(test, + list_empty(&adev->dm.irq_handler_list_low_tab[DC_IRQ_SOURCE_HPD5])); + KUNIT_EXPECT_FALSE(test, + list_empty(&adev->dm.irq_handler_list_high_tab[DC_IRQ_SOURCE_HPD5])); + + /* A second call removes the remaining high context handler. */ + amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_HPD5, + dm_test_irq_handler); + + KUNIT_EXPECT_TRUE(test, + list_empty(&adev->dm.irq_handler_list_high_tab[DC_IRQ_SOURCE_HPD5])); +} + +/* Tests for amdgpu_dm_irq_unregister_interrupt() */ + +/** + * dm_test_irq_unregister_rejects_invalid_source - Test unregister rejects source + * @test: The KUnit test context + */ +static void dm_test_irq_unregister_rejects_invalid_source(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_INVALID, + dm_test_irq_handler); + + KUNIT_EXPECT_TRUE(test, + list_empty(&adev->dm.irq_handler_list_low_tab[DC_IRQ_SOURCE_HPD1])); + KUNIT_EXPECT_TRUE(test, + list_empty(&adev->dm.irq_handler_list_high_tab[DC_IRQ_SOURCE_HPD1])); +} + +/** + * dm_test_irq_unregister_rejects_null_handler - Test unregister rejects handler + * @test: The KUnit test context + */ +static void dm_test_irq_unregister_rejects_null_handler(struct kunit *test) +{ + struct amdgpu_device *adev; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_HPD1, + DAL_INVALID_IRQ_HANDLER_IDX); + + KUNIT_EXPECT_TRUE(test, + list_empty(&adev->dm.irq_handler_list_low_tab[DC_IRQ_SOURCE_HPD1])); + KUNIT_EXPECT_TRUE(test, + list_empty(&adev->dm.irq_handler_list_high_tab[DC_IRQ_SOURCE_HPD1])); +} + +/** + * dm_test_irq_unregister_handler_not_found - Test unregister keeps unmatched handler + * @test: The KUnit test context + */ +static void dm_test_irq_unregister_handler_not_found(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_interrupt_params int_params = { 0 }; + void *handler; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; + int_params.irq_source = DC_IRQ_SOURCE_HPD1; + handler = amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler, adev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler); + + /* Unregister a handler that was never registered for this source. */ + amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_HPD1, + dm_test_irq_handler_alt); + + /* The originally registered handler must still be present. */ + KUNIT_EXPECT_FALSE(test, + list_empty(&adev->dm.irq_handler_list_low_tab[DC_IRQ_SOURCE_HPD1])); + + amdgpu_dm_irq_unregister_interrupt(adev, DC_IRQ_SOURCE_HPD1, + dm_test_irq_handler); + KUNIT_EXPECT_TRUE(test, + list_empty(&adev->dm.irq_handler_list_low_tab[DC_IRQ_SOURCE_HPD1])); +} + +/* Tests for amdgpu_dm_irq_fini() */ + +/** + * dm_test_irq_fini_removes_registered_handlers - Test fini removes handlers + * @test: The KUnit test context + */ +static void dm_test_irq_fini_removes_registered_handlers(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_interrupt_params int_params = { 0 }; + void *handler; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + int_params.int_context = INTERRUPT_LOW_IRQ_CONTEXT; + int_params.irq_source = DC_IRQ_SOURCE_HPD3; + handler = amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler, adev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler); + + int_params.int_context = INTERRUPT_HIGH_IRQ_CONTEXT; + int_params.irq_source = DC_IRQ_SOURCE_HPD4; + handler = amdgpu_dm_irq_register_interrupt(adev, &int_params, + dm_test_irq_handler, adev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, handler); + + amdgpu_dm_irq_fini(adev); + + KUNIT_EXPECT_TRUE(test, + list_empty(&adev->dm.irq_handler_list_low_tab[DC_IRQ_SOURCE_HPD3])); + KUNIT_EXPECT_TRUE(test, + list_empty(&adev->dm.irq_handler_list_high_tab[DC_IRQ_SOURCE_HPD4])); +} + +/** + * dm_test_irq_fini_on_empty_tables - Test fini on tables with no handlers + * @test: The KUnit test context + */ +static void dm_test_irq_fini_on_empty_tables(struct kunit *test) +{ + struct amdgpu_device *adev; + int src; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); + + amdgpu_dm_irq_fini(adev); + + for (src = 0; src < DAL_IRQ_SOURCES_NUMBER; src++) { + KUNIT_EXPECT_TRUE(test, + list_empty(&adev->dm.irq_handler_list_low_tab[src])); + KUNIT_EXPECT_TRUE(test, + list_empty(&adev->dm.irq_handler_list_high_tab[src])); + } +} + +/* Tests for amdgpu_dm_get_crtc_by_otg_inst() */ + +/** + * dm_test_get_crtc_by_otg_inst_returns_match - Test CRTC lookup by OTG instance + * @test: The KUnit test context + */ +static void dm_test_get_crtc_by_otg_inst_returns_match(struct kunit *test) +{ + struct amdgpu_crtc *acrtc_a, *acrtc_b; + struct amdgpu_device *adev; + struct drm_device *drm; + struct device *dev; + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + drm = __drm_kunit_helper_alloc_drm_device(test, dev, + sizeof(*adev), + offsetof(struct amdgpu_device, ddev), + DRIVER_MODESET); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + adev = drm_to_adev(drm); + + acrtc_a = kunit_kzalloc(test, sizeof(*acrtc_a), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc_a); + acrtc_b = kunit_kzalloc(test, sizeof(*acrtc_b), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc_b); + + INIT_LIST_HEAD(&acrtc_a->base.head); + INIT_LIST_HEAD(&acrtc_b->base.head); + acrtc_a->otg_inst = 1; + acrtc_b->otg_inst = 3; + + list_add_tail(&acrtc_a->base.head, &drm->mode_config.crtc_list); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_crtc_list_del, + acrtc_a), 0); + list_add_tail(&acrtc_b->base.head, &drm->mode_config.crtc_list); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_crtc_list_del, + acrtc_b), 0); + + KUNIT_EXPECT_PTR_EQ(test, amdgpu_dm_get_crtc_by_otg_inst(adev, 3), acrtc_b); +} + +/** + * dm_test_get_crtc_by_otg_inst_returns_null - Test CRTC lookup misses unknown OTG + * @test: The KUnit test context + */ +static void dm_test_get_crtc_by_otg_inst_returns_null(struct kunit *test) +{ + struct amdgpu_crtc *acrtc; + struct amdgpu_device *adev; + struct drm_device *drm; + struct device *dev; + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + drm = __drm_kunit_helper_alloc_drm_device(test, dev, + sizeof(*adev), + offsetof(struct amdgpu_device, ddev), + DRIVER_MODESET); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + adev = drm_to_adev(drm); + + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); + + INIT_LIST_HEAD(&acrtc->base.head); + acrtc->otg_inst = 2; + + list_add_tail(&acrtc->base.head, &drm->mode_config.crtc_list); + KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_crtc_list_del, + acrtc), 0); + + KUNIT_EXPECT_NULL(test, amdgpu_dm_get_crtc_by_otg_inst(adev, 5)); +} + +/** + * dm_test_get_crtc_by_otg_inst_empty_list - Test CRTC lookup on empty CRTC list + * @test: The KUnit test context + */ +static void dm_test_get_crtc_by_otg_inst_empty_list(struct kunit *test) +{ + struct amdgpu_device *adev; + struct drm_device *drm; + struct device *dev; + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + drm = __drm_kunit_helper_alloc_drm_device(test, dev, + sizeof(*adev), + offsetof(struct amdgpu_device, ddev), + DRIVER_MODESET); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + adev = drm_to_adev(drm); + + KUNIT_EXPECT_NULL(test, amdgpu_dm_get_crtc_by_otg_inst(adev, 0)); +} + +static struct kunit_case amdgpu_dm_irq_tests[] = { + /* amdgpu_dm_hpd_to_dal_irq_source */ + KUNIT_CASE(dm_test_hpd_to_dal_irq_source_hpd1), + KUNIT_CASE(dm_test_hpd_to_dal_irq_source_hpd2), + KUNIT_CASE(dm_test_hpd_to_dal_irq_source_hpd3), + KUNIT_CASE(dm_test_hpd_to_dal_irq_source_hpd4), + KUNIT_CASE(dm_test_hpd_to_dal_irq_source_hpd5), + KUNIT_CASE(dm_test_hpd_to_dal_irq_source_hpd6), + KUNIT_CASE(dm_test_hpd_to_dal_irq_source_invalid), + KUNIT_CASE(dm_test_hpd_to_dal_irq_source_out_of_range), + /* are_sinks_equal */ + KUNIT_CASE(dm_test_are_sinks_equal_both_null), + KUNIT_CASE(dm_test_are_sinks_equal_first_null), + KUNIT_CASE(dm_test_are_sinks_equal_second_null), + KUNIT_CASE(dm_test_are_sinks_equal_different_signal), + KUNIT_CASE(dm_test_are_sinks_equal_different_edid_length), + KUNIT_CASE(dm_test_are_sinks_equal_different_edid_data), + KUNIT_CASE(dm_test_are_sinks_equal_identical), + KUNIT_CASE(dm_test_are_sinks_equal_zero_length), + KUNIT_CASE(dm_test_are_sinks_equal_full_edid_identical), + KUNIT_CASE(dm_test_are_sinks_equal_full_edid_last_byte_differs), + /* dmub_notification_type_str */ + KUNIT_CASE(dm_test_notification_str_no_data), + KUNIT_CASE(dm_test_notification_str_aux_reply), + KUNIT_CASE(dm_test_notification_str_hpd), + KUNIT_CASE(dm_test_notification_str_hpd_irq), + KUNIT_CASE(dm_test_notification_str_set_config), + KUNIT_CASE(dm_test_notification_str_dpia), + KUNIT_CASE(dm_test_notification_str_hpd_sense), + KUNIT_CASE(dm_test_notification_str_fused_io), + KUNIT_CASE(dm_test_notification_str_unknown), + /* amdgpu_dm_irq_init */ + KUNIT_CASE(dm_test_irq_init_initializes_lists), + /* amdgpu_dm_irq_register_interrupt */ + KUNIT_CASE(dm_test_irq_register_rejects_null_params), + KUNIT_CASE(dm_test_irq_register_rejects_invalid_context), + KUNIT_CASE(dm_test_irq_register_rejects_invalid_source), + KUNIT_CASE(dm_test_irq_register_adds_low_context_handler), + KUNIT_CASE(dm_test_irq_register_adds_high_context_handler), + KUNIT_CASE(dm_test_irq_register_multiple_handlers), + KUNIT_CASE(dm_test_irq_register_separate_contexts), + /* amdgpu_dm_irq_unregister_interrupt */ + KUNIT_CASE(dm_test_irq_unregister_rejects_invalid_source), + KUNIT_CASE(dm_test_irq_unregister_rejects_null_handler), + KUNIT_CASE(dm_test_irq_unregister_handler_not_found), + /* amdgpu_dm_irq_fini */ + KUNIT_CASE(dm_test_irq_fini_removes_registered_handlers), + KUNIT_CASE(dm_test_irq_fini_on_empty_tables), + /* amdgpu_dm_get_crtc_by_otg_inst */ + KUNIT_CASE(dm_test_get_crtc_by_otg_inst_returns_match), + KUNIT_CASE(dm_test_get_crtc_by_otg_inst_returns_null), + KUNIT_CASE(dm_test_get_crtc_by_otg_inst_empty_list), + {} +}; + +static struct kunit_suite amdgpu_dm_irq_test_suite = { + .name = "amdgpu_dm_irq", + .test_cases = amdgpu_dm_irq_tests, +}; + +kunit_test_suite(amdgpu_dm_irq_test_suite); + +MODULE_AUTHOR("AMD"); +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_irq"); +MODULE_LICENSE("Dual MIT/GPL"); -- cgit v1.2.3 From dbfad676ff1ad260a2f0c3b1eeb8e663eaab1126 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Thu, 30 Apr 2026 16:29:06 -0600 Subject: drm/amd/display: Add KUnit tests for amdgpu_dm_crtc Add KUnit coverage for functions in amdgpu_dm_crtc.c: - amdgpu_dm_crtc_modeset_required: verify active+needs_modeset combinations (mode_changed, active_changed, connectors_changed) - amdgpu_dm_crtc_vrr_active_irq: verify all VRR state enum values - amdgpu_dm_crtc_vrr_active: verify all VRR state enum values - amdgpu_dm_is_headless: null adev, no connectors, writeback-only, disconnected display, connected display, and mixed connector cases - amdgpu_dm_crtc_helper_mode_fixup: verify it accepts the mode - amdgpu_dm_crtc_set_vupdate_irq: verify the otg_inst == -1 early return using a DRM mock device - idle_create_workqueue: verify the idle workqueue is allocated and initialized in a disabled, non-running state Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c | 14 +- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.h | 6 + .../gpu/drm/amd/display/amdgpu_dm/tests/Makefile | 1 + .../display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c | 532 +++++++++++++++++++++ 4 files changed, 550 insertions(+), 3 deletions(-) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c index 3dcedaa67ed8..f7fcce6e76bb 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c @@ -34,6 +34,7 @@ #include "amdgpu_dm_plane.h" #include "amdgpu_dm_trace.h" #include "amdgpu_dm_debugfs.h" +#include "amdgpu_dm_kunit_helpers.h" #include "modules/inc/mod_power.h" #define HPD_DETECTION_PERIOD_uS 2000000 @@ -65,6 +66,7 @@ bool amdgpu_dm_crtc_modeset_required(struct drm_crtc_state *crtc_state, { return crtc_state->active && drm_atomic_crtc_needs_modeset(crtc_state); } +EXPORT_IF_KUNIT(amdgpu_dm_crtc_modeset_required); bool amdgpu_dm_crtc_vrr_active_irq(struct amdgpu_crtc *acrtc) @@ -74,6 +76,7 @@ bool amdgpu_dm_crtc_vrr_active_irq(struct amdgpu_crtc *acrtc) acrtc->dm_irq_params.freesync_config.state == VRR_STATE_ACTIVE_FIXED; } +EXPORT_IF_KUNIT(amdgpu_dm_crtc_vrr_active_irq); int amdgpu_dm_crtc_set_vupdate_irq(struct drm_crtc *crtc, bool enable) { @@ -93,12 +96,14 @@ int amdgpu_dm_crtc_set_vupdate_irq(struct drm_crtc *crtc, bool enable) acrtc->crtc_id, enable ? "en" : "dis", rc); return rc; } +EXPORT_IF_KUNIT(amdgpu_dm_crtc_set_vupdate_irq); bool amdgpu_dm_crtc_vrr_active(const struct dm_crtc_state *dm_state) { return dm_state->freesync_config.state == VRR_STATE_ACTIVE_VARIABLE || dm_state->freesync_config.state == VRR_STATE_ACTIVE_FIXED; } +EXPORT_IF_KUNIT(amdgpu_dm_crtc_vrr_active); /** * amdgpu_dm_crtc_set_static_screen_optimze() - Toggle static screen optimizations. @@ -156,6 +161,7 @@ bool amdgpu_dm_is_headless(struct amdgpu_device *adev) drm_connector_list_iter_end(&iter); return is_headless; } +EXPORT_IF_KUNIT(amdgpu_dm_is_headless); static void amdgpu_dm_idle_worker(struct work_struct *work) { @@ -207,6 +213,7 @@ struct idle_workqueue *idle_create_workqueue(struct amdgpu_device *adev) return idle_work; } +EXPORT_IF_KUNIT(idle_create_workqueue); static void amdgpu_dm_crtc_vblank_control_worker(struct work_struct *work) { @@ -595,12 +602,13 @@ static void amdgpu_dm_crtc_update_crtc_active_planes(struct drm_crtc *crtc, amdgpu_dm_crtc_count_crtc_active_planes(new_crtc_state); } -static bool amdgpu_dm_crtc_helper_mode_fixup(struct drm_crtc *crtc, - const struct drm_display_mode *mode, - struct drm_display_mode *adjusted_mode) +STATIC_IFN_KUNIT bool amdgpu_dm_crtc_helper_mode_fixup(struct drm_crtc *crtc, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) { return true; } +EXPORT_IF_KUNIT(amdgpu_dm_crtc_helper_mode_fixup); static int amdgpu_dm_crtc_helper_atomic_check(struct drm_crtc *crtc, struct drm_atomic_commit *state) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.h index e9fb52f0e66d..d8b004f613ab 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.h @@ -42,6 +42,12 @@ int amdgpu_dm_crtc_set_vupdate_irq(struct drm_crtc *crtc, bool enable); bool amdgpu_dm_crtc_vrr_active_irq(struct amdgpu_crtc *acrtc); +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +bool amdgpu_dm_crtc_helper_mode_fixup(struct drm_crtc *crtc, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode); +#endif + bool amdgpu_dm_crtc_vrr_active(const struct dm_crtc_state *dm_state); int amdgpu_dm_crtc_enable_vblank(struct drm_crtc *crtc); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile index 583604914753..cde8f7748bc5 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile @@ -27,3 +27,4 @@ obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_wb_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_mst_types_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_pp_smu_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_test.o +obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_crtc_test.o diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c new file mode 100644 index 000000000000..c83bd3e074f1 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c @@ -0,0 +1,532 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit tests for amdgpu_dm_crtc.c + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include +#include +#include +#include + +#include "dc.h" +#include "amdgpu.h" +#include "amdgpu_mode.h" +#include "amdgpu_dm.h" +#include "amdgpu_dm_crtc.h" +#include "amdgpu_dm_irq_params.h" + +/* Tests for amdgpu_dm_crtc_modeset_required() */ + +/** + * dm_test_crtc_modeset_required_active_mode_changed - Test Crtc modeset required active mode changed + * @test: The KUnit test context + */ +static void dm_test_crtc_modeset_required_active_mode_changed(struct kunit *test) +{ + struct drm_crtc_state state = {}; + + state.active = true; + state.mode_changed = true; + + KUNIT_EXPECT_TRUE(test, + amdgpu_dm_crtc_modeset_required(&state, NULL, NULL)); +} + +/** + * dm_test_crtc_modeset_required_active_active_changed - Test Crtc modeset required active active changed + * @test: The KUnit test context + */ +static void dm_test_crtc_modeset_required_active_active_changed(struct kunit *test) +{ + struct drm_crtc_state state = {}; + + state.active = true; + state.active_changed = true; + + KUNIT_EXPECT_TRUE(test, + amdgpu_dm_crtc_modeset_required(&state, NULL, NULL)); +} + +/** + * dm_test_crtc_modeset_required_active_connectors_changed - Test Crtc modeset required active connectors changed + * @test: The KUnit test context + */ +static void dm_test_crtc_modeset_required_active_connectors_changed(struct kunit *test) +{ + struct drm_crtc_state state = {}; + + state.active = true; + state.connectors_changed = true; + + KUNIT_EXPECT_TRUE(test, + amdgpu_dm_crtc_modeset_required(&state, NULL, NULL)); +} + +/** + * dm_test_crtc_modeset_required_inactive - Test Crtc modeset required inactive + * @test: The KUnit test context + */ +static void dm_test_crtc_modeset_required_inactive(struct kunit *test) +{ + struct drm_crtc_state state = {}; + + state.active = false; + state.mode_changed = true; + + KUNIT_EXPECT_FALSE(test, + amdgpu_dm_crtc_modeset_required(&state, NULL, NULL)); +} + +/** + * dm_test_crtc_modeset_required_no_changes - Test Crtc modeset required no changes + * @test: The KUnit test context + */ +static void dm_test_crtc_modeset_required_no_changes(struct kunit *test) +{ + struct drm_crtc_state state = {}; + + state.active = true; + state.mode_changed = false; + state.active_changed = false; + state.connectors_changed = false; + + KUNIT_EXPECT_FALSE(test, + amdgpu_dm_crtc_modeset_required(&state, NULL, NULL)); +} + +/* Tests for amdgpu_dm_crtc_vrr_active_irq() */ + +/** + * dm_test_crtc_vrr_active_irq_variable - Test Crtc vrr active irq variable + * @test: The KUnit test context + */ +static void dm_test_crtc_vrr_active_irq_variable(struct kunit *test) +{ + struct amdgpu_crtc *acrtc = kunit_kzalloc(test, sizeof(*acrtc), + GFP_KERNEL); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); + + acrtc->dm_irq_params.freesync_config.state = VRR_STATE_ACTIVE_VARIABLE; + + KUNIT_EXPECT_TRUE(test, amdgpu_dm_crtc_vrr_active_irq(acrtc)); +} + +/** + * dm_test_crtc_vrr_active_irq_fixed - Test Crtc vrr active irq fixed + * @test: The KUnit test context + */ +static void dm_test_crtc_vrr_active_irq_fixed(struct kunit *test) +{ + struct amdgpu_crtc *acrtc = kunit_kzalloc(test, sizeof(*acrtc), + GFP_KERNEL); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); + + acrtc->dm_irq_params.freesync_config.state = VRR_STATE_ACTIVE_FIXED; + + KUNIT_EXPECT_TRUE(test, amdgpu_dm_crtc_vrr_active_irq(acrtc)); +} + +/** + * dm_test_crtc_vrr_active_irq_inactive - Test Crtc vrr active irq inactive + * @test: The KUnit test context + */ +static void dm_test_crtc_vrr_active_irq_inactive(struct kunit *test) +{ + struct amdgpu_crtc *acrtc = kunit_kzalloc(test, sizeof(*acrtc), + GFP_KERNEL); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); + + acrtc->dm_irq_params.freesync_config.state = VRR_STATE_INACTIVE; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_crtc_vrr_active_irq(acrtc)); +} + +/** + * dm_test_crtc_vrr_active_irq_disabled - Test Crtc vrr active irq disabled + * @test: The KUnit test context + */ +static void dm_test_crtc_vrr_active_irq_disabled(struct kunit *test) +{ + struct amdgpu_crtc *acrtc = kunit_kzalloc(test, sizeof(*acrtc), + GFP_KERNEL); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); + + acrtc->dm_irq_params.freesync_config.state = VRR_STATE_DISABLED; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_crtc_vrr_active_irq(acrtc)); +} + +/** + * dm_test_crtc_vrr_active_irq_unsupported - Test Crtc vrr active irq unsupported + * @test: The KUnit test context + */ +static void dm_test_crtc_vrr_active_irq_unsupported(struct kunit *test) +{ + struct amdgpu_crtc *acrtc = kunit_kzalloc(test, sizeof(*acrtc), + GFP_KERNEL); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); + + acrtc->dm_irq_params.freesync_config.state = VRR_STATE_UNSUPPORTED; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_crtc_vrr_active_irq(acrtc)); +} + +/* Tests for amdgpu_dm_crtc_vrr_active() */ + +/** + * dm_test_crtc_vrr_active_variable - Test Crtc vrr active variable + * @test: The KUnit test context + */ +static void dm_test_crtc_vrr_active_variable(struct kunit *test) +{ + struct dm_crtc_state *dm_state = kunit_kzalloc(test, + sizeof(*dm_state), + GFP_KERNEL); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm_state); + + dm_state->freesync_config.state = VRR_STATE_ACTIVE_VARIABLE; + + KUNIT_EXPECT_TRUE(test, amdgpu_dm_crtc_vrr_active(dm_state)); +} + +/** + * dm_test_crtc_vrr_active_fixed - Test Crtc vrr active fixed + * @test: The KUnit test context + */ +static void dm_test_crtc_vrr_active_fixed(struct kunit *test) +{ + struct dm_crtc_state *dm_state = kunit_kzalloc(test, + sizeof(*dm_state), + GFP_KERNEL); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm_state); + + dm_state->freesync_config.state = VRR_STATE_ACTIVE_FIXED; + + KUNIT_EXPECT_TRUE(test, amdgpu_dm_crtc_vrr_active(dm_state)); +} + +/** + * dm_test_crtc_vrr_active_inactive - Test Crtc vrr active inactive + * @test: The KUnit test context + */ +static void dm_test_crtc_vrr_active_inactive(struct kunit *test) +{ + struct dm_crtc_state *dm_state = kunit_kzalloc(test, + sizeof(*dm_state), + GFP_KERNEL); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm_state); + + dm_state->freesync_config.state = VRR_STATE_INACTIVE; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_crtc_vrr_active(dm_state)); +} + +/** + * dm_test_crtc_vrr_active_disabled - Test Crtc vrr active disabled + * @test: The KUnit test context + */ +static void dm_test_crtc_vrr_active_disabled(struct kunit *test) +{ + struct dm_crtc_state *dm_state = kunit_kzalloc(test, + sizeof(*dm_state), + GFP_KERNEL); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm_state); + + dm_state->freesync_config.state = VRR_STATE_DISABLED; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_crtc_vrr_active(dm_state)); +} + +/** + * dm_test_crtc_vrr_active_unsupported - Test Crtc vrr active unsupported + * @test: The KUnit test context + */ +static void dm_test_crtc_vrr_active_unsupported(struct kunit *test) +{ + struct dm_crtc_state *dm_state = kunit_kzalloc(test, + sizeof(*dm_state), + GFP_KERNEL); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm_state); + + dm_state->freesync_config.state = VRR_STATE_UNSUPPORTED; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_crtc_vrr_active(dm_state)); +} + +/* Tests for amdgpu_dm_is_headless() */ + +static void dm_test_add_connector(struct drm_device *dev, + struct drm_connector *connector, + int connector_type, + enum drm_connector_status status) +{ + INIT_LIST_HEAD(&connector->head); + kref_init(&connector->base.refcount); + connector->connector_type = connector_type; + connector->status = status; + list_add_tail(&connector->head, &dev->mode_config.connector_list); +} + +/** + * dm_test_crtc_is_headless_null_adev - Test Crtc is headless null adev + * @test: The KUnit test context + */ +static void dm_test_crtc_is_headless_null_adev(struct kunit *test) +{ + KUNIT_EXPECT_TRUE(test, amdgpu_dm_is_headless(NULL)); +} + +/** + * dm_test_crtc_is_headless_no_connectors - Test Crtc is headless no connectors + * @test: The KUnit test context + */ +static void dm_test_crtc_is_headless_no_connectors(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct drm_device *dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + INIT_LIST_HEAD(&dev->mode_config.connector_list); + spin_lock_init(&dev->mode_config.connector_list_lock); + adev->dm.ddev = dev; + + KUNIT_EXPECT_TRUE(test, amdgpu_dm_is_headless(adev)); +} + +/** + * dm_test_crtc_is_headless_writeback_only - Test Crtc is headless writeback only + * @test: The KUnit test context + */ +static void dm_test_crtc_is_headless_writeback_only(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct drm_device *dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL); + struct drm_connector *wb = kunit_kzalloc(test, sizeof(*wb), GFP_KERNEL); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, wb); + + INIT_LIST_HEAD(&dev->mode_config.connector_list); + spin_lock_init(&dev->mode_config.connector_list_lock); + adev->dm.ddev = dev; + + dm_test_add_connector(dev, wb, DRM_MODE_CONNECTOR_WRITEBACK, + connector_status_connected); + + KUNIT_EXPECT_TRUE(test, amdgpu_dm_is_headless(adev)); +} + +/** + * dm_test_crtc_is_headless_disconnected_display - Test Crtc is headless disconnected display + * @test: The KUnit test context + */ +static void dm_test_crtc_is_headless_disconnected_display(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct drm_device *dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL); + struct drm_connector *display = kunit_kzalloc(test, sizeof(*display), GFP_KERNEL); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, display); + + INIT_LIST_HEAD(&dev->mode_config.connector_list); + spin_lock_init(&dev->mode_config.connector_list_lock); + adev->dm.ddev = dev; + + dm_test_add_connector(dev, display, DRM_MODE_CONNECTOR_HDMIA, + connector_status_disconnected); + + KUNIT_EXPECT_TRUE(test, amdgpu_dm_is_headless(adev)); +} + +/** + * dm_test_crtc_is_headless_connected_display - Test Crtc is headless connected display + * @test: The KUnit test context + */ +static void dm_test_crtc_is_headless_connected_display(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct drm_device *dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL); + struct drm_connector *display = kunit_kzalloc(test, sizeof(*display), GFP_KERNEL); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, display); + + INIT_LIST_HEAD(&dev->mode_config.connector_list); + spin_lock_init(&dev->mode_config.connector_list_lock); + adev->dm.ddev = dev; + + dm_test_add_connector(dev, display, DRM_MODE_CONNECTOR_HDMIA, + connector_status_connected); + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_is_headless(adev)); +} + +/** + * dm_test_crtc_is_headless_mixed_connectors - Test headless skips WB and finds display + * @test: The KUnit test context + */ +static void dm_test_crtc_is_headless_mixed_connectors(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct drm_device *dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL); + struct drm_connector *wb = kunit_kzalloc(test, sizeof(*wb), GFP_KERNEL); + struct drm_connector *display = kunit_kzalloc(test, sizeof(*display), GFP_KERNEL); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, wb); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, display); + + INIT_LIST_HEAD(&dev->mode_config.connector_list); + spin_lock_init(&dev->mode_config.connector_list_lock); + adev->dm.ddev = dev; + + dm_test_add_connector(dev, wb, DRM_MODE_CONNECTOR_WRITEBACK, + connector_status_connected); + dm_test_add_connector(dev, display, DRM_MODE_CONNECTOR_DisplayPort, + connector_status_connected); + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_is_headless(adev)); +} + +/* Tests for amdgpu_dm_crtc_helper_mode_fixup() */ + +/** + * dm_test_crtc_helper_mode_fixup_returns_true - Test mode_fixup accepts mode + * @test: The KUnit test context + */ +static void dm_test_crtc_helper_mode_fixup_returns_true(struct kunit *test) +{ + struct drm_display_mode mode = { 0 }; + struct drm_display_mode adjusted_mode = { 0 }; + + KUNIT_EXPECT_TRUE(test, + amdgpu_dm_crtc_helper_mode_fixup(NULL, &mode, &adjusted_mode)); +} + +/* Tests for amdgpu_dm_crtc_set_vupdate_irq() */ + +/** + * dm_test_crtc_set_vupdate_irq_no_otg - Test vupdate irq with unassigned OTG + * @test: The KUnit test context + * + * When the CRTC has no OTG instance assigned (otg_inst == -1) the function + * must return 0 immediately without touching the DC interrupt state. + */ +static void dm_test_crtc_set_vupdate_irq_no_otg(struct kunit *test) +{ + struct amdgpu_crtc *acrtc; + struct amdgpu_device *adev; + struct drm_device *drm; + struct device *dev; + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + drm = __drm_kunit_helper_alloc_drm_device(test, dev, + sizeof(*adev), + offsetof(struct amdgpu_device, ddev), + DRIVER_MODESET); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + adev = drm_to_adev(drm); + + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); + + acrtc->base.dev = drm; + acrtc->otg_inst = -1; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_crtc_set_vupdate_irq(&acrtc->base, true), 0); + KUNIT_EXPECT_EQ(test, amdgpu_dm_crtc_set_vupdate_irq(&acrtc->base, false), 0); +} + +/* Tests for idle_create_workqueue() */ + +/** + * dm_test_idle_create_workqueue - Test idle workqueue creation + * @test: The KUnit test context + * + * Verify that idle_create_workqueue() allocates an idle workqueue tied to the + * device's display manager and initializes it in a disabled, non-running state. + */ +static void dm_test_idle_create_workqueue(struct kunit *test) +{ + struct amdgpu_device *adev; + struct idle_workqueue *idle_work; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); + + idle_work = idle_create_workqueue(adev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, idle_work); + + KUNIT_EXPECT_PTR_EQ(test, idle_work->dm, &adev->dm); + KUNIT_EXPECT_FALSE(test, idle_work->enable); + KUNIT_EXPECT_FALSE(test, idle_work->running); + + kfree(idle_work); +} + +static struct kunit_case amdgpu_dm_crtc_tests[] = { + /* amdgpu_dm_crtc_modeset_required */ + KUNIT_CASE(dm_test_crtc_modeset_required_active_mode_changed), + KUNIT_CASE(dm_test_crtc_modeset_required_active_active_changed), + KUNIT_CASE(dm_test_crtc_modeset_required_active_connectors_changed), + KUNIT_CASE(dm_test_crtc_modeset_required_inactive), + KUNIT_CASE(dm_test_crtc_modeset_required_no_changes), + /* amdgpu_dm_crtc_vrr_active_irq */ + KUNIT_CASE(dm_test_crtc_vrr_active_irq_variable), + KUNIT_CASE(dm_test_crtc_vrr_active_irq_fixed), + KUNIT_CASE(dm_test_crtc_vrr_active_irq_inactive), + KUNIT_CASE(dm_test_crtc_vrr_active_irq_disabled), + KUNIT_CASE(dm_test_crtc_vrr_active_irq_unsupported), + /* amdgpu_dm_crtc_vrr_active */ + KUNIT_CASE(dm_test_crtc_vrr_active_variable), + KUNIT_CASE(dm_test_crtc_vrr_active_fixed), + KUNIT_CASE(dm_test_crtc_vrr_active_inactive), + KUNIT_CASE(dm_test_crtc_vrr_active_disabled), + KUNIT_CASE(dm_test_crtc_vrr_active_unsupported), + /* amdgpu_dm_is_headless */ + KUNIT_CASE(dm_test_crtc_is_headless_null_adev), + KUNIT_CASE(dm_test_crtc_is_headless_no_connectors), + KUNIT_CASE(dm_test_crtc_is_headless_writeback_only), + KUNIT_CASE(dm_test_crtc_is_headless_disconnected_display), + KUNIT_CASE(dm_test_crtc_is_headless_connected_display), + KUNIT_CASE(dm_test_crtc_is_headless_mixed_connectors), + /* amdgpu_dm_crtc_helper_mode_fixup */ + KUNIT_CASE(dm_test_crtc_helper_mode_fixup_returns_true), + /* amdgpu_dm_crtc_set_vupdate_irq */ + KUNIT_CASE(dm_test_crtc_set_vupdate_irq_no_otg), + /* idle_create_workqueue */ + KUNIT_CASE(dm_test_idle_create_workqueue), + {} +}; + +static struct kunit_suite amdgpu_dm_crtc_test_suite = { + .name = "amdgpu_dm_crtc", + .test_cases = amdgpu_dm_crtc_tests, +}; + +kunit_test_suite(amdgpu_dm_crtc_test_suite); + +MODULE_AUTHOR("AMD"); +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_crtc"); +MODULE_LICENSE("Dual MIT/GPL"); -- cgit v1.2.3 From 6c61907396853a41ce316073411758adf848f1c1 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Wed, 6 May 2026 15:54:47 -0600 Subject: drm/amd/display: Add KUnit tests for amdgpu_dm_services Add amdgpu_dm_services_test.c with KUnit coverage for five functions in amdgpu_dm_services.c: - dm_get_elapse_time_in_ns(): four arithmetic cases covering zero delta, positive delta, ULLONG_MAX span, and unsigned wraparound. - dm_perf_trace_timestamp(): one case verifying the function dereferences ctx->perf_trace safely (the tracepoint is a no-op without an attached probe). - dm_trace_smu_enter(): two cases for the empty stub with NULL ctx and with non-zero parameters. - dm_trace_smu_exit(): three cases for the empty stub covering success, failure, and a non-zero response value. - dm_query_extended_brightness_caps(): four guard-clause cases (NULL ctx, NULL caps, NULL ctx->driver_context, NULL ctx with LCD2) plus two success cases covering the LCD1 slot with luminance data copy and a non-LCD1 display using the second backlight slot with zero data points. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- .../drm/amd/display/amdgpu_dm/amdgpu_dm_services.c | 6 + .../gpu/drm/amd/display/amdgpu_dm/tests/Makefile | 1 + .../amdgpu_dm/tests/amdgpu_dm_services_test.c | 313 +++++++++++++++++++++ 3 files changed, 320 insertions(+) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_services_test.c (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_services.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_services.c index 0fdcf70256cc..6c0464754ed8 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_services.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_services.c @@ -36,6 +36,7 @@ #include "amdgpu_dm_irq.h" #include "amdgpu_pm.h" #include "amdgpu_dm_trace.h" +#include "amdgpu_dm_kunit_helpers.h" unsigned long long dm_get_elapse_time_in_ns(struct dc_context *ctx, @@ -44,6 +45,7 @@ { return current_time_stamp - last_time_stamp; } +EXPORT_IF_KUNIT(dm_get_elapse_time_in_ns); void dm_perf_trace_timestamp(const char *func_name, unsigned int line, struct dc_context *ctx) { @@ -53,14 +55,17 @@ void dm_perf_trace_timestamp(const char *func_name, unsigned int line, struct dc &ctx->perf_trace->last_entry_write, func_name, line); } +EXPORT_IF_KUNIT(dm_perf_trace_timestamp); void dm_trace_smu_enter(uint32_t msg_id, uint32_t param_in, unsigned int delay, struct dc_context *ctx) { } +EXPORT_IF_KUNIT(dm_trace_smu_enter); void dm_trace_smu_exit(bool success, uint32_t response, struct dc_context *ctx) { } +EXPORT_IF_KUNIT(dm_trace_smu_exit); /**** power component interfaces ****/ @@ -90,3 +95,4 @@ bool dm_query_extended_brightness_caps(struct dc_context *ctx, sizeof(struct dm_bl_data_point) * pCaps->num_data_points); return true; } +EXPORT_IF_KUNIT(dm_query_extended_brightness_caps); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile index cde8f7748bc5..364b4f3c783f 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile @@ -28,3 +28,4 @@ obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_mst_types_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_pp_smu_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_crtc_test.o +obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_services_test.o diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_services_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_services_test.c new file mode 100644 index 000000000000..e48bac7fb024 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_services_test.c @@ -0,0 +1,313 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit tests for amdgpu_dm_services.c + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include + +#include "dc.h" +#include "amdgpu.h" +#include "amdgpu_mode.h" +#include "amdgpu_dm.h" +#include "dm_services.h" +#include "dm_services_types.h" + +/* Tests for dm_get_elapse_time_in_ns() */ + +/** + * dm_test_get_elapse_time_zero_delta - Test Get elapse time zero delta + * @test: The KUnit test context + */ +static void dm_test_get_elapse_time_zero_delta(struct kunit *test) +{ + unsigned long long ts = 1000000ULL; + + KUNIT_EXPECT_EQ(test, dm_get_elapse_time_in_ns(NULL, ts, ts), 0ULL); +} + +/** + * dm_test_get_elapse_time_positive_delta - Test Get elapse time positive delta + * @test: The KUnit test context + */ +static void dm_test_get_elapse_time_positive_delta(struct kunit *test) +{ + unsigned long long current_ts = 5000000ULL; + unsigned long long last_ts = 1000000ULL; + + KUNIT_EXPECT_EQ(test, dm_get_elapse_time_in_ns(NULL, current_ts, last_ts), + 4000000ULL); +} + +/** + * dm_test_get_elapse_time_large_delta - Test Get elapse time large delta + * @test: The KUnit test context + */ +static void dm_test_get_elapse_time_large_delta(struct kunit *test) +{ + unsigned long long current_ts = ULLONG_MAX; + unsigned long long last_ts = 0ULL; + + KUNIT_EXPECT_EQ(test, dm_get_elapse_time_in_ns(NULL, current_ts, last_ts), + ULLONG_MAX); +} + +/** + * dm_test_get_elapse_time_wraparound - Test Get elapse time wraparound + * @test: The KUnit test context + */ +static void dm_test_get_elapse_time_wraparound(struct kunit *test) +{ + /* Unsigned wraparound: result = ULLONG_MAX - last + current + 1 */ + unsigned long long current_ts = 5ULL; + unsigned long long last_ts = ULLONG_MAX - 4ULL; + + KUNIT_EXPECT_EQ(test, dm_get_elapse_time_in_ns(NULL, current_ts, last_ts), + 10ULL); +} + +/* Tests for dm_perf_trace_timestamp() */ + +/** + * dm_test_perf_trace_timestamp_basic - Test Perf trace timestamp basic + * @test: The KUnit test context + * + * The tracepoint is a no-op without an attached probe, so this verifies the + * function dereferences ctx->perf_trace safely and does not crash. + */ +static void dm_test_perf_trace_timestamp_basic(struct kunit *test) +{ + struct dc_context *ctx; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + ctx->perf_trace = kunit_kzalloc(test, sizeof(*ctx->perf_trace), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->perf_trace); + + ctx->perf_trace->read_count = 10; + ctx->perf_trace->write_count = 20; + + dm_perf_trace_timestamp(__func__, __LINE__, ctx); +} + +/* Tests for dm_trace_smu_enter() */ + +/** + * dm_test_trace_smu_enter_null_ctx - Test Trace smu enter null ctx + * @test: The KUnit test context + */ +static void dm_test_trace_smu_enter_null_ctx(struct kunit *test) +{ + /* Empty stub — must not crash with NULL ctx */ + dm_trace_smu_enter(0, 0, 0, NULL); +} + +/** + * dm_test_trace_smu_enter_with_params - Test Trace smu enter with params + * @test: The KUnit test context + */ +static void dm_test_trace_smu_enter_with_params(struct kunit *test) +{ + /* Exercise non-zero msg_id, param_in, and delay */ + dm_trace_smu_enter(0xFF, 0x12345678, 1000, NULL); +} + +/* Tests for dm_trace_smu_exit() */ + +/** + * dm_test_trace_smu_exit_success_null_ctx - Test Trace smu exit success null ctx + * @test: The KUnit test context + */ +static void dm_test_trace_smu_exit_success_null_ctx(struct kunit *test) +{ + /* Empty stub — must not crash on success path with NULL ctx */ + dm_trace_smu_exit(true, 0x0, NULL); +} + +/** + * dm_test_trace_smu_exit_failure_null_ctx - Test Trace smu exit failure null ctx + * @test: The KUnit test context + */ +static void dm_test_trace_smu_exit_failure_null_ctx(struct kunit *test) +{ + /* Empty stub — must not crash on failure path with NULL ctx */ + dm_trace_smu_exit(false, 0x0, NULL); +} + +/** + * dm_test_trace_smu_exit_with_response - Test Trace smu exit with response + * @test: The KUnit test context + */ +static void dm_test_trace_smu_exit_with_response(struct kunit *test) +{ + /* Exercise non-zero response value */ + dm_trace_smu_exit(true, 0xDEADBEEF, NULL); +} + +/* Tests for dm_query_extended_brightness_caps() */ + +/** + * dm_test_query_brightness_caps_null_ctx - Test Query brightness caps null ctx + * @test: The KUnit test context + */ +static void dm_test_query_brightness_caps_null_ctx(struct kunit *test) +{ + struct dm_acpi_atif_backlight_caps caps = {}; + + KUNIT_EXPECT_FALSE(test, + dm_query_extended_brightness_caps(NULL, AcpiDisplayType_LCD1, &caps)); +} + +/** + * dm_test_query_brightness_caps_null_caps - Test Query brightness caps null caps + * @test: The KUnit test context + */ +static void dm_test_query_brightness_caps_null_caps(struct kunit *test) +{ + struct dc_context ctx = {}; + + ctx.driver_context = (void *)0x1; /* non-NULL sentinel */ + + KUNIT_EXPECT_FALSE(test, + dm_query_extended_brightness_caps(&ctx, AcpiDisplayType_LCD1, NULL)); +} + +/** + * dm_test_query_brightness_caps_null_driver_ctx - Test Query brightness caps null driver ctx + * @test: The KUnit test context + */ +static void dm_test_query_brightness_caps_null_driver_ctx(struct kunit *test) +{ + struct dc_context ctx = {}; + struct dm_acpi_atif_backlight_caps caps = {}; + + ctx.driver_context = NULL; + + KUNIT_EXPECT_FALSE(test, + dm_query_extended_brightness_caps(&ctx, AcpiDisplayType_LCD1, &caps)); +} + +/** + * dm_test_query_brightness_caps_lcd2_null_ctx - Test Query brightness caps lcd2 null ctx + * @test: The KUnit test context + */ +static void dm_test_query_brightness_caps_lcd2_null_ctx(struct kunit *test) +{ + struct dm_acpi_atif_backlight_caps caps = {}; + + KUNIT_EXPECT_FALSE(test, + dm_query_extended_brightness_caps(NULL, AcpiDisplayType_LCD2, &caps)); +} + +/** + * dm_test_query_brightness_caps_lcd1_success - Test Query brightness caps lcd1 success + * @test: The KUnit test context + */ +static void dm_test_query_brightness_caps_lcd1_success(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_dm_backlight_caps *source_caps; + struct dc_context ctx = {}; + struct dm_acpi_atif_backlight_caps caps = {}; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + + source_caps = &adev->dm.backlight_caps[0]; + source_caps->caps_valid = true; + source_caps->min_input_signal = 12; + source_caps->max_input_signal = 240; + source_caps->ac_level = 80; + source_caps->dc_level = 40; + source_caps->data_points = 2; + source_caps->luminance_data[0].luminance = 10; + source_caps->luminance_data[0].input_signal = 22; + source_caps->luminance_data[1].luminance = 90; + source_caps->luminance_data[1].input_signal = 200; + ctx.driver_context = adev; + + KUNIT_EXPECT_TRUE(test, + dm_query_extended_brightness_caps(&ctx, AcpiDisplayType_LCD1, &caps)); + KUNIT_EXPECT_EQ(test, caps.num_data_points, 2); + KUNIT_EXPECT_EQ(test, caps.max_input_signal, 240); + KUNIT_EXPECT_EQ(test, caps.min_input_signal, 12); + KUNIT_EXPECT_EQ(test, caps.ac_level_percentage, 80); + KUNIT_EXPECT_EQ(test, caps.dc_level_percentage, 40); + KUNIT_EXPECT_EQ(test, caps.data_points[0].luminance, 10); + KUNIT_EXPECT_EQ(test, caps.data_points[0].signal_level, 22); + KUNIT_EXPECT_EQ(test, caps.data_points[1].luminance, 90); + KUNIT_EXPECT_EQ(test, caps.data_points[1].signal_level, 200); +} + +/** + * dm_test_query_brightness_caps_non_lcd1_uses_second_slot - Test Query brightness caps non lcd1 uses second slot + * @test: The KUnit test context + */ +static void dm_test_query_brightness_caps_non_lcd1_uses_second_slot(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_dm_backlight_caps *source_caps; + struct dc_context ctx = {}; + struct dm_acpi_atif_backlight_caps caps = {}; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + + adev->dm.backlight_caps[0].caps_valid = true; + adev->dm.backlight_caps[0].min_input_signal = 1; + adev->dm.backlight_caps[0].max_input_signal = 2; + source_caps = &adev->dm.backlight_caps[1]; + source_caps->caps_valid = true; + source_caps->min_input_signal = 33; + source_caps->max_input_signal = 199; + source_caps->ac_level = 70; + source_caps->dc_level = 30; + source_caps->data_points = 0; + ctx.driver_context = adev; + + KUNIT_EXPECT_TRUE(test, + dm_query_extended_brightness_caps(&ctx, AcpiDisplayType_DFP1, &caps)); + KUNIT_EXPECT_EQ(test, caps.num_data_points, 0); + KUNIT_EXPECT_EQ(test, caps.max_input_signal, 199); + KUNIT_EXPECT_EQ(test, caps.min_input_signal, 33); + KUNIT_EXPECT_EQ(test, caps.ac_level_percentage, 70); + KUNIT_EXPECT_EQ(test, caps.dc_level_percentage, 30); + KUNIT_EXPECT_EQ(test, caps.data_points[0].luminance, 0); + KUNIT_EXPECT_EQ(test, caps.data_points[0].signal_level, 0); +} + +static struct kunit_case amdgpu_dm_services_test_cases[] = { + /* dm_get_elapse_time_in_ns */ + KUNIT_CASE(dm_test_get_elapse_time_zero_delta), + KUNIT_CASE(dm_test_get_elapse_time_positive_delta), + KUNIT_CASE(dm_test_get_elapse_time_large_delta), + KUNIT_CASE(dm_test_get_elapse_time_wraparound), + /* dm_perf_trace_timestamp */ + KUNIT_CASE(dm_test_perf_trace_timestamp_basic), + /* dm_trace_smu_enter */ + KUNIT_CASE(dm_test_trace_smu_enter_null_ctx), + KUNIT_CASE(dm_test_trace_smu_enter_with_params), + /* dm_trace_smu_exit */ + KUNIT_CASE(dm_test_trace_smu_exit_success_null_ctx), + KUNIT_CASE(dm_test_trace_smu_exit_failure_null_ctx), + KUNIT_CASE(dm_test_trace_smu_exit_with_response), + /* dm_query_extended_brightness_caps */ + KUNIT_CASE(dm_test_query_brightness_caps_null_ctx), + KUNIT_CASE(dm_test_query_brightness_caps_null_caps), + KUNIT_CASE(dm_test_query_brightness_caps_null_driver_ctx), + KUNIT_CASE(dm_test_query_brightness_caps_lcd2_null_ctx), + KUNIT_CASE(dm_test_query_brightness_caps_lcd1_success), + KUNIT_CASE(dm_test_query_brightness_caps_non_lcd1_uses_second_slot), + {} +}; + +static struct kunit_suite amdgpu_dm_services_test_suite = { + .name = "amdgpu_dm_services", + .test_cases = amdgpu_dm_services_test_cases, +}; + +kunit_test_suite(amdgpu_dm_services_test_suite); + +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_services"); +MODULE_LICENSE("Dual MIT/GPL"); -- cgit v1.2.3 From d974b7865f170803c4bda5706f13fb1e22506cb7 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Wed, 6 May 2026 16:39:07 -0600 Subject: drm/amd/display: Add KUnit tests for amdgpu_dm_helpers Add amdgpu_dm_helpers_test.c with 32 KUnit test cases covering the following functions in amdgpu_dm_helpers.c: - edid_extract_panel_id(): basic extraction with known mfg_id and prod_code; zero inputs produce zero output. - dm_is_freesync_pcon_whitelist(): every entry in the whitelist table returns true; an unknown ID and a zero ID return false. - populate_hdmi_info_from_connector(): scdc_present is copied from hdmi->scdc.supported for both true and false; FRL DSC fields map 10bpc and 12bpc correctly and ignore unknown values. - dm_get_adaptive_sync_support_type(): five cases covering the default non-converter path, HDMI converter without conditions, partial conditions, all conditions met with a whitelist device (FREESYNC_TYPE_PCON_IN_WHITELIST), and all conditions met with a non-whitelisted device. - dm_helpers_is_fullscreen() / dm_helpers_is_hdr_on(): stubs always return false. - get_max_frl_rate(): all six valid lane/rate combinations plus the unknown combination returning 0. - dm_dtn_log_begin()/dm_dtn_log_append_v()/dm_dtn_log_end(): buffer accumulation and NULL-context handling without crashing. - dm_helpers_dp_read_dpcd()/dm_helpers_dp_write_dpcd(): NULL link private data returns false. - dm_helpers_dp_mst_start_top_mgr()/dm_helpers_dp_mst_stop_top_mgr(): NULL link private data and the boot path. - dm_helpers_dp_write_hblank_reduction(): stub returns false. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- .../drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 58 +- .../drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h | 20 + .../gpu/drm/amd/display/amdgpu_dm/tests/Makefile | 1 + .../amdgpu_dm/tests/amdgpu_dm_helpers_test.c | 645 +++++++++++++++++++++ 4 files changed, 707 insertions(+), 17 deletions(-) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c index eef031022be2..71e2627f9a9d 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c @@ -48,6 +48,8 @@ #include "dm_helpers.h" #include "ddc_service_types.h" #include "clk_mgr.h" +#include "amdgpu_dm_kunit_helpers.h" +#include "amdgpu_dm_helpers.h" #define MCCS_DEST_ADDR (0x6E >> 1) #define MCCS_SRC_ADDR 0x51 @@ -88,12 +90,13 @@ union vcp_reply { unsigned char raw[11]; }; -static u32 edid_extract_panel_id(struct edid *edid) +STATIC_IFN_KUNIT u32 edid_extract_panel_id(struct edid *edid) { return (u32)edid->mfg_id[0] << 24 | (u32)edid->mfg_id[1] << 16 | (u32)EDID_PRODUCT_ID(edid); } +EXPORT_IF_KUNIT(edid_extract_panel_id); static void apply_edid_quirks(struct dc_link *link, struct edid *edid, struct dc_edid_caps *edid_caps) @@ -495,6 +498,7 @@ void dm_dtn_log_begin(struct dc_context *ctx, dm_dtn_log_append_v(ctx, log_ctx, "%s", msg); } +EXPORT_IF_KUNIT(dm_dtn_log_begin); __printf(3, 4) void dm_dtn_log_append_v(struct dc_context *ctx, @@ -557,6 +561,7 @@ void dm_dtn_log_append_v(struct dc_context *ctx, if (n > 0) log_ctx->pos += n; } +EXPORT_IF_KUNIT(dm_dtn_log_append_v); void dm_dtn_log_end(struct dc_context *ctx, struct dc_log_buffer_ctx *log_ctx) @@ -570,6 +575,7 @@ void dm_dtn_log_end(struct dc_context *ctx, dm_dtn_log_append_v(ctx, log_ctx, "%s", msg); } +EXPORT_IF_KUNIT(dm_dtn_log_end); bool dm_helpers_dp_mst_start_top_mgr( struct dc_context *ctx, @@ -604,6 +610,7 @@ bool dm_helpers_dp_mst_start_top_mgr( return true; } +EXPORT_IF_KUNIT(dm_helpers_dp_mst_start_top_mgr); bool dm_helpers_dp_mst_stop_top_mgr( struct dc_context *ctx, @@ -626,6 +633,7 @@ bool dm_helpers_dp_mst_stop_top_mgr( return false; } +EXPORT_IF_KUNIT(dm_helpers_dp_mst_stop_top_mgr); bool dm_helpers_dp_read_dpcd( struct dc_context *ctx, @@ -643,6 +651,7 @@ bool dm_helpers_dp_read_dpcd( return drm_dp_dpcd_read(&aconnector->dm_dp_aux.aux, address, data, size) == size; } +EXPORT_IF_KUNIT(dm_helpers_dp_read_dpcd); bool dm_helpers_dp_write_dpcd( struct dc_context *ctx, @@ -659,6 +668,7 @@ bool dm_helpers_dp_write_dpcd( return drm_dp_dpcd_write(&aconnector->dm_dp_aux.aux, address, (uint8_t *)data, size) > 0; } +EXPORT_IF_KUNIT(dm_helpers_dp_write_dpcd); bool dm_helpers_submit_i2c( struct dc_context *ctx, @@ -974,6 +984,7 @@ bool dm_helpers_dp_write_hblank_reduction(struct dc_context *ctx, const struct d // TODO return false; } +EXPORT_IF_KUNIT(dm_helpers_dp_write_hblank_reduction); bool dm_helpers_is_dp_sink_present(struct dc_link *link) { @@ -1091,7 +1102,7 @@ dm_helpers_read_vbios_hardcoded_edid(struct dc_link *link, struct amdgpu_dm_conn return edid; } -static uint8_t get_max_frl_rate(uint8_t max_lanes, uint8_t max_rate_per_lane) +STATIC_IFN_KUNIT uint8_t get_max_frl_rate(uint8_t max_lanes, uint8_t max_rate_per_lane) { uint8_t max_frl_rate; @@ -1112,6 +1123,7 @@ static uint8_t get_max_frl_rate(uint8_t max_lanes, uint8_t max_rate_per_lane) return max_frl_rate; } +EXPORT_IF_KUNIT(get_max_frl_rate); static uint8_t get_dsc_max_slices(uint8_t max_slices, int clk_per_slice) { @@ -1156,6 +1168,7 @@ void populate_hdmi_info_from_connector(bool enable_frl, struct drm_hdmi_info *hd } } } +EXPORT_IF_KUNIT(populate_hdmi_info_from_connector); enum dc_edid_status dm_helpers_read_local_edid( struct dc_context *ctx, @@ -1556,24 +1569,32 @@ void dm_helpers_dp_mst_update_branch_bandwidth( // TODO } -static bool dm_is_freesync_pcon_whitelist(const uint32_t branch_dev_id) +STATIC_IFN_KUNIT const uint32_t dm_freesync_pcon_whitelist[] = { + DP_BRANCH_DEVICE_ID_0060AD, + DP_BRANCH_DEVICE_ID_00E04C, + DP_BRANCH_DEVICE_ID_90CC24, + DP_BRANCH_DEVICE_ID_001CF8, + DP_BRANCH_DEVICE_ID_001FF2, +}; +EXPORT_IF_KUNIT(dm_freesync_pcon_whitelist); + +STATIC_IFN_KUNIT uint32_t dm_freesync_pcon_whitelist_count(void) { - bool ret_val = false; - - switch (branch_dev_id) { - case DP_BRANCH_DEVICE_ID_0060AD: - case DP_BRANCH_DEVICE_ID_00E04C: - case DP_BRANCH_DEVICE_ID_90CC24: - case DP_BRANCH_DEVICE_ID_001CF8: - case DP_BRANCH_DEVICE_ID_001FF2: - ret_val = true; - break; - default: - break; - } + return ARRAY_SIZE(dm_freesync_pcon_whitelist); +} +EXPORT_IF_KUNIT(dm_freesync_pcon_whitelist_count); + +STATIC_IFN_KUNIT bool dm_is_freesync_pcon_whitelist(const uint32_t branch_dev_id) +{ + u32 i; - return ret_val; + for (i = 0; i < dm_freesync_pcon_whitelist_count(); i++) + if (dm_freesync_pcon_whitelist[i] == branch_dev_id) + return true; + + return false; } +EXPORT_IF_KUNIT(dm_is_freesync_pcon_whitelist); enum adaptive_sync_type dm_get_adaptive_sync_support_type(struct dc_link *link) { @@ -1593,18 +1614,21 @@ enum adaptive_sync_type dm_get_adaptive_sync_support_type(struct dc_link *link) return as_type; } +EXPORT_IF_KUNIT(dm_get_adaptive_sync_support_type); bool dm_helpers_is_fullscreen(struct dc_context *ctx, struct dc_stream_state *stream) { // TODO return false; } +EXPORT_IF_KUNIT(dm_helpers_is_fullscreen); bool dm_helpers_is_hdr_on(struct dc_context *ctx, struct dc_stream_state *stream) { // TODO return false; } +EXPORT_IF_KUNIT(dm_helpers_is_hdr_on); static int mccs_operation_vcp_request(unsigned int vcp_code, struct dc_link *link, union vcp_reply *reply) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h new file mode 100644 index 000000000000..2ac9762895ec --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#ifndef __AMDGPU_DM_HELPERS_H__ +#define __AMDGPU_DM_HELPERS_H__ + +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +#include + +/* Exported for KUnit testing */ +u32 edid_extract_panel_id(struct edid *edid); +uint8_t get_max_frl_rate(uint8_t max_lanes, uint8_t max_rate_per_lane); +bool dm_is_freesync_pcon_whitelist(const uint32_t branch_dev_id); +extern const uint32_t dm_freesync_pcon_whitelist[]; +uint32_t dm_freesync_pcon_whitelist_count(void); +#endif /* CONFIG_DRM_AMD_DC_KUNIT_TEST */ + +#endif /* __AMDGPU_DM_HELPERS_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile index 364b4f3c783f..a067332f9f41 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile @@ -29,3 +29,4 @@ obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_pp_smu_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_crtc_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_services_test.o +obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_helpers_test.o diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c new file mode 100644 index 000000000000..14004ff87c9b --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c @@ -0,0 +1,645 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit tests for amdgpu_dm_helpers.c + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include +#include +#include + +#include "dc.h" +#include "amdgpu.h" +#include "amdgpu_mode.h" +#include "amdgpu_dm.h" +#include "dm_helpers.h" +#include "ddc_service_types.h" +#include "amdgpu_dm_helpers.h" + +/* Tests for edid_extract_panel_id() */ + +/** + * dm_test_edid_extract_panel_id_basic - Test Edid extract panel id basic + * @test: The KUnit test context + */ +static void dm_test_edid_extract_panel_id_basic(struct kunit *test) +{ + struct edid *edid; + u32 panel_id; + + edid = kunit_kzalloc(test, sizeof(*edid), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, edid); + + edid->mfg_id[0] = 0x12; + edid->mfg_id[1] = 0x34; + edid->prod_code[0] = 0xAB; + edid->prod_code[1] = 0xCD; + + panel_id = edid_extract_panel_id(edid); + + /* + * Expected: (0x12 << 24) | (0x34 << 16) | EDID_PRODUCT_ID(edid) + * EDID_PRODUCT_ID = prod_code[0] | (prod_code[1] << 8) = 0xAB | 0xCD00 = 0xCDAB + * Result: 0x12340000 | 0x0000CDAB = 0x1234CDAB + */ + KUNIT_EXPECT_EQ(test, panel_id, (u32)0x1234CDAB); +} + +/** + * dm_test_edid_extract_panel_id_zeros - Test Edid extract panel id zeros + * @test: The KUnit test context + */ +static void dm_test_edid_extract_panel_id_zeros(struct kunit *test) +{ + struct edid *edid; + + edid = kunit_kzalloc(test, sizeof(*edid), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, edid); + + KUNIT_EXPECT_EQ(test, edid_extract_panel_id(edid), 0U); +} + +/* Tests for dm_is_freesync_pcon_whitelist() */ + +/** + * dm_test_freesync_pcon_whitelist_all_known - Test all known Freesync Pcon whitelist entries + * @test: The KUnit test context + * + * Iterates over the driver's whitelist table directly so that any ID added + * to dm_freesync_pcon_whitelist[] is automatically covered by this test. + */ +static void dm_test_freesync_pcon_whitelist_all_known(struct kunit *test) +{ + u32 i; + + for (i = 0; i < dm_freesync_pcon_whitelist_count(); i++) + KUNIT_EXPECT_TRUE(test, + dm_is_freesync_pcon_whitelist(dm_freesync_pcon_whitelist[i])); +} + +/** + * dm_test_freesync_pcon_whitelist_not_in_list - Test Freesync pcon whitelist not in list + * @test: The KUnit test context + */ +static void dm_test_freesync_pcon_whitelist_not_in_list(struct kunit *test) +{ + /* 0xFFFFFF is not a known whitelist device */ + KUNIT_EXPECT_FALSE(test, dm_is_freesync_pcon_whitelist(0xFFFFFF)); +} + +/** + * dm_test_freesync_pcon_whitelist_zero - Test Freesync pcon whitelist zero + * @test: The KUnit test context + */ +static void dm_test_freesync_pcon_whitelist_zero(struct kunit *test) +{ + KUNIT_EXPECT_FALSE(test, dm_is_freesync_pcon_whitelist(0)); +} + +/* Tests for populate_hdmi_info_from_connector() */ + +/** + * dm_test_populate_hdmi_scdc_present_true - Test Populate hdmi scdc present true + * @test: The KUnit test context + */ +static void dm_test_populate_hdmi_scdc_present_true(struct kunit *test) +{ + struct drm_hdmi_info *hdmi; + struct dc_edid_caps *caps; + + hdmi = kunit_kzalloc(test, sizeof(*hdmi), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, hdmi); + caps = kunit_kzalloc(test, sizeof(*caps), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, caps); + + hdmi->scdc.supported = true; + + populate_hdmi_info_from_connector(true, hdmi, caps); + + KUNIT_EXPECT_TRUE(test, caps->scdc_present); +} + +/** + * dm_test_populate_hdmi_scdc_present_false - Test Populate hdmi scdc present false + * @test: The KUnit test context + */ +static void dm_test_populate_hdmi_scdc_present_false(struct kunit *test) +{ + struct drm_hdmi_info *hdmi; + struct dc_edid_caps *caps; + + hdmi = kunit_kzalloc(test, sizeof(*hdmi), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, hdmi); + caps = kunit_kzalloc(test, sizeof(*caps), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, caps); + + hdmi->scdc.supported = false; + caps->scdc_present = true; /* pre-set to confirm it gets cleared */ + + populate_hdmi_info_from_connector(true, hdmi, caps); + + KUNIT_EXPECT_FALSE(test, caps->scdc_present); +} + +/** + * dm_test_populate_hdmi_frl_dsc_10bpc - Test HDMI FRL DSC 10 bpc caps + * @test: The KUnit test context + */ +static void dm_test_populate_hdmi_frl_dsc_10bpc(struct kunit *test) +{ + struct drm_hdmi_info *hdmi; + struct dc_edid_caps *caps; + + hdmi = kunit_kzalloc(test, sizeof(*hdmi), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, hdmi); + caps = kunit_kzalloc(test, sizeof(*caps), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, caps); + + hdmi->max_lanes = 4; + hdmi->max_frl_rate_per_lane = 12; + hdmi->dsc_cap.v_1p2 = true; + hdmi->dsc_cap.bpc_supported = 10; + hdmi->dsc_cap.all_bpp = true; + hdmi->dsc_cap.native_420 = true; + hdmi->dsc_cap.max_slices = 8; + hdmi->dsc_cap.clk_per_slice = 400; + hdmi->dsc_cap.max_lanes = 4; + hdmi->dsc_cap.max_frl_rate_per_lane = 10; + hdmi->dsc_cap.total_chunk_kbytes = 7; + + populate_hdmi_info_from_connector(true, hdmi, caps); + + KUNIT_EXPECT_EQ(test, caps->max_frl_rate, 6); + KUNIT_EXPECT_TRUE(test, caps->frl_dsc_support); + KUNIT_EXPECT_TRUE(test, caps->frl_dsc_10bpc); + KUNIT_EXPECT_FALSE(test, caps->frl_dsc_12bpc); + KUNIT_EXPECT_TRUE(test, caps->frl_dsc_all_bpp); + KUNIT_EXPECT_TRUE(test, caps->frl_dsc_native_420); + KUNIT_EXPECT_EQ(test, caps->frl_dsc_max_slices, 5); + KUNIT_EXPECT_EQ(test, caps->frl_dsc_max_frl_rate, 5); + KUNIT_EXPECT_EQ(test, caps->frl_dsc_total_chunk_kbytes, 7); +} + +/** + * dm_test_populate_hdmi_frl_dsc_12bpc - Test HDMI FRL DSC 12 bpc caps + * @test: The KUnit test context + */ +static void dm_test_populate_hdmi_frl_dsc_12bpc(struct kunit *test) +{ + struct drm_hdmi_info *hdmi; + struct dc_edid_caps *caps; + + hdmi = kunit_kzalloc(test, sizeof(*hdmi), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, hdmi); + caps = kunit_kzalloc(test, sizeof(*caps), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, caps); + + hdmi->max_lanes = 3; + hdmi->max_frl_rate_per_lane = 6; + hdmi->dsc_cap.v_1p2 = true; + hdmi->dsc_cap.bpc_supported = 12; + hdmi->dsc_cap.max_slices = 16; + hdmi->dsc_cap.clk_per_slice = 400; + hdmi->dsc_cap.max_lanes = 3; + hdmi->dsc_cap.max_frl_rate_per_lane = 3; + + populate_hdmi_info_from_connector(true, hdmi, caps); + + KUNIT_EXPECT_EQ(test, caps->max_frl_rate, 2); + KUNIT_EXPECT_TRUE(test, caps->frl_dsc_support); + KUNIT_EXPECT_FALSE(test, caps->frl_dsc_10bpc); + KUNIT_EXPECT_TRUE(test, caps->frl_dsc_12bpc); + KUNIT_EXPECT_EQ(test, caps->frl_dsc_max_slices, 7); + KUNIT_EXPECT_EQ(test, caps->frl_dsc_max_frl_rate, 1); +} + +/** + * dm_test_populate_hdmi_frl_dsc_unknown_values - Test HDMI FRL DSC unknown values + * @test: The KUnit test context + */ +static void dm_test_populate_hdmi_frl_dsc_unknown_values(struct kunit *test) +{ + struct drm_hdmi_info *hdmi; + struct dc_edid_caps *caps; + + hdmi = kunit_kzalloc(test, sizeof(*hdmi), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, hdmi); + caps = kunit_kzalloc(test, sizeof(*caps), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, caps); + + hdmi->max_lanes = 2; + hdmi->max_frl_rate_per_lane = 3; + hdmi->dsc_cap.v_1p2 = true; + hdmi->dsc_cap.bpc_supported = 8; + hdmi->dsc_cap.max_slices = 3; + hdmi->dsc_cap.clk_per_slice = 340; + hdmi->dsc_cap.max_lanes = 2; + hdmi->dsc_cap.max_frl_rate_per_lane = 12; + + populate_hdmi_info_from_connector(true, hdmi, caps); + + KUNIT_EXPECT_EQ(test, caps->max_frl_rate, 0); + KUNIT_EXPECT_TRUE(test, caps->frl_dsc_support); + KUNIT_EXPECT_FALSE(test, caps->frl_dsc_10bpc); + KUNIT_EXPECT_FALSE(test, caps->frl_dsc_12bpc); + KUNIT_EXPECT_EQ(test, caps->frl_dsc_max_slices, 0); + KUNIT_EXPECT_EQ(test, caps->frl_dsc_max_frl_rate, 0); +} + +/* Tests for dm_get_adaptive_sync_support_type() */ + +/** + * dm_test_adaptive_sync_type_none_default - Test Adaptive sync type none default + * @test: The KUnit test context + */ +static void dm_test_adaptive_sync_type_none_default(struct kunit *test) +{ + struct dc_link *link; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + /* dongle_type = 0 (DISPLAY_DONGLE_NONE) → default case → TYPE_NONE */ + KUNIT_EXPECT_EQ(test, + (int)dm_get_adaptive_sync_support_type(link), + (int)ADAPTIVE_SYNC_TYPE_NONE); +} + +/** + * dm_test_adaptive_sync_type_converter_no_conditions - Converter without caps + * @test: The KUnit test context + */ +static void dm_test_adaptive_sync_type_converter_no_conditions(struct kunit *test) +{ + struct dc_link *link; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + /* HDMI converter but no adaptive sync cap → still NONE */ + link->dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_HDMI_CONVERTER; + + KUNIT_EXPECT_EQ(test, + (int)dm_get_adaptive_sync_support_type(link), + (int)ADAPTIVE_SYNC_TYPE_NONE); +} + +/** + * dm_test_adaptive_sync_type_converter_partial_conditions - Partial caps + * @test: The KUnit test context + */ +static void dm_test_adaptive_sync_type_converter_partial_conditions(struct kunit *test) +{ + struct dc_link *link; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + /* Cap set and whitelist ID, but allow_invalid_MSA_timing_param = false */ + link->dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_HDMI_CONVERTER; + link->dpcd_caps.adaptive_sync_caps.dp_adap_sync_caps.bits.ADAPTIVE_SYNC_SDP_SUPPORT = 1; + link->dpcd_caps.allow_invalid_MSA_timing_param = false; + link->dpcd_caps.branch_dev_id = DP_BRANCH_DEVICE_ID_0060AD; + + KUNIT_EXPECT_EQ(test, + (int)dm_get_adaptive_sync_support_type(link), + (int)ADAPTIVE_SYNC_TYPE_NONE); +} + +/** + * dm_test_adaptive_sync_type_pcon_whitelist - Test Adaptive sync type pcon whitelist + * @test: The KUnit test context + */ +static void dm_test_adaptive_sync_type_pcon_whitelist(struct kunit *test) +{ + struct dc_link *link; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + /* All conditions met → FREESYNC_TYPE_PCON_IN_WHITELIST */ + link->dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_HDMI_CONVERTER; + link->dpcd_caps.adaptive_sync_caps.dp_adap_sync_caps.bits.ADAPTIVE_SYNC_SDP_SUPPORT = 1; + link->dpcd_caps.allow_invalid_MSA_timing_param = true; + link->dpcd_caps.branch_dev_id = DP_BRANCH_DEVICE_ID_0060AD; + + KUNIT_EXPECT_EQ(test, + (int)dm_get_adaptive_sync_support_type(link), + (int)FREESYNC_TYPE_PCON_IN_WHITELIST); +} + +/** + * dm_test_adaptive_sync_type_converter_nonwhitelist - Converter not whitelisted + * @test: The KUnit test context + */ +static void dm_test_adaptive_sync_type_converter_nonwhitelist(struct kunit *test) +{ + struct dc_link *link; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + /* All conditions met but branch_dev_id not in whitelist → NONE */ + link->dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_HDMI_CONVERTER; + link->dpcd_caps.adaptive_sync_caps.dp_adap_sync_caps.bits.ADAPTIVE_SYNC_SDP_SUPPORT = 1; + link->dpcd_caps.allow_invalid_MSA_timing_param = true; + link->dpcd_caps.branch_dev_id = 0xFFFFFF; + + KUNIT_EXPECT_EQ(test, + (int)dm_get_adaptive_sync_support_type(link), + (int)ADAPTIVE_SYNC_TYPE_NONE); +} + +/* Tests for dm_helpers_is_fullscreen() and dm_helpers_is_hdr_on() */ + +/** + * dm_test_helpers_is_fullscreen_returns_false - Test Helpers is fullscreen returns false + * @test: The KUnit test context + */ +static void dm_test_helpers_is_fullscreen_returns_false(struct kunit *test) +{ + /* Stub — always returns false */ + KUNIT_EXPECT_FALSE(test, dm_helpers_is_fullscreen(NULL, NULL)); +} + +/** + * dm_test_helpers_is_hdr_on_returns_false - Test Helpers is hdr on returns false + * @test: The KUnit test context + */ +static void dm_test_helpers_is_hdr_on_returns_false(struct kunit *test) +{ + /* Stub — always returns false */ + KUNIT_EXPECT_FALSE(test, dm_helpers_is_hdr_on(NULL, NULL)); +} + +/* Tests for get_max_frl_rate() */ + +/** + * dm_test_get_max_frl_rate_3lanes_3gbps - Test Get max frl rate 3lanes 3gbps + * @test: The KUnit test context + */ +static void dm_test_get_max_frl_rate_3lanes_3gbps(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, get_max_frl_rate(3, 3), 1); +} + +/** + * dm_test_get_max_frl_rate_3lanes_6gbps - Test Get max frl rate 3lanes 6gbps + * @test: The KUnit test context + */ +static void dm_test_get_max_frl_rate_3lanes_6gbps(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, get_max_frl_rate(3, 6), 2); +} + +/** + * dm_test_get_max_frl_rate_4lanes_6gbps - Test Get max frl rate 4lanes 6gbps + * @test: The KUnit test context + */ +static void dm_test_get_max_frl_rate_4lanes_6gbps(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, get_max_frl_rate(4, 6), 3); +} + +/** + * dm_test_get_max_frl_rate_4lanes_8gbps - Test Get max frl rate 4lanes 8gbps + * @test: The KUnit test context + */ +static void dm_test_get_max_frl_rate_4lanes_8gbps(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, get_max_frl_rate(4, 8), 4); +} + +/** + * dm_test_get_max_frl_rate_4lanes_10gbps - Test Get max frl rate 4lanes 10gbps + * @test: The KUnit test context + */ +static void dm_test_get_max_frl_rate_4lanes_10gbps(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, get_max_frl_rate(4, 10), 5); +} + +/** + * dm_test_get_max_frl_rate_4lanes_12gbps - Test Get max frl rate 4lanes 12gbps + * @test: The KUnit test context + */ +static void dm_test_get_max_frl_rate_4lanes_12gbps(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, get_max_frl_rate(4, 12), 6); +} + +/** + * dm_test_get_max_frl_rate_unknown - Test Get max frl rate unknown + * @test: The KUnit test context + */ +static void dm_test_get_max_frl_rate_unknown(struct kunit *test) +{ + /* Unknown lane/rate combination → 0 */ + KUNIT_EXPECT_EQ(test, get_max_frl_rate(2, 3), 0); +} + +/* Tests for dm_dtn_log_begin() / dm_dtn_log_append_v() / dm_dtn_log_end() */ + +/** + * dm_test_dtn_log_buffer_accumulates - Test DTN log buffer accumulation + * @test: The KUnit test context + */ +static void dm_test_dtn_log_buffer_accumulates(struct kunit *test) +{ + struct dc_log_buffer_ctx log_ctx = {0}; + + dm_dtn_log_begin(NULL, &log_ctx); + dm_dtn_log_append_v(NULL, &log_ctx, "x=%d\n", 7); + dm_dtn_log_end(NULL, &log_ctx); + + KUNIT_ASSERT_NOT_NULL(test, log_ctx.buf); + KUNIT_EXPECT_STREQ(test, log_ctx.buf, "[dtn begin]\nx=7\n[dtn end]\n"); + KUNIT_EXPECT_EQ(test, log_ctx.pos, strlen("[dtn begin]\nx=7\n[dtn end]\n")); + + kvfree(log_ctx.buf); +} + +/** + * dm_test_dtn_log_null_ctx_no_crash - Test DTN log helpers with NULL log buffer + * @test: The KUnit test context + */ +static void dm_test_dtn_log_null_ctx_no_crash(struct kunit *test) +{ + /* NULL log_ctx redirects to dmesg and must not dereference a buffer */ + dm_dtn_log_begin(NULL, NULL); + dm_dtn_log_append_v(NULL, NULL, "value %d\n", 1); + dm_dtn_log_end(NULL, NULL); + + KUNIT_EXPECT_TRUE(test, true); +} + +/* Tests for dm_helpers_dp_read_dpcd() / dm_helpers_dp_write_dpcd() */ + +/** + * dm_test_dp_read_dpcd_null_priv - Test DPCD read returns false without connector + * @test: The KUnit test context + */ +static void dm_test_dp_read_dpcd_null_priv(struct kunit *test) +{ + struct dc_link *link; + uint8_t data = 0; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + /* link->priv (aconnector) is NULL → early return false */ + KUNIT_EXPECT_FALSE(test, + dm_helpers_dp_read_dpcd(NULL, link, 0, &data, sizeof(data))); +} + +/** + * dm_test_dp_write_dpcd_null_priv - Test DPCD write returns false without connector + * @test: The KUnit test context + */ +static void dm_test_dp_write_dpcd_null_priv(struct kunit *test) +{ + struct dc_link *link; + uint8_t data = 0; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + /* link->priv (aconnector) is NULL → early return false */ + KUNIT_EXPECT_FALSE(test, + dm_helpers_dp_write_dpcd(NULL, link, 0, &data, sizeof(data))); +} + +/* Tests for dm_helpers_dp_mst_start_top_mgr() / dm_helpers_dp_mst_stop_top_mgr() */ + +/** + * dm_test_mst_start_top_mgr_null_priv - Test MST start returns false without connector + * @test: The KUnit test context + */ +static void dm_test_mst_start_top_mgr_null_priv(struct kunit *test) +{ + struct dc_link *link; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + KUNIT_EXPECT_FALSE(test, dm_helpers_dp_mst_start_top_mgr(NULL, link, false)); +} + +/** + * dm_test_mst_stop_top_mgr_null_priv - Test MST stop returns false without connector + * @test: The KUnit test context + */ +static void dm_test_mst_stop_top_mgr_null_priv(struct kunit *test) +{ + struct dc_link *link; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + KUNIT_EXPECT_FALSE(test, dm_helpers_dp_mst_stop_top_mgr(NULL, link)); +} + +/** + * dm_test_mst_start_top_mgr_boot - Test MST start boot path on a connector-backed link + * @test: The KUnit test context + * + * Uses the DRM KUnit mock device to back the connector so the link is a + * realistic connector-backed link. The boot path short-circuits and returns + * true without touching the MST topology manager. + */ +static void dm_test_mst_start_top_mgr_boot(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct amdgpu_device *adev; + struct drm_device *drm; + struct device *dev; + struct dc_link *link; + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + drm = __drm_kunit_helper_alloc_drm_device(test, dev, + sizeof(*adev), + offsetof(struct amdgpu_device, ddev), + DRIVER_MODESET); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + adev = drm_to_adev(drm); + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + aconnector->base.dev = drm; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + link->priv = aconnector; + + KUNIT_EXPECT_TRUE(test, dm_helpers_dp_mst_start_top_mgr(NULL, link, true)); +} + +/* Tests for dm_helpers_dp_write_hblank_reduction() */ + +/** + * dm_test_dp_write_hblank_reduction_false - Test hblank reduction stub returns false + * @test: The KUnit test context + */ +static void dm_test_dp_write_hblank_reduction_false(struct kunit *test) +{ + KUNIT_EXPECT_FALSE(test, dm_helpers_dp_write_hblank_reduction(NULL, NULL)); +} + +static struct kunit_case amdgpu_dm_helpers_test_cases[] = { + /* edid_extract_panel_id */ + KUNIT_CASE(dm_test_edid_extract_panel_id_basic), + KUNIT_CASE(dm_test_edid_extract_panel_id_zeros), + /* dm_is_freesync_pcon_whitelist */ + KUNIT_CASE(dm_test_freesync_pcon_whitelist_all_known), + KUNIT_CASE(dm_test_freesync_pcon_whitelist_not_in_list), + KUNIT_CASE(dm_test_freesync_pcon_whitelist_zero), + /* populate_hdmi_info_from_connector */ + KUNIT_CASE(dm_test_populate_hdmi_scdc_present_true), + KUNIT_CASE(dm_test_populate_hdmi_scdc_present_false), + KUNIT_CASE(dm_test_populate_hdmi_frl_dsc_10bpc), + KUNIT_CASE(dm_test_populate_hdmi_frl_dsc_12bpc), + KUNIT_CASE(dm_test_populate_hdmi_frl_dsc_unknown_values), + /* dm_get_adaptive_sync_support_type */ + KUNIT_CASE(dm_test_adaptive_sync_type_none_default), + KUNIT_CASE(dm_test_adaptive_sync_type_converter_no_conditions), + KUNIT_CASE(dm_test_adaptive_sync_type_converter_partial_conditions), + KUNIT_CASE(dm_test_adaptive_sync_type_pcon_whitelist), + KUNIT_CASE(dm_test_adaptive_sync_type_converter_nonwhitelist), + /* dm_helpers_is_fullscreen / dm_helpers_is_hdr_on */ + KUNIT_CASE(dm_test_helpers_is_fullscreen_returns_false), + KUNIT_CASE(dm_test_helpers_is_hdr_on_returns_false), + /* get_max_frl_rate */ + KUNIT_CASE(dm_test_get_max_frl_rate_3lanes_3gbps), + KUNIT_CASE(dm_test_get_max_frl_rate_3lanes_6gbps), + KUNIT_CASE(dm_test_get_max_frl_rate_4lanes_6gbps), + KUNIT_CASE(dm_test_get_max_frl_rate_4lanes_8gbps), + KUNIT_CASE(dm_test_get_max_frl_rate_4lanes_10gbps), + KUNIT_CASE(dm_test_get_max_frl_rate_4lanes_12gbps), + KUNIT_CASE(dm_test_get_max_frl_rate_unknown), + /* dm_dtn_log_begin / dm_dtn_log_append_v / dm_dtn_log_end */ + KUNIT_CASE(dm_test_dtn_log_buffer_accumulates), + KUNIT_CASE(dm_test_dtn_log_null_ctx_no_crash), + /* dm_helpers_dp_read_dpcd / dm_helpers_dp_write_dpcd */ + KUNIT_CASE(dm_test_dp_read_dpcd_null_priv), + KUNIT_CASE(dm_test_dp_write_dpcd_null_priv), + /* dm_helpers_dp_mst_start_top_mgr / dm_helpers_dp_mst_stop_top_mgr */ + KUNIT_CASE(dm_test_mst_start_top_mgr_null_priv), + KUNIT_CASE(dm_test_mst_stop_top_mgr_null_priv), + KUNIT_CASE(dm_test_mst_start_top_mgr_boot), + /* dm_helpers_dp_write_hblank_reduction */ + KUNIT_CASE(dm_test_dp_write_hblank_reduction_false), + {} +}; + +static struct kunit_suite amdgpu_dm_helpers_test_suite = { + .name = "amdgpu_dm_helpers", + .test_cases = amdgpu_dm_helpers_test_cases, +}; + +kunit_test_suite(amdgpu_dm_helpers_test_suite); + +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_helpers"); +MODULE_LICENSE("Dual MIT/GPL"); -- cgit v1.2.3 From 43531d423240095579ef8df6efc91e8f25840a04 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Thu, 7 May 2026 10:56:40 -0600 Subject: drm/amd/display: Add KUnit tests for amdgpu_dm_quirks Add KUnit test file amdgpu_dm_quirks_test.c covering retrieve_dmi_info(). Three test cases are provided: - Verify aux_hpd_discon_quirk is reset to false even when previously true - Verify edp0_on_dp1_quirk is reset to false even when previously true - Verify both quirks remain false on a zero-initialised dm when no DMI match is found (expected in UML/KUnit environment) Register the new test object in the tests/Makefile under CONFIG_DRM_AMD_DC_KUNIT_TEST. Assisted-by: Copilot:Claude-Sonnet-4.6 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- .../drm/amd/display/amdgpu_dm/amdgpu_dm_quirks.c | 2 + .../gpu/drm/amd/display/amdgpu_dm/tests/Makefile | 1 + .../amdgpu_dm/tests/amdgpu_dm_quirks_test.c | 103 +++++++++++++++++++++ 3 files changed, 106 insertions(+) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_quirks_test.c (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_quirks.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_quirks.c index 1da07ebf9217..cf28d50c3b5e 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_quirks.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_quirks.c @@ -28,6 +28,7 @@ #include "amdgpu.h" #include "amdgpu_dm.h" +#include "amdgpu_dm_kunit_helpers.h" struct amdgpu_dm_quirks { bool aux_hpd_discon; @@ -176,3 +177,4 @@ void retrieve_dmi_info(struct amdgpu_display_manager *dm) drm_info(dev, "support_edp0_on_dp1 attached\n"); } } +EXPORT_IF_KUNIT(retrieve_dmi_info); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile index a067332f9f41..168ad064e7cb 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile @@ -30,3 +30,4 @@ obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_crtc_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_services_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_helpers_test.o +obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_quirks_test.o diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_quirks_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_quirks_test.c new file mode 100644 index 000000000000..a09f31ee0a2a --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_quirks_test.c @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit tests for amdgpu_dm_quirks.c + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include + +#include "dc.h" +#include "amdgpu_mode.h" +#include "amdgpu_dm.h" + +/* Tests for retrieve_dmi_info() */ + +/* + * Verify that retrieve_dmi_info() always initialises aux_hpd_discon_quirk to + * false, even when the caller had previously set it to true. + */ +/** + * dm_test_quirks_aux_hpd_discon_reset - Test Quirks aux hpd discon reset + * @test: The KUnit test context + */ +static void dm_test_quirks_aux_hpd_discon_reset(struct kunit *test) +{ + struct amdgpu_display_manager *dm; + + dm = kunit_kzalloc(test, sizeof(*dm), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm); + + dm->aux_hpd_discon_quirk = true; + + retrieve_dmi_info(dm); + + /* + * In a KUnit / UML environment no real DMI table is present, so + * dmi_check_system() returns 0 and retrieve_dmi_info() leaves the + * quirk at its initialised-to-false value. + */ + KUNIT_EXPECT_FALSE(test, dm->aux_hpd_discon_quirk); +} + +/* + * Verify that retrieve_dmi_info() always initialises edp0_on_dp1_quirk to + * false, even when the caller had previously set it to true. + */ +/** + * dm_test_quirks_edp0_on_dp1_reset - Test Quirks edp0 on dp1 reset + * @test: The KUnit test context + */ +static void dm_test_quirks_edp0_on_dp1_reset(struct kunit *test) +{ + struct amdgpu_display_manager *dm; + + dm = kunit_kzalloc(test, sizeof(*dm), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm); + + dm->edp0_on_dp1_quirk = true; + + retrieve_dmi_info(dm); + + KUNIT_EXPECT_FALSE(test, dm->edp0_on_dp1_quirk); +} + +/* + * Verify that when no DMI match is found both quirks remain false after a + * fresh (zero-initialised) dm is passed to retrieve_dmi_info(). + */ +/** + * dm_test_quirks_no_dmi_match_both_false - Test Quirks no dmi match both false + * @test: The KUnit test context + */ +static void dm_test_quirks_no_dmi_match_both_false(struct kunit *test) +{ + struct amdgpu_display_manager *dm; + + dm = kunit_kzalloc(test, sizeof(*dm), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm); + + retrieve_dmi_info(dm); + + KUNIT_EXPECT_FALSE(test, dm->aux_hpd_discon_quirk); + KUNIT_EXPECT_FALSE(test, dm->edp0_on_dp1_quirk); +} + +static struct kunit_case amdgpu_dm_quirks_tests[] = { + /* retrieve_dmi_info */ + KUNIT_CASE(dm_test_quirks_aux_hpd_discon_reset), + KUNIT_CASE(dm_test_quirks_edp0_on_dp1_reset), + KUNIT_CASE(dm_test_quirks_no_dmi_match_both_false), + {} +}; + +static struct kunit_suite amdgpu_dm_quirks_test_suite = { + .name = "amdgpu_dm_quirks", + .test_cases = amdgpu_dm_quirks_tests, +}; + +kunit_test_suite(amdgpu_dm_quirks_test_suite); + +MODULE_AUTHOR("AMD"); +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_quirks"); +MODULE_LICENSE("Dual MIT/GPL"); -- cgit v1.2.3 From 652021e4be963b5ec1c86ba844fd40d0e01decc9 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Fri, 29 May 2026 17:12:31 -0600 Subject: drm/amd/display: Add more KUnit tests for amdgpu_dm_pp_smu Expand KUnit coverage of amdgpu_dm_pp_smu.c and extract several pure translation helpers so they can be unit tested in isolation. Extract pure logic into testable helpers: - build_pm_display_cfg() from dm_pp_apply_display_requirements() - build_wm_clock_ranges_soc15() from pp_rv_set_wm_ranges() - cap_clock_levels_to_validation() from dm_pp_get_clock_levels_by_type() - pp_smu_nv_clock_id_to_pp() from pp_nv_set_voltage_by_freq() Tests cover: - pp_to_dc_clock_levels: within-limit copy and count capping - pp_to_dc_clock_levels_with_latency: field copy and count capping - pp_to_dc_clock_levels_with_voltage: field copy and count capping - dm_pp_get_funcs: RV, RV 1.01, NV, RN, and unsupported versions - dm_pp_apply_display_requirements: DPM-disabled early-return path - dm_pp_apply_clock_for_voltage_request: invalid clock type path - build_pm_display_cfg: scalar field scaling and per-display mapping - build_wm_clock_ranges_soc15: DMIF and MCIF range translation - cap_clock_levels_to_validation: engine/memory capping and floor - pp_smu_nv_clock_id_to_pp: valid ids and invalid-id rejection Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- .../drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c | 234 ++++--- .../drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.h | 22 + .../amdgpu_dm/tests/amdgpu_dm_pp_smu_test.c | 736 +++++++++++++++++++++ 3 files changed, 889 insertions(+), 103 deletions(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c index ca7141dbdf6a..e0fe4cb97f31 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c @@ -36,72 +36,64 @@ #include "amdgpu_dm_kunit_helpers.h" #include "amdgpu_dm_pp_smu.h" -bool dm_pp_apply_display_requirements( - const struct dc_context *ctx, +STATIC_IFN_KUNIT void build_pm_display_cfg( + struct amd_pp_display_configuration *pm_display_cfg, const struct dm_pp_display_configuration *pp_display_cfg) { - struct amdgpu_device *adev = ctx->driver_context; int i; - if (adev->pm.dpm_enabled) { + memset(pm_display_cfg, 0, sizeof(*pm_display_cfg)); - memset(&adev->pm.pm_display_cfg, 0, - sizeof(adev->pm.pm_display_cfg)); + pm_display_cfg->cpu_cc6_disable = pp_display_cfg->cpu_cc6_disable; + pm_display_cfg->cpu_pstate_disable = pp_display_cfg->cpu_pstate_disable; + pm_display_cfg->cpu_pstate_separation_time = pp_display_cfg->cpu_pstate_separation_time; + pm_display_cfg->nb_pstate_switch_disable = pp_display_cfg->nb_pstate_switch_disable; - adev->pm.pm_display_cfg.cpu_cc6_disable = - pp_display_cfg->cpu_cc6_disable; + pm_display_cfg->num_display = pp_display_cfg->display_count; + pm_display_cfg->num_path_including_non_display = pp_display_cfg->display_count; - adev->pm.pm_display_cfg.cpu_pstate_disable = - pp_display_cfg->cpu_pstate_disable; + pm_display_cfg->min_core_set_clock = pp_display_cfg->min_engine_clock_khz/10; + pm_display_cfg->min_core_set_clock_in_sr = + pp_display_cfg->min_engine_clock_deep_sleep_khz/10; + pm_display_cfg->min_mem_set_clock = pp_display_cfg->min_memory_clock_khz/10; - adev->pm.pm_display_cfg.cpu_pstate_separation_time = - pp_display_cfg->cpu_pstate_separation_time; + pm_display_cfg->min_dcef_deep_sleep_set_clk = + pp_display_cfg->min_engine_clock_deep_sleep_khz/10; + pm_display_cfg->min_dcef_set_clk = pp_display_cfg->min_dcfclock_khz/10; - adev->pm.pm_display_cfg.nb_pstate_switch_disable = - pp_display_cfg->nb_pstate_switch_disable; + pm_display_cfg->multi_monitor_in_sync = pp_display_cfg->all_displays_in_sync; + pm_display_cfg->min_vblank_time = pp_display_cfg->avail_mclk_switch_time_us; - adev->pm.pm_display_cfg.num_display = - pp_display_cfg->display_count; - adev->pm.pm_display_cfg.num_path_including_non_display = - pp_display_cfg->display_count; + pm_display_cfg->display_clk = pp_display_cfg->disp_clk_khz/10; - adev->pm.pm_display_cfg.min_core_set_clock = - pp_display_cfg->min_engine_clock_khz/10; - adev->pm.pm_display_cfg.min_core_set_clock_in_sr = - pp_display_cfg->min_engine_clock_deep_sleep_khz/10; - adev->pm.pm_display_cfg.min_mem_set_clock = - pp_display_cfg->min_memory_clock_khz/10; + pm_display_cfg->dce_tolerable_mclk_in_active_latency = + pp_display_cfg->avail_mclk_switch_time_in_disp_active_us; - adev->pm.pm_display_cfg.min_dcef_deep_sleep_set_clk = - pp_display_cfg->min_engine_clock_deep_sleep_khz/10; - adev->pm.pm_display_cfg.min_dcef_set_clk = - pp_display_cfg->min_dcfclock_khz/10; + pm_display_cfg->crtc_index = pp_display_cfg->crtc_index; + pm_display_cfg->line_time_in_us = pp_display_cfg->line_time_in_us; - adev->pm.pm_display_cfg.multi_monitor_in_sync = - pp_display_cfg->all_displays_in_sync; - adev->pm.pm_display_cfg.min_vblank_time = - pp_display_cfg->avail_mclk_switch_time_us; + pm_display_cfg->vrefresh = pp_display_cfg->disp_configs[0].v_refresh; + pm_display_cfg->crossfire_display_index = -1; + pm_display_cfg->min_bus_bandwidth = 0; - adev->pm.pm_display_cfg.display_clk = - pp_display_cfg->disp_clk_khz/10; - - adev->pm.pm_display_cfg.dce_tolerable_mclk_in_active_latency = - pp_display_cfg->avail_mclk_switch_time_in_disp_active_us; + for (i = 0; i < pp_display_cfg->display_count; i++) { + const struct dm_pp_single_disp_config *dc_cfg = + &pp_display_cfg->disp_configs[i]; + pm_display_cfg->displays[i].controller_id = dc_cfg->pipe_idx + 1; + pm_display_cfg->displays[i].pixel_clock = dc_cfg->pixel_clock; + } +} +EXPORT_IF_KUNIT(build_pm_display_cfg); - adev->pm.pm_display_cfg.crtc_index = pp_display_cfg->crtc_index; - adev->pm.pm_display_cfg.line_time_in_us = - pp_display_cfg->line_time_in_us; +bool dm_pp_apply_display_requirements( + const struct dc_context *ctx, + const struct dm_pp_display_configuration *pp_display_cfg) +{ + struct amdgpu_device *adev = ctx->driver_context; - adev->pm.pm_display_cfg.vrefresh = pp_display_cfg->disp_configs[0].v_refresh; - adev->pm.pm_display_cfg.crossfire_display_index = -1; - adev->pm.pm_display_cfg.min_bus_bandwidth = 0; + if (adev->pm.dpm_enabled) { - for (i = 0; i < pp_display_cfg->display_count; i++) { - const struct dm_pp_single_disp_config *dc_cfg = - &pp_display_cfg->disp_configs[i]; - adev->pm.pm_display_cfg.displays[i].controller_id = dc_cfg->pipe_idx + 1; - adev->pm.pm_display_cfg.displays[i].pixel_clock = dc_cfg->pixel_clock; - } + build_pm_display_cfg(&adev->pm.pm_display_cfg, pp_display_cfg); amdgpu_dpm_display_configuration_change(adev, &adev->pm.pm_display_cfg); @@ -110,6 +102,7 @@ bool dm_pp_apply_display_requirements( return true; } +EXPORT_IF_KUNIT(dm_pp_apply_display_requirements); STATIC_IFN_KUNIT void get_default_clock_levels( enum dm_pp_clock_type clk_type, @@ -187,7 +180,7 @@ STATIC_IFN_KUNIT enum amd_pp_clock_type dc_to_pp_clock_type( } EXPORT_IF_KUNIT(dc_to_pp_clock_type); -static void pp_to_dc_clock_levels( +STATIC_IFN_KUNIT void pp_to_dc_clock_levels( const struct amd_pp_clocks *pp_clks, struct dm_pp_clock_levels *dc_clks, enum dm_pp_clock_type dc_clk_type) @@ -212,8 +205,9 @@ static void pp_to_dc_clock_levels( dc_clks->clocks_in_khz[i] = pp_clks->clock[i]; } } +EXPORT_IF_KUNIT(pp_to_dc_clock_levels); -static void pp_to_dc_clock_levels_with_latency( +STATIC_IFN_KUNIT void pp_to_dc_clock_levels_with_latency( const struct pp_clock_levels_with_latency *pp_clks, struct dm_pp_clock_levels_with_latency *clk_level_info, enum dm_pp_clock_type dc_clk_type) @@ -239,8 +233,9 @@ static void pp_to_dc_clock_levels_with_latency( clk_level_info->data[i].latency_in_us = pp_clks->data[i].latency_in_us; } } +EXPORT_IF_KUNIT(pp_to_dc_clock_levels_with_latency); -static void pp_to_dc_clock_levels_with_voltage( +STATIC_IFN_KUNIT void pp_to_dc_clock_levels_with_voltage( const struct pp_clock_levels_with_voltage *pp_clks, struct dm_pp_clock_levels_with_voltage *clk_level_info, enum dm_pp_clock_type dc_clk_type) @@ -267,6 +262,41 @@ static void pp_to_dc_clock_levels_with_voltage( clk_level_info->data[i].voltage_in_mv = pp_clks->data[i].voltage_in_mv; } } +EXPORT_IF_KUNIT(pp_to_dc_clock_levels_with_voltage); + +STATIC_IFN_KUNIT void cap_clock_levels_to_validation( + struct dm_pp_clock_levels *dc_clks, + enum dm_pp_clock_type clk_type, + const struct amd_pp_simple_clock_info *validation_clks) +{ + uint32_t i; + + /* Determine the highest non-boosted level from the Validation Clocks */ + if (clk_type == DM_PP_CLOCK_TYPE_ENGINE_CLK) { + for (i = 0; i < dc_clks->num_levels; i++) { + if (dc_clks->clocks_in_khz[i] > validation_clks->engine_max_clock) { + /* This clock is higher the validation clock. + * Than means the previous one is the highest + * non-boosted one. + */ + DRM_INFO("DM_PPLIB: reducing engine clock level from %d to %d\n", + dc_clks->num_levels, i); + dc_clks->num_levels = i > 0 ? i : 1; + break; + } + } + } else if (clk_type == DM_PP_CLOCK_TYPE_MEMORY_CLK) { + for (i = 0; i < dc_clks->num_levels; i++) { + if (dc_clks->clocks_in_khz[i] > validation_clks->memory_max_clock) { + DRM_INFO("DM_PPLIB: reducing memory clock level from %d to %d\n", + dc_clks->num_levels, i); + dc_clks->num_levels = i > 0 ? i : 1; + break; + } + } + } +} +EXPORT_IF_KUNIT(cap_clock_levels_to_validation); bool dm_pp_get_clock_levels_by_type( const struct dc_context *ctx, @@ -276,7 +306,6 @@ bool dm_pp_get_clock_levels_by_type( struct amdgpu_device *adev = ctx->driver_context; struct amd_pp_clocks pp_clks = { 0 }; struct amd_pp_simple_clock_info validation_clks = { 0 }; - uint32_t i; if (amdgpu_dpm_get_clock_by_type(adev, dc_to_pp_clock_type(clk_type), &pp_clks)) { @@ -304,30 +333,7 @@ bool dm_pp_get_clock_levels_by_type( validation_clks.engine_max_clock *= 10; validation_clks.memory_max_clock *= 10; - /* Determine the highest non-boosted level from the Validation Clocks */ - if (clk_type == DM_PP_CLOCK_TYPE_ENGINE_CLK) { - for (i = 0; i < dc_clks->num_levels; i++) { - if (dc_clks->clocks_in_khz[i] > validation_clks.engine_max_clock) { - /* This clock is higher the validation clock. - * Than means the previous one is the highest - * non-boosted one. - */ - DRM_INFO("DM_PPLIB: reducing engine clock level from %d to %d\n", - dc_clks->num_levels, i); - dc_clks->num_levels = i > 0 ? i : 1; - break; - } - } - } else if (clk_type == DM_PP_CLOCK_TYPE_MEMORY_CLK) { - for (i = 0; i < dc_clks->num_levels; i++) { - if (dc_clks->clocks_in_khz[i] > validation_clks.memory_max_clock) { - DRM_INFO("DM_PPLIB: reducing memory clock level from %d to %d\n", - dc_clks->num_levels, i); - dc_clks->num_levels = i > 0 ? i : 1; - break; - } - } - } + cap_clock_levels_to_validation(dc_clks, clk_type, &validation_clks); return true; } @@ -411,26 +417,26 @@ bool dm_pp_apply_clock_for_voltage_request( return true; } +EXPORT_IF_KUNIT(dm_pp_apply_clock_for_voltage_request); -static void pp_rv_set_wm_ranges(struct pp_smu *pp, - struct pp_smu_wm_range_sets *ranges) +STATIC_IFN_KUNIT void build_wm_clock_ranges_soc15( + const struct pp_smu_wm_range_sets *ranges, + struct dm_pp_wm_sets_with_clock_ranges_soc15 *wm_with_clock_ranges) { - const struct dc_context *ctx = pp->dm; - struct amdgpu_device *adev = ctx->driver_context; - struct dm_pp_wm_sets_with_clock_ranges_soc15 wm_with_clock_ranges; - struct dm_pp_clock_range_for_dmif_wm_set_soc15 *wm_dce_clocks = wm_with_clock_ranges.wm_dmif_clocks_ranges; - struct dm_pp_clock_range_for_mcif_wm_set_soc15 *wm_soc_clocks = wm_with_clock_ranges.wm_mcif_clocks_ranges; + struct dm_pp_clock_range_for_dmif_wm_set_soc15 *wm_dce_clocks = + wm_with_clock_ranges->wm_dmif_clocks_ranges; + struct dm_pp_clock_range_for_mcif_wm_set_soc15 *wm_soc_clocks = + wm_with_clock_ranges->wm_mcif_clocks_ranges; int32_t i; - wm_with_clock_ranges.num_wm_dmif_sets = ranges->num_reader_wm_sets; - wm_with_clock_ranges.num_wm_mcif_sets = ranges->num_writer_wm_sets; + wm_with_clock_ranges->num_wm_dmif_sets = ranges->num_reader_wm_sets; + wm_with_clock_ranges->num_wm_mcif_sets = ranges->num_writer_wm_sets; - for (i = 0; i < wm_with_clock_ranges.num_wm_dmif_sets; i++) { + for (i = 0; i < wm_with_clock_ranges->num_wm_dmif_sets; i++) { if (ranges->reader_wm_sets[i].wm_inst > 3) wm_dce_clocks[i].wm_set_id = WM_SET_A; else - wm_dce_clocks[i].wm_set_id = - ranges->reader_wm_sets[i].wm_inst; + wm_dce_clocks[i].wm_set_id = ranges->reader_wm_sets[i].wm_inst; wm_dce_clocks[i].wm_max_dcfclk_clk_in_khz = ranges->reader_wm_sets[i].max_drain_clk_mhz * 1000; wm_dce_clocks[i].wm_min_dcfclk_clk_in_khz = @@ -441,12 +447,11 @@ static void pp_rv_set_wm_ranges(struct pp_smu *pp, ranges->reader_wm_sets[i].min_fill_clk_mhz * 1000; } - for (i = 0; i < wm_with_clock_ranges.num_wm_mcif_sets; i++) { + for (i = 0; i < wm_with_clock_ranges->num_wm_mcif_sets; i++) { if (ranges->writer_wm_sets[i].wm_inst > 3) wm_soc_clocks[i].wm_set_id = WM_SET_A; else - wm_soc_clocks[i].wm_set_id = - ranges->writer_wm_sets[i].wm_inst; + wm_soc_clocks[i].wm_set_id = ranges->writer_wm_sets[i].wm_inst; wm_soc_clocks[i].wm_max_socclk_clk_in_khz = ranges->writer_wm_sets[i].max_fill_clk_mhz * 1000; wm_soc_clocks[i].wm_min_socclk_clk_in_khz = @@ -456,6 +461,17 @@ static void pp_rv_set_wm_ranges(struct pp_smu *pp, wm_soc_clocks[i].wm_min_mem_clk_in_khz = ranges->writer_wm_sets[i].min_drain_clk_mhz * 1000; } +} +EXPORT_IF_KUNIT(build_wm_clock_ranges_soc15); + +static void pp_rv_set_wm_ranges(struct pp_smu *pp, + struct pp_smu_wm_range_sets *ranges) +{ + const struct dc_context *ctx = pp->dm; + struct amdgpu_device *adev = ctx->driver_context; + struct dm_pp_wm_sets_with_clock_ranges_soc15 wm_with_clock_ranges; + + build_wm_clock_ranges_soc15(ranges, &wm_with_clock_ranges); amdgpu_dpm_set_watermarks_for_clocks_ranges(adev, &wm_with_clock_ranges); @@ -604,27 +620,38 @@ static enum pp_smu_status pp_nv_set_pstate_handshake_support( return PP_SMU_RESULT_OK; } -static enum pp_smu_status pp_nv_set_voltage_by_freq(struct pp_smu *pp, - enum pp_smu_nv_clock_id clock_id, int mhz) +STATIC_IFN_KUNIT bool pp_smu_nv_clock_id_to_pp(enum pp_smu_nv_clock_id clock_id, + enum amd_pp_clock_type *clock_type) { - const struct dc_context *ctx = pp->dm; - struct amdgpu_device *adev = ctx->driver_context; - struct pp_display_clock_request clock_req; - int ret = 0; - switch (clock_id) { case PP_SMU_NV_DISPCLK: - clock_req.clock_type = amd_pp_disp_clock; + *clock_type = amd_pp_disp_clock; break; case PP_SMU_NV_PHYCLK: - clock_req.clock_type = amd_pp_phy_clock; + *clock_type = amd_pp_phy_clock; break; case PP_SMU_NV_PIXELCLK: - clock_req.clock_type = amd_pp_pixel_clock; + *clock_type = amd_pp_pixel_clock; break; default: - break; + return false; } + + return true; +} +EXPORT_IF_KUNIT(pp_smu_nv_clock_id_to_pp); + +static enum pp_smu_status pp_nv_set_voltage_by_freq(struct pp_smu *pp, + enum pp_smu_nv_clock_id clock_id, int mhz) +{ + const struct dc_context *ctx = pp->dm; + struct amdgpu_device *adev = ctx->driver_context; + struct pp_display_clock_request clock_req; + int ret = 0; + + if (!pp_smu_nv_clock_id_to_pp(clock_id, &clock_req.clock_type)) + return PP_SMU_RESULT_FAIL; + clock_req.clock_freq_in_khz = mhz * 1000; /* 0: successful or smu.ppt_funcs->display_clock_voltage_request = NULL @@ -744,3 +771,4 @@ void dm_pp_get_funcs( break; } } +EXPORT_IF_KUNIT(dm_pp_get_funcs); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.h index 827b60d5affe..e851e3ee5b63 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.h @@ -8,9 +8,31 @@ #include "dm_pp_interface.h" +struct amd_pp_display_configuration; +struct pp_smu_wm_range_sets; +struct dm_pp_wm_sets_with_clock_ranges_soc15; + #if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +void build_pm_display_cfg(struct amd_pp_display_configuration *pm_display_cfg, + const struct dm_pp_display_configuration *pp_display_cfg); +void build_wm_clock_ranges_soc15(const struct pp_smu_wm_range_sets *ranges, + struct dm_pp_wm_sets_with_clock_ranges_soc15 *wm_with_clock_ranges); void get_default_clock_levels(enum dm_pp_clock_type clk_type, struct dm_pp_clock_levels *clks); enum amd_pp_clock_type dc_to_pp_clock_type(enum dm_pp_clock_type dm_pp_clk_type); +void pp_to_dc_clock_levels(const struct amd_pp_clocks *pp_clks, + struct dm_pp_clock_levels *dc_clks, + enum dm_pp_clock_type dc_clk_type); +void pp_to_dc_clock_levels_with_latency(const struct pp_clock_levels_with_latency *pp_clks, + struct dm_pp_clock_levels_with_latency *clk_level_info, + enum dm_pp_clock_type dc_clk_type); +void pp_to_dc_clock_levels_with_voltage(const struct pp_clock_levels_with_voltage *pp_clks, + struct dm_pp_clock_levels_with_voltage *clk_level_info, + enum dm_pp_clock_type dc_clk_type); +void cap_clock_levels_to_validation(struct dm_pp_clock_levels *dc_clks, + enum dm_pp_clock_type clk_type, + const struct amd_pp_simple_clock_info *validation_clks); +bool pp_smu_nv_clock_id_to_pp(enum pp_smu_nv_clock_id clock_id, + enum amd_pp_clock_type *clock_type); #endif #endif /* __AMDGPU_DM_PP_SMU_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_pp_smu_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_pp_smu_test.c index 556473f55ebe..dbb6dfd5c284 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_pp_smu_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_pp_smu_test.c @@ -9,6 +9,9 @@ #include #include "dc.h" +#include "dm_services.h" +#include "dm_pp_smu.h" +#include "amdgpu.h" #include "amdgpu_mode.h" #include "amdgpu_dm.h" #include "amdgpu_dm_pp_smu.h" @@ -210,6 +213,704 @@ static void dm_test_dc_to_pp_clock_type_invalid(struct kunit *test) KUNIT_EXPECT_EQ(test, (int)dc_to_pp_clock_type(0), 0); } +/* ---- Tests for pp_to_dc_clock_levels ---- */ + +/** + * dm_test_pp_to_dc_clock_levels_within_limit - Test normal copy within limit + * @test: KUnit test context + * + * Verify that pp_to_dc_clock_levels correctly copies clock values when the + * count is within DM_PP_MAX_CLOCK_LEVELS. + */ +static void dm_test_pp_to_dc_clock_levels_within_limit(struct kunit *test) +{ + struct amd_pp_clocks pp_clks = {}; + struct dm_pp_clock_levels dc_clks = {}; + + pp_clks.count = 3; + pp_clks.clock[0] = 300000; + pp_clks.clock[1] = 500000; + pp_clks.clock[2] = 700000; + + pp_to_dc_clock_levels(&pp_clks, &dc_clks, DM_PP_CLOCK_TYPE_ENGINE_CLK); + + KUNIT_EXPECT_EQ(test, dc_clks.num_levels, 3U); + KUNIT_EXPECT_EQ(test, dc_clks.clocks_in_khz[0], 300000U); + KUNIT_EXPECT_EQ(test, dc_clks.clocks_in_khz[1], 500000U); + KUNIT_EXPECT_EQ(test, dc_clks.clocks_in_khz[2], 700000U); +} + +/** + * dm_test_pp_to_dc_clock_levels_caps_at_max - Test count capping at max + * @test: KUnit test context + * + * Verify that pp_to_dc_clock_levels caps num_levels at DM_PP_MAX_CLOCK_LEVELS + * when the input count exceeds the maximum. + */ +static void dm_test_pp_to_dc_clock_levels_caps_at_max(struct kunit *test) +{ + struct amd_pp_clocks pp_clks = {}; + struct dm_pp_clock_levels dc_clks = {}; + uint32_t i; + + pp_clks.count = DM_PP_MAX_CLOCK_LEVELS + 1; + for (i = 0; i < DM_PP_MAX_CLOCK_LEVELS; i++) + pp_clks.clock[i] = (i + 1) * 100000; + + pp_to_dc_clock_levels(&pp_clks, &dc_clks, DM_PP_CLOCK_TYPE_ENGINE_CLK); + + KUNIT_EXPECT_EQ(test, dc_clks.num_levels, (uint32_t)DM_PP_MAX_CLOCK_LEVELS); +} + +/* ---- Tests for pp_to_dc_clock_levels_with_latency ---- */ + +/** + * dm_test_pp_to_dc_clock_levels_latency_within_limit - Test normal copy + * @test: KUnit test context + * + * Verify that pp_to_dc_clock_levels_with_latency correctly copies clock + * and latency values when count is within limits. + */ +static void dm_test_pp_to_dc_clock_levels_latency_within_limit(struct kunit *test) +{ + struct pp_clock_levels_with_latency pp_clks = {}; + struct dm_pp_clock_levels_with_latency dc_clks = {}; + + pp_clks.num_levels = 2; + pp_clks.data[0].clocks_in_khz = 400000; + pp_clks.data[0].latency_in_us = 10; + pp_clks.data[1].clocks_in_khz = 800000; + pp_clks.data[1].latency_in_us = 20; + + pp_to_dc_clock_levels_with_latency(&pp_clks, &dc_clks, + DM_PP_CLOCK_TYPE_ENGINE_CLK); + + KUNIT_EXPECT_EQ(test, dc_clks.num_levels, 2U); + KUNIT_EXPECT_EQ(test, dc_clks.data[0].clocks_in_khz, 400000U); + KUNIT_EXPECT_EQ(test, dc_clks.data[0].latency_in_us, 10U); + KUNIT_EXPECT_EQ(test, dc_clks.data[1].clocks_in_khz, 800000U); + KUNIT_EXPECT_EQ(test, dc_clks.data[1].latency_in_us, 20U); +} + +/** + * dm_test_pp_to_dc_clock_levels_latency_caps_at_max - Test count capping + * @test: KUnit test context + * + * Verify that pp_to_dc_clock_levels_with_latency caps num_levels at + * DM_PP_MAX_CLOCK_LEVELS when input exceeds the maximum. + */ +static void dm_test_pp_to_dc_clock_levels_latency_caps_at_max(struct kunit *test) +{ + struct pp_clock_levels_with_latency pp_clks = {}; + struct dm_pp_clock_levels_with_latency dc_clks = {}; + + pp_clks.num_levels = DM_PP_MAX_CLOCK_LEVELS + 1; + + pp_to_dc_clock_levels_with_latency(&pp_clks, &dc_clks, + DM_PP_CLOCK_TYPE_ENGINE_CLK); + + KUNIT_EXPECT_EQ(test, dc_clks.num_levels, (uint32_t)DM_PP_MAX_CLOCK_LEVELS); +} + +/* ---- Tests for pp_to_dc_clock_levels_with_voltage ---- */ + +/** + * dm_test_pp_to_dc_clock_levels_voltage_within_limit - Test normal copy + * @test: KUnit test context + * + * Verify that pp_to_dc_clock_levels_with_voltage correctly copies clock + * and voltage values when count is within limits. + */ +static void dm_test_pp_to_dc_clock_levels_voltage_within_limit(struct kunit *test) +{ + struct pp_clock_levels_with_voltage pp_clks = {}; + struct dm_pp_clock_levels_with_voltage dc_clks = {}; + + pp_clks.num_levels = 2; + pp_clks.data[0].clocks_in_khz = 300000; + pp_clks.data[0].voltage_in_mv = 800; + pp_clks.data[1].clocks_in_khz = 600000; + pp_clks.data[1].voltage_in_mv = 950; + + pp_to_dc_clock_levels_with_voltage(&pp_clks, &dc_clks, + DM_PP_CLOCK_TYPE_MEMORY_CLK); + + KUNIT_EXPECT_EQ(test, dc_clks.num_levels, 2U); + KUNIT_EXPECT_EQ(test, dc_clks.data[0].clocks_in_khz, 300000U); + KUNIT_EXPECT_EQ(test, dc_clks.data[0].voltage_in_mv, 800U); + KUNIT_EXPECT_EQ(test, dc_clks.data[1].clocks_in_khz, 600000U); + KUNIT_EXPECT_EQ(test, dc_clks.data[1].voltage_in_mv, 950U); +} + +/** + * dm_test_pp_to_dc_clock_levels_voltage_caps_at_max - Test count capping + * @test: KUnit test context + * + * Verify that pp_to_dc_clock_levels_with_voltage caps num_levels at + * DM_PP_MAX_CLOCK_LEVELS when input exceeds the maximum. + */ +static void dm_test_pp_to_dc_clock_levels_voltage_caps_at_max(struct kunit *test) +{ + struct pp_clock_levels_with_voltage pp_clks = {}; + struct dm_pp_clock_levels_with_voltage dc_clks = {}; + + pp_clks.num_levels = DM_PP_MAX_CLOCK_LEVELS + 1; + + pp_to_dc_clock_levels_with_voltage(&pp_clks, &dc_clks, + DM_PP_CLOCK_TYPE_MEMORY_CLK); + + KUNIT_EXPECT_EQ(test, dc_clks.num_levels, (uint32_t)DM_PP_MAX_CLOCK_LEVELS); +} + +/* ---- Tests for dm_pp_get_funcs ---- */ + +/** + * dm_test_get_funcs_rv - Test Raven PP SMU function table setup + * @test: KUnit test context + * + * Verify that DCN 1.0 initializes the Raven SMU function table and stores + * the DC context in the PP SMU handle. + */ +static void dm_test_get_funcs_rv(struct kunit *test) +{ + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu_funcs *funcs = kunit_kzalloc(test, sizeof(*funcs), GFP_KERNEL); + + KUNIT_ASSERT_NOT_NULL(test, ctx); + KUNIT_ASSERT_NOT_NULL(test, funcs); + + ctx->dce_version = DCN_VERSION_1_0; + + dm_pp_get_funcs(ctx, funcs); + + KUNIT_EXPECT_EQ(test, funcs->ctx.ver, PP_SMU_VER_RV); + KUNIT_EXPECT_PTR_EQ(test, funcs->rv_funcs.pp_smu.dm, ctx); + KUNIT_EXPECT_TRUE(test, funcs->rv_funcs.set_wm_ranges != NULL); + KUNIT_EXPECT_TRUE(test, funcs->rv_funcs.set_pme_wa_enable != NULL); + KUNIT_EXPECT_TRUE(test, funcs->rv_funcs.set_display_count != NULL); + KUNIT_EXPECT_TRUE(test, funcs->rv_funcs.set_min_deep_sleep_dcfclk != NULL); + KUNIT_EXPECT_TRUE(test, funcs->rv_funcs.set_hard_min_dcfclk_by_freq != NULL); + KUNIT_EXPECT_TRUE(test, funcs->rv_funcs.set_hard_min_fclk_by_freq != NULL); + KUNIT_EXPECT_FALSE(test, funcs->rv_funcs.set_hard_min_socclk_by_freq != NULL); +} + +/** + * dm_test_get_funcs_rv_101 - Test DCN 1.01 Raven PP SMU setup + * @test: KUnit test context + * + * Verify that DCN 1.01 uses the same Raven SMU function table as DCN 1.0. + */ +static void dm_test_get_funcs_rv_101(struct kunit *test) +{ + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu_funcs *funcs = kunit_kzalloc(test, sizeof(*funcs), GFP_KERNEL); + + KUNIT_ASSERT_NOT_NULL(test, ctx); + KUNIT_ASSERT_NOT_NULL(test, funcs); + + ctx->dce_version = DCN_VERSION_1_01; + + dm_pp_get_funcs(ctx, funcs); + + KUNIT_EXPECT_EQ(test, funcs->ctx.ver, PP_SMU_VER_RV); + KUNIT_EXPECT_PTR_EQ(test, funcs->rv_funcs.pp_smu.dm, ctx); + KUNIT_EXPECT_TRUE(test, funcs->rv_funcs.set_display_count != NULL); +} + +/** + * dm_test_get_funcs_nv - Test Navi PP SMU function table setup + * @test: KUnit test context + * + * Verify that DCN 2.0 initializes the Navi SMU function table and leaves the + * unsupported PME workaround callback unset. + */ +static void dm_test_get_funcs_nv(struct kunit *test) +{ + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu_funcs *funcs = kunit_kzalloc(test, sizeof(*funcs), GFP_KERNEL); + + KUNIT_ASSERT_NOT_NULL(test, ctx); + KUNIT_ASSERT_NOT_NULL(test, funcs); + + ctx->dce_version = DCN_VERSION_2_0; + + dm_pp_get_funcs(ctx, funcs); + + KUNIT_EXPECT_EQ(test, funcs->ctx.ver, PP_SMU_VER_NV); + KUNIT_EXPECT_PTR_EQ(test, funcs->nv_funcs.pp_smu.dm, ctx); + KUNIT_EXPECT_TRUE(test, funcs->nv_funcs.set_display_count != NULL); + KUNIT_EXPECT_TRUE(test, funcs->nv_funcs.set_hard_min_dcfclk_by_freq != NULL); + KUNIT_EXPECT_TRUE(test, funcs->nv_funcs.set_min_deep_sleep_dcfclk != NULL); + KUNIT_EXPECT_TRUE(test, funcs->nv_funcs.set_voltage_by_freq != NULL); + KUNIT_EXPECT_TRUE(test, funcs->nv_funcs.set_wm_ranges != NULL); + KUNIT_EXPECT_FALSE(test, funcs->nv_funcs.set_pme_wa_enable != NULL); + KUNIT_EXPECT_TRUE(test, funcs->nv_funcs.set_hard_min_uclk_by_freq != NULL); + KUNIT_EXPECT_TRUE(test, funcs->nv_funcs.get_maximum_sustainable_clocks != NULL); + KUNIT_EXPECT_TRUE(test, funcs->nv_funcs.get_uclk_dpm_states != NULL); + KUNIT_EXPECT_TRUE(test, funcs->nv_funcs.set_pstate_handshake_support != NULL); +} + +/** + * dm_test_get_funcs_rn - Test Renoir PP SMU function table setup + * @test: KUnit test context + * + * Verify that DCN 2.1 initializes the Renoir SMU function table. + */ +static void dm_test_get_funcs_rn(struct kunit *test) +{ + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu_funcs *funcs = kunit_kzalloc(test, sizeof(*funcs), GFP_KERNEL); + + KUNIT_ASSERT_NOT_NULL(test, ctx); + KUNIT_ASSERT_NOT_NULL(test, funcs); + + ctx->dce_version = DCN_VERSION_2_1; + + dm_pp_get_funcs(ctx, funcs); + + KUNIT_EXPECT_EQ(test, funcs->ctx.ver, PP_SMU_VER_RN); + KUNIT_EXPECT_PTR_EQ(test, funcs->rn_funcs.pp_smu.dm, ctx); + KUNIT_EXPECT_TRUE(test, funcs->rn_funcs.set_wm_ranges != NULL); + KUNIT_EXPECT_TRUE(test, funcs->rn_funcs.get_dpm_clock_table != NULL); +} + +/** + * dm_test_get_funcs_unsupported - Test unsupported DCE version handling + * @test: KUnit test context + * + * Verify that unsupported DCE versions do not initialize a PP SMU version or + * function table callbacks. + */ +static void dm_test_get_funcs_unsupported(struct kunit *test) +{ + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu_funcs *funcs = kunit_kzalloc(test, sizeof(*funcs), GFP_KERNEL); + + KUNIT_ASSERT_NOT_NULL(test, ctx); + KUNIT_ASSERT_NOT_NULL(test, funcs); + + ctx->dce_version = DCE_VERSION_MAX; + + dm_pp_get_funcs(ctx, funcs); + + KUNIT_EXPECT_EQ(test, funcs->ctx.ver, PP_SMU_UNSUPPORTED); + KUNIT_EXPECT_FALSE(test, funcs->rv_funcs.set_wm_ranges != NULL); +} + +/* ---- Tests for amdgpu_device-backed entry points ---- */ + +/** + * dm_test_apply_display_requirements_dpm_disabled - Test DPM-disabled path + * @test: KUnit test context + * + * Verify that dm_pp_apply_display_requirements returns true without touching + * the display configuration when DPM is disabled. + */ +static void dm_test_apply_display_requirements_dpm_disabled(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct dm_pp_display_configuration cfg = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + adev->pm.dpm_enabled = false; + ctx->driver_context = adev; + + KUNIT_EXPECT_TRUE(test, dm_pp_apply_display_requirements(ctx, &cfg)); +} + +/** + * dm_test_apply_clock_for_voltage_invalid_type - Test invalid clock type path + * @test: KUnit test context + * + * Verify that dm_pp_apply_clock_for_voltage_request returns false for a clock + * type that does not map to a valid PP clock type, taking the early-return + * path before any SMU request is issued. + */ +static void dm_test_apply_clock_for_voltage_invalid_type(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct dm_pp_clock_for_voltage_req req = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + ctx->driver_context = adev; + req.clk_type = (enum dm_pp_clock_type)0xffff; + req.clocks_in_khz = 500000; + + KUNIT_EXPECT_FALSE(test, dm_pp_apply_clock_for_voltage_request(ctx, &req)); +} + +/* ---- Tests for build_pm_display_cfg ---- */ + +/** + * dm_test_build_pm_display_cfg_scalar_fields - Test scalar field translation + * @test: KUnit test context + * + * Verify that build_pm_display_cfg copies the pass-through fields and applies + * the /10 (10 kHz) scaling, and sets the fixed constants. + */ +static void dm_test_build_pm_display_cfg_scalar_fields(struct kunit *test) +{ + struct amd_pp_display_configuration *pm = + kunit_kzalloc(test, sizeof(*pm), GFP_KERNEL); + struct dm_pp_display_configuration *pp = + kunit_kzalloc(test, sizeof(*pp), GFP_KERNEL); + + KUNIT_ASSERT_NOT_NULL(test, pm); + KUNIT_ASSERT_NOT_NULL(test, pp); + + pp->cpu_cc6_disable = true; + pp->cpu_pstate_disable = true; + pp->cpu_pstate_separation_time = 7; + pp->nb_pstate_switch_disable = true; + pp->display_count = 2; + pp->min_engine_clock_khz = 300000; + pp->min_engine_clock_deep_sleep_khz = 50000; + pp->min_memory_clock_khz = 800000; + pp->min_dcfclock_khz = 600000; + pp->all_displays_in_sync = true; + pp->avail_mclk_switch_time_us = 11; + pp->disp_clk_khz = 400000; + pp->avail_mclk_switch_time_in_disp_active_us = 13; + pp->crtc_index = 3; + pp->line_time_in_us = 17; + pp->disp_configs[0].v_refresh = 60; + + build_pm_display_cfg(pm, pp); + + KUNIT_EXPECT_TRUE(test, pm->cpu_cc6_disable); + KUNIT_EXPECT_TRUE(test, pm->cpu_pstate_disable); + KUNIT_EXPECT_EQ(test, pm->cpu_pstate_separation_time, 7); + KUNIT_EXPECT_TRUE(test, pm->nb_pstate_switch_disable); + KUNIT_EXPECT_EQ(test, pm->num_display, 2); + KUNIT_EXPECT_EQ(test, pm->num_path_including_non_display, 2); + KUNIT_EXPECT_EQ(test, pm->min_core_set_clock, 30000); + KUNIT_EXPECT_EQ(test, pm->min_core_set_clock_in_sr, 5000); + KUNIT_EXPECT_EQ(test, pm->min_mem_set_clock, 80000); + KUNIT_EXPECT_EQ(test, pm->min_dcef_deep_sleep_set_clk, 5000); + KUNIT_EXPECT_EQ(test, pm->min_dcef_set_clk, 60000); + KUNIT_EXPECT_TRUE(test, pm->multi_monitor_in_sync); + KUNIT_EXPECT_EQ(test, pm->min_vblank_time, 11); + KUNIT_EXPECT_EQ(test, pm->display_clk, 40000); + KUNIT_EXPECT_EQ(test, pm->dce_tolerable_mclk_in_active_latency, 13); + KUNIT_EXPECT_EQ(test, pm->crtc_index, 3); + KUNIT_EXPECT_EQ(test, pm->line_time_in_us, 17); + KUNIT_EXPECT_EQ(test, pm->vrefresh, 60); + KUNIT_EXPECT_EQ(test, pm->crossfire_display_index, -1); + KUNIT_EXPECT_EQ(test, pm->min_bus_bandwidth, 0); +} + +/** + * dm_test_build_pm_display_cfg_per_display - Test per-display translation + * @test: KUnit test context + * + * Verify that build_pm_display_cfg maps each display config, applying the + * controller_id = pipe_idx + 1 offset and copying the pixel clock. + */ +static void dm_test_build_pm_display_cfg_per_display(struct kunit *test) +{ + struct amd_pp_display_configuration *pm = + kunit_kzalloc(test, sizeof(*pm), GFP_KERNEL); + struct dm_pp_display_configuration *pp = + kunit_kzalloc(test, sizeof(*pp), GFP_KERNEL); + + KUNIT_ASSERT_NOT_NULL(test, pm); + KUNIT_ASSERT_NOT_NULL(test, pp); + + pp->display_count = 2; + pp->disp_configs[0].pipe_idx = 0; + pp->disp_configs[0].pixel_clock = 148500; + pp->disp_configs[1].pipe_idx = 4; + pp->disp_configs[1].pixel_clock = 297000; + + build_pm_display_cfg(pm, pp); + + KUNIT_EXPECT_EQ(test, pm->displays[0].controller_id, 1); + KUNIT_EXPECT_EQ(test, pm->displays[0].pixel_clock, 148500); + KUNIT_EXPECT_EQ(test, pm->displays[1].controller_id, 5); + KUNIT_EXPECT_EQ(test, pm->displays[1].pixel_clock, 297000); +} + +/* ---- Tests for build_wm_clock_ranges_soc15 ---- */ + +/** + * dm_test_build_wm_clock_ranges_dmif - Test reader (DMIF) watermark sets + * @test: KUnit test context + * + * Verify that build_wm_clock_ranges_soc15 copies the reader set count, + * maps wm_inst to wm_set_id (clamping instances > 3 to WM_SET_A), and + * converts every clock from MHz to kHz (x1000) into the DMIF clock ranges. + */ +static void dm_test_build_wm_clock_ranges_dmif(struct kunit *test) +{ + struct pp_smu_wm_range_sets *ranges = + kunit_kzalloc(test, sizeof(*ranges), GFP_KERNEL); + struct dm_pp_wm_sets_with_clock_ranges_soc15 *wm = + kunit_kzalloc(test, sizeof(*wm), GFP_KERNEL); + + KUNIT_ASSERT_NOT_NULL(test, ranges); + KUNIT_ASSERT_NOT_NULL(test, wm); + + ranges->num_reader_wm_sets = 2; + /* set 0: wm_inst within range -> preserved */ + ranges->reader_wm_sets[0].wm_inst = 2; + ranges->reader_wm_sets[0].max_drain_clk_mhz = 600; + ranges->reader_wm_sets[0].min_drain_clk_mhz = 300; + ranges->reader_wm_sets[0].max_fill_clk_mhz = 800; + ranges->reader_wm_sets[0].min_fill_clk_mhz = 400; + /* set 1: wm_inst > 3 -> clamped to WM_SET_A */ + ranges->reader_wm_sets[1].wm_inst = 5; + ranges->reader_wm_sets[1].max_drain_clk_mhz = 700; + ranges->reader_wm_sets[1].min_drain_clk_mhz = 350; + ranges->reader_wm_sets[1].max_fill_clk_mhz = 900; + ranges->reader_wm_sets[1].min_fill_clk_mhz = 450; + + build_wm_clock_ranges_soc15(ranges, wm); + + KUNIT_EXPECT_EQ(test, wm->num_wm_dmif_sets, 2U); + KUNIT_EXPECT_EQ(test, wm->num_wm_mcif_sets, 0U); + + KUNIT_EXPECT_EQ(test, wm->wm_dmif_clocks_ranges[0].wm_set_id, WM_SET_C); + KUNIT_EXPECT_EQ(test, wm->wm_dmif_clocks_ranges[0].wm_max_dcfclk_clk_in_khz, 600000U); + KUNIT_EXPECT_EQ(test, wm->wm_dmif_clocks_ranges[0].wm_min_dcfclk_clk_in_khz, 300000U); + KUNIT_EXPECT_EQ(test, wm->wm_dmif_clocks_ranges[0].wm_max_mem_clk_in_khz, 800000U); + KUNIT_EXPECT_EQ(test, wm->wm_dmif_clocks_ranges[0].wm_min_mem_clk_in_khz, 400000U); + + KUNIT_EXPECT_EQ(test, wm->wm_dmif_clocks_ranges[1].wm_set_id, WM_SET_A); + KUNIT_EXPECT_EQ(test, wm->wm_dmif_clocks_ranges[1].wm_max_dcfclk_clk_in_khz, 700000U); + KUNIT_EXPECT_EQ(test, wm->wm_dmif_clocks_ranges[1].wm_min_dcfclk_clk_in_khz, 350000U); + KUNIT_EXPECT_EQ(test, wm->wm_dmif_clocks_ranges[1].wm_max_mem_clk_in_khz, 900000U); + KUNIT_EXPECT_EQ(test, wm->wm_dmif_clocks_ranges[1].wm_min_mem_clk_in_khz, 450000U); +} + +/** + * dm_test_build_wm_clock_ranges_mcif - Test writer (MCIF) watermark sets + * @test: KUnit test context + * + * Verify that build_wm_clock_ranges_soc15 copies the writer set count and + * maps the writer clocks into the MCIF ranges: fill clocks become socclk + * and drain clocks become mem clk, each converted from MHz to kHz. + */ +static void dm_test_build_wm_clock_ranges_mcif(struct kunit *test) +{ + struct pp_smu_wm_range_sets *ranges = + kunit_kzalloc(test, sizeof(*ranges), GFP_KERNEL); + struct dm_pp_wm_sets_with_clock_ranges_soc15 *wm = + kunit_kzalloc(test, sizeof(*wm), GFP_KERNEL); + + KUNIT_ASSERT_NOT_NULL(test, ranges); + KUNIT_ASSERT_NOT_NULL(test, wm); + + ranges->num_writer_wm_sets = 1; + ranges->writer_wm_sets[0].wm_inst = 1; + ranges->writer_wm_sets[0].max_fill_clk_mhz = 1200; + ranges->writer_wm_sets[0].min_fill_clk_mhz = 600; + ranges->writer_wm_sets[0].max_drain_clk_mhz = 1000; + ranges->writer_wm_sets[0].min_drain_clk_mhz = 500; + + build_wm_clock_ranges_soc15(ranges, wm); + + KUNIT_EXPECT_EQ(test, wm->num_wm_dmif_sets, 0U); + KUNIT_EXPECT_EQ(test, wm->num_wm_mcif_sets, 1U); + + KUNIT_EXPECT_EQ(test, wm->wm_mcif_clocks_ranges[0].wm_set_id, WM_SET_B); + KUNIT_EXPECT_EQ(test, wm->wm_mcif_clocks_ranges[0].wm_max_socclk_clk_in_khz, 1200000U); + KUNIT_EXPECT_EQ(test, wm->wm_mcif_clocks_ranges[0].wm_min_socclk_clk_in_khz, 600000U); + KUNIT_EXPECT_EQ(test, wm->wm_mcif_clocks_ranges[0].wm_max_mem_clk_in_khz, 1000000U); + KUNIT_EXPECT_EQ(test, wm->wm_mcif_clocks_ranges[0].wm_min_mem_clk_in_khz, 500000U); +} + +/* ---- Tests for cap_clock_levels_to_validation ---- */ + +/** + * dm_test_cap_clock_levels_engine_caps - Test engine clock level capping + * @test: KUnit test context + * + * Verify that for engine clocks, num_levels is reduced to the index of the + * first level whose frequency exceeds the engine validation clock. + */ +static void dm_test_cap_clock_levels_engine_caps(struct kunit *test) +{ + struct dm_pp_clock_levels clks = { 0 }; + struct amd_pp_simple_clock_info validation = { + .engine_max_clock = 450000, + .memory_max_clock = 800000, + }; + + clks.num_levels = 3; + clks.clocks_in_khz[0] = 300000; + clks.clocks_in_khz[1] = 400000; + clks.clocks_in_khz[2] = 500000; + + cap_clock_levels_to_validation(&clks, DM_PP_CLOCK_TYPE_ENGINE_CLK, &validation); + + KUNIT_EXPECT_EQ(test, clks.num_levels, 2U); +} + +/** + * dm_test_cap_clock_levels_engine_first_exceeds - Test floor of one level + * @test: KUnit test context + * + * Verify that when the very first engine clock level already exceeds the + * validation clock, num_levels is clamped to 1 rather than 0. + */ +static void dm_test_cap_clock_levels_engine_first_exceeds(struct kunit *test) +{ + struct dm_pp_clock_levels clks = { 0 }; + struct amd_pp_simple_clock_info validation = { + .engine_max_clock = 100000, + .memory_max_clock = 800000, + }; + + clks.num_levels = 3; + clks.clocks_in_khz[0] = 300000; + clks.clocks_in_khz[1] = 400000; + clks.clocks_in_khz[2] = 500000; + + cap_clock_levels_to_validation(&clks, DM_PP_CLOCK_TYPE_ENGINE_CLK, &validation); + + KUNIT_EXPECT_EQ(test, clks.num_levels, 1U); +} + +/** + * dm_test_cap_clock_levels_memory_caps - Test memory clock level capping + * @test: KUnit test context + * + * Verify that for memory clocks, num_levels is reduced based on the memory + * validation clock (and is unaffected by the engine validation clock). + */ +static void dm_test_cap_clock_levels_memory_caps(struct kunit *test) +{ + struct dm_pp_clock_levels clks = { 0 }; + struct amd_pp_simple_clock_info validation = { + .engine_max_clock = 100000, + .memory_max_clock = 700000, + }; + + clks.num_levels = 2; + clks.clocks_in_khz[0] = 333000; + clks.clocks_in_khz[1] = 800000; + + cap_clock_levels_to_validation(&clks, DM_PP_CLOCK_TYPE_MEMORY_CLK, &validation); + + KUNIT_EXPECT_EQ(test, clks.num_levels, 1U); +} + +/** + * dm_test_cap_clock_levels_within_limit - Test no capping when within limit + * @test: KUnit test context + * + * Verify that num_levels is left unchanged when no level exceeds the + * validation clock. + */ +static void dm_test_cap_clock_levels_within_limit(struct kunit *test) +{ + struct dm_pp_clock_levels clks = { 0 }; + struct amd_pp_simple_clock_info validation = { + .engine_max_clock = 999000, + .memory_max_clock = 999000, + }; + + clks.num_levels = 3; + clks.clocks_in_khz[0] = 300000; + clks.clocks_in_khz[1] = 400000; + clks.clocks_in_khz[2] = 500000; + + cap_clock_levels_to_validation(&clks, DM_PP_CLOCK_TYPE_ENGINE_CLK, &validation); + + KUNIT_EXPECT_EQ(test, clks.num_levels, 3U); +} + +/** + * dm_test_cap_clock_levels_other_type - Test non-engine/memory types ignored + * @test: KUnit test context + * + * Verify that for clock types other than engine or memory, num_levels is + * left unchanged regardless of the validation clocks. + */ +static void dm_test_cap_clock_levels_other_type(struct kunit *test) +{ + struct dm_pp_clock_levels clks = { 0 }; + struct amd_pp_simple_clock_info validation = { + .engine_max_clock = 1, + .memory_max_clock = 1, + }; + + clks.num_levels = 3; + clks.clocks_in_khz[0] = 300000; + clks.clocks_in_khz[1] = 400000; + clks.clocks_in_khz[2] = 500000; + + cap_clock_levels_to_validation(&clks, DM_PP_CLOCK_TYPE_DISPLAY_CLK, &validation); + + KUNIT_EXPECT_EQ(test, clks.num_levels, 3U); +} + +/* ---- Tests for pp_smu_nv_clock_id_to_pp ---- */ + +/** + * dm_test_nv_clock_id_dispclk - Test DISPCLK id mapping + * @test: KUnit test context + * + * Verify that PP_SMU_NV_DISPCLK maps to amd_pp_disp_clock and returns true. + */ +static void dm_test_nv_clock_id_dispclk(struct kunit *test) +{ + enum amd_pp_clock_type clock_type = amd_pp_mem_clock; + + KUNIT_EXPECT_TRUE(test, pp_smu_nv_clock_id_to_pp(PP_SMU_NV_DISPCLK, &clock_type)); + KUNIT_EXPECT_EQ(test, clock_type, amd_pp_disp_clock); +} + +/** + * dm_test_nv_clock_id_phyclk - Test PHYCLK id mapping + * @test: KUnit test context + * + * Verify that PP_SMU_NV_PHYCLK maps to amd_pp_phy_clock and returns true. + */ +static void dm_test_nv_clock_id_phyclk(struct kunit *test) +{ + enum amd_pp_clock_type clock_type = amd_pp_mem_clock; + + KUNIT_EXPECT_TRUE(test, pp_smu_nv_clock_id_to_pp(PP_SMU_NV_PHYCLK, &clock_type)); + KUNIT_EXPECT_EQ(test, clock_type, amd_pp_phy_clock); +} + +/** + * dm_test_nv_clock_id_pixelclk - Test PIXELCLK id mapping + * @test: KUnit test context + * + * Verify that PP_SMU_NV_PIXELCLK maps to amd_pp_pixel_clock and returns true. + */ +static void dm_test_nv_clock_id_pixelclk(struct kunit *test) +{ + enum amd_pp_clock_type clock_type = amd_pp_mem_clock; + + KUNIT_EXPECT_TRUE(test, pp_smu_nv_clock_id_to_pp(PP_SMU_NV_PIXELCLK, &clock_type)); + KUNIT_EXPECT_EQ(test, clock_type, amd_pp_pixel_clock); +} + +/** + * dm_test_nv_clock_id_invalid - Test unknown id is rejected + * @test: KUnit test context + * + * Verify that an unknown clock id returns false and leaves the output + * clock_type untouched, guarding against the previously uninitialized path. + */ +static void dm_test_nv_clock_id_invalid(struct kunit *test) +{ + enum amd_pp_clock_type clock_type = amd_pp_dcef_clock; + + KUNIT_EXPECT_FALSE(test, pp_smu_nv_clock_id_to_pp((enum pp_smu_nv_clock_id)0xff, + &clock_type)); + KUNIT_EXPECT_EQ(test, clock_type, amd_pp_dcef_clock); +} + static struct kunit_case dm_pp_smu_test_cases[] = { /* get_default_clock_levels */ KUNIT_CASE(dm_test_default_clock_levels_display), @@ -227,6 +928,41 @@ static struct kunit_case dm_pp_smu_test_cases[] = { KUNIT_CASE(dm_test_dc_to_pp_clock_type_phyclk), KUNIT_CASE(dm_test_dc_to_pp_clock_type_dppclk), KUNIT_CASE(dm_test_dc_to_pp_clock_type_invalid), + /* pp_to_dc_clock_levels */ + KUNIT_CASE(dm_test_pp_to_dc_clock_levels_within_limit), + KUNIT_CASE(dm_test_pp_to_dc_clock_levels_caps_at_max), + /* pp_to_dc_clock_levels_with_latency */ + KUNIT_CASE(dm_test_pp_to_dc_clock_levels_latency_within_limit), + KUNIT_CASE(dm_test_pp_to_dc_clock_levels_latency_caps_at_max), + /* pp_to_dc_clock_levels_with_voltage */ + KUNIT_CASE(dm_test_pp_to_dc_clock_levels_voltage_within_limit), + KUNIT_CASE(dm_test_pp_to_dc_clock_levels_voltage_caps_at_max), + /* dm_pp_get_funcs */ + KUNIT_CASE(dm_test_get_funcs_rv), + KUNIT_CASE(dm_test_get_funcs_rv_101), + KUNIT_CASE(dm_test_get_funcs_nv), + KUNIT_CASE(dm_test_get_funcs_rn), + KUNIT_CASE(dm_test_get_funcs_unsupported), + /* amdgpu_device-backed entry points */ + KUNIT_CASE(dm_test_apply_display_requirements_dpm_disabled), + KUNIT_CASE(dm_test_apply_clock_for_voltage_invalid_type), + /* build_pm_display_cfg */ + KUNIT_CASE(dm_test_build_pm_display_cfg_scalar_fields), + KUNIT_CASE(dm_test_build_pm_display_cfg_per_display), + /* build_wm_clock_ranges_soc15 */ + KUNIT_CASE(dm_test_build_wm_clock_ranges_dmif), + KUNIT_CASE(dm_test_build_wm_clock_ranges_mcif), + /* cap_clock_levels_to_validation */ + KUNIT_CASE(dm_test_cap_clock_levels_engine_caps), + KUNIT_CASE(dm_test_cap_clock_levels_engine_first_exceeds), + KUNIT_CASE(dm_test_cap_clock_levels_memory_caps), + KUNIT_CASE(dm_test_cap_clock_levels_within_limit), + KUNIT_CASE(dm_test_cap_clock_levels_other_type), + /* pp_smu_nv_clock_id_to_pp */ + KUNIT_CASE(dm_test_nv_clock_id_dispclk), + KUNIT_CASE(dm_test_nv_clock_id_phyclk), + KUNIT_CASE(dm_test_nv_clock_id_pixelclk), + KUNIT_CASE(dm_test_nv_clock_id_invalid), {} }; -- cgit v1.2.3 From 3576a045cd200688cad25a7ad321bb1708f6bb6f Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Fri, 29 May 2026 17:16:58 -0600 Subject: drm/amd/display: Add more KUnit tests for amdgpu_dm_mst_types The following existing functions are also exported for the test module: - needs_dsc_aux_workaround: detect branches needing the DSC AUX workaround - dm_mst_get_pbn_divider: compute the PBN divider from link bandwidth - amdgpu_dm_mst_reset_mst_connector_setting: reset per-connector MST state - retrieve_downstream_port_device: read downstream port presence from DPCD - retrieve_branch_specific_data: read branch OUI from the upstream device Several self-contained pieces of logic are extracted from larger functions into small testable helpers. - dm_dp_aux_transfer_result: AUX return-code to errno mapping - dm_dp_aux_fill_payload_flags: AUX request bit decode - dm_mst_msg_ready_mask: MST sideband ESI mask selection - dm_mst_select_esi_dpcd: DPCD ESI address/length selection Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_mst_types.c | 143 +++++--- .../amd/display/amdgpu_dm/amdgpu_dm_mst_types.h | 12 + .../amdgpu_dm/tests/amdgpu_dm_mst_types_test.c | 385 +++++++++++++++++++++ 3 files changed, 491 insertions(+), 49 deletions(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c index 9e1916f8f99b..b6bfe56eeb68 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c @@ -34,6 +34,7 @@ #include "dm_services.h" #include "amdgpu.h" #include "amdgpu_dm.h" +#include "dmub_cmd.h" #include "amdgpu_dm_mst_types.h" #include "amdgpu_dm_hdcp.h" @@ -44,7 +45,6 @@ #include "ddc_service_types.h" #include "dpcd_defs.h" -#include "dmub_cmd.h" #if defined(CONFIG_DEBUG_FS) #include "amdgpu_dm_debugfs.h" #endif @@ -53,6 +53,49 @@ #define PEAK_FACTOR_X1000 1006 +/* + * Translate a failed AUX transaction's operation result into an errno-style + * return value. @result is returned unchanged for AUX_RET_SUCCESS. + */ +STATIC_IFN_KUNIT ssize_t dm_dp_aux_transfer_result(ssize_t result, + enum aux_return_code_type operation_result) +{ + switch (operation_result) { + case AUX_RET_SUCCESS: + break; + case AUX_RET_ERROR_HPD_DISCON: + case AUX_RET_ERROR_UNKNOWN: + case AUX_RET_ERROR_INVALID_OPERATION: + case AUX_RET_ERROR_PROTOCOL_ERROR: + result = -EIO; + break; + case AUX_RET_ERROR_INVALID_REPLY: + case AUX_RET_ERROR_ENGINE_ACQUIRE: + result = -EBUSY; + break; + case AUX_RET_ERROR_TIMEOUT: + result = -ETIMEDOUT; + break; + } + + return result; +} +EXPORT_IF_KUNIT(dm_dp_aux_transfer_result); + +/* + * Derive the AUX payload transaction flags from a DP AUX request field. + */ +STATIC_IFN_KUNIT void dm_dp_aux_fill_payload_flags(u8 request, + struct aux_payload *payload) +{ + payload->i2c_over_aux = (request & DP_AUX_NATIVE_WRITE) == 0; + payload->write = (request & DP_AUX_I2C_READ) == 0; + payload->mot = (request & DP_AUX_I2C_MOT) != 0; + payload->write_status_update = + (request & DP_AUX_I2C_WRITE_STATUS_UPDATE) != 0; +} +EXPORT_IF_KUNIT(dm_dp_aux_fill_payload_flags); + /* * This function handles both native AUX and I2C-Over-AUX transactions. */ @@ -73,11 +116,7 @@ static ssize_t dm_dp_aux_transfer(struct drm_dp_aux *aux, payload.data = msg->buffer; payload.length = msg->size; payload.reply = &msg->reply; - payload.i2c_over_aux = (msg->request & DP_AUX_NATIVE_WRITE) == 0; - payload.write = (msg->request & DP_AUX_I2C_READ) == 0; - payload.mot = (msg->request & DP_AUX_I2C_MOT) != 0; - payload.write_status_update = - (msg->request & DP_AUX_I2C_WRITE_STATUS_UPDATE) != 0; + dm_dp_aux_fill_payload_flags(msg->request, &payload); payload.defer_delay = 0; if (payload.write) { @@ -117,23 +156,7 @@ static ssize_t dm_dp_aux_transfer(struct drm_dp_aux *aux, } if (result < 0) { - switch (operation_result) { - case AUX_RET_SUCCESS: - break; - case AUX_RET_ERROR_HPD_DISCON: - case AUX_RET_ERROR_UNKNOWN: - case AUX_RET_ERROR_INVALID_OPERATION: - case AUX_RET_ERROR_PROTOCOL_ERROR: - result = -EIO; - break; - case AUX_RET_ERROR_INVALID_REPLY: - case AUX_RET_ERROR_ENGINE_ACQUIRE: - result = -EBUSY; - break; - case AUX_RET_ERROR_TIMEOUT: - result = -ETIMEDOUT; - break; - } + result = dm_dp_aux_transfer_result(result, operation_result); drm_dbg_dp(adev_to_drm(adev), "DP AUX transfer fail:%d\n", operation_result); } @@ -184,7 +207,7 @@ amdgpu_dm_mst_connector_late_register(struct drm_connector *connector) } -static inline void +STATIC_IFN_KUNIT void amdgpu_dm_mst_reset_mst_connector_setting(struct amdgpu_dm_connector *aconnector) { aconnector->drm_edid = NULL; @@ -193,6 +216,7 @@ amdgpu_dm_mst_reset_mst_connector_setting(struct amdgpu_dm_connector *aconnector aconnector->mst_local_bw = 0; aconnector->vc_full_pbn = 0; } +EXPORT_IF_KUNIT(amdgpu_dm_mst_reset_mst_connector_setting); static void amdgpu_dm_mst_connector_early_unregister(struct drm_connector *connector) @@ -313,7 +337,7 @@ static bool validate_dsc_caps_on_connector(struct amdgpu_dm_connector *aconnecto } #endif -static bool retrieve_downstream_port_device(struct amdgpu_dm_connector *aconnector) +STATIC_IFN_KUNIT bool retrieve_downstream_port_device(struct amdgpu_dm_connector *aconnector) { union dp_downstream_port_present ds_port_present; @@ -331,8 +355,9 @@ static bool retrieve_downstream_port_device(struct amdgpu_dm_connector *aconnect return true; } +EXPORT_IF_KUNIT(retrieve_downstream_port_device); -static bool retrieve_branch_specific_data(struct amdgpu_dm_connector *aconnector) +STATIC_IFN_KUNIT bool retrieve_branch_specific_data(struct amdgpu_dm_connector *aconnector) { struct drm_connector *connector = &aconnector->base; struct drm_dp_mst_port *port = aconnector->mst_output_port; @@ -359,6 +384,7 @@ static bool retrieve_branch_specific_data(struct amdgpu_dm_connector *aconnector return true; } +EXPORT_IF_KUNIT(retrieve_branch_specific_data); static int dm_dp_mst_get_modes(struct drm_connector *connector) { @@ -708,6 +734,44 @@ dm_dp_add_mst_connector(struct drm_dp_mst_topology_mgr *mgr, return connector; } +/* + * Select the ESI[1] mask used to filter the MST sideband ready bits for a + * given message-ready event type. + */ +STATIC_IFN_KUNIT u8 dm_mst_msg_ready_mask(enum mst_msg_ready_type msg_rdy_type) +{ + switch (msg_rdy_type) { + case DOWN_REP_MSG_RDY_EVENT: + /* Only handle DOWN_REP_MSG_RDY case*/ + return DP_DOWN_REP_MSG_RDY; + case UP_REQ_MSG_RDY_EVENT: + /* Only handle UP_REQ_MSG_RDY case*/ + return DP_UP_REQ_MSG_RDY; + default: + /* Handle both cases*/ + return DP_DOWN_REP_MSG_RDY | DP_UP_REQ_MSG_RDY; + } +} +EXPORT_IF_KUNIT(dm_mst_msg_ready_mask); + +/* + * Select the DPCD ESI address and read length based on the DPCD revision. + */ +STATIC_IFN_KUNIT void dm_mst_select_esi_dpcd(u8 dpcd_rev, int *dpcd_addr, + u8 *dpcd_bytes_to_read) +{ + if (dpcd_rev < 0x12) { + *dpcd_bytes_to_read = DP_LANE0_1_STATUS - DP_SINK_COUNT; + /* DPCD 0x200 - 0x201 for downstream IRQ */ + *dpcd_addr = DP_SINK_COUNT; + } else { + *dpcd_bytes_to_read = DP_PSR_ERROR_STATUS - DP_SINK_COUNT_ESI; + /* DPCD 0x2002 - 0x2005 for downstream IRQ */ + *dpcd_addr = DP_SINK_COUNT_ESI; + } +} +EXPORT_IF_KUNIT(dm_mst_select_esi_dpcd); + void dm_handle_mst_sideband_msg_ready_event( struct drm_dp_mst_topology_mgr *mgr, enum mst_msg_ready_type msg_rdy_type) @@ -726,15 +790,8 @@ void dm_handle_mst_sideband_msg_ready_event( const struct dc_link_status *link_status = dc_link_get_status(aconnector->dc_link); - if (link_status->dpcd_caps->dpcd_rev.raw < 0x12) { - dpcd_bytes_to_read = DP_LANE0_1_STATUS - DP_SINK_COUNT; - /* DPCD 0x200 - 0x201 for downstream IRQ */ - dpcd_addr = DP_SINK_COUNT; - } else { - dpcd_bytes_to_read = DP_PSR_ERROR_STATUS - DP_SINK_COUNT_ESI; - /* DPCD 0x2002 - 0x2005 for downstream IRQ */ - dpcd_addr = DP_SINK_COUNT_ESI; - } + dm_mst_select_esi_dpcd(link_status->dpcd_caps->dpcd_rev.raw, &dpcd_addr, + &dpcd_bytes_to_read); mutex_lock(&aconnector->handle_mst_msg_ready); @@ -756,20 +813,7 @@ void dm_handle_mst_sideband_msg_ready_event( DRM_DEBUG_DRIVER("ESI %02x %02x %02x\n", esi[0], esi[1], esi[2]); - switch (msg_rdy_type) { - case DOWN_REP_MSG_RDY_EVENT: - /* Only handle DOWN_REP_MSG_RDY case*/ - esi[1] &= DP_DOWN_REP_MSG_RDY; - break; - case UP_REQ_MSG_RDY_EVENT: - /* Only handle UP_REQ_MSG_RDY case*/ - esi[1] &= DP_UP_REQ_MSG_RDY; - break; - default: - /* Handle both cases*/ - esi[1] &= (DP_DOWN_REP_MSG_RDY | DP_UP_REQ_MSG_RDY); - break; - } + esi[1] &= dm_mst_msg_ready_mask(msg_rdy_type); if (!esi[1]) break; @@ -866,6 +910,7 @@ uint32_t dm_mst_get_pbn_divider(struct dc_link *link) return dfixed_const(pbn_div_x100) / 100; } +EXPORT_IF_KUNIT(dm_mst_get_pbn_divider); struct dsc_mst_fairness_params { struct dc_crtc_timing *timing; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.h index 208629ca3721..2aefab5264d0 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.h @@ -60,6 +60,7 @@ enum mst_msg_ready_type { struct amdgpu_device; struct amdgpu_display_manager; struct amdgpu_dm_connector; +struct aux_payload; struct dc_state; struct dc_stream_state; struct dm_atomic_state; @@ -100,4 +101,15 @@ enum dc_status dm_dp_mst_is_port_support_mode( struct amdgpu_dm_connector *aconnector, struct dc_stream_state *stream); +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +void amdgpu_dm_mst_reset_mst_connector_setting(struct amdgpu_dm_connector *aconnector); +bool retrieve_downstream_port_device(struct amdgpu_dm_connector *aconnector); +bool retrieve_branch_specific_data(struct amdgpu_dm_connector *aconnector); +ssize_t dm_dp_aux_transfer_result(ssize_t result, + enum aux_return_code_type operation_result); +void dm_dp_aux_fill_payload_flags(u8 request, struct aux_payload *payload); +u8 dm_mst_msg_ready_mask(enum mst_msg_ready_type msg_rdy_type); +void dm_mst_select_esi_dpcd(u8 dpcd_rev, int *dpcd_addr, u8 *dpcd_bytes_to_read); +#endif + #endif diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c index e21386819ea1..e3b171992be1 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c @@ -7,10 +7,44 @@ #include +#include +#include +#include + #include "dc.h" #include "dpcd_defs.h" +#include "dmub_cmd.h" +#include "amdgpu.h" +#include "amdgpu_mode.h" +#include "amdgpu_dm.h" #include "amdgpu_dm_mst_types.h" +/* + * Minimal mock DPCD backing store and AUX transfer callback used to exercise + * the DPCD read paths without real hardware. + */ +static u8 dm_mst_test_dpcd[0x10]; + +static ssize_t dm_mst_test_aux_transfer(struct drm_dp_aux *aux, + struct drm_dp_aux_msg *msg) +{ + size_t i; + + switch (msg->request & ~DP_AUX_I2C_MOT) { + case DP_AUX_NATIVE_READ: + for (i = 0; i < msg->size; i++) + ((u8 *)msg->buffer)[i] = + dm_mst_test_dpcd[(msg->address + i) & 0xf]; + msg->reply = DP_AUX_NATIVE_REPLY_ACK; + return msg->size; + case DP_AUX_NATIVE_WRITE: + msg->reply = DP_AUX_NATIVE_REPLY_ACK; + return msg->size; + default: + return -EINVAL; + } +} + /* Tests for needs_dsc_aux_workaround */ /** @@ -103,6 +137,332 @@ static void dm_mst_test_needs_dsc_aux_workaround_low_sink_count(struct kunit *te KUNIT_EXPECT_FALSE(test, needs_dsc_aux_workaround(&link)); } +/** + * dm_mst_test_needs_dsc_aux_workaround_zero_sink_count - Test workaround skipped for zero sinks + * @test: KUnit test context + * + * Verify that needs_dsc_aux_workaround() returns false when the sink + * count is zero, even if device ID and DPCD rev match. + */ +static void dm_mst_test_needs_dsc_aux_workaround_zero_sink_count(struct kunit *test) +{ + struct dc_link link = {0}; + + link.dpcd_caps.branch_dev_id = DP_BRANCH_DEVICE_ID_90CC24; + link.dpcd_caps.dpcd_rev.raw = DPCD_REV_14; + link.dpcd_caps.sink_count.bits.SINK_COUNT = 0; + + KUNIT_EXPECT_FALSE(test, needs_dsc_aux_workaround(&link)); +} + +/* Tests for dm_mst_get_pbn_divider */ + +/** + * dm_mst_test_pbn_divider_null_link - Test pbn_divider with NULL link + * @test: KUnit test context + * + * Verify that dm_mst_get_pbn_divider() returns 0 when passed a NULL + * link pointer without crashing. + */ +static void dm_mst_test_pbn_divider_null_link(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, dm_mst_get_pbn_divider(NULL), 0U); +} + +/* Tests for amdgpu_dm_mst_reset_mst_connector_setting */ + +/** + * dm_mst_test_reset_connector_setting - Test MST connector setting reset + * @test: KUnit test context + * + * Verify that amdgpu_dm_mst_reset_mst_connector_setting() clears the cached + * EDID, DSC AUX, passthrough AUX, local bandwidth, and VC PBN state. + */ +static void dm_mst_test_reset_connector_setting(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct drm_dp_mst_port *port; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + port = kunit_kzalloc(test, sizeof(*port), GFP_KERNEL); + + KUNIT_ASSERT_NOT_NULL(test, aconnector); + KUNIT_ASSERT_NOT_NULL(test, port); + + aconnector->drm_edid = (const struct drm_edid *)test; + aconnector->dsc_aux = (struct drm_dp_aux *)test; + aconnector->mst_output_port = port; + aconnector->mst_output_port->passthrough_aux = (struct drm_dp_aux *)test; + aconnector->mst_local_bw = 12345; + aconnector->vc_full_pbn = 678; + + amdgpu_dm_mst_reset_mst_connector_setting(aconnector); + + KUNIT_EXPECT_TRUE(test, aconnector->drm_edid == NULL); + KUNIT_EXPECT_TRUE(test, aconnector->dsc_aux == NULL); + KUNIT_EXPECT_TRUE(test, aconnector->mst_output_port->passthrough_aux == NULL); + KUNIT_EXPECT_EQ(test, aconnector->mst_local_bw, 0U); + KUNIT_EXPECT_EQ(test, aconnector->vc_full_pbn, 0U); +} + +/* Tests for retrieve_downstream_port_device */ + +/** + * dm_mst_test_retrieve_downstream_no_aux - Test retrieval bails out without AUX + * @test: KUnit test context + * + * Verify that retrieve_downstream_port_device() returns false when the + * connector has no DSC AUX channel and therefore cannot read DPCD. + */ +static void dm_mst_test_retrieve_downstream_no_aux(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + aconnector->dsc_aux = NULL; + + KUNIT_EXPECT_FALSE(test, retrieve_downstream_port_device(aconnector)); +} + +/** + * dm_mst_test_retrieve_downstream_present - Test retrieval parses DPCD 0x05 + * @test: KUnit test context + * + * Verify that retrieve_downstream_port_device() reads DP_DOWNSTREAMPORT_PRESENT + * over a mock AUX channel and caches the parsed downstream port fields. + */ +static void dm_mst_test_retrieve_downstream_present(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct drm_dp_aux *aux; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + aux = kunit_kzalloc(test, sizeof(*aux), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + KUNIT_ASSERT_NOT_NULL(test, aux); + + memset(dm_mst_test_dpcd, 0, sizeof(dm_mst_test_dpcd)); + /* PORT_PRESENT = 1, PORT_TYPE = 2 (0b101) */ + dm_mst_test_dpcd[DP_DOWNSTREAMPORT_PRESENT] = 0x05; + + aux->name = "dm_mst_test_aux"; + aux->transfer = dm_mst_test_aux_transfer; + drm_dp_aux_init(aux); + drm_dp_dpcd_set_probe(aux, false); + aconnector->dsc_aux = aux; + + KUNIT_EXPECT_TRUE(test, retrieve_downstream_port_device(aconnector)); + KUNIT_EXPECT_EQ(test, + (int)aconnector->mst_downstream_port_present.fields.PORT_PRESENT, 1); + KUNIT_EXPECT_EQ(test, + (int)aconnector->mst_downstream_port_present.fields.PORT_TYPE, 2); +} + +/* Tests for retrieve_branch_specific_data */ + +/** + * dm_mst_test_retrieve_branch_no_parent - Test branch lookup needs a parent port + * @test: KUnit test context + * + * Verify that retrieve_branch_specific_data() returns false when the MST + * output port has no parent branch device to query. + */ +static void dm_mst_test_retrieve_branch_no_parent(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct drm_dp_mst_port *port; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + port = kunit_kzalloc(test, sizeof(*port), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + KUNIT_ASSERT_NOT_NULL(test, port); + + port->parent = NULL; + aconnector->mst_output_port = port; + + KUNIT_EXPECT_FALSE(test, retrieve_branch_specific_data(aconnector)); +} + +/** + * dm_mst_test_aux_result_success - AUX_RET_SUCCESS preserves the input result. + * @test: KUnit test context. + * + * On success the original (negative) transfer result must be returned unchanged. + */ +static void dm_mst_test_aux_result_success(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, dm_dp_aux_transfer_result(-5, AUX_RET_SUCCESS), (ssize_t)-5); + KUNIT_EXPECT_EQ(test, dm_dp_aux_transfer_result(3, AUX_RET_SUCCESS), (ssize_t)3); +} + +/** + * dm_mst_test_aux_result_eio - HPD/unknown/protocol errors map to -EIO. + * @test: KUnit test context. + * + * AUX_RET_ERROR_HPD_DISCON, AUX_RET_ERROR_UNKNOWN, + * AUX_RET_ERROR_INVALID_OPERATION and AUX_RET_ERROR_PROTOCOL_ERROR all map to -EIO. + */ +static void dm_mst_test_aux_result_eio(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, dm_dp_aux_transfer_result(-1, AUX_RET_ERROR_HPD_DISCON), + (ssize_t)-EIO); + KUNIT_EXPECT_EQ(test, dm_dp_aux_transfer_result(-1, AUX_RET_ERROR_UNKNOWN), + (ssize_t)-EIO); + KUNIT_EXPECT_EQ(test, dm_dp_aux_transfer_result(-1, AUX_RET_ERROR_INVALID_OPERATION), + (ssize_t)-EIO); + KUNIT_EXPECT_EQ(test, dm_dp_aux_transfer_result(-1, AUX_RET_ERROR_PROTOCOL_ERROR), + (ssize_t)-EIO); +} + +/** + * dm_mst_test_aux_result_ebusy - invalid reply / engine acquire map to -EBUSY. + * @test: KUnit test context. + * + * AUX_RET_ERROR_INVALID_REPLY and AUX_RET_ERROR_ENGINE_ACQUIRE map to -EBUSY. + */ +static void dm_mst_test_aux_result_ebusy(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, dm_dp_aux_transfer_result(-1, AUX_RET_ERROR_INVALID_REPLY), + (ssize_t)-EBUSY); + KUNIT_EXPECT_EQ(test, dm_dp_aux_transfer_result(-1, AUX_RET_ERROR_ENGINE_ACQUIRE), + (ssize_t)-EBUSY); +} + +/** + * dm_mst_test_aux_result_timeout - AUX_RET_ERROR_TIMEOUT maps to -ETIMEDOUT. + * @test: KUnit test context. + */ +static void dm_mst_test_aux_result_timeout(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, dm_dp_aux_transfer_result(-1, AUX_RET_ERROR_TIMEOUT), + (ssize_t)-ETIMEDOUT); +} + +/** + * dm_mst_test_fill_payload_flags_native_write - native write request decode. + * @test: KUnit test context. + * + * DP_AUX_NATIVE_WRITE clears i2c_over_aux and sets write; no I2C bits set. + */ +static void dm_mst_test_fill_payload_flags_native_write(struct kunit *test) +{ + struct aux_payload payload = { 0 }; + + dm_dp_aux_fill_payload_flags(DP_AUX_NATIVE_WRITE, &payload); + + KUNIT_EXPECT_FALSE(test, payload.i2c_over_aux); + KUNIT_EXPECT_TRUE(test, payload.write); + KUNIT_EXPECT_FALSE(test, payload.mot); + KUNIT_EXPECT_FALSE(test, payload.write_status_update); +} + +/** + * dm_mst_test_fill_payload_flags_native_read - native read request decode. + * @test: KUnit test context. + * + * DP_AUX_NATIVE_READ keeps i2c_over_aux clear; the I2C_READ bit clears write. + */ +static void dm_mst_test_fill_payload_flags_native_read(struct kunit *test) +{ + struct aux_payload payload = { 0 }; + + dm_dp_aux_fill_payload_flags(DP_AUX_NATIVE_READ, &payload); + + KUNIT_EXPECT_FALSE(test, payload.i2c_over_aux); + KUNIT_EXPECT_FALSE(test, payload.write); + KUNIT_EXPECT_FALSE(test, payload.mot); +} + +/** + * dm_mst_test_fill_payload_flags_i2c_read_mot - I2C read with MOT request decode. + * @test: KUnit test context. + * + * DP_AUX_I2C_READ sets i2c_over_aux and clears write; DP_AUX_I2C_MOT sets mot. + */ +static void dm_mst_test_fill_payload_flags_i2c_read_mot(struct kunit *test) +{ + struct aux_payload payload = { 0 }; + + dm_dp_aux_fill_payload_flags(DP_AUX_I2C_READ | DP_AUX_I2C_MOT, &payload); + + KUNIT_EXPECT_TRUE(test, payload.i2c_over_aux); + KUNIT_EXPECT_FALSE(test, payload.write); + KUNIT_EXPECT_TRUE(test, payload.mot); +} + +/** + * dm_mst_test_fill_payload_flags_write_status - write status update decode. + * @test: KUnit test context. + * + * DP_AUX_I2C_WRITE_STATUS_UPDATE sets write_status_update. + */ +static void dm_mst_test_fill_payload_flags_write_status(struct kunit *test) +{ + struct aux_payload payload = { 0 }; + + dm_dp_aux_fill_payload_flags(DP_AUX_I2C_WRITE | DP_AUX_I2C_WRITE_STATUS_UPDATE, + &payload); + + KUNIT_EXPECT_TRUE(test, payload.i2c_over_aux); + KUNIT_EXPECT_TRUE(test, payload.write_status_update); +} + +/** + * dm_mst_test_msg_ready_mask - ESI mask selection per message-ready type. + * @test: KUnit test context. + * + * DOWN_REP and UP_REQ each select their single bit; other types select both. + */ +static void dm_mst_test_msg_ready_mask(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, dm_mst_msg_ready_mask(DOWN_REP_MSG_RDY_EVENT), + (u8)DP_DOWN_REP_MSG_RDY); + KUNIT_EXPECT_EQ(test, dm_mst_msg_ready_mask(UP_REQ_MSG_RDY_EVENT), + (u8)DP_UP_REQ_MSG_RDY); + KUNIT_EXPECT_EQ(test, dm_mst_msg_ready_mask(DOWN_OR_UP_MSG_RDY_EVENT), + (u8)(DP_DOWN_REP_MSG_RDY | DP_UP_REQ_MSG_RDY)); + KUNIT_EXPECT_EQ(test, dm_mst_msg_ready_mask(NONE_MSG_RDY_EVENT), + (u8)(DP_DOWN_REP_MSG_RDY | DP_UP_REQ_MSG_RDY)); +} + +/** + * dm_mst_test_select_esi_dpcd_legacy - pre-1.2 DPCD ESI address/length. + * @test: KUnit test context. + * + * For DPCD rev < 0x12 the legacy DP_SINK_COUNT address/length pair is selected. + */ +static void dm_mst_test_select_esi_dpcd_legacy(struct kunit *test) +{ + int dpcd_addr = -1; + u8 dpcd_bytes_to_read = 0; + + dm_mst_select_esi_dpcd(0x11, &dpcd_addr, &dpcd_bytes_to_read); + + KUNIT_EXPECT_EQ(test, dpcd_addr, DP_SINK_COUNT); + KUNIT_EXPECT_EQ(test, (int)dpcd_bytes_to_read, + (int)(DP_LANE0_1_STATUS - DP_SINK_COUNT)); +} + +/** + * dm_mst_test_select_esi_dpcd_esi - 1.2+ DPCD ESI address/length. + * @test: KUnit test context. + * + * For DPCD rev >= 0x12 the ESI DP_SINK_COUNT_ESI address/length pair is selected. + */ +static void dm_mst_test_select_esi_dpcd_esi(struct kunit *test) +{ + int dpcd_addr = -1; + u8 dpcd_bytes_to_read = 0; + + dm_mst_select_esi_dpcd(0x14, &dpcd_addr, &dpcd_bytes_to_read); + + KUNIT_EXPECT_EQ(test, dpcd_addr, DP_SINK_COUNT_ESI); + KUNIT_EXPECT_EQ(test, (int)dpcd_bytes_to_read, + (int)(DP_PSR_ERROR_STATUS - DP_SINK_COUNT_ESI)); +} + static struct kunit_case dm_mst_types_test_cases[] = { /* needs_dsc_aux_workaround tests */ KUNIT_CASE(dm_mst_test_needs_dsc_aux_workaround_match), @@ -110,6 +470,31 @@ static struct kunit_case dm_mst_types_test_cases[] = { KUNIT_CASE(dm_mst_test_needs_dsc_aux_workaround_wrong_dev_id), KUNIT_CASE(dm_mst_test_needs_dsc_aux_workaround_wrong_rev), KUNIT_CASE(dm_mst_test_needs_dsc_aux_workaround_low_sink_count), + KUNIT_CASE(dm_mst_test_needs_dsc_aux_workaround_zero_sink_count), + /* dm_mst_get_pbn_divider tests */ + KUNIT_CASE(dm_mst_test_pbn_divider_null_link), + /* amdgpu_dm_mst_reset_mst_connector_setting tests */ + KUNIT_CASE(dm_mst_test_reset_connector_setting), + /* retrieve_downstream_port_device tests */ + KUNIT_CASE(dm_mst_test_retrieve_downstream_no_aux), + KUNIT_CASE(dm_mst_test_retrieve_downstream_present), + /* retrieve_branch_specific_data tests */ + KUNIT_CASE(dm_mst_test_retrieve_branch_no_parent), + /* dm_dp_aux_transfer_result tests */ + KUNIT_CASE(dm_mst_test_aux_result_success), + KUNIT_CASE(dm_mst_test_aux_result_eio), + KUNIT_CASE(dm_mst_test_aux_result_ebusy), + KUNIT_CASE(dm_mst_test_aux_result_timeout), + /* dm_dp_aux_fill_payload_flags tests */ + KUNIT_CASE(dm_mst_test_fill_payload_flags_native_write), + KUNIT_CASE(dm_mst_test_fill_payload_flags_native_read), + KUNIT_CASE(dm_mst_test_fill_payload_flags_i2c_read_mot), + KUNIT_CASE(dm_mst_test_fill_payload_flags_write_status), + /* dm_mst_msg_ready_mask tests */ + KUNIT_CASE(dm_mst_test_msg_ready_mask), + /* dm_mst_select_esi_dpcd tests */ + KUNIT_CASE(dm_mst_test_select_esi_dpcd_legacy), + KUNIT_CASE(dm_mst_test_select_esi_dpcd_esi), {} }; -- cgit v1.2.3 From e3d0810f50add1e63883a993bac9e01603a81b53 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Thu, 4 Jun 2026 09:38:12 -0500 Subject: drm/amd/display: Set default backlight without ACPI support [Why] If BIOS doesn't include ATIF method it will not specify default AC or DC levels. This means that backlight will always start at 0%, which isn't expected behavior. [How] Set default AC and DC level when no valid caps found. Also reduce code duplication for ACPI and non-ACPI cases. Reported-by: Edson Juliano Drosdeck Closes: https://lore.kernel.org/dri-devel/20260526210048.1162477-1-edson.drosdeck@gmail.com/ Reviewed-by: Alex Hung Signed-off-by: Mario Limonciello Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c index f101aed75bb3..0a861d846677 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c @@ -78,20 +78,16 @@ void amdgpu_dm_update_backlight_caps(struct amdgpu_display_manager *dm, caps->caps_valid = false; } } - +#else + if (caps->aux_support) + return; +#endif if (!caps->caps_valid) { caps->min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT; caps->max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; + caps->ac_level = caps->dc_level = 50; caps->caps_valid = true; } -#else - if (caps->aux_support) - return; - - caps->min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT; - caps->max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; - caps->caps_valid = true; -#endif } EXPORT_IF_KUNIT(amdgpu_dm_update_backlight_caps); -- cgit v1.2.3 From 75ac474209514d2aa1b9bf43d391bcafa50384d0 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Fri, 5 Jun 2026 10:37:38 -0600 Subject: drm/amd/display: Move backlight macros to backlight header [WHAT] Move AMDGPU_DM_DEFAULT_MIN_BACKLIGHT, AMDGPU_DM_DEFAULT_MAX_BACKLIGHT, AMDGPU_DM_MIN_SPREAD, and AUX_BL_DEFAULT_TRANSITION_TIME_MS from amdgpu_dm_backlight.c to amdgpu_dm_backlight.h so they can be reused by KUnit tests. Update the test file to use these macros instead of hardcoded literal values. Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_backlight.c | 5 -- .../amd/display/amdgpu_dm/amdgpu_dm_backlight.h | 5 ++ .../amdgpu_dm/tests/amdgpu_dm_backlight_test.c | 62 +++++++++++----------- 3 files changed, 36 insertions(+), 36 deletions(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c index 0a861d846677..f19092a3237e 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c @@ -49,11 +49,6 @@ #include "amd_shared.h" #include "amdgpu_dm_kunit_helpers.h" -#define AMDGPU_DM_DEFAULT_MIN_BACKLIGHT 12 -#define AMDGPU_DM_DEFAULT_MAX_BACKLIGHT 255 -#define AMDGPU_DM_MIN_SPREAD ((AMDGPU_DM_DEFAULT_MAX_BACKLIGHT - AMDGPU_DM_DEFAULT_MIN_BACKLIGHT) / 2) -#define AUX_BL_DEFAULT_TRANSITION_TIME_MS 50 - void amdgpu_dm_update_backlight_caps(struct amdgpu_display_manager *dm, int bl_idx) { diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h index 5234da6ae484..a6c01b7ccab3 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h @@ -29,6 +29,11 @@ struct amdgpu_dm_connector; struct drm_connector; struct attribute_group; +#define AMDGPU_DM_DEFAULT_MIN_BACKLIGHT 12 +#define AMDGPU_DM_DEFAULT_MAX_BACKLIGHT 255 +#define AMDGPU_DM_MIN_SPREAD ((AMDGPU_DM_DEFAULT_MAX_BACKLIGHT - AMDGPU_DM_DEFAULT_MIN_BACKLIGHT) / 2) +#define AUX_BL_DEFAULT_TRANSITION_TIME_MS 50 + void amdgpu_dm_update_backlight_caps(struct amdgpu_display_manager *dm, int bl_idx); void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c index 2f4293cfd478..8763cd635ae1 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c @@ -110,8 +110,8 @@ static void dm_test_backlight_caps_non_aux_sets_defaults(struct kunit *test) amdgpu_dm_update_backlight_caps(dm, 0); KUNIT_EXPECT_TRUE(test, caps->caps_valid); - KUNIT_EXPECT_EQ(test, caps->min_input_signal, 12); - KUNIT_EXPECT_EQ(test, caps->max_input_signal, 255); + KUNIT_EXPECT_EQ(test, caps->min_input_signal, AMDGPU_DM_DEFAULT_MIN_BACKLIGHT); + KUNIT_EXPECT_EQ(test, caps->max_input_signal, AMDGPU_DM_DEFAULT_MAX_BACKLIGHT); } #endif @@ -141,13 +141,13 @@ static void dm_test_brightness_range_pwm(struct kunit *test) unsigned int min, max; caps.aux_support = false; - caps.min_input_signal = 12; - caps.max_input_signal = 255; + caps.min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT; + caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; KUNIT_EXPECT_EQ(test, get_brightness_range(&caps, &min, &max), 1); - /* 0x101 * 12 = 3084, 0x101 * 255 = 65535 */ - KUNIT_EXPECT_EQ(test, min, 0x101U * 12); - KUNIT_EXPECT_EQ(test, max, 0x101U * 255); + /* 0x101 * AMDGPU_DM_DEFAULT_MIN_BACKLIGHT, 0x101 * AMDGPU_DM_DEFAULT_MAX_BACKLIGHT */ + KUNIT_EXPECT_EQ(test, min, 0x101U * AMDGPU_DM_DEFAULT_MIN_BACKLIGHT); + KUNIT_EXPECT_EQ(test, max, 0x101U * AMDGPU_DM_DEFAULT_MAX_BACKLIGHT); } /** @@ -195,10 +195,10 @@ static void dm_test_brightness_to_user_below_min(struct kunit *test) struct amdgpu_dm_backlight_caps caps = {}; caps.aux_support = false; - caps.min_input_signal = 12; - caps.max_input_signal = 255; + caps.min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT; + caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; - /* brightness < min (0x101*12 = 3084), should return 0 */ + /* brightness < min (0x101*AMDGPU_DM_DEFAULT_MIN_BACKLIGHT), should return 0 */ KUNIT_EXPECT_EQ(test, convert_brightness_to_user(&caps, 100), 0U); } @@ -212,8 +212,8 @@ static void dm_test_brightness_to_user_at_max(struct kunit *test) unsigned int min, max; caps.aux_support = false; - caps.min_input_signal = 12; - caps.max_input_signal = 255; + caps.min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT; + caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; get_brightness_range(&caps, &min, &max); @@ -231,8 +231,8 @@ static void dm_test_brightness_to_user_at_min(struct kunit *test) unsigned int min, max; caps.aux_support = false; - caps.min_input_signal = 12; - caps.max_input_signal = 255; + caps.min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT; + caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; get_brightness_range(&caps, &min, &max); @@ -251,8 +251,8 @@ static void dm_test_brightness_to_user_midpoint_pwm(struct kunit *test) u64 expected; caps.aux_support = false; - caps.min_input_signal = 12; - caps.max_input_signal = 255; + caps.min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT; + caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; get_brightness_range(&caps, &min, &max); @@ -286,8 +286,8 @@ static void dm_test_brightness_from_user_zero(struct kunit *test) unsigned int min, max; caps.aux_support = false; - caps.min_input_signal = 12; - caps.max_input_signal = 255; + caps.min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT; + caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; /* no custom curve */ caps.data_points = 0; @@ -307,8 +307,8 @@ static void dm_test_brightness_from_user_max(struct kunit *test) unsigned int min, max; caps.aux_support = false; - caps.min_input_signal = 12; - caps.max_input_signal = 255; + caps.min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT; + caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; caps.data_points = 0; get_brightness_range(&caps, &min, &max); @@ -403,7 +403,7 @@ static void dm_test_custom_brightness_exact_match(struct kunit *test) caps.aux_support = false; caps.min_input_signal = 0; - caps.max_input_signal = 255; + caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; caps.data_points = 3; caps.luminance_data[0].input_signal = 50; caps.luminance_data[0].luminance = 20; @@ -453,7 +453,7 @@ static void dm_test_custom_brightness_below_first(struct kunit *test) caps.aux_support = false; caps.min_input_signal = 0; - caps.max_input_signal = 255; + caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; caps.data_points = 2; caps.luminance_data[0].input_signal = 100; caps.luminance_data[0].luminance = 40; @@ -498,7 +498,7 @@ static void dm_test_custom_brightness_interpolation(struct kunit *test) caps.aux_support = false; caps.min_input_signal = 0; - caps.max_input_signal = 255; + caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; caps.data_points = 2; caps.luminance_data[0].input_signal = 50; caps.luminance_data[0].luminance = 20; @@ -539,7 +539,7 @@ static void dm_test_custom_brightness_above_last(struct kunit *test) caps.aux_support = false; caps.min_input_signal = 0; - caps.max_input_signal = 255; + caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; caps.data_points = 2; caps.luminance_data[0].input_signal = 50; caps.luminance_data[0].luminance = 20; @@ -580,7 +580,7 @@ static void dm_test_custom_brightness_single_data_point(struct kunit *test) caps.aux_support = false; caps.min_input_signal = 0; - caps.max_input_signal = 255; + caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; caps.data_points = 1; caps.luminance_data[0].input_signal = 128; caps.luminance_data[0].luminance = 50; @@ -616,7 +616,7 @@ static void dm_test_custom_brightness_lower_lum_zero(struct kunit *test) caps.aux_support = false; caps.min_input_signal = 0; - caps.max_input_signal = 255; + caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; caps.data_points = 2; caps.luminance_data[0].input_signal = 50; caps.luminance_data[0].luminance = 0; /* zero lower luminance */ @@ -650,8 +650,8 @@ static void dm_test_brightness_to_user_above_max(struct kunit *test) unsigned int min, max, result; caps.aux_support = false; - caps.min_input_signal = 12; - caps.max_input_signal = 255; + caps.min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT; + caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; get_brightness_range(&caps, &min, &max); @@ -672,8 +672,8 @@ static void dm_test_brightness_from_user_midrange(struct kunit *test) u32 result; caps.aux_support = false; - caps.min_input_signal = 12; - caps.max_input_signal = 255; + caps.min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT; + caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; caps.data_points = 0; get_brightness_range(&caps, &min, &max); @@ -700,7 +700,7 @@ static void dm_test_brightness_from_user_with_curve(struct kunit *test) caps.aux_support = false; caps.min_input_signal = 0; - caps.max_input_signal = 255; + caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; caps.data_points = 2; caps.luminance_data[0].input_signal = 50; caps.luminance_data[0].luminance = 20; -- cgit v1.2.3 From 79d21b50956d89a86565014574e6798f2a5a6cc9 Mon Sep 17 00:00:00 2001 From: Chenyu Chen Date: Thu, 11 Jun 2026 23:25:11 +0800 Subject: Revert "drm/amd/display: Use handle_hpd_irq_helper for HPD RX" This reverts commit 60597d2cb21990face4ac60bb0f9a642c00ff6d2. Reason for revert: This change is found to cause hang on DP2 link layer compliance 4.2.2.8. Signed-off-by: Chenyu Chen Reviewed-by: Jerry Zuo Tested-by: Mark Broadworth Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c | 41 +++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c index 0759c1d92b61..57dd176e4cc1 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c @@ -1425,12 +1425,14 @@ static void handle_hpd_rx_irq(void *param) struct dc_link *dc_link = aconnector->dc_link; bool is_mst_root_connector = aconnector->mst_mgr.mst_state; bool result = false; + enum dc_connection_type new_connection_type = dc_connection_none; struct amdgpu_device *adev = drm_to_adev(dev); union hpd_irq_data hpd_irq_data; bool link_loss = false; bool has_left_work = false; int idx = dc_link->link_index; struct hpd_rx_irq_offload_work_queue *offload_wq = &adev->dm.hpd_rx_offload_wq[idx]; + struct dc *dc = aconnector->dc_link->ctx->dc; memset(&hpd_irq_data, 0, sizeof(hpd_irq_data)); @@ -1499,7 +1501,44 @@ static void handle_hpd_rx_irq(void *param) out: if (result && !is_mst_root_connector) { /* Downstream Port status changed. */ - handle_hpd_irq_helper(aconnector, DETECT_REASON_HPDRX); + if (!dc_link_detect_connection_type(dc_link, &new_connection_type)) + drm_err(adev_to_drm(adev), "KMS: Failed to detect connector\n"); + + if (aconnector->base.force && new_connection_type == dc_connection_none) { + amdgpu_dm_emulated_link_detect(dc_link); + + if (aconnector->fake_enable) + aconnector->fake_enable = false; + + amdgpu_dm_update_connector_after_detect(aconnector); + + + drm_modeset_lock_all(dev); + dm_restore_drm_connector_state(dev, connector); + drm_modeset_unlock_all(dev); + + drm_kms_helper_connector_hotplug_event(connector); + } else { + bool ret = false; + + mutex_lock(&adev->dm.dc_lock); + dc_exit_ips_for_hw_access(dc); + ret = dc_link_detect(dc_link, DETECT_REASON_HPDRX); + mutex_unlock(&adev->dm.dc_lock); + + if (ret) { + if (aconnector->fake_enable) + aconnector->fake_enable = false; + + amdgpu_dm_update_connector_after_detect(aconnector); + + drm_modeset_lock_all(dev); + dm_restore_drm_connector_state(dev, connector); + drm_modeset_unlock_all(dev); + + drm_kms_helper_connector_hotplug_event(connector); + } + } } if (hpd_irq_data.bytes.device_service_irq.bits.CP_IRQ) { if (adev->dm.hdcp_workqueue) -- cgit v1.2.3 From 8e2d7bbd6b184c0c1b0fe7cb404c9b5214d89931 Mon Sep 17 00:00:00 2001 From: James Lin Date: Fri, 12 Jun 2026 10:05:29 -0400 Subject: drm/amd/display: Add IN_FORMATS_ASYNC support for planes [Why] The DRM core exposes an IN_FORMATS_ASYNC plane property describing the set of format/modifier pairs that are valid for asynchronous (immediate) page flips. amdgpu already advertises async page flip support via mode_config.async_page_flip = true, but never implemented the .format_mod_supported_async plane callback, so the IN_FORMATS_ASYNC property was not created. This inconsistency (advertising async flips while exposing IN_FORMATS but no IN_FORMATS_ASYNC) causes userspace, such as igt-gpu-tools, to emit a repeated warning during plane initialization, which in turn demotes many otherwise passing KMS subtests to a WARN result. [How] Wire up .format_mod_supported_async to the existing amdgpu_dm_plane_format_mod_supported callback so the async format list is populated. amdgpu does not restrict async flips at the format/modifier level: the async flip constraints are enforced at atomic check and commit time and only require a fast update (no change to FB pitch, DCC state, rotation or memory type) between the old and new buffers. Therefore the set of formats/modifiers valid for async flips is identical to the regular IN_FORMATS set, and the same callback can be reused. Reviewed-by: Aurabindo Pillai Signed-off-by: James Lin Signed-off-by: Ivan Lipski Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c index e957657b06c7..c7f8e08feaf4 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c @@ -1859,6 +1859,7 @@ static const struct drm_plane_funcs dm_plane_funcs = { .atomic_duplicate_state = amdgpu_dm_plane_drm_plane_duplicate_state, .atomic_destroy_state = amdgpu_dm_plane_drm_plane_destroy_state, .format_mod_supported = amdgpu_dm_plane_format_mod_supported, + .format_mod_supported_async = amdgpu_dm_plane_format_mod_supported, #ifdef AMD_PRIVATE_COLOR .atomic_set_property = dm_atomic_plane_set_property, .atomic_get_property = dm_atomic_plane_get_property, -- cgit v1.2.3 From 13158e5dbd896281f3e9982b5437cffa5fd621b2 Mon Sep 17 00:00:00 2001 From: Matthew Schwartz Date: Thu, 11 Jun 2026 08:44:38 -0700 Subject: drm/amd/display: Fix mem_type change detection for async flips [Why] amdgpu_dm_crtc_mem_type_changed() fetches the "old" and "new" plane state with two drm_atomic_get_plane_state() calls, which both return the new state. It compares a state against itself, so it never detects a mem_type change and never rejects the async flip. On DCN 3.0.1, this shows up as intermittent corruption when a single DCC plane is scanned out with immediate flips under gamescope and its buffer moves between the VRAM carveout and GTT. [How] Use drm_atomic_get_old_plane_state() and drm_atomic_get_new_plane_state() to compare the actual old and new states. These return NULL rather than an error pointer for a plane that is not part of the commit, so the IS_ERR() check becomes a NULL check that skips those planes, such as an unmodified cursor still in the CRTC's plane_mask. Fixes: 4caacd1671b7 ("drm/amd/display: Do not elevate mem_type change to full update") Reviewed-by: Harry Wentland Reviewed-by: Melissa Wen Signed-off-by: Matthew Schwartz Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') 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 d23d9d85e567..2e74ff94dcac 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -6684,13 +6684,11 @@ static bool amdgpu_dm_crtc_mem_type_changed(struct drm_device *dev, struct drm_plane_state *new_plane_state, *old_plane_state; drm_for_each_plane_mask(plane, dev, crtc_state->plane_mask) { - new_plane_state = drm_atomic_get_plane_state(state, plane); - old_plane_state = drm_atomic_get_plane_state(state, plane); + new_plane_state = drm_atomic_get_new_plane_state(state, plane); + old_plane_state = drm_atomic_get_old_plane_state(state, plane); - if (IS_ERR(new_plane_state) || IS_ERR(old_plane_state)) { - drm_err(dev, "Failed to get plane state for plane %s\n", plane->name); - return false; - } + if (!old_plane_state || !new_plane_state) + continue; if (old_plane_state->fb && new_plane_state->fb && get_mem_type(old_plane_state->fb) != get_mem_type(new_plane_state->fb)) -- cgit v1.2.3 From 168f51adecd7c71e59a50ebcd0d24b010f981746 Mon Sep 17 00:00:00 2001 From: Chenyu Chen Date: Tue, 26 May 2026 09:53:15 +0800 Subject: drm/amd/display: use DisplayID panel type in dm_set_panel_type Wire up the newly parsed panel_type from drm_display_info into amdgpu_dm's panel type detection path. When neither the AMD VSDB nor DPCD determines the panel type, fall back to the DisplayID Display Device Technology field to set PANEL_TYPE_LCD or PANEL_TYPE_OLED accordingly. Also expose LCD to userspace via the panel_type connector property. Assisted-by: Copilot:Claude-Opus-4.6 Signed-off-by: Chenyu Chen Reviewed-by: Mario Limonciello (AMD) Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') 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 27f8fb2e8c12..300ee26f26ff 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 @@ -420,11 +420,13 @@ static void dm_set_panel_type(struct amdgpu_dm_connector *aconnector) link->panel_type = PANEL_TYPE_OLED; } - /* - * TODO: get panel type from DID2 that has device technology field - * to specify if it's OLED or not. But we need to wait for DID2 - * support in DC and EDID parser to be able to use it here. - */ + /* If VSDB and DPCD didn't determine panel type, check DID */ + if (link->panel_type == PANEL_TYPE_NONE) { + if (display_info->panel_type == DRM_MODE_PANEL_TYPE_LCD) + link->panel_type = PANEL_TYPE_LCD; + else if (display_info->panel_type == DRM_MODE_PANEL_TYPE_OLED) + link->panel_type = PANEL_TYPE_OLED; + } if (link->panel_type == PANEL_TYPE_NONE) { struct drm_amd_vsdb_info *vsdb = &display_info->amd_vsdb; @@ -442,6 +444,10 @@ static void dm_set_panel_type(struct amdgpu_dm_connector *aconnector) drm_object_property_set_value(&connector->base, adev_to_drm(adev)->mode_config.panel_type_property, DRM_MODE_PANEL_TYPE_OLED); + else if (link->panel_type == PANEL_TYPE_LCD) + drm_object_property_set_value(&connector->base, + adev_to_drm(adev)->mode_config.panel_type_property, + DRM_MODE_PANEL_TYPE_LCD); else drm_object_property_set_value(&connector->base, adev_to_drm(adev)->mode_config.panel_type_property, -- cgit v1.2.3 From 2ad08b9c798d4d255f5218428e55f81217dbc5a4 Mon Sep 17 00:00:00 2001 From: Markus Elfring Date: Fri, 5 Jun 2026 12:40:31 +0200 Subject: drm/amd/display: Simplify data output in ips_status_show() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the specification for a line break from a seq_puts() call to a previous seq_printf() call. This issue was detected by using the Coccinelle software. Reviewed-by: Timur Kristóf Signed-off-by: Markus Elfring Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c index 2d455359fdb4..133f3af0e4e3 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c @@ -2710,11 +2710,10 @@ static int ips_status_show(struct seq_file *m, void *unused) rcg_count = ips_fw->rcg_exit_count; ips1_count = ips_fw->ips1_exit_count; ips2_count = ips_fw->ips2_exit_count; - seq_printf(m, "exit counts: rcg=%u ips1=%u ips2=%u", + seq_printf(m, "exit counts: rcg=%u ips1=%u ips2=%u\n", rcg_count, ips1_count, ips2_count); - seq_puts(m, "\n"); } return 0; } -- cgit v1.2.3 From 99e37b3ba8c8284b2b6a9c5a5fe6a511e486352a Mon Sep 17 00:00:00 2001 From: Markus Elfring Date: Fri, 5 Jun 2026 12:44:01 +0200 Subject: drm/amd/display: Use seq_putc() in three functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Single characters should occasionally be put into a sequence. Thus use the corresponding function “seq_putc”. The source code was transformed by using the Coccinelle software. Reviewed-by: Timur Kristóf Signed-off-by: Markus Elfring Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c index 133f3af0e4e3..830cf8da06b4 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c @@ -606,7 +606,7 @@ static int dp_lttpr_status_show(struct seq_file *m, void *unused) break; } - seq_puts(m, "\n"); + seq_putc(m, '\n'); return 0; } @@ -1081,7 +1081,7 @@ static int psr_capability_show(struct seq_file *m, void *data) seq_printf(m, "Driver support: %s", str_yes_no(link->psr_settings.psr_feature_enabled)); if (link->psr_settings.psr_version) seq_printf(m, " [0x%02x]", link->psr_settings.psr_version); - seq_puts(m, "\n"); + seq_putc(m, '\n'); return 0; } @@ -1266,7 +1266,7 @@ static int hdcp_sink_capability_show(struct seq_file *m, void *data) if (!hdcp_cap && !hdcp2_cap) seq_printf(m, "%s ", "None"); - seq_puts(m, "\n"); + seq_putc(m, '\n'); return 0; } -- cgit v1.2.3 From 947e46eb2fb9f66da0d659c7e4f28fc18e05ada2 Mon Sep 17 00:00:00 2001 From: Timur Kristóf Date: Wed, 17 Jun 2026 21:14:16 +0200 Subject: drm/amdgpu: Delete check_soft_reset() from amd_ip_funcs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This function is not called from anywhere anymore and every implementation was bogus. Some implementations checked busy flags of the IP blocks, which are not really indicative of whether the block is hung and needs to be reset. For example the blocks could be busy just normally executing submissions, and not need to be reset. Other implementations checked IB tests, which is actually more useful, but could still just indicate that an IP block is executing submissions normally. It is also unnecessary because the GPU recovery code path already knows which ring is hung so we know exactly what we need to reset. Just delete check_soft_reset() entirely. Reviewed-by: Alex Deucher Signed-off-by: Timur Kristóf Reviewed-by: Christian König Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c | 25 --------- drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c | 63 ----------------------- drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.c | 1 - drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_2.c | 1 - drivers/gpu/drm/amd/amdgpu/sdma_v3_0.c | 22 -------- drivers/gpu/drm/amd/amdgpu/sdma_v6_0.c | 18 ------- drivers/gpu/drm/amd/amdgpu/sdma_v7_0.c | 18 ------- drivers/gpu/drm/amd/amdgpu/sdma_v7_1.c | 18 ------- drivers/gpu/drm/amd/amdgpu/tonga_ih.c | 20 ------- drivers/gpu/drm/amd/amdgpu/uvd_v6_0.c | 20 ------- drivers/gpu/drm/amd/amdgpu/vce_v3_0.c | 42 --------------- drivers/gpu/drm/amd/amdgpu/vcn_v5_0_1.c | 1 - drivers/gpu/drm/amd/amdgpu/vcn_v5_0_2.c | 1 - drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 6 --- drivers/gpu/drm/amd/include/amd_shared.h | 1 - drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c | 1 - 16 files changed, 258 deletions(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c index 6346f16c4e61..4315a6b6c1be 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c @@ -5259,30 +5259,6 @@ static int gfx_v11_0_soft_reset(struct amdgpu_ip_block *ip_block) return gfx_v11_0_cp_resume(adev); } -static bool gfx_v11_0_check_soft_reset(struct amdgpu_ip_block *ip_block) -{ - int i, r; - struct amdgpu_device *adev = ip_block->adev; - struct amdgpu_ring *ring; - long tmo = msecs_to_jiffies(1000); - - for (i = 0; i < adev->gfx.num_gfx_rings; i++) { - ring = &adev->gfx.gfx_ring[i]; - r = amdgpu_ring_test_ib(ring, tmo); - if (r) - return true; - } - - for (i = 0; i < adev->gfx.num_compute_rings; i++) { - ring = &adev->gfx.compute_ring[i]; - r = amdgpu_ring_test_ib(ring, tmo); - if (r) - return true; - } - - return false; -} - static int gfx_v11_0_post_soft_reset(struct amdgpu_ip_block *ip_block) { struct amdgpu_device *adev = ip_block->adev; @@ -7015,7 +6991,6 @@ static const struct amd_ip_funcs gfx_v11_0_ip_funcs = { .is_idle = gfx_v11_0_is_idle, .wait_for_idle = gfx_v11_0_wait_for_idle, .soft_reset = gfx_v11_0_soft_reset, - .check_soft_reset = gfx_v11_0_check_soft_reset, .post_soft_reset = gfx_v11_0_post_soft_reset, .set_clockgating_state = gfx_v11_0_set_clockgating_state, .set_powergating_state = gfx_v11_0_set_powergating_state, diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c index 130196859ff3..dd1823bd89ad 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c @@ -4891,68 +4891,6 @@ static int gfx_v8_0_resume(struct amdgpu_ip_block *ip_block) return gfx_v8_0_hw_init(ip_block); } -static bool gfx_v8_0_check_soft_reset(struct amdgpu_ip_block *ip_block) -{ - struct amdgpu_device *adev = ip_block->adev; - u32 grbm_soft_reset = 0, srbm_soft_reset = 0; - u32 tmp; - - /* GRBM_STATUS */ - tmp = RREG32(mmGRBM_STATUS); - if (tmp & (GRBM_STATUS__PA_BUSY_MASK | GRBM_STATUS__SC_BUSY_MASK | - GRBM_STATUS__BCI_BUSY_MASK | GRBM_STATUS__SX_BUSY_MASK | - GRBM_STATUS__TA_BUSY_MASK | GRBM_STATUS__VGT_BUSY_MASK | - GRBM_STATUS__DB_BUSY_MASK | GRBM_STATUS__CB_BUSY_MASK | - GRBM_STATUS__GDS_BUSY_MASK | GRBM_STATUS__SPI_BUSY_MASK | - GRBM_STATUS__IA_BUSY_MASK | GRBM_STATUS__IA_BUSY_NO_DMA_MASK | - GRBM_STATUS__CP_BUSY_MASK | GRBM_STATUS__CP_COHERENCY_BUSY_MASK)) { - grbm_soft_reset = REG_SET_FIELD(grbm_soft_reset, - GRBM_SOFT_RESET, SOFT_RESET_CP, 1); - grbm_soft_reset = REG_SET_FIELD(grbm_soft_reset, - GRBM_SOFT_RESET, SOFT_RESET_GFX, 1); - srbm_soft_reset = REG_SET_FIELD(srbm_soft_reset, - SRBM_SOFT_RESET, SOFT_RESET_GRBM, 1); - } - - /* GRBM_STATUS2 */ - tmp = RREG32(mmGRBM_STATUS2); - if (REG_GET_FIELD(tmp, GRBM_STATUS2, RLC_BUSY)) - grbm_soft_reset = REG_SET_FIELD(grbm_soft_reset, - GRBM_SOFT_RESET, SOFT_RESET_RLC, 1); - - if (REG_GET_FIELD(tmp, GRBM_STATUS2, CPF_BUSY) || - REG_GET_FIELD(tmp, GRBM_STATUS2, CPC_BUSY) || - REG_GET_FIELD(tmp, GRBM_STATUS2, CPG_BUSY)) { - grbm_soft_reset = REG_SET_FIELD(grbm_soft_reset, GRBM_SOFT_RESET, - SOFT_RESET_CPF, 1); - grbm_soft_reset = REG_SET_FIELD(grbm_soft_reset, GRBM_SOFT_RESET, - SOFT_RESET_CPC, 1); - grbm_soft_reset = REG_SET_FIELD(grbm_soft_reset, GRBM_SOFT_RESET, - SOFT_RESET_CPG, 1); - srbm_soft_reset = REG_SET_FIELD(srbm_soft_reset, SRBM_SOFT_RESET, - SOFT_RESET_GRBM, 1); - } - - /* SRBM_STATUS */ - tmp = RREG32(mmSRBM_STATUS); - if (REG_GET_FIELD(tmp, SRBM_STATUS, GRBM_RQ_PENDING)) - srbm_soft_reset = REG_SET_FIELD(srbm_soft_reset, - SRBM_SOFT_RESET, SOFT_RESET_GRBM, 1); - if (REG_GET_FIELD(tmp, SRBM_STATUS, SEM_BUSY)) - srbm_soft_reset = REG_SET_FIELD(srbm_soft_reset, - SRBM_SOFT_RESET, SOFT_RESET_SEM, 1); - - if (grbm_soft_reset || srbm_soft_reset) { - adev->gfx.grbm_soft_reset = grbm_soft_reset; - adev->gfx.srbm_soft_reset = srbm_soft_reset; - return true; - } else { - adev->gfx.grbm_soft_reset = 0; - adev->gfx.srbm_soft_reset = 0; - return false; - } -} - static int gfx_v8_0_pre_soft_reset(struct amdgpu_ip_block *ip_block) { struct amdgpu_device *adev = ip_block->adev; @@ -6862,7 +6800,6 @@ static const struct amd_ip_funcs gfx_v8_0_ip_funcs = { .resume = gfx_v8_0_resume, .is_idle = gfx_v8_0_is_idle, .wait_for_idle = gfx_v8_0_wait_for_idle, - .check_soft_reset = gfx_v8_0_check_soft_reset, .pre_soft_reset = gfx_v8_0_pre_soft_reset, .soft_reset = gfx_v8_0_soft_reset, .post_soft_reset = gfx_v8_0_post_soft_reset, diff --git a/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.c b/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.c index e023ae958459..26a3f759ea94 100644 --- a/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.c +++ b/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_1.c @@ -888,7 +888,6 @@ static const struct amd_ip_funcs jpeg_v5_0_1_ip_funcs = { .resume = jpeg_v5_0_1_resume, .is_idle = jpeg_v5_0_1_is_idle, .wait_for_idle = jpeg_v5_0_1_wait_for_idle, - .check_soft_reset = NULL, .pre_soft_reset = NULL, .soft_reset = NULL, .post_soft_reset = NULL, diff --git a/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_2.c b/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_2.c index 7a4ecea6b39a..717eaf43c9a6 100644 --- a/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_2.c +++ b/drivers/gpu/drm/amd/amdgpu/jpeg_v5_0_2.c @@ -690,7 +690,6 @@ static const struct amd_ip_funcs jpeg_v5_0_2_ip_funcs = { .resume = jpeg_v5_0_2_resume, .is_idle = jpeg_v5_0_2_is_idle, .wait_for_idle = jpeg_v5_0_2_wait_for_idle, - .check_soft_reset = NULL, .pre_soft_reset = NULL, .soft_reset = NULL, .post_soft_reset = NULL, diff --git a/drivers/gpu/drm/amd/amdgpu/sdma_v3_0.c b/drivers/gpu/drm/amd/amdgpu/sdma_v3_0.c index 3fde9be74690..e77261a64cf8 100644 --- a/drivers/gpu/drm/amd/amdgpu/sdma_v3_0.c +++ b/drivers/gpu/drm/amd/amdgpu/sdma_v3_0.c @@ -1237,27 +1237,6 @@ static int sdma_v3_0_wait_for_idle(struct amdgpu_ip_block *ip_block) return -ETIMEDOUT; } -static bool sdma_v3_0_check_soft_reset(struct amdgpu_ip_block *ip_block) -{ - struct amdgpu_device *adev = ip_block->adev; - u32 srbm_soft_reset = 0; - u32 tmp = RREG32(mmSRBM_STATUS2); - - if ((tmp & SRBM_STATUS2__SDMA_BUSY_MASK) || - (tmp & SRBM_STATUS2__SDMA1_BUSY_MASK)) { - srbm_soft_reset |= SRBM_SOFT_RESET__SOFT_RESET_SDMA_MASK; - srbm_soft_reset |= SRBM_SOFT_RESET__SOFT_RESET_SDMA1_MASK; - } - - if (srbm_soft_reset) { - adev->sdma.srbm_soft_reset = srbm_soft_reset; - return true; - } else { - adev->sdma.srbm_soft_reset = 0; - return false; - } -} - static int sdma_v3_0_pre_soft_reset(struct amdgpu_ip_block *ip_block) { struct amdgpu_device *adev = ip_block->adev; @@ -1552,7 +1531,6 @@ static const struct amd_ip_funcs sdma_v3_0_ip_funcs = { .resume = sdma_v3_0_resume, .is_idle = sdma_v3_0_is_idle, .wait_for_idle = sdma_v3_0_wait_for_idle, - .check_soft_reset = sdma_v3_0_check_soft_reset, .pre_soft_reset = sdma_v3_0_pre_soft_reset, .post_soft_reset = sdma_v3_0_post_soft_reset, .soft_reset = sdma_v3_0_soft_reset, diff --git a/drivers/gpu/drm/amd/amdgpu/sdma_v6_0.c b/drivers/gpu/drm/amd/amdgpu/sdma_v6_0.c index d894b7599c18..c208c584f912 100644 --- a/drivers/gpu/drm/amd/amdgpu/sdma_v6_0.c +++ b/drivers/gpu/drm/amd/amdgpu/sdma_v6_0.c @@ -793,23 +793,6 @@ static int sdma_v6_0_soft_reset(struct amdgpu_ip_block *ip_block) return sdma_v6_0_start(adev); } -static bool sdma_v6_0_check_soft_reset(struct amdgpu_ip_block *ip_block) -{ - struct amdgpu_device *adev = ip_block->adev; - struct amdgpu_ring *ring; - int i, r; - long tmo = msecs_to_jiffies(1000); - - for (i = 0; i < adev->sdma.num_instances; i++) { - ring = &adev->sdma.instance[i].ring; - r = amdgpu_ring_test_ib(ring, tmo); - if (r) - return true; - } - - return false; -} - /** * sdma_v6_0_start - setup and start the async dma engines * @@ -1747,7 +1730,6 @@ const struct amd_ip_funcs sdma_v6_0_ip_funcs = { .is_idle = sdma_v6_0_is_idle, .wait_for_idle = sdma_v6_0_wait_for_idle, .soft_reset = sdma_v6_0_soft_reset, - .check_soft_reset = sdma_v6_0_check_soft_reset, .set_clockgating_state = sdma_v6_0_set_clockgating_state, .set_powergating_state = sdma_v6_0_set_powergating_state, .get_clockgating_state = sdma_v6_0_get_clockgating_state, diff --git a/drivers/gpu/drm/amd/amdgpu/sdma_v7_0.c b/drivers/gpu/drm/amd/amdgpu/sdma_v7_0.c index f154b68dda70..9f232805cd76 100644 --- a/drivers/gpu/drm/amd/amdgpu/sdma_v7_0.c +++ b/drivers/gpu/drm/amd/amdgpu/sdma_v7_0.c @@ -784,23 +784,6 @@ static int sdma_v7_0_soft_reset(struct amdgpu_ip_block *ip_block) return sdma_v7_0_start(adev); } -static bool sdma_v7_0_check_soft_reset(struct amdgpu_ip_block *ip_block) -{ - struct amdgpu_device *adev = ip_block->adev; - struct amdgpu_ring *ring; - int i, r; - long tmo = msecs_to_jiffies(1000); - - for (i = 0; i < adev->sdma.num_instances; i++) { - ring = &adev->sdma.instance[i].ring; - r = amdgpu_ring_test_ib(ring, tmo); - if (r) - return true; - } - - return false; -} - static int sdma_v7_0_reset_queue(struct amdgpu_ring *ring, unsigned int vmid, struct amdgpu_fence *timedout_fence) @@ -1679,7 +1662,6 @@ const struct amd_ip_funcs sdma_v7_0_ip_funcs = { .is_idle = sdma_v7_0_is_idle, .wait_for_idle = sdma_v7_0_wait_for_idle, .soft_reset = sdma_v7_0_soft_reset, - .check_soft_reset = sdma_v7_0_check_soft_reset, .set_clockgating_state = sdma_v7_0_set_clockgating_state, .set_powergating_state = sdma_v7_0_set_powergating_state, .get_clockgating_state = sdma_v7_0_get_clockgating_state, diff --git a/drivers/gpu/drm/amd/amdgpu/sdma_v7_1.c b/drivers/gpu/drm/amd/amdgpu/sdma_v7_1.c index cd9668605a50..14186e0ddb2c 100644 --- a/drivers/gpu/drm/amd/amdgpu/sdma_v7_1.c +++ b/drivers/gpu/drm/amd/amdgpu/sdma_v7_1.c @@ -775,23 +775,6 @@ static int sdma_v7_1_soft_reset(struct amdgpu_ip_block *ip_block) return sdma_v7_1_inst_start(adev, inst_mask); } -static bool sdma_v7_1_check_soft_reset(struct amdgpu_ip_block *ip_block) -{ - struct amdgpu_device *adev = ip_block->adev; - struct amdgpu_ring *ring; - int i, r; - long tmo = msecs_to_jiffies(1000); - - for (i = 0; i < adev->sdma.num_instances; i++) { - ring = &adev->sdma.instance[i].ring; - r = amdgpu_ring_test_ib(ring, tmo); - if (r) - return true; - } - - return false; -} - static int sdma_v7_1_reset_queue(struct amdgpu_ring *ring, unsigned int vmid, struct amdgpu_fence *timedout_fence) @@ -1644,7 +1627,6 @@ const struct amd_ip_funcs sdma_v7_1_ip_funcs = { .is_idle = sdma_v7_1_is_idle, .wait_for_idle = sdma_v7_1_wait_for_idle, .soft_reset = sdma_v7_1_soft_reset, - .check_soft_reset = sdma_v7_1_check_soft_reset, .set_clockgating_state = sdma_v7_1_set_clockgating_state, .set_powergating_state = sdma_v7_1_set_powergating_state, .get_clockgating_state = sdma_v7_1_get_clockgating_state, diff --git a/drivers/gpu/drm/amd/amdgpu/tonga_ih.c b/drivers/gpu/drm/amd/amdgpu/tonga_ih.c index ee8038df17e3..671f5bf18a3a 100644 --- a/drivers/gpu/drm/amd/amdgpu/tonga_ih.c +++ b/drivers/gpu/drm/amd/amdgpu/tonga_ih.c @@ -390,25 +390,6 @@ static int tonga_ih_wait_for_idle(struct amdgpu_ip_block *ip_block) return -ETIMEDOUT; } -static bool tonga_ih_check_soft_reset(struct amdgpu_ip_block *ip_block) -{ - struct amdgpu_device *adev = ip_block->adev; - u32 srbm_soft_reset = 0; - u32 tmp = RREG32(mmSRBM_STATUS); - - if (tmp & SRBM_STATUS__IH_BUSY_MASK) - srbm_soft_reset = REG_SET_FIELD(srbm_soft_reset, SRBM_SOFT_RESET, - SOFT_RESET_IH, 1); - - if (srbm_soft_reset) { - adev->irq.srbm_soft_reset = srbm_soft_reset; - return true; - } else { - adev->irq.srbm_soft_reset = 0; - return false; - } -} - static int tonga_ih_pre_soft_reset(struct amdgpu_ip_block *ip_block) { if (!ip_block->adev->irq.srbm_soft_reset) @@ -481,7 +462,6 @@ static const struct amd_ip_funcs tonga_ih_ip_funcs = { .resume = tonga_ih_resume, .is_idle = tonga_ih_is_idle, .wait_for_idle = tonga_ih_wait_for_idle, - .check_soft_reset = tonga_ih_check_soft_reset, .pre_soft_reset = tonga_ih_pre_soft_reset, .soft_reset = tonga_ih_soft_reset, .post_soft_reset = tonga_ih_post_soft_reset, diff --git a/drivers/gpu/drm/amd/amdgpu/uvd_v6_0.c b/drivers/gpu/drm/amd/amdgpu/uvd_v6_0.c index ecd7ead7a60b..7a6b6277cadd 100644 --- a/drivers/gpu/drm/amd/amdgpu/uvd_v6_0.c +++ b/drivers/gpu/drm/amd/amdgpu/uvd_v6_0.c @@ -1165,25 +1165,6 @@ static int uvd_v6_0_wait_for_idle(struct amdgpu_ip_block *ip_block) } #define AMDGPU_UVD_STATUS_BUSY_MASK 0xfd -static bool uvd_v6_0_check_soft_reset(struct amdgpu_ip_block *ip_block) -{ - struct amdgpu_device *adev = ip_block->adev; - u32 srbm_soft_reset = 0; - u32 tmp = RREG32(mmSRBM_STATUS); - - if (REG_GET_FIELD(tmp, SRBM_STATUS, UVD_RQ_PENDING) || - REG_GET_FIELD(tmp, SRBM_STATUS, UVD_BUSY) || - (RREG32(mmUVD_STATUS) & AMDGPU_UVD_STATUS_BUSY_MASK)) - srbm_soft_reset = REG_SET_FIELD(srbm_soft_reset, SRBM_SOFT_RESET, SOFT_RESET_UVD, 1); - - if (srbm_soft_reset) { - adev->uvd.inst->srbm_soft_reset = srbm_soft_reset; - return true; - } else { - adev->uvd.inst->srbm_soft_reset = 0; - return false; - } -} static int uvd_v6_0_pre_soft_reset(struct amdgpu_ip_block *ip_block) { @@ -1538,7 +1519,6 @@ static const struct amd_ip_funcs uvd_v6_0_ip_funcs = { .resume = uvd_v6_0_resume, .is_idle = uvd_v6_0_is_idle, .wait_for_idle = uvd_v6_0_wait_for_idle, - .check_soft_reset = uvd_v6_0_check_soft_reset, .pre_soft_reset = uvd_v6_0_pre_soft_reset, .soft_reset = uvd_v6_0_soft_reset, .post_soft_reset = uvd_v6_0_post_soft_reset, diff --git a/drivers/gpu/drm/amd/amdgpu/vce_v3_0.c b/drivers/gpu/drm/amd/amdgpu/vce_v3_0.c index c69f7d82060f..e01c4af46db1 100644 --- a/drivers/gpu/drm/amd/amdgpu/vce_v3_0.c +++ b/drivers/gpu/drm/amd/amdgpu/vce_v3_0.c @@ -631,47 +631,6 @@ static int vce_v3_0_wait_for_idle(struct amdgpu_ip_block *ip_block) #define AMDGPU_VCE_STATUS_BUSY_MASK (VCE_STATUS_VCPU_REPORT_AUTO_BUSY_MASK | \ VCE_STATUS_VCPU_REPORT_RB0_BUSY_MASK) -static bool vce_v3_0_check_soft_reset(struct amdgpu_ip_block *ip_block) -{ - struct amdgpu_device *adev = ip_block->adev; - u32 srbm_soft_reset = 0; - - /* According to VCE team , we should use VCE_STATUS instead - * SRBM_STATUS.VCE_BUSY bit for busy status checking. - * GRBM_GFX_INDEX.INSTANCE_INDEX is used to specify which VCE - * instance's registers are accessed - * (0 for 1st instance, 10 for 2nd instance). - * - *VCE_STATUS - *|UENC|ACPI|AUTO ACTIVE|RB1 |RB0 |RB2 | |FW_LOADED|JOB | - *|----+----+-----------+----+----+----+----------+---------+----| - *|bit8|bit7| bit6 |bit5|bit4|bit3| bit2 | bit1 |bit0| - * - * VCE team suggest use bit 3--bit 6 for busy status check - */ - mutex_lock(&adev->grbm_idx_mutex); - WREG32(mmGRBM_GFX_INDEX, GET_VCE_INSTANCE(0)); - if (RREG32(mmVCE_STATUS) & AMDGPU_VCE_STATUS_BUSY_MASK) { - srbm_soft_reset = REG_SET_FIELD(srbm_soft_reset, SRBM_SOFT_RESET, SOFT_RESET_VCE0, 1); - srbm_soft_reset = REG_SET_FIELD(srbm_soft_reset, SRBM_SOFT_RESET, SOFT_RESET_VCE1, 1); - } - WREG32(mmGRBM_GFX_INDEX, GET_VCE_INSTANCE(1)); - if (RREG32(mmVCE_STATUS) & AMDGPU_VCE_STATUS_BUSY_MASK) { - srbm_soft_reset = REG_SET_FIELD(srbm_soft_reset, SRBM_SOFT_RESET, SOFT_RESET_VCE0, 1); - srbm_soft_reset = REG_SET_FIELD(srbm_soft_reset, SRBM_SOFT_RESET, SOFT_RESET_VCE1, 1); - } - WREG32(mmGRBM_GFX_INDEX, GET_VCE_INSTANCE(0)); - mutex_unlock(&adev->grbm_idx_mutex); - - if (srbm_soft_reset) { - adev->vce.srbm_soft_reset = srbm_soft_reset; - return true; - } else { - adev->vce.srbm_soft_reset = 0; - return false; - } -} - static int vce_v3_0_soft_reset(struct amdgpu_ip_block *ip_block) { struct amdgpu_device *adev = ip_block->adev; @@ -909,7 +868,6 @@ static const struct amd_ip_funcs vce_v3_0_ip_funcs = { .resume = vce_v3_0_resume, .is_idle = vce_v3_0_is_idle, .wait_for_idle = vce_v3_0_wait_for_idle, - .check_soft_reset = vce_v3_0_check_soft_reset, .pre_soft_reset = vce_v3_0_pre_soft_reset, .soft_reset = vce_v3_0_soft_reset, .post_soft_reset = vce_v3_0_post_soft_reset, diff --git a/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_1.c b/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_1.c index 95f55bab528a..0e1a309a3e3a 100644 --- a/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_1.c +++ b/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_1.c @@ -1674,7 +1674,6 @@ static const struct amd_ip_funcs vcn_v5_0_1_ip_funcs = { .resume = vcn_v5_0_1_resume, .is_idle = vcn_v5_0_1_is_idle, .wait_for_idle = vcn_v5_0_1_wait_for_idle, - .check_soft_reset = NULL, .pre_soft_reset = NULL, .soft_reset = NULL, .post_soft_reset = NULL, diff --git a/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_2.c b/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_2.c index bbc172db91a1..1fb1dea3f129 100644 --- a/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_2.c +++ b/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_2.c @@ -1203,7 +1203,6 @@ static const struct amd_ip_funcs vcn_v5_0_2_ip_funcs = { .resume = vcn_v5_0_2_resume, .is_idle = vcn_v5_0_2_is_idle, .wait_for_idle = vcn_v5_0_2_wait_for_idle, - .check_soft_reset = NULL, .pre_soft_reset = NULL, .soft_reset = NULL, .post_soft_reset = NULL, 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 2e74ff94dcac..ec14a0f3a34b 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -237,11 +237,6 @@ static int dm_wait_for_idle(struct amdgpu_ip_block *ip_block) return 0; } -static bool dm_check_soft_reset(struct amdgpu_ip_block *ip_block) -{ - return false; -} - static int dm_soft_reset(struct amdgpu_ip_block *ip_block) { /* XXX todo */ @@ -2201,7 +2196,6 @@ static const struct amd_ip_funcs amdgpu_dm_funcs = { .resume = dm_resume, .is_idle = dm_is_idle, .wait_for_idle = dm_wait_for_idle, - .check_soft_reset = dm_check_soft_reset, .soft_reset = dm_soft_reset, .set_clockgating_state = dm_set_clockgating_state, .set_powergating_state = dm_set_powergating_state, diff --git a/drivers/gpu/drm/amd/include/amd_shared.h b/drivers/gpu/drm/amd/include/amd_shared.h index 3fd38323a88b..e698e4411eb0 100644 --- a/drivers/gpu/drm/amd/include/amd_shared.h +++ b/drivers/gpu/drm/amd/include/amd_shared.h @@ -471,7 +471,6 @@ struct amd_ip_funcs { void (*complete)(struct amdgpu_ip_block *ip_block); bool (*is_idle)(struct amdgpu_ip_block *ip_block); int (*wait_for_idle)(struct amdgpu_ip_block *ip_block); - bool (*check_soft_reset)(struct amdgpu_ip_block *ip_block); int (*pre_soft_reset)(struct amdgpu_ip_block *ip_block); int (*soft_reset)(struct amdgpu_ip_block *ip_block); int (*post_soft_reset)(struct amdgpu_ip_block *ip_block); diff --git a/drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c b/drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c index 208a2fba6d40..5e73594efdf0 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c +++ b/drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c @@ -2772,7 +2772,6 @@ const struct amd_ip_funcs smu_ip_funcs = { .suspend = smu_suspend, .resume = smu_resume, .is_idle = NULL, - .check_soft_reset = NULL, .wait_for_idle = NULL, .soft_reset = NULL, .set_clockgating_state = smu_set_clockgating_state, -- cgit v1.2.3 From 65485e86e34e7189ee14f3c1285cf7c65a9e8edc Mon Sep 17 00:00:00 2001 From: Harry Wentland Date: Wed, 10 Jun 2026 12:49:01 -0400 Subject: drm/amd/display: drop redundant colorop type and TF checks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DRM core builds the curve_1d_type enum property with only the supported TF values, so any curve_1d_type that reaches atomic_commit is already guaranteed to be in the supported set. The per-colorop type field is immutable — it cannot change between the loop that finds colorop_state and the if block that uses it, so re-checking colorop->type there is dead code. Remove the redundant checks: - colorop->type == DRM_COLOROP_1D_CURVE in the shaper TF if block - colorop->type == DRM_COLOROP_1D_LUT in the shaper LUT if block - colorop->type == DRM_COLOROP_1D_CURVE in the blend TF if block - colorop->type == DRM_COLOROP_1D_LUT in the blend LUT if block - BIT(colorop_state->curve_1d_type) & supported_blnd_tfs in the blend TF if block (already guaranteed by the loop filter) - BIT(colorop_state->curve_1d_type) & supported_blnd_tfs in the blend LUT if block (nonsensical: a 1D_LUT colorop has no curve_1d_type) No functional change. Assisted-by: Copilot:claude-opus-4.8 Reviewed-by: Alex Hung Signed-off-by: Harry Wentland Signed-off-by: George Zhang Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c index 69a3783e5223..60ca4356da9a 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c @@ -1660,7 +1660,7 @@ __set_dm_plane_colorop_shaper(struct drm_plane_state *plane_state, } } - if (colorop_state && !colorop_state->bypass && colorop->type == DRM_COLOROP_1D_CURVE) { + if (colorop_state && !colorop_state->bypass) { drm_dbg(dev, "Shaper TF colorop with ID: %d\n", colorop->base.id); tf->type = TF_TYPE_DISTRIBUTED_POINTS; tf->tf = default_tf = amdgpu_colorop_tf_to_dc_tf(colorop_state->curve_1d_type); @@ -1687,7 +1687,7 @@ __set_dm_plane_colorop_shaper(struct drm_plane_state *plane_state, } } - if (colorop_state && !colorop_state->bypass && colorop->type == DRM_COLOROP_1D_LUT) { + if (colorop_state && !colorop_state->bypass) { drm_dbg(dev, "Shaper LUT colorop with ID: %d\n", colorop->base.id); tf->type = TF_TYPE_DISTRIBUTED_POINTS; tf->tf = default_tf; @@ -1833,8 +1833,7 @@ __set_dm_plane_colorop_blend(struct drm_plane_state *plane_state, } } - if (colorop_state && !colorop_state->bypass && colorop->type == DRM_COLOROP_1D_CURVE && - (BIT(colorop_state->curve_1d_type) & amdgpu_dm_supported_blnd_tfs)) { + if (colorop_state && !colorop_state->bypass) { drm_dbg(dev, "Blend TF colorop with ID: %d\n", colorop->base.id); tf->type = TF_TYPE_DISTRIBUTED_POINTS; tf->tf = default_tf = amdgpu_colorop_tf_to_dc_tf(colorop_state->curve_1d_type); @@ -1859,8 +1858,7 @@ __set_dm_plane_colorop_blend(struct drm_plane_state *plane_state, } } - if (colorop_state && !colorop_state->bypass && colorop->type == DRM_COLOROP_1D_LUT && - (BIT(colorop_state->curve_1d_type) & amdgpu_dm_supported_blnd_tfs)) { + if (colorop_state && !colorop_state->bypass) { drm_dbg(dev, "Blend LUT colorop with ID: %d\n", colorop->base.id); tf->type = TF_TYPE_DISTRIBUTED_POINTS; tf->tf = default_tf; -- cgit v1.2.3 From 1e453f7e776bbbd4d7848f43fad1e98bb97be673 Mon Sep 17 00:00:00 2001 From: Harry Wentland Date: Wed, 10 Jun 2026 12:49:58 -0400 Subject: drm/amd/display: split TF/LUT colorop state lookups into separate upfront phases In __set_dm_plane_colorop_shaper and __set_dm_plane_colorop_blend the single colorop_state variable was reused sequentially: first to capture the TF state, then (after mutating the colorop pointer) to capture the LUT state. Split into separate tf_state / lut_state pointers and introduce a dedicated lut_colorop local. Resolve both pointers upfront before any computation begins. This separates the concern of "find the states" from "use the states" and makes the code easier to follow. No functional change. Assisted-by: Copilot:claude-opus-4.8 Reviewed-by: Alex Hung Signed-off-by: Harry Wentland Signed-off-by: George Zhang Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- .../drm/amd/display/amdgpu_dm/amdgpu_dm_color.c | 94 +++++++++++----------- 1 file changed, 48 insertions(+), 46 deletions(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c index 60ca4356da9a..9bcb73c95fef 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c @@ -1640,8 +1640,10 @@ __set_dm_plane_colorop_shaper(struct drm_plane_state *plane_state, struct drm_colorop *colorop) { struct drm_colorop *old_colorop; - struct drm_colorop_state *colorop_state = NULL, *new_colorop_state; + struct drm_colorop_state *new_colorop_state; + struct drm_colorop_state *tf_state = NULL, *lut_state = NULL; struct drm_atomic_commit *state = plane_state->state; + struct drm_colorop *lut_colorop; enum dc_transfer_func_predefined default_tf = TRANSFER_FUNCTION_LINEAR; struct dc_transfer_func *tf = &dc_plane_state->cm.shaper_func; const struct drm_color_lut32 *shaper_lut; @@ -1650,53 +1652,52 @@ __set_dm_plane_colorop_shaper(struct drm_plane_state *plane_state, u32 shaper_size; int i = 0, ret = 0; - /* 1D Curve - SHAPER TF */ + /* 1D Curve - SHAPER TF: find state */ old_colorop = colorop; for_each_new_colorop_in_state(state, colorop, new_colorop_state, i) { if (new_colorop_state->colorop == old_colorop && (BIT(new_colorop_state->curve_1d_type) & amdgpu_dm_supported_shaper_tfs)) { - colorop_state = new_colorop_state; + tf_state = new_colorop_state; break; } } - if (colorop_state && !colorop_state->bypass) { - drm_dbg(dev, "Shaper TF colorop with ID: %d\n", colorop->base.id); - tf->type = TF_TYPE_DISTRIBUTED_POINTS; - tf->tf = default_tf = amdgpu_colorop_tf_to_dc_tf(colorop_state->curve_1d_type); - tf->sdr_ref_white_level = SDR_WHITE_LEVEL_INIT_VALUE; - ret = __set_output_tf(tf, 0, 0, false); - if (ret) - return ret; - enabled = true; - } - - /* 1D LUT - SHAPER LUT */ - colorop = old_colorop->next; - if (!colorop) { + /* 1D LUT - SHAPER LUT: find state */ + lut_colorop = old_colorop->next; + if (!lut_colorop) { drm_dbg(dev, "no Shaper LUT colorop found\n"); return -EINVAL; } - old_colorop = colorop; for_each_new_colorop_in_state(state, colorop, new_colorop_state, i) { - if (new_colorop_state->colorop == old_colorop && + if (new_colorop_state->colorop == lut_colorop && new_colorop_state->colorop->type == DRM_COLOROP_1D_LUT) { - colorop_state = new_colorop_state; + lut_state = new_colorop_state; break; } } - if (colorop_state && !colorop_state->bypass) { - drm_dbg(dev, "Shaper LUT colorop with ID: %d\n", colorop->base.id); + if (tf_state && !tf_state->bypass) { + drm_dbg(dev, "Shaper TF colorop with ID: %d\n", old_colorop->base.id); + tf->type = TF_TYPE_DISTRIBUTED_POINTS; + tf->tf = default_tf = amdgpu_colorop_tf_to_dc_tf(tf_state->curve_1d_type); + tf->sdr_ref_white_level = SDR_WHITE_LEVEL_INIT_VALUE; + ret = __set_output_tf(tf, 0, 0, false); + if (ret) + return ret; + enabled = true; + } + + if (lut_state && !lut_state->bypass) { + drm_dbg(dev, "Shaper LUT colorop with ID: %d\n", lut_colorop->base.id); tf->type = TF_TYPE_DISTRIBUTED_POINTS; tf->tf = default_tf; tf->sdr_ref_white_level = SDR_WHITE_LEVEL_INIT_VALUE; - shaper_lut = __extract_blob_lut32(colorop_state->data, &shaper_size); + shaper_lut = __extract_blob_lut32(lut_state->data, &shaper_size); shaper_size = shaper_lut != NULL ? shaper_size : 0; /* Custom LUT size must be the same as supported size */ - if (shaper_size == colorop->size) { + if (shaper_size == lut_colorop->size) { ret = __set_output_tf_32(tf, shaper_lut, shaper_size, false); if (ret) return ret; @@ -1812,8 +1813,10 @@ __set_dm_plane_colorop_blend(struct drm_plane_state *plane_state, struct drm_colorop *colorop) { struct drm_colorop *old_colorop; - struct drm_colorop_state *colorop_state = NULL, *new_colorop_state; + struct drm_colorop_state *new_colorop_state; + struct drm_colorop_state *tf_state = NULL, *lut_state = NULL; struct drm_atomic_commit *state = plane_state->state; + struct drm_colorop *lut_colorop; enum dc_transfer_func_predefined default_tf = TRANSFER_FUNCTION_LINEAR; struct dc_transfer_func *tf = &dc_plane_state->cm.blend_func; const struct drm_color_lut32 *blend_lut = NULL; @@ -1823,52 +1826,51 @@ __set_dm_plane_colorop_blend(struct drm_plane_state *plane_state, dc_plane_state->cm.flags.bits.blend_enable = 0; - /* 1D Curve - BLND TF */ + /* 1D Curve - BLND TF: find state */ old_colorop = colorop; for_each_new_colorop_in_state(state, colorop, new_colorop_state, i) { if (new_colorop_state->colorop == old_colorop && (BIT(new_colorop_state->curve_1d_type) & amdgpu_dm_supported_blnd_tfs)) { - colorop_state = new_colorop_state; + tf_state = new_colorop_state; break; } } - if (colorop_state && !colorop_state->bypass) { - drm_dbg(dev, "Blend TF colorop with ID: %d\n", colorop->base.id); - tf->type = TF_TYPE_DISTRIBUTED_POINTS; - tf->tf = default_tf = amdgpu_colorop_tf_to_dc_tf(colorop_state->curve_1d_type); - tf->sdr_ref_white_level = SDR_WHITE_LEVEL_INIT_VALUE; - dc_plane_state->cm.flags.bits.blend_enable = 1; - __set_input_tf_32(NULL, tf, blend_lut, blend_size); - } - - /* 1D Curve - BLND LUT */ - colorop = old_colorop->next; - if (!colorop) { + /* 1D LUT - BLND LUT: find state */ + lut_colorop = old_colorop->next; + if (!lut_colorop) { drm_dbg(dev, "no Blend LUT colorop found\n"); return -EINVAL; } - old_colorop = colorop; for_each_new_colorop_in_state(state, colorop, new_colorop_state, i) { - if (new_colorop_state->colorop == old_colorop && + if (new_colorop_state->colorop == lut_colorop && new_colorop_state->colorop->type == DRM_COLOROP_1D_LUT) { - colorop_state = new_colorop_state; + lut_state = new_colorop_state; break; } } - if (colorop_state && !colorop_state->bypass) { - drm_dbg(dev, "Blend LUT colorop with ID: %d\n", colorop->base.id); + if (tf_state && !tf_state->bypass) { + drm_dbg(dev, "Blend TF colorop with ID: %d\n", old_colorop->base.id); + tf->type = TF_TYPE_DISTRIBUTED_POINTS; + tf->tf = default_tf = amdgpu_colorop_tf_to_dc_tf(tf_state->curve_1d_type); + tf->sdr_ref_white_level = SDR_WHITE_LEVEL_INIT_VALUE; + dc_plane_state->cm.flags.bits.blend_enable = 1; + __set_input_tf_32(NULL, tf, blend_lut, blend_size); + } + + if (lut_state && !lut_state->bypass) { + drm_dbg(dev, "Blend LUT colorop with ID: %d\n", lut_colorop->base.id); tf->type = TF_TYPE_DISTRIBUTED_POINTS; tf->tf = default_tf; tf->sdr_ref_white_level = SDR_WHITE_LEVEL_INIT_VALUE; dc_plane_state->cm.flags.bits.blend_enable = 1; - blend_lut = __extract_blob_lut32(colorop_state->data, &blend_size); + blend_lut = __extract_blob_lut32(lut_state->data, &blend_size); blend_size = blend_lut != NULL ? blend_size : 0; /* Custom LUT size must be the same as supported size */ - if (blend_size == colorop->size) + if (blend_size == lut_colorop->size) __set_input_tf_32(NULL, tf, blend_lut, blend_size); } -- cgit v1.2.3 From 932d6696d527ec498c0f3d54b7c3c98f11863d2b Mon Sep 17 00:00:00 2001 From: Chandana G B Date: Mon, 1 Jun 2026 11:09:55 +0530 Subject: drm/amd/display: Fix intermittently CRC open failure during active rendering [Why] Opening the CRC data file during active rendering can fail with -EINVAL. Closing the CRC data file with ctrl+C (which will send SIGINT to the kernel and if the wait thread in sleep, kernel will send the -ERESTARTSYS to the wait_for_completion_interruptible_timeout) resulting in intermittently getting -ERESTARTSYS. which will just do the clean up without releasing the vblank reference causing -EINVAL while opening the crc data file in the next iteration [How] Ignoring the ERESTARTSYS as this is a return value for the wait_for_completion_interruptible_timeout() Reviewed-by: Chen-Yu Chen Signed-off-by: Chandana G B Signed-off-by: George Zhang Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c index 54d3c5c9e652..970490c401e9 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crc.c @@ -681,7 +681,7 @@ int amdgpu_dm_crtc_set_crc_source(struct drm_crtc *crtc, const char *src_name) */ ret = wait_for_completion_interruptible_timeout( &commit->hw_done, 10 * HZ); - if (ret < 0) + if (ret < 0 && ret != -ERESTARTSYS) goto cleanup; if (ret == 0) { -- cgit v1.2.3 From bc1afb9ff985bb806e10eb7d81af974cbe86b57a Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 15 Jun 2026 12:10:58 -0600 Subject: drm/amd/display: Remove redundant IPS mode case for DCN 4.2 [WHAT] Remove the redundant IP_VERSION(4, 2, 0) case from dm_get_default_ips_mode() since it only reassigns the same DMUB_IPS_ENABLE value already set at initialization. Also remove the corresponding KUnit test. Reviewed-by: Chenyu Chen Signed-off-by: Alex Hung Signed-off-by: George Zhang Tested-by: Dan Wheeler Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c | 4 ---- .../amd/display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c | 18 ------------------ 2 files changed, 22 deletions(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c index b4c3371f5757..7519219db0f8 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c @@ -453,10 +453,6 @@ enum dmub_ips_disable_type dm_get_default_ips_mode( case IP_VERSION(3, 5, 1): ret = DMUB_IPS_RCG_IN_ACTIVE_IPS2_IN_OFF; break; - case IP_VERSION(4, 2, 0): - case IP_VERSION(4, 2, 1): - ret = DMUB_IPS_ENABLE; - break; default: /* ASICs older than DCN35 do not have IPSs */ if (amdgpu_ip_version(adev, DCE_HWIP, 0) < IP_VERSION(3, 5, 0)) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c index b82dd301a896..bf90ccfbf431 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_dmub_test.c @@ -350,23 +350,6 @@ static void dm_test_get_default_ips_mode_dcn36(struct kunit *test) DMUB_IPS_RCG_IN_ACTIVE_IPS2_IN_OFF); } -/** - * dm_test_get_default_ips_mode_dcn42 - Test Get default ips mode dcn42 - * @test: The KUnit test context - */ -static void dm_test_get_default_ips_mode_dcn42(struct kunit *test) -{ - struct amdgpu_device *adev; - - adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); - - adev->ip_versions[DCE_HWIP][0] = IP_VERSION(4, 2, 0); - - KUNIT_EXPECT_EQ(test, dm_get_default_ips_mode(adev), - DMUB_IPS_DISABLE_ALL); -} - /** * dm_test_get_default_ips_mode_older_than_dcn35 - Test Get default ips mode older than dcn35 * @test: The KUnit test context @@ -572,7 +555,6 @@ static struct kunit_case amdgpu_dm_dmub_tests[] = { KUNIT_CASE(dm_test_get_default_ips_mode_dcn35), KUNIT_CASE(dm_test_get_default_ips_mode_dcn351), KUNIT_CASE(dm_test_get_default_ips_mode_dcn36), - KUNIT_CASE(dm_test_get_default_ips_mode_dcn42), KUNIT_CASE(dm_test_get_default_ips_mode_older_than_dcn35), KUNIT_CASE(dm_test_get_default_ips_mode_newer_default), /* dm_dmub_hw_init() */ -- cgit v1.2.3 From 0a06bc174f07753526a680a541515eb2391cdbca Mon Sep 17 00:00:00 2001 From: Melissa Wen Date: Tue, 23 Jun 2026 17:58:57 +0200 Subject: drm/amd/display: use GAMCOR for degamma private props in subsampled format When setting plane degamma TF via AMD driver-specific color properties, the driver uses PRE_DEGAM color block (ROM). However, this block cannot be used with subsampled formats as it affects the linearity of color space in which HW scaler operates. For subsampled format, use the AMD color module to map plane degamma predefined curve to LUT and use GAMCOR block instead (RAM). This is based on Harry's implementation for Fixed Matrix Colorop. Link: https://lore.kernel.org/dri-devel/20260330153451.99472-1-harry.wentland@amd.com/ Co-developed-by: Harry Wentland Signed-off-by: Harry Wentland Tested-by: Matthew Schwartz Reviewed-by: Harry Wentland Signed-off-by: Melissa Wen Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c index 9bcb73c95fef..357c7c5c85cf 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c @@ -1469,7 +1469,7 @@ __set_dm_plane_degamma(struct drm_plane_state *plane_state, const struct drm_color_lut *degamma_lut; enum amdgpu_transfer_function tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT; uint32_t degamma_size; - bool has_degamma_lut; + bool has_degamma_lut, is_subsampled_format; int ret; degamma_lut = __extract_blob_lut(dm_plane_state->degamma_lut, @@ -1499,12 +1499,20 @@ __set_dm_plane_degamma(struct drm_plane_state *plane_state, if (ret) return ret; } else { - dc_plane_state->in_transfer_func.type = - TF_TYPE_PREDEFINED; + /* Check if format requires post-scale color processing (subsampled formats) */ + is_subsampled_format = (dc_plane_state->format >= SURFACE_PIXEL_FORMAT_VIDEO_BEGIN && + dc_plane_state->format < SURFACE_PIXEL_FORMAT_SUBSAMPLE_END); + + dc_plane_state->in_transfer_func.type = TF_TYPE_PREDEFINED; if (!mod_color_calculate_degamma_params(color_caps, - &dc_plane_state->in_transfer_func, NULL, false)) + &dc_plane_state->in_transfer_func, + NULL, + is_subsampled_format)) { + drm_err(plane_state->state->dev, + "Failed to calculate degamma params.\n"); return -ENOMEM; + } } return 0; } -- cgit v1.2.3 From a24019f6480fad5c077b5956eed942c8960323d6 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Wed, 10 Jun 2026 17:18:17 +0200 Subject: drm/amd/display: Handle struct drm_plane_state.ignore_damage_clips The mode-setting pipeline can disabled damage clippings for a commit by setting ignore_damage_clips in struct drm_plane_state. The commit will then do a full display update. Test the flag in DCN code and do a full update in DCN code if it has been set. Commit 35ed38d58257 ("drm: Allow drivers to indicate the damage helpers to ignore damage clips") introduced ignore_damage_clips to selectively ignore damage clipping in certain framebuffer changes. This driver does not do that, but DRM's damage iterator will soon rely on the flag. Therefore supporting it here as well make sense for consistency. Signed-off-by: Thomas Zimmermann Fixes: 35ed38d58257 ("drm: Allow drivers to indicate the damage helpers to ignore damage clips") Cc: Javier Martinez Canillas Cc: Thomas Zimmermann Cc: Zack Rusin Cc: dri-devel@lists.freedesktop.org Reviewed-by: Javier Martinez Canillas Reviewed-by: Harry Wentland Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') 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 ec14a0f3a34b..6bcd447f4f5d 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -3266,8 +3266,8 @@ static void fill_dc_dirty_rects(struct drm_plane *plane, { struct dm_crtc_state *dm_crtc_state = to_dm_crtc_state(crtc_state); struct rect *dirty_rects = flip_addrs->dirty_rects; - u32 num_clips; - struct drm_mode_rect *clips; + u32 num_clips = 0; + struct drm_mode_rect *clips = NULL; bool bb_changed; bool fb_changed; u32 i = 0; @@ -3283,8 +3283,10 @@ static void fill_dc_dirty_rects(struct drm_plane *plane, if (new_plane_state->rotation != DRM_MODE_ROTATE_0) goto ffu; - num_clips = drm_plane_get_damage_clips_count(new_plane_state); - clips = drm_plane_get_damage_clips(new_plane_state); + if (!new_plane_state->ignore_damage_clips) { + num_clips = drm_plane_get_damage_clips_count(new_plane_state); + clips = drm_plane_get_damage_clips(new_plane_state); + } if (num_clips && (!amdgpu_damage_clips || (amdgpu_damage_clips < 0 && is_psr_su))) -- cgit v1.2.3 From c4a5160e3be079848d5f9b8da6463c7b5156c626 Mon Sep 17 00:00:00 2001 From: Werner Sembach Date: Tue, 9 Jun 2026 14:43:48 +0200 Subject: drm/amd/display: Remove unnecessary SIGNAL_TYPE_HDMI_TYPE_A check Remove unnecessary SIGNAL_TYPE_HDMI_TYPE_A check that was performed in the drm_mode_is_420_only() case, but not in the drm_mode_is_420_also() && force_yuv420_output case. Without further knowledge if YCbCr 4:2:0 is supported outside of HDMI, there is no reason to use RGB when the display reports drm_mode_is_420_only() even on a non HDMI connection. This patch also moves both checks in the same if-case. This eliminates an extra else-if-case. Signed-off-by: Werner Sembach Signed-off-by: Andri Yngvason Tested-by: Andri Yngvason Reviewed-by: Daniel Stone Signed-off-by: Nicolas Frattaroli Reviewed-by: Harry Wentland Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') 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 300ee26f26ff..959c843fb77c 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 @@ -824,16 +824,11 @@ static void fill_stream_properties_from_drm_display_mode( timing_out->v_border_top = 0; timing_out->v_border_bottom = 0; /* TODO: un-hardcode */ - if (drm_mode_is_420_only(info, mode_in) - && (stream->signal == SIGNAL_TYPE_HDMI_TYPE_A || - stream->signal == SIGNAL_TYPE_HDMI_FRL) - && aconnector - && aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR420) - timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR420; - else if (drm_mode_is_420_also(info, mode_in) - && aconnector - && (aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR420 - || aconnector->force_yuv420_output)) + if (drm_mode_is_420_only(info, mode_in) || + (aconnector && + (aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR420 || + aconnector->force_yuv420_output) && + drm_mode_is_420_also(info, mode_in))) timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR420; else if ((connector->display_info.color_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422)) && aconnector -- cgit v1.2.3 From 703e3ae7565d0b7eeaa91d679b4ef3e38f257735 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Fri, 12 Jun 2026 17:20:27 -0600 Subject: drm/amd/display: Extract backlight helpers for KUnit tests [WHAT] Extract shared backlight device index lookup and property setup into testable helpers. The duplicated bd-to-index scan in update_status/get_brightness is replaced by amdgpu_dm_backlight_get_device_index(), and the inline backlight_properties calculation is replaced by amdgpu_dm_backlight_fill_props(). Add KUnit coverage for both new helpers. Keep the runtime power_supply_is_system_supplied() call at the caller so the helpers remain pure and deterministic under test. Assisted-by: Copilot:GPT-5.5 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_backlight.c | 84 ++++++++------ .../amd/display/amdgpu_dm/amdgpu_dm_backlight.h | 8 ++ .../amdgpu_dm/tests/amdgpu_dm_backlight_test.c | 123 +++++++++++++++++++++ 3 files changed, 184 insertions(+), 31 deletions(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c index f19092a3237e..33f4be403a65 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c @@ -236,6 +236,21 @@ static struct dc_stream_state *dm_find_stream_with_link( return NULL; } +STATIC_IFN_KUNIT +int amdgpu_dm_backlight_get_device_index(struct amdgpu_display_manager *dm, + struct backlight_device *bd) +{ + int i; + + for (i = 0; i < dm->num_of_edps; i++) { + if (bd == dm->backlight_dev[i]) + return i; + } + + return 0; +} +EXPORT_IF_KUNIT(amdgpu_dm_backlight_get_device_index); + void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm, int bl_idx, u32 user_brightness) @@ -335,14 +350,8 @@ void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm, static int amdgpu_dm_backlight_update_status(struct backlight_device *bd) { struct amdgpu_display_manager *dm = bl_get_data(bd); - int i; + int i = amdgpu_dm_backlight_get_device_index(dm, bd); - for (i = 0; i < dm->num_of_edps; i++) { - if (bd == dm->backlight_dev[i]) - break; - } - if (i >= AMDGPU_DM_MAX_NUM_EDP) - i = 0; amdgpu_dm_backlight_set_level(dm, i, bd->props.brightness); return 0; @@ -377,14 +386,8 @@ static u32 amdgpu_dm_backlight_get_level(struct amdgpu_display_manager *dm, static int amdgpu_dm_backlight_get_brightness(struct backlight_device *bd) { struct amdgpu_display_manager *dm = bl_get_data(bd); - int i; + int i = amdgpu_dm_backlight_get_device_index(dm, bd); - for (i = 0; i < dm->num_of_edps; i++) { - if (bd == dm->backlight_dev[i]) - break; - } - if (i >= AMDGPU_DM_MAX_NUM_EDP) - i = 0; return amdgpu_dm_backlight_get_level(dm, i); } @@ -394,6 +397,35 @@ static const struct backlight_ops amdgpu_dm_backlight_ops = { .update_status = amdgpu_dm_backlight_update_status, }; +STATIC_IFN_KUNIT +void amdgpu_dm_backlight_fill_props(const struct amdgpu_dm_backlight_caps *caps, + bool is_system_supplied, + bool custom_curve_enabled, + struct backlight_properties *props) +{ + unsigned int min, max; + + if (get_brightness_range(caps, &min, &max)) { + if (is_system_supplied) + props->brightness = DIV_ROUND_CLOSEST((max - min) * caps->ac_level, + 100); + else + props->brightness = DIV_ROUND_CLOSEST((max - min) * caps->dc_level, + 100); + props->max_brightness = max - min; + } else { + props->brightness = MAX_BACKLIGHT_LEVEL; + props->max_brightness = MAX_BACKLIGHT_LEVEL; + } + + if (caps && caps->data_points && custom_curve_enabled) + props->scale = BACKLIGHT_SCALE_NON_LINEAR; + else + props->scale = BACKLIGHT_SCALE_LINEAR; + props->type = BACKLIGHT_RAW; +} +EXPORT_IF_KUNIT(amdgpu_dm_backlight_fill_props); + void amdgpu_dm_register_backlight_device(struct amdgpu_dm_connector *aconnector) { @@ -402,7 +434,6 @@ amdgpu_dm_register_backlight_device(struct amdgpu_dm_connector *aconnector) struct backlight_properties props = { 0 }; struct amdgpu_dm_backlight_caps *caps; char bl_name[16]; - int min, max; int real_brightness; int init_brightness; @@ -417,26 +448,17 @@ amdgpu_dm_register_backlight_device(struct amdgpu_dm_connector *aconnector) } caps = &dm->backlight_caps[aconnector->bl_idx]; - if (get_brightness_range(caps, &min, &max)) { - if (power_supply_is_system_supplied() > 0) - props.brightness = DIV_ROUND_CLOSEST((max - min) * caps->ac_level, 100); - else - props.brightness = DIV_ROUND_CLOSEST((max - min) * caps->dc_level, 100); - /* min is zero, so max needs to be adjusted */ - props.max_brightness = max - min; - drm_dbg(drm, "Backlight caps: min: %d, max: %d, ac %d, dc %d\n", min, max, - caps->ac_level, caps->dc_level); - } else - props.brightness = props.max_brightness = MAX_BACKLIGHT_LEVEL; + amdgpu_dm_backlight_fill_props(caps, power_supply_is_system_supplied() > 0, + !(amdgpu_dc_debug_mask & + DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE), + &props); + drm_dbg(drm, "Backlight caps: max_brightness: %d, ac %d, dc %d\n", + props.max_brightness, caps->ac_level, caps->dc_level); init_brightness = props.brightness; - if (caps->data_points && !(amdgpu_dc_debug_mask & DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE)) { + if (props.scale == BACKLIGHT_SCALE_NON_LINEAR) drm_info(drm, "Using custom brightness curve\n"); - props.scale = BACKLIGHT_SCALE_NON_LINEAR; - } else - props.scale = BACKLIGHT_SCALE_LINEAR; - props.type = BACKLIGHT_RAW; snprintf(bl_name, sizeof(bl_name), "amdgpu_bl%d", drm->primary->index + aconnector->bl_idx); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h index a6c01b7ccab3..98d612c60ae9 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h @@ -26,6 +26,8 @@ struct amdgpu_display_manager; struct amdgpu_dm_connector; +struct backlight_device; +struct backlight_properties; struct drm_connector; struct attribute_group; @@ -56,6 +58,12 @@ u32 convert_brightness_from_user(const struct amdgpu_dm_backlight_caps *caps, uint32_t brightness); u32 convert_brightness_to_user(const struct amdgpu_dm_backlight_caps *caps, uint32_t brightness); +int amdgpu_dm_backlight_get_device_index(struct amdgpu_display_manager *dm, + struct backlight_device *bd); +void amdgpu_dm_backlight_fill_props(const struct amdgpu_dm_backlight_caps *caps, + bool is_system_supplied, + bool custom_curve_enabled, + struct backlight_properties *props); uint amdgpu_dm_get_dc_debug_mask(void); void amdgpu_dm_set_dc_debug_mask(uint val); int amdgpu_dm_get_abm_level_param(void); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c index 8763cd635ae1..0e9de940e5a8 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c @@ -6,6 +6,7 @@ */ #include +#include #include "dc.h" #include "amdgpu.h" @@ -13,6 +14,7 @@ #include "amdgpu_dm.h" #include "amdgpu_dm_backlight.h" #include "amd_shared.h" +#include "dc/inc/hw/panel_cntl.h" struct dm_backlight_connector_fixture { struct amdgpu_device *adev; @@ -47,6 +49,51 @@ static void setup_test_connector(struct kunit *test, fixture->link->connector_signal = signal; } +/* Tests for amdgpu_dm_backlight_get_device_index() */ + +/** + * dm_test_backlight_device_index_matches_second - Test matching second backlight device + * @test: The KUnit test context + */ +static void dm_test_backlight_device_index_matches_second(struct kunit *test) +{ + struct amdgpu_display_manager *dm = alloc_test_dm(test); + struct backlight_device *bd0; + struct backlight_device *bd1; + + bd0 = kunit_kzalloc(test, sizeof(*bd0), GFP_KERNEL); + bd1 = kunit_kzalloc(test, sizeof(*bd1), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, bd0); + KUNIT_ASSERT_NOT_NULL(test, bd1); + + dm->num_of_edps = 2; + dm->backlight_dev[0] = bd0; + dm->backlight_dev[1] = bd1; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_backlight_get_device_index(dm, bd1), 1); +} + +/** + * dm_test_backlight_device_index_missing_fallback - Test missing backlight device fallback + * @test: The KUnit test context + */ +static void dm_test_backlight_device_index_missing_fallback(struct kunit *test) +{ + struct amdgpu_display_manager *dm = alloc_test_dm(test); + struct backlight_device *known_bd; + struct backlight_device *unknown_bd; + + known_bd = kunit_kzalloc(test, sizeof(*known_bd), GFP_KERNEL); + unknown_bd = kunit_kzalloc(test, sizeof(*unknown_bd), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, known_bd); + KUNIT_ASSERT_NOT_NULL(test, unknown_bd); + + dm->num_of_edps = 1; + dm->backlight_dev[0] = known_bd; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_backlight_get_device_index(dm, unknown_bd), 0); +} + /* Tests for amdgpu_dm_update_backlight_caps() */ /** @@ -740,6 +787,75 @@ static void dm_test_brightness_range_zero_signals(struct kunit *test) KUNIT_EXPECT_EQ(test, max, 0U); } +/* Tests for amdgpu_dm_backlight_fill_props() */ + +/** + * dm_test_backlight_fill_props_ac_linear - Test AC brightness and linear scale + * @test: The KUnit test context + */ +static void dm_test_backlight_fill_props_ac_linear(struct kunit *test) +{ + struct backlight_properties props = {}; + struct amdgpu_dm_backlight_caps caps = {}; + unsigned int min, max; + + caps.min_input_signal = 12; + caps.max_input_signal = 255; + caps.ac_level = 40; + caps.dc_level = 20; + + get_brightness_range(&caps, &min, &max); + amdgpu_dm_backlight_fill_props(&caps, true, false, &props); + + KUNIT_EXPECT_EQ(test, props.brightness, + DIV_ROUND_CLOSEST((max - min) * caps.ac_level, 100)); + KUNIT_EXPECT_EQ(test, props.max_brightness, max - min); + KUNIT_EXPECT_EQ(test, props.scale, BACKLIGHT_SCALE_LINEAR); + KUNIT_EXPECT_EQ(test, props.type, BACKLIGHT_RAW); +} + +/** + * dm_test_backlight_fill_props_dc_nonlinear - Test DC brightness and non-linear scale + * @test: The KUnit test context + */ +static void dm_test_backlight_fill_props_dc_nonlinear(struct kunit *test) +{ + struct backlight_properties props = {}; + struct amdgpu_dm_backlight_caps caps = {}; + unsigned int min, max; + + caps.min_input_signal = 12; + caps.max_input_signal = 255; + caps.ac_level = 40; + caps.dc_level = 20; + caps.data_points = 2; + + get_brightness_range(&caps, &min, &max); + amdgpu_dm_backlight_fill_props(&caps, false, true, &props); + + KUNIT_EXPECT_EQ(test, props.brightness, + DIV_ROUND_CLOSEST((max - min) * caps.dc_level, 100)); + KUNIT_EXPECT_EQ(test, props.max_brightness, max - min); + KUNIT_EXPECT_EQ(test, props.scale, BACKLIGHT_SCALE_NON_LINEAR); + KUNIT_EXPECT_EQ(test, props.type, BACKLIGHT_RAW); +} + +/** + * dm_test_backlight_fill_props_default_range - Test default properties without caps + * @test: The KUnit test context + */ +static void dm_test_backlight_fill_props_default_range(struct kunit *test) +{ + struct backlight_properties props = {}; + + amdgpu_dm_backlight_fill_props(NULL, false, true, &props); + + KUNIT_EXPECT_EQ(test, props.brightness, MAX_BACKLIGHT_LEVEL); + KUNIT_EXPECT_EQ(test, props.max_brightness, MAX_BACKLIGHT_LEVEL); + KUNIT_EXPECT_EQ(test, props.scale, BACKLIGHT_SCALE_LINEAR); + KUNIT_EXPECT_EQ(test, props.type, BACKLIGHT_RAW); +} + /* Tests for amdgpu_dm_update_connector_ext_caps() */ /** @@ -1062,6 +1178,9 @@ static void dm_test_setup_backlight_device_oled_success(struct kunit *test) } static struct kunit_case dm_backlight_test_cases[] = { + /* amdgpu_dm_backlight_get_device_index */ + KUNIT_CASE(dm_test_backlight_device_index_matches_second), + KUNIT_CASE(dm_test_backlight_device_index_missing_fallback), KUNIT_CASE(dm_test_backlight_caps_valid_short_circuit), #if !defined(CONFIG_ACPI) KUNIT_CASE(dm_test_backlight_caps_aux_support_noop), @@ -1095,6 +1214,10 @@ static struct kunit_case dm_backlight_test_cases[] = { KUNIT_CASE(dm_test_brightness_from_user_midrange), KUNIT_CASE(dm_test_brightness_from_user_with_curve), KUNIT_CASE(dm_test_brightness_range_zero_signals), + /* amdgpu_dm_backlight_fill_props */ + KUNIT_CASE(dm_test_backlight_fill_props_ac_linear), + KUNIT_CASE(dm_test_backlight_fill_props_dc_nonlinear), + KUNIT_CASE(dm_test_backlight_fill_props_default_range), /* amdgpu_dm_update_connector_ext_caps */ KUNIT_CASE(dm_test_update_connector_ext_caps_negative_bl_idx), KUNIT_CASE(dm_test_update_connector_ext_caps_non_edp), -- cgit v1.2.3 From 88ae862060f05cd8279e764832f04eafafa505d8 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Fri, 12 Jun 2026 20:25:05 -0600 Subject: drm/amd/display: Add more KUnit tests for amdgpu_dm_colorop [WHAT] Add KUnit coverage for amdgpu_dm_initialize_default_pipeline() using an amdgpu_device-backed DRM mock so drm_to_adev() and the DC color capability checks are exercised. Assisted-by: Copilot:GPT-5.5 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../drm/amd/display/amdgpu_dm/amdgpu_dm_colorop.c | 1 + .../amdgpu_dm/tests/amdgpu_dm_colorop_test.c | 147 ++++++++++++++++++--- 2 files changed, 133 insertions(+), 15 deletions(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_colorop.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_colorop.c index 48f5c431eaf9..056a76b88f43 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_colorop.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_colorop.c @@ -235,3 +235,4 @@ int amdgpu_dm_initialize_default_pipeline(struct drm_plane *plane, struct drm_pr return amdgpu_dm_build_default_pipeline(dev, plane, hw_3d_lut, list); } +EXPORT_IF_KUNIT(amdgpu_dm_initialize_default_pipeline); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_colorop_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_colorop_test.c index fa270ff28c6a..b28a165b213e 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_colorop_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_colorop_test.c @@ -9,6 +9,8 @@ #include #include +#include "dc.h" +#include "amdgpu.h" #include "amdgpu_dm_colorop.h" /* Tests for amdgpu_dm_supported_degam_tfs */ @@ -133,6 +135,30 @@ static void kunit_colorop_pipeline_destroy(void *drm) drm_colorop_pipeline_destroy((struct drm_device *)drm); } +static void dm_expect_colorop_pipeline(struct kunit *test, struct drm_device *drm, + const struct drm_prop_enum_list *list, + const enum drm_colorop_type *expected, + int expected_count) +{ + struct drm_colorop *op, *first = NULL; + int i = 0; + + drm_for_each_colorop(op, drm) { + if (op->base.id == (uint32_t)list->type) { + first = op; + break; + } + } + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, first); + + for (op = first; op; op = op->next, i++) { + KUNIT_ASSERT_LT(test, i, expected_count); + KUNIT_EXPECT_EQ(test, op->type, expected[i]); + KUNIT_EXPECT_NOT_NULL(test, op->bypass_property); + } + KUNIT_EXPECT_EQ(test, i, expected_count); +} + /** * dm_test_initialize_default_pipeline() - Verify amdgpu_dm_build_default_pipeline() * produces the expected colorop chain with all ops bypassable. @@ -154,8 +180,6 @@ static void dm_test_initialize_default_pipeline(struct kunit *test) struct drm_device *drm; struct drm_plane *plane; struct drm_prop_enum_list list = {}; - struct drm_colorop *op, *first = NULL; - int i = 0; int ret; dev = drm_kunit_helper_alloc_device(test); @@ -185,20 +209,110 @@ static void dm_test_initialize_default_pipeline(struct kunit *test) KUNIT_ASSERT_EQ(test, ret, 0); kfree(list.name); - drm_for_each_colorop(op, drm) { - if (op->base.id == (uint32_t)list.type) { - first = op; - break; - } - } - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, first); + dm_expect_colorop_pipeline(test, drm, &list, expected, ARRAY_SIZE(expected)); +} - for (op = first; op; op = op->next, i++) { - KUNIT_ASSERT_LT(test, i, (int)ARRAY_SIZE(expected)); - KUNIT_EXPECT_EQ(test, op->type, expected[i]); - KUNIT_EXPECT_NOT_NULL(test, op->bypass_property); - } - KUNIT_EXPECT_EQ(test, i, (int)ARRAY_SIZE(expected)); +static void dm_test_initialize_default_pipeline_caps(struct kunit *test, + bool dpp_hw_3d_lut, + bool mpc_preblend, + const enum drm_colorop_type *expected, + int expected_count) +{ + struct drm_prop_enum_list list = {}; + struct amdgpu_device *adev; + struct drm_device *drm; + struct drm_plane *plane; + struct device *dev; + struct dc *dc; + int ret; + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + drm = __drm_kunit_helper_alloc_drm_device(test, dev, + sizeof(*adev), + offsetof(struct amdgpu_device, ddev), + DRIVER_MODESET); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + adev = drm_to_adev(drm); + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); + adev->dm.dc = dc; + adev->dm.dc->caps.color.dpp.hw_3d_lut = dpp_hw_3d_lut; + adev->dm.dc->caps.color.mpc.preblend = mpc_preblend; + + plane = drm_kunit_helper_create_primary_plane(test, drm, + NULL, NULL, NULL, 0, NULL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane); + + kunit_add_action(test, kunit_colorop_pipeline_destroy, drm); + + ret = amdgpu_dm_initialize_default_pipeline(plane, &list); + KUNIT_ASSERT_EQ(test, ret, 0); + kfree(list.name); + + dm_expect_colorop_pipeline(test, drm, &list, expected, expected_count); +} + +/** + * dm_test_initialize_default_pipeline_dpp_3d_lut() - Test DPP 3D LUT cap. + * @test: KUnit test context. + */ +static void dm_test_initialize_default_pipeline_dpp_3d_lut(struct kunit *test) +{ + static const enum drm_colorop_type expected[] = { + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_MULTIPLIER, + DRM_COLOROP_CTM_3X4, + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_1D_LUT, + DRM_COLOROP_3D_LUT, + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_1D_LUT, + }; + + dm_test_initialize_default_pipeline_caps(test, true, false, + expected, ARRAY_SIZE(expected)); +} + +/** + * dm_test_initialize_default_pipeline_mpc_preblend() - Test MPC preblend cap. + * @test: KUnit test context. + */ +static void dm_test_initialize_default_pipeline_mpc_preblend(struct kunit *test) +{ + static const enum drm_colorop_type expected[] = { + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_MULTIPLIER, + DRM_COLOROP_CTM_3X4, + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_1D_LUT, + DRM_COLOROP_3D_LUT, + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_1D_LUT, + }; + + dm_test_initialize_default_pipeline_caps(test, false, true, + expected, ARRAY_SIZE(expected)); +} + +/** + * dm_test_initialize_default_pipeline_no_3d_lut() - Test no 3D LUT caps. + * @test: KUnit test context. + */ +static void dm_test_initialize_default_pipeline_no_3d_lut(struct kunit *test) +{ + static const enum drm_colorop_type expected[] = { + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_MULTIPLIER, + DRM_COLOROP_CTM_3X4, + DRM_COLOROP_1D_CURVE, + DRM_COLOROP_1D_LUT, + }; + + dm_test_initialize_default_pipeline_caps(test, false, false, + expected, ARRAY_SIZE(expected)); } static struct kunit_case dm_colorop_test_cases[] = { @@ -224,6 +338,9 @@ static struct kunit_case dm_colorop_test_cases[] = { KUNIT_CASE(dm_test_degam_and_blnd_tfs_match), /* amdgpu_dm_initialize_default_pipeline */ KUNIT_CASE(dm_test_initialize_default_pipeline), + KUNIT_CASE(dm_test_initialize_default_pipeline_dpp_3d_lut), + KUNIT_CASE(dm_test_initialize_default_pipeline_mpc_preblend), + KUNIT_CASE(dm_test_initialize_default_pipeline_no_3d_lut), {} }; -- cgit v1.2.3 From 2b147895be109e0860269a7a72c697cdf049a885 Mon Sep 17 00:00:00 2001 From: Bhawanpreet Lakha Date: Fri, 12 Jun 2026 16:12:21 -0400 Subject: drm/amd/display: Add kunit tests for amdgpu_dm_plane Add kunit tests for some functions in amdgpu_dm_plane. Assisted-by: Copilot:Claude-Opus-4.8 Reviewed-by: Alex Hung Signed-off-by: Bhawanpreet Lakha Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c | 115 +- .../drm/amd/display/amdgpu_dm/amdgpu_dm_plane.h | 51 + .../gpu/drm/amd/display/amdgpu_dm/tests/Makefile | 2 + .../display/amdgpu_dm/tests/amdgpu_dm_plane_test.c | 1204 ++++++++++++++++++++ 4 files changed, 1325 insertions(+), 47 deletions(-) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c index c7f8e08feaf4..62f1ad1ff7b5 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c @@ -37,6 +37,7 @@ #include "amdgpu_display.h" #include "amdgpu_dm_trace.h" #include "amdgpu_dm_plane.h" +#include "amdgpu_dm_kunit_helpers.h" #include "amdgpu_dm_colorop.h" #include "gc/gc_11_0_0_offset.h" #include "gc/gc_11_0_0_sh_mask.h" @@ -97,6 +98,7 @@ const struct drm_format_info *amdgpu_dm_plane_get_format_info(u32 pixel_format, { return amdgpu_lookup_format_info(pixel_format, modifier); } +EXPORT_IF_KUNIT(amdgpu_dm_plane_get_format_info); void amdgpu_dm_plane_fill_blending_from_plane_state(const struct drm_plane_state *plane_state, bool *per_pixel_alpha, bool *pre_multiplied_alpha, @@ -139,8 +141,10 @@ void amdgpu_dm_plane_fill_blending_from_plane_state(const struct drm_plane_state *global_alpha_value = plane_state->alpha >> 8; } } +EXPORT_IF_KUNIT(amdgpu_dm_plane_fill_blending_from_plane_state); -static void amdgpu_dm_plane_add_modifier(uint64_t **mods, uint64_t *size, uint64_t *cap, uint64_t mod) +STATIC_IFN_KUNIT void amdgpu_dm_plane_add_modifier(uint64_t **mods, uint64_t *size, + uint64_t *cap, uint64_t mod) { if (!*mods) return; @@ -164,27 +168,29 @@ static void amdgpu_dm_plane_add_modifier(uint64_t **mods, uint64_t *size, uint64 (*mods)[*size] = mod; *size += 1; } +EXPORT_IF_KUNIT(amdgpu_dm_plane_add_modifier); -static bool amdgpu_dm_plane_modifier_has_dcc(uint64_t modifier) +STATIC_IFN_KUNIT bool amdgpu_dm_plane_modifier_has_dcc(uint64_t modifier) { return IS_AMD_FMT_MOD(modifier) && AMD_FMT_MOD_GET(DCC, modifier); } +EXPORT_IF_KUNIT(amdgpu_dm_plane_modifier_has_dcc); -static unsigned int amdgpu_dm_plane_modifier_gfx9_swizzle_mode(uint64_t modifier) +STATIC_IFN_KUNIT unsigned int amdgpu_dm_plane_modifier_gfx9_swizzle_mode(uint64_t modifier) { if (modifier == DRM_FORMAT_MOD_LINEAR) return 0; return AMD_FMT_MOD_GET(TILE, modifier); } +EXPORT_IF_KUNIT(amdgpu_dm_plane_modifier_gfx9_swizzle_mode); -static void amdgpu_dm_plane_fill_gfx8_tiling_info_from_flags(struct dc_tiling_info *tiling_info, - uint64_t tiling_flags) +STATIC_IFN_KUNIT void amdgpu_dm_plane_fill_gfx8_tiling_info_from_flags(struct dc_tiling_info *tiling_info, + uint64_t tiling_flags) { /* Fill GFX8 params */ if (AMDGPU_TILING_GET(tiling_flags, ARRAY_MODE) == DC_ARRAY_2D_TILED_THIN1) { unsigned int bankw, bankh, mtaspect, tile_split, num_banks; - bankw = AMDGPU_TILING_GET(tiling_flags, BANK_WIDTH); bankh = AMDGPU_TILING_GET(tiling_flags, BANK_HEIGHT); mtaspect = AMDGPU_TILING_GET(tiling_flags, MACRO_TILE_ASPECT); @@ -210,9 +216,10 @@ static void amdgpu_dm_plane_fill_gfx8_tiling_info_from_flags(struct dc_tiling_in tiling_info->gfx8.pipe_config = AMDGPU_TILING_GET(tiling_flags, PIPE_CONFIG); } +EXPORT_IF_KUNIT(amdgpu_dm_plane_fill_gfx8_tiling_info_from_flags); -static void amdgpu_dm_plane_fill_gfx9_tiling_info_from_device(const struct amdgpu_device *adev, - struct dc_tiling_info *tiling_info) +STATIC_IFN_KUNIT void amdgpu_dm_plane_fill_gfx9_tiling_info_from_device(const struct amdgpu_device *adev, + struct dc_tiling_info *tiling_info) { /* Fill GFX9 params */ tiling_info->gfx9.num_pipes = @@ -231,10 +238,11 @@ static void amdgpu_dm_plane_fill_gfx9_tiling_info_from_device(const struct amdgp if (amdgpu_ip_version(adev, GC_HWIP, 0) >= IP_VERSION(10, 3, 0)) tiling_info->gfx9.num_pkrs = adev->gfx.config.gb_addr_config_fields.num_pkrs; } +EXPORT_IF_KUNIT(amdgpu_dm_plane_fill_gfx9_tiling_info_from_device); -static void amdgpu_dm_plane_fill_gfx9_tiling_info_from_modifier(const struct amdgpu_device *adev, - struct dc_tiling_info *tiling_info, - uint64_t modifier) +STATIC_IFN_KUNIT void amdgpu_dm_plane_fill_gfx9_tiling_info_from_modifier(const struct amdgpu_device *adev, + struct dc_tiling_info *tiling_info, + uint64_t modifier) { unsigned int mod_bank_xor_bits = AMD_FMT_MOD_GET(BANK_XOR_BITS, modifier); unsigned int mod_pipe_xor_bits = AMD_FMT_MOD_GET(PIPE_XOR_BITS, modifier); @@ -259,14 +267,15 @@ static void amdgpu_dm_plane_fill_gfx9_tiling_info_from_modifier(const struct amd /* for DCC we know it isn't rb aligned, so rb_per_se doesn't matter. */ } } - -static int amdgpu_dm_plane_validate_dcc(struct amdgpu_device *adev, - const enum surface_pixel_format format, - const enum dc_rotation_angle rotation, - const struct dc_tiling_info *tiling_info, - const struct dc_plane_dcc_param *dcc, - const struct dc_plane_address *address, - const struct plane_size *plane_size) +EXPORT_IF_KUNIT(amdgpu_dm_plane_fill_gfx9_tiling_info_from_modifier); + +STATIC_IFN_KUNIT int amdgpu_dm_plane_validate_dcc(struct amdgpu_device *adev, + const enum surface_pixel_format format, + const enum dc_rotation_angle rotation, + const struct dc_tiling_info *tiling_info, + const struct dc_plane_dcc_param *dcc, + const struct dc_plane_address *address, + const struct plane_size *plane_size) { struct dc *dc = adev->dm.dc; struct dc_dcc_surface_param input; @@ -307,15 +316,16 @@ static int amdgpu_dm_plane_validate_dcc(struct amdgpu_device *adev, return 0; } - -static int amdgpu_dm_plane_fill_gfx9_plane_attributes_from_modifiers(struct amdgpu_device *adev, - const struct amdgpu_framebuffer *afb, - const enum surface_pixel_format format, - const enum dc_rotation_angle rotation, - const struct plane_size *plane_size, - struct dc_tiling_info *tiling_info, - struct dc_plane_dcc_param *dcc, - struct dc_plane_address *address) +EXPORT_IF_KUNIT(amdgpu_dm_plane_validate_dcc); + +STATIC_IFN_KUNIT int amdgpu_dm_plane_fill_gfx9_plane_attributes_from_modifiers(struct amdgpu_device *adev, + const struct amdgpu_framebuffer *afb, + const enum surface_pixel_format format, + const enum dc_rotation_angle rotation, + const struct plane_size *plane_size, + struct dc_tiling_info *tiling_info, + struct dc_plane_dcc_param *dcc, + struct dc_plane_address *address) { const uint64_t modifier = afb->base.modifier; int ret = 0; @@ -358,15 +368,16 @@ static int amdgpu_dm_plane_fill_gfx9_plane_attributes_from_modifiers(struct amdg return ret; } - -static int amdgpu_dm_plane_fill_gfx12_plane_attributes_from_modifiers(struct amdgpu_device *adev, - const struct amdgpu_framebuffer *afb, - const enum surface_pixel_format format, - const enum dc_rotation_angle rotation, - const struct plane_size *plane_size, - struct dc_tiling_info *tiling_info, - struct dc_plane_dcc_param *dcc, - struct dc_plane_address *address) +EXPORT_IF_KUNIT(amdgpu_dm_plane_fill_gfx9_plane_attributes_from_modifiers); + +STATIC_IFN_KUNIT int amdgpu_dm_plane_fill_gfx12_plane_attributes_from_modifiers(struct amdgpu_device *adev, + const struct amdgpu_framebuffer *afb, + const enum surface_pixel_format format, + const enum dc_rotation_angle rotation, + const struct plane_size *plane_size, + struct dc_tiling_info *tiling_info, + struct dc_plane_dcc_param *dcc, + struct dc_plane_address *address) { const uint64_t modifier = afb->base.modifier; int ret = 0; @@ -398,6 +409,7 @@ static int amdgpu_dm_plane_fill_gfx12_plane_attributes_from_modifiers(struct amd return ret; } +EXPORT_IF_KUNIT(amdgpu_dm_plane_fill_gfx12_plane_attributes_from_modifiers); static void amdgpu_dm_plane_add_gfx10_1_modifiers(const struct amdgpu_device *adev, uint64_t **mods, @@ -724,7 +736,7 @@ static void amdgpu_dm_plane_add_gfx12_modifiers(struct amdgpu_device *adev, } -static int amdgpu_dm_plane_get_plane_modifiers(struct amdgpu_device *adev, unsigned int plane_type, uint64_t **mods) +STATIC_IFN_KUNIT int amdgpu_dm_plane_get_plane_modifiers(struct amdgpu_device *adev, unsigned int plane_type, uint64_t **mods) { uint64_t size = 0, capacity = 128; *mods = NULL; @@ -777,10 +789,11 @@ static int amdgpu_dm_plane_get_plane_modifiers(struct amdgpu_device *adev, unsig return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_plane_get_plane_modifiers); -static int amdgpu_dm_plane_get_plane_formats(const struct drm_plane *plane, - const struct dc_plane_cap *plane_cap, - uint32_t *formats, int max_formats) +STATIC_IFN_KUNIT int amdgpu_dm_plane_get_plane_formats(const struct drm_plane *plane, + const struct dc_plane_cap *plane_cap, + uint32_t *formats, int max_formats) { int i, num_formats = 0; @@ -836,6 +849,7 @@ static int amdgpu_dm_plane_get_plane_formats(const struct drm_plane *plane, return num_formats; } +EXPORT_IF_KUNIT(amdgpu_dm_plane_get_plane_formats); int amdgpu_dm_plane_fill_plane_buffer_attributes(struct amdgpu_device *adev, const struct amdgpu_framebuffer *afb, @@ -922,6 +936,7 @@ int amdgpu_dm_plane_fill_plane_buffer_attributes(struct amdgpu_device *adev, return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_plane_fill_plane_buffer_attributes); static int amdgpu_dm_plane_helper_prepare_fb(struct drm_plane *plane, struct drm_plane_state *new_state) @@ -1042,9 +1057,9 @@ static void amdgpu_dm_plane_helper_cleanup_fb(struct drm_plane *plane, amdgpu_bo_unref(&rbo); } -static void amdgpu_dm_plane_get_min_max_dc_plane_scaling(struct drm_device *dev, - struct drm_framebuffer *fb, - int *min_downscale, int *max_upscale) +STATIC_IFN_KUNIT void amdgpu_dm_plane_get_min_max_dc_plane_scaling(struct drm_device *dev, + struct drm_framebuffer *fb, + int *min_downscale, int *max_upscale) { struct amdgpu_device *adev = drm_to_adev(dev); struct dc *dc = adev->dm.dc; @@ -1088,6 +1103,7 @@ static void amdgpu_dm_plane_get_min_max_dc_plane_scaling(struct drm_device *dev, if (*min_downscale == 1) *min_downscale = 1000; } +EXPORT_IF_KUNIT(amdgpu_dm_plane_get_min_max_dc_plane_scaling); int amdgpu_dm_plane_helper_check_state(struct drm_plane_state *state, struct drm_crtc_state *new_crtc_state) @@ -1142,6 +1158,7 @@ int amdgpu_dm_plane_helper_check_state(struct drm_plane_state *state, return drm_atomic_helper_check_plane_state( state, new_crtc_state, min_scale, max_scale, true, true); } +EXPORT_IF_KUNIT(amdgpu_dm_plane_helper_check_state); int amdgpu_dm_plane_fill_dc_scaling_info(struct amdgpu_device *adev, const struct drm_plane_state *state, @@ -1225,6 +1242,7 @@ int amdgpu_dm_plane_fill_dc_scaling_info(struct amdgpu_device *adev, return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_plane_fill_dc_scaling_info); static int amdgpu_dm_plane_atomic_check(struct drm_plane *plane, struct drm_atomic_commit *state) @@ -1343,6 +1361,7 @@ int amdgpu_dm_plane_get_cursor_position(struct drm_plane *plane, struct drm_crtc return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_plane_get_cursor_position); void amdgpu_dm_plane_handle_cursor_update(struct drm_plane *plane, struct drm_plane_state *old_plane_state) @@ -1546,9 +1565,9 @@ static struct drm_plane_state *amdgpu_dm_plane_drm_plane_duplicate_state(struct return &dm_plane_state->base; } -static bool amdgpu_dm_plane_format_mod_supported(struct drm_plane *plane, - uint32_t format, - uint64_t modifier) +STATIC_IFN_KUNIT bool amdgpu_dm_plane_format_mod_supported(struct drm_plane *plane, + uint32_t format, + uint64_t modifier) { struct amdgpu_device *adev = drm_to_adev(plane->dev); const struct drm_format_info *info = drm_format_info(format); @@ -1607,6 +1626,7 @@ static bool amdgpu_dm_plane_format_mod_supported(struct drm_plane *plane, return true; } +EXPORT_IF_KUNIT(amdgpu_dm_plane_format_mod_supported); static void amdgpu_dm_plane_drm_plane_destroy_state(struct drm_plane *plane, struct drm_plane_state *state) @@ -1982,4 +2002,5 @@ bool amdgpu_dm_plane_is_video_format(uint32_t format) return false; } +EXPORT_IF_KUNIT(amdgpu_dm_plane_is_video_format); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.h index ea2619b507db..911fb2d73e22 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.h @@ -28,6 +28,8 @@ #define __AMDGPU_DM_PLANE_H__ #include "dc.h" +#include +#include "amdgpu.h" int amdgpu_dm_plane_get_cursor_position(struct drm_plane *plane, struct drm_crtc *crtc, struct dc_cursor_position *position); @@ -65,4 +67,53 @@ void amdgpu_dm_plane_fill_blending_from_plane_state(const struct drm_plane_state bool *global_alpha, int *global_alpha_value); bool amdgpu_dm_plane_is_video_format(uint32_t format); + +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +void amdgpu_dm_plane_add_modifier(uint64_t **mods, uint64_t *size, + uint64_t *cap, uint64_t mod); +void amdgpu_dm_plane_fill_gfx8_tiling_info_from_flags(struct dc_tiling_info *tiling_info, + uint64_t tiling_flags); +void amdgpu_dm_plane_fill_gfx9_tiling_info_from_device(const struct amdgpu_device *adev, + struct dc_tiling_info *tiling_info); +void amdgpu_dm_plane_fill_gfx9_tiling_info_from_modifier(const struct amdgpu_device *adev, + struct dc_tiling_info *tiling_info, + uint64_t modifier); +int amdgpu_dm_plane_validate_dcc(struct amdgpu_device *adev, + const enum surface_pixel_format format, + const enum dc_rotation_angle rotation, + const struct dc_tiling_info *tiling_info, + const struct dc_plane_dcc_param *dcc, + const struct dc_plane_address *address, + const struct plane_size *plane_size); +bool amdgpu_dm_plane_modifier_has_dcc(uint64_t modifier); +unsigned int amdgpu_dm_plane_modifier_gfx9_swizzle_mode(uint64_t modifier); +int amdgpu_dm_plane_get_plane_modifiers(struct amdgpu_device *adev, + unsigned int plane_type, uint64_t **mods); +int amdgpu_dm_plane_get_plane_formats(const struct drm_plane *plane, + const struct dc_plane_cap *plane_cap, + uint32_t *formats, int max_formats); +int amdgpu_dm_plane_fill_gfx9_plane_attributes_from_modifiers(struct amdgpu_device *adev, + const struct amdgpu_framebuffer *afb, + const enum surface_pixel_format format, + const enum dc_rotation_angle rotation, + const struct plane_size *plane_size, + struct dc_tiling_info *tiling_info, + struct dc_plane_dcc_param *dcc, + struct dc_plane_address *address); +int amdgpu_dm_plane_fill_gfx12_plane_attributes_from_modifiers(struct amdgpu_device *adev, + const struct amdgpu_framebuffer *afb, + const enum surface_pixel_format format, + const enum dc_rotation_angle rotation, + const struct plane_size *plane_size, + struct dc_tiling_info *tiling_info, + struct dc_plane_dcc_param *dcc, + struct dc_plane_address *address); +bool amdgpu_dm_plane_format_mod_supported(struct drm_plane *plane, + uint32_t format, + uint64_t modifier); +void amdgpu_dm_plane_get_min_max_dc_plane_scaling(struct drm_device *dev, + struct drm_framebuffer *fb, + int *min_downscale, + int *max_upscale); +#endif #endif diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile index 168ad064e7cb..4d89ad8a6df6 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile @@ -24,6 +24,7 @@ obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_replay_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_ism_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_irq_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_wb_test.o +obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_plane_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_mst_types_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_pp_smu_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_test.o @@ -31,3 +32,4 @@ obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_crtc_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_services_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_helpers_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_quirks_test.o +obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_plane_test.o diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c new file mode 100644 index 000000000000..deec75857c0e --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c @@ -0,0 +1,1204 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit tests for amdgpu_dm_plane.c + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + + #include + #include + #include "link_enc_cfg.h" + #include "amdgpu_dm_plane.h" + #include + #include + + +struct dm_test_dcc_cap_ctx { + bool callback_ret; + bool capable; + bool output_independent_64b_blks; + bool called; + struct dc_dcc_surface_param captured_input; +}; + +static struct dm_test_dcc_cap_ctx *dm_test_dcc_ctx; + +static bool dm_test_get_dcc_compression_cap(const struct dc *dc, + const struct dc_dcc_surface_param *input, + struct dc_surface_dcc_cap *output) +{ + if (!dm_test_dcc_ctx) + return false; + + dm_test_dcc_ctx->called = true; + dm_test_dcc_ctx->captured_input = *input; + output->capable = dm_test_dcc_ctx->capable; + output->grph.rgb.independent_64b_blks = dm_test_dcc_ctx->output_independent_64b_blks; + + return dm_test_dcc_ctx->callback_ret; +} + +static void dm_test_init_validate_dcc_inputs(struct amdgpu_device **adev, + struct dc **dc, + struct dc_tiling_info *tiling_info, + struct dc_plane_dcc_param *dcc, + struct dc_plane_address *address, + struct plane_size *plane_size, + struct kunit *test) +{ + *adev = kunit_kzalloc(test, sizeof(**adev), GFP_KERNEL); + *dc = kunit_kzalloc(test, sizeof(**dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, *adev); + KUNIT_ASSERT_NOT_NULL(test, *dc); + + (*adev)->dm.dc = *dc; + (*adev)->family = AMDGPU_FAMILY_NV; + + tiling_info->gfx9.swizzle = 9; + dcc->enable = 1; + dcc->independent_64b_blks = 1; + plane_size->surface_size.width = 1920; + plane_size->surface_size.height = 1080; + + (void)address; +} + + +/** + * dm_test_plane_is_video_format_known_video() - Verify known video formats. + * @test: KUnit test context. + * + * Verify if NV12, NV21, and P010 are treated as video formats. + */ +static void dm_test_plane_is_video_format_known_video(struct kunit *test) +{ + KUNIT_EXPECT_TRUE(test, amdgpu_dm_plane_is_video_format(DRM_FORMAT_NV12)); + KUNIT_EXPECT_TRUE(test, amdgpu_dm_plane_is_video_format(DRM_FORMAT_NV21)); + KUNIT_EXPECT_TRUE(test, amdgpu_dm_plane_is_video_format(DRM_FORMAT_P010)); +} + +/** + * dm_test_fill_blending_defaults() - Verify default blending output values. + * @test: KUnit test context. + * + * Verify if default blending output values are used for opaque alpha and no + * per-pixel blending. + */ +static void dm_test_fill_blending_defaults(struct kunit *test) +{ + struct drm_plane_state state = { 0 }; + bool per_pixel_alpha; + bool pre_multiplied_alpha; + bool global_alpha; + int global_alpha_value; + + state.pixel_blend_mode = DRM_MODE_BLEND_PIXEL_NONE; + state.alpha = 0xffff; + + amdgpu_dm_plane_fill_blending_from_plane_state(&state, + &per_pixel_alpha, + &pre_multiplied_alpha, + &global_alpha, + &global_alpha_value); + + KUNIT_EXPECT_FALSE(test, per_pixel_alpha); + KUNIT_EXPECT_TRUE(test, pre_multiplied_alpha); + KUNIT_EXPECT_FALSE(test, global_alpha); + KUNIT_EXPECT_EQ(test, global_alpha_value, 0xff); +} + +/** + * dm_test_fill_blending_premulti_alpha_format() - Verify premultiplied alpha path. + * @test: KUnit test context. + * + * Verify if premultiplied mode enables per-pixel alpha for ARGB8888. + */ +static void dm_test_fill_blending_premulti_alpha_format(struct kunit *test) +{ + struct drm_plane_state state = { 0 }; + struct drm_framebuffer fb = { 0 }; + bool per_pixel_alpha; + bool pre_multiplied_alpha; + bool global_alpha; + int global_alpha_value; + + fb.format = drm_format_info(DRM_FORMAT_ARGB8888); + KUNIT_ASSERT_NOT_NULL(test, fb.format); + + state.fb = &fb; + state.pixel_blend_mode = DRM_MODE_BLEND_PREMULTI; + state.alpha = 0xffff; + + amdgpu_dm_plane_fill_blending_from_plane_state(&state, + &per_pixel_alpha, + &pre_multiplied_alpha, + &global_alpha, + &global_alpha_value); + + KUNIT_EXPECT_TRUE(test, per_pixel_alpha); + KUNIT_EXPECT_TRUE(test, pre_multiplied_alpha); + KUNIT_EXPECT_FALSE(test, global_alpha); + KUNIT_EXPECT_EQ(test, global_alpha_value, 0xff); +} + +/** + * dm_test_fill_blending_coverage_alpha_format() - Verify coverage mode behavior. + * @test: KUnit test context. + * + * Verify if coverage mode sets per-pixel alpha and disables + * pre_multiplied_alpha for ARGB8888. + */ +static void dm_test_fill_blending_coverage_alpha_format(struct kunit *test) +{ + struct drm_plane_state state = { 0 }; + struct drm_framebuffer fb = { 0 }; + bool per_pixel_alpha; + bool pre_multiplied_alpha; + bool global_alpha; + int global_alpha_value; + + fb.format = drm_format_info(DRM_FORMAT_ARGB8888); + KUNIT_ASSERT_NOT_NULL(test, fb.format); + + state.fb = &fb; + state.pixel_blend_mode = DRM_MODE_BLEND_COVERAGE; + state.alpha = 0xffff; + + amdgpu_dm_plane_fill_blending_from_plane_state(&state, + &per_pixel_alpha, + &pre_multiplied_alpha, + &global_alpha, + &global_alpha_value); + + KUNIT_EXPECT_TRUE(test, per_pixel_alpha); + KUNIT_EXPECT_FALSE(test, pre_multiplied_alpha); + KUNIT_EXPECT_FALSE(test, global_alpha); + KUNIT_EXPECT_EQ(test, global_alpha_value, 0xff); +} + +/** + * dm_test_fill_blending_global_alpha() - Verify global alpha conversion to 8 bits. + * @test: KUnit test context. + * + * Verify if global alpha is enabled and converted from 16-bit to 8-bit. + */ +static void dm_test_fill_blending_global_alpha(struct kunit *test) +{ + struct drm_plane_state state = { 0 }; + bool per_pixel_alpha; + bool pre_multiplied_alpha; + bool global_alpha; + int global_alpha_value; + + state.pixel_blend_mode = DRM_MODE_BLEND_PIXEL_NONE; + state.alpha = 0x8000; + + amdgpu_dm_plane_fill_blending_from_plane_state(&state, + &per_pixel_alpha, + &pre_multiplied_alpha, + &global_alpha, + &global_alpha_value); + + KUNIT_EXPECT_FALSE(test, per_pixel_alpha); + KUNIT_EXPECT_TRUE(test, pre_multiplied_alpha); + KUNIT_EXPECT_TRUE(test, global_alpha); + KUNIT_EXPECT_EQ(test, global_alpha_value, 0x80); +} + +/** + * dm_test_modifier_has_dcc() - Verify helper detects AMD DCC modifiers. + * @test: KUnit test context. + * + * Verify if DCC detection works for linear and AMD DCC modifiers. + */ +static void dm_test_modifier_has_dcc(struct kunit *test) +{ + uint64_t dcc_mod = AMD_FMT_MOD | AMD_FMT_MOD_SET(DCC, 1); + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_plane_modifier_has_dcc(DRM_FORMAT_MOD_LINEAR)); + KUNIT_EXPECT_TRUE(test, amdgpu_dm_plane_modifier_has_dcc(dcc_mod)); +} + +/** + * dm_test_modifier_gfx9_swizzle_mode() - Verify swizzle helper for linear and AMD modifiers. + * @test: KUnit test context. + * + * Verify if swizzle mode decoding works for linear and AMD tiled modifiers. + */ +static void dm_test_modifier_gfx9_swizzle_mode(struct kunit *test) +{ + uint64_t mod = AMD_FMT_MOD | AMD_FMT_MOD_SET(TILE, AMD_FMT_MOD_TILE_GFX9_64K_S_X); + + KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_modifier_gfx9_swizzle_mode(DRM_FORMAT_MOD_LINEAR), 0U); + KUNIT_EXPECT_EQ(test, + amdgpu_dm_plane_modifier_gfx9_swizzle_mode(mod), + (unsigned int)AMD_FMT_MOD_TILE_GFX9_64K_S_X); +} + +/** + * dm_test_get_plane_formats() - Verify plane format counts for key plane types. + * @test: KUnit test context. + * + * Verify if returned format counts match primary, overlay, and cursor planes. + */ +static void dm_test_get_plane_formats(struct kunit *test) +{ + struct drm_plane plane = {0}; + struct dc_plane_cap cap = {0}; + uint32_t formats[32] = {0}; + + plane.type = DRM_PLANE_TYPE_PRIMARY; + KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_get_plane_formats(&plane, NULL, formats, 32), 14); + + cap.pixel_format_support.nv12 = true; + cap.pixel_format_support.p010 = true; + cap.pixel_format_support.fp16 = true; + KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_get_plane_formats(&plane, &cap, formats, 32), 20); + + plane.type = DRM_PLANE_TYPE_OVERLAY; + KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_get_plane_formats(&plane, NULL, formats, 32), 9); + + plane.type = DRM_PLANE_TYPE_CURSOR; + KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_get_plane_formats(&plane, NULL, formats, 32), 1); +} + +/** + * dm_test_get_plane_modifiers() - Verify early-return and cursor modifier list. + * @test: KUnit test context. + * + * Verify if modifier list handling works for unsupported families and cursor planes. + */ +static void dm_test_get_plane_modifiers(struct kunit *test) +{ + struct amdgpu_device *adev; + uint64_t *mods = NULL; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + + adev->family = AMDGPU_FAMILY_SI; + KUNIT_EXPECT_EQ(test, + amdgpu_dm_plane_get_plane_modifiers(adev, DRM_PLANE_TYPE_PRIMARY, &mods), + 0); + KUNIT_EXPECT_PTR_EQ(test, mods, NULL); + + adev->family = AMDGPU_FAMILY_NV; + KUNIT_ASSERT_EQ(test, + amdgpu_dm_plane_get_plane_modifiers(adev, DRM_PLANE_TYPE_CURSOR, &mods), + 0); + KUNIT_ASSERT_NOT_NULL(test, mods); + KUNIT_EXPECT_EQ(test, mods[0], DRM_FORMAT_MOD_LINEAR); + KUNIT_EXPECT_EQ(test, mods[1], DRM_FORMAT_MOD_INVALID); + kfree(mods); +} + +/** + * dm_test_fill_dc_scaling_info() - Verify basic error and success paths. + * @test: KUnit test context. + * + * Verify if scaling info rejects invalid sizes and accepts valid sizes. + */ +static void dm_test_fill_dc_scaling_info(struct kunit *test) +{ + struct amdgpu_device *adev; + struct drm_plane_state state = {0}; + struct dc_scaling_info info = {0}; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + + state.src_w = 0; + state.src_h = 100 << 16; + state.crtc_w = 100; + state.crtc_h = 100; + KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_fill_dc_scaling_info(adev, &state, &info), -EINVAL); + + state.src_w = 100 << 16; + state.src_h = 100 << 16; + state.crtc_w = 100; + state.crtc_h = 100; + KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_fill_dc_scaling_info(adev, &state, &info), 0); +} + +/** + * dm_test_get_min_max_dc_plane_scaling() - Verify format-specific cap selection and 1->1000 conversion. + * @test: KUnit test context. + * + * Verify if min/max scaling values are correct for NV12 and XRGB8888 formats. + */ +static void dm_test_get_min_max_dc_plane_scaling(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct drm_framebuffer *fb; + int min_downscale = 0; + int max_upscale = 0; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + fb = kunit_kzalloc(test, sizeof(*fb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, fb); + + adev->dm.dc = dc; + dc->caps.planes[0].max_upscale_factor.nv12 = 1; + dc->caps.planes[0].max_downscale_factor.nv12 = 1; + dc->caps.planes[0].max_upscale_factor.argb8888 = 1600; + dc->caps.planes[0].max_downscale_factor.argb8888 = 250; + + fb->format = drm_format_info(DRM_FORMAT_NV12); + KUNIT_ASSERT_NOT_NULL(test, fb->format); + amdgpu_dm_plane_get_min_max_dc_plane_scaling(&adev->ddev, fb, &min_downscale, &max_upscale); + KUNIT_EXPECT_EQ(test, min_downscale, 1000); + KUNIT_EXPECT_EQ(test, max_upscale, 1000); + + fb->format = drm_format_info(DRM_FORMAT_XRGB8888); + KUNIT_ASSERT_NOT_NULL(test, fb->format); + amdgpu_dm_plane_get_min_max_dc_plane_scaling(&adev->ddev, fb, &min_downscale, &max_upscale); + KUNIT_EXPECT_EQ(test, min_downscale, 250); + KUNIT_EXPECT_EQ(test, max_upscale, 1600); +} + +/** + * dm_test_fill_plane_buffer_attributes_gfx8() - Verify graphics path and GFX8 tiling fill. + * @test: KUnit test context. + * + * Verify if GFX8 plane buffer attributes and tiling fields are filled correctly. + */ +static void dm_test_fill_plane_buffer_attributes_gfx8(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_framebuffer *afb; + struct dc_tiling_info *tiling_info; + struct plane_size *plane_size; + struct dc_plane_dcc_param *dcc; + struct dc_plane_address *address; + uint64_t tiling_flags = 0; + int ret; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + afb = kunit_kzalloc(test, sizeof(*afb), GFP_KERNEL); + tiling_info = kunit_kzalloc(test, sizeof(*tiling_info), GFP_KERNEL); + plane_size = kunit_kzalloc(test, sizeof(*plane_size), GFP_KERNEL); + dcc = kunit_kzalloc(test, sizeof(*dcc), GFP_KERNEL); + address = kunit_kzalloc(test, sizeof(*address), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, afb); + KUNIT_ASSERT_NOT_NULL(test, tiling_info); + KUNIT_ASSERT_NOT_NULL(test, plane_size); + KUNIT_ASSERT_NOT_NULL(test, dcc); + KUNIT_ASSERT_NOT_NULL(test, address); + + adev->family = AMDGPU_FAMILY_SI; + afb->address = 0x12345000ULL; + afb->base.width = 1920; + afb->base.height = 1080; + afb->base.offsets[0] = 0x1000; + afb->base.pitches[0] = 7680; + afb->base.format = drm_format_info(DRM_FORMAT_XRGB8888); + KUNIT_ASSERT_NOT_NULL(test, afb->base.format); + + tiling_flags |= AMDGPU_TILING_SET(ARRAY_MODE, DC_ARRAY_1D_TILED_THIN1); + tiling_flags |= AMDGPU_TILING_SET(PIPE_CONFIG, 5); + + ret = amdgpu_dm_plane_fill_plane_buffer_attributes(adev, afb, + SURFACE_PIXEL_FORMAT_GRPH_ARGB8888, ROTATION_ANGLE_0, + tiling_flags, tiling_info, plane_size, dcc, address, true); + + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, plane_size->surface_size.width, 1920); + KUNIT_EXPECT_EQ(test, plane_size->surface_size.height, 1080); + KUNIT_EXPECT_EQ(test, plane_size->surface_pitch, 1920); + KUNIT_EXPECT_EQ(test, address->type, (int)PLN_ADDR_TYPE_GRAPHICS); + KUNIT_EXPECT_TRUE(test, address->tmz_surface); + KUNIT_EXPECT_EQ(test, (int)tiling_info->gfx8.array_mode, (int)DC_ARRAY_1D_TILED_THIN1); + KUNIT_EXPECT_EQ(test, tiling_info->gfx8.pipe_config, 5U); +} + +/** + * dm_test_get_cursor_position() - Verify cursor clipping and off-screen handling. + * @test: KUnit test context. + * + * Verify if cursor clipping, hotspot adjustment, and off-screen disable behavior work. + */ +static void dm_test_get_cursor_position(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_crtc *amdgpu_crtc; + struct drm_plane plane = {0}; + struct drm_plane_state state = {0}; + struct drm_framebuffer fb = {0}; + struct dc_cursor_position position = {0}; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + amdgpu_crtc = kunit_kzalloc(test, sizeof(*amdgpu_crtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, amdgpu_crtc); + + adev->ip_versions[DCE_HWIP][0] = IP_VERSION(4, 0, 0); + amdgpu_crtc->max_cursor_width = 64; + amdgpu_crtc->max_cursor_height = 64; + + plane.dev = &adev->ddev; + plane.state = &state; + state.fb = &fb; + state.crtc_x = -5; + state.crtc_y = -7; + state.crtc_w = 32; + state.crtc_h = 32; + + KUNIT_ASSERT_EQ(test, + amdgpu_dm_plane_get_cursor_position(&plane, &amdgpu_crtc->base, &position), + 0); + KUNIT_EXPECT_TRUE(test, position.enable); + KUNIT_EXPECT_EQ(test, position.x, 0); + KUNIT_EXPECT_EQ(test, position.y, 0); + KUNIT_EXPECT_EQ(test, position.x_hotspot, 5); + KUNIT_EXPECT_EQ(test, position.y_hotspot, 7); + KUNIT_EXPECT_TRUE(test, position.translate_by_source); + + memset(&position, 0, sizeof(position)); + state.crtc_x = -64; + state.crtc_y = 0; + KUNIT_ASSERT_EQ(test, + amdgpu_dm_plane_get_cursor_position(&plane, &amdgpu_crtc->base, &position), + 0); + KUNIT_EXPECT_FALSE(test, position.enable); +} + +/** + * dm_test_format_mod_supported() - Verify key format/modifier acceptance and rejection paths. + * @test: KUnit test context. + * + * Verify if format-modifier support checks match accepted and rejected cases. + */ +static void dm_test_format_mod_supported(struct kunit *test) +{ + struct amdgpu_device *adev; + struct drm_plane plane = {0}; + uint64_t listed_mod; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + + adev->family = AMDGPU_FAMILY_NV; + plane.dev = &adev->ddev; + + KUNIT_EXPECT_TRUE(test, + amdgpu_dm_plane_format_mod_supported(&plane, DRM_FORMAT_XRGB8888, + DRM_FORMAT_MOD_LINEAR)); + KUNIT_EXPECT_TRUE(test, + amdgpu_dm_plane_format_mod_supported(&plane, DRM_FORMAT_XRGB8888, + DRM_FORMAT_MOD_INVALID)); + + KUNIT_EXPECT_FALSE(test, + amdgpu_dm_plane_format_mod_supported(&plane, DRM_FORMAT_XRGB8888, + DRM_FORMAT_MOD_VENDOR_AMD)); + + listed_mod = AMD_FMT_MOD | + AMD_FMT_MOD_SET(TILE, AMD_FMT_MOD_TILE_GFX9_64K_S_X) | + AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX9) | + AMD_FMT_MOD_SET(DCC, 1); + plane.modifiers = &listed_mod; + plane.modifier_count = 1; + + KUNIT_EXPECT_FALSE(test, + amdgpu_dm_plane_format_mod_supported(&plane, DRM_FORMAT_NV12, listed_mod)); +} + +/** + * dm_test_fill_gfx12_plane_attributes_from_modifiers() - Verify GFX12 DCC mapping path. + * @test: KUnit test context. + * + * Verify if GFX12 modifier parsing enables DCC and sets expected DCC block mode. + */ +static void dm_test_fill_gfx12_plane_attributes_from_modifiers(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct amdgpu_framebuffer *afb; + struct plane_size plane_size = {0}; + struct dc_tiling_info tiling_info = {0}; + struct dc_plane_dcc_param dcc = {0}; + struct dc_plane_address address = {0}; + struct dm_test_dcc_cap_ctx ctx = { + .callback_ret = true, + .capable = true, + .output_independent_64b_blks = false, + }; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + afb = kunit_kzalloc(test, sizeof(*afb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, afb); + + adev->family = AMDGPU_FAMILY_GC_12_0_0; + adev->dm.dc = dc; + adev->gfx.config.gb_addr_config_fields.num_pipes = 2; + adev->gfx.config.gb_addr_config_fields.num_banks = 4; + adev->gfx.config.gb_addr_config_fields.pipe_interleave_size = 256; + adev->gfx.config.gb_addr_config_fields.num_se = 1; + adev->gfx.config.gb_addr_config_fields.max_compress_frags = 2; + adev->gfx.config.gb_addr_config_fields.num_rb_per_se = 1; + dc->cap_funcs.get_dcc_compression_cap = dm_test_get_dcc_compression_cap; + dm_test_dcc_ctx = &ctx; + + afb->base.modifier = AMD_FMT_MOD | + AMD_FMT_MOD_SET(TILE, AMD_FMT_MOD_TILE_GFX12_64K_2D) | + AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX12) | + AMD_FMT_MOD_SET(DCC, 1) | + AMD_FMT_MOD_SET(DCC_MAX_COMPRESSED_BLOCK, 1); + plane_size.surface_size.width = 1920; + plane_size.surface_size.height = 1080; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_plane_fill_gfx12_plane_attributes_from_modifiers( + adev, afb, SURFACE_PIXEL_FORMAT_GRPH_ARGB8888, + ROTATION_ANGLE_0, &plane_size, &tiling_info, &dcc, &address), + 0); + KUNIT_EXPECT_EQ(test, (int)tiling_info.gfxversion, (int)DcGfxAddr3); + KUNIT_EXPECT_TRUE(test, dcc.enable); + KUNIT_EXPECT_EQ(test, (int)dcc.dcc_ind_blk, (int)hubp_ind_block_128b); + + dm_test_dcc_ctx = NULL; +} + +/** + * dm_test_fill_gfx9_plane_attributes_from_modifiers() - Verify basic GFX9 linear modifier path. + * @test: KUnit test context. + * + * Verify if GFX9 linear modifier handling keeps DCC disabled. + */ +static void dm_test_fill_gfx9_plane_attributes_from_modifiers(struct kunit *test) +{ + struct amdgpu_device *adev; + struct amdgpu_framebuffer *afb; + struct plane_size plane_size = {0}; + struct dc_tiling_info tiling_info = {0}; + struct dc_plane_dcc_param dcc = {0}; + struct dc_plane_address address = {0}; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + afb = kunit_kzalloc(test, sizeof(*afb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, afb); + + adev->family = AMDGPU_FAMILY_NV; + adev->gfx.config.gb_addr_config_fields.num_pipes = 2; + adev->gfx.config.gb_addr_config_fields.num_banks = 4; + adev->gfx.config.gb_addr_config_fields.pipe_interleave_size = 256; + adev->gfx.config.gb_addr_config_fields.num_se = 1; + adev->gfx.config.gb_addr_config_fields.max_compress_frags = 2; + adev->gfx.config.gb_addr_config_fields.num_rb_per_se = 1; + adev->gfx.config.gb_addr_config_fields.num_pkrs = 2; + adev->ip_versions[GC_HWIP][0] = IP_VERSION(10, 3, 0); + + afb->base.modifier = DRM_FORMAT_MOD_LINEAR; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_plane_fill_gfx9_plane_attributes_from_modifiers( + adev, afb, SURFACE_PIXEL_FORMAT_GRPH_ARGB8888, + ROTATION_ANGLE_0, &plane_size, &tiling_info, &dcc, &address), + 0); + KUNIT_EXPECT_EQ(test, (int)tiling_info.gfxversion, (int)DcGfxVersion9); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.swizzle, 0U); + KUNIT_EXPECT_FALSE(test, dcc.enable); +} + +/** + * dm_test_helper_check_state_viewport_reject() - Verify viewport outside screen rejects state. + * @test: KUnit test context. + * + * Verify if plane state is rejected when the viewport is outside display bounds. + */ +static void dm_test_helper_check_state_viewport_reject(struct kunit *test) +{ + struct drm_plane *plane; + struct drm_plane_state *state; + struct drm_crtc *crtc; + struct drm_crtc_state *new_crtc_state; + struct drm_framebuffer *fb; + + plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL); + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + crtc = kunit_kzalloc(test, sizeof(*crtc), GFP_KERNEL); + new_crtc_state = kunit_kzalloc(test, sizeof(*new_crtc_state), GFP_KERNEL); + fb = kunit_kzalloc(test, sizeof(*fb), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, plane); + KUNIT_ASSERT_NOT_NULL(test, state); + KUNIT_ASSERT_NOT_NULL(test, crtc); + KUNIT_ASSERT_NOT_NULL(test, new_crtc_state); + KUNIT_ASSERT_NOT_NULL(test, fb); + + plane->type = DRM_PLANE_TYPE_OVERLAY; + state->plane = plane; + state->fb = fb; + state->crtc = crtc; + state->crtc_x = 200; + state->crtc_y = 0; + state->crtc_w = 100; + state->crtc_h = 100; + new_crtc_state->mode.crtc_hdisplay = 100; + new_crtc_state->mode.crtc_vdisplay = 100; + + KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_helper_check_state(state, new_crtc_state), -EINVAL); +} + +/** + * dm_test_validate_dcc_disabled_returns_success() - Verify disabled DCC is accepted. + * @test: KUnit test context. + * + * Verify if DCC validation succeeds when DCC is disabled. + */ +static void dm_test_validate_dcc_disabled_returns_success(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct dc_tiling_info tiling_info = {0}; + struct dc_plane_dcc_param dcc = {0}; + struct dc_plane_address address = {0}; + struct plane_size plane_size = {0}; + + dm_test_init_validate_dcc_inputs(&adev, &dc, &tiling_info, &dcc, &address, + &plane_size, test); + dcc.enable = 0; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_plane_validate_dcc(adev, SURFACE_PIXEL_FORMAT_GRPH_ARGB8888, + ROTATION_ANGLE_0, &tiling_info, &dcc, + &address, &plane_size), + 0); +} + +/** + * dm_test_validate_dcc_video_non_gfx12_fails() - Verify video format restriction on pre-GFX12. + * @test: KUnit test context. + * + * Verify if video format DCC validation fails on non-GFX12 devices. + */ +static void dm_test_validate_dcc_video_non_gfx12_fails(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct dc_tiling_info tiling_info = {0}; + struct dc_plane_dcc_param dcc = {0}; + struct dc_plane_address address = {0}; + struct plane_size plane_size = {0}; + + dm_test_init_validate_dcc_inputs(&adev, &dc, &tiling_info, &dcc, &address, + &plane_size, test); + adev->family = AMDGPU_FAMILY_NV; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_plane_validate_dcc(adev, SURFACE_PIXEL_FORMAT_VIDEO_420_YCbCr, + ROTATION_ANGLE_0, &tiling_info, &dcc, + &address, &plane_size), + -EINVAL); +} + +/** + * dm_test_validate_dcc_missing_cap_func_fails() - Verify missing capability callback fails. + * @test: KUnit test context. + * + * Verify if validation fails when DCC capability callback is not provided. + */ +static void dm_test_validate_dcc_missing_cap_func_fails(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct dc_tiling_info tiling_info = {0}; + struct dc_plane_dcc_param dcc = {0}; + struct dc_plane_address address = {0}; + struct plane_size plane_size = {0}; + + dm_test_init_validate_dcc_inputs(&adev, &dc, &tiling_info, &dcc, &address, + &plane_size, test); + dc->cap_funcs.get_dcc_compression_cap = NULL; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_plane_validate_dcc(adev, SURFACE_PIXEL_FORMAT_GRPH_ARGB8888, + ROTATION_ANGLE_0, &tiling_info, &dcc, + &address, &plane_size), + -EINVAL); +} + +/** + * dm_test_validate_dcc_success_and_scan_mapping() - Verify success path and rotation-to-scan mapping. + * @test: KUnit test context. + * + * Verify if DCC validation succeeds and rotation-to-scan mapping is correct. + */ +static void dm_test_validate_dcc_success_and_scan_mapping(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct dc_tiling_info tiling_info = {0}; + struct dc_plane_dcc_param dcc = {0}; + struct dc_plane_address address = {0}; + struct plane_size plane_size = {0}; + struct dm_test_dcc_cap_ctx ctx = { + .callback_ret = true, + .capable = true, + .output_independent_64b_blks = true, + }; + + dm_test_init_validate_dcc_inputs(&adev, &dc, &tiling_info, &dcc, &address, + &plane_size, test); + dc->cap_funcs.get_dcc_compression_cap = dm_test_get_dcc_compression_cap; + dm_test_dcc_ctx = &ctx; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_plane_validate_dcc(adev, SURFACE_PIXEL_FORMAT_GRPH_ARGB8888, + ROTATION_ANGLE_90, &tiling_info, &dcc, + &address, &plane_size), + 0); + KUNIT_EXPECT_TRUE(test, ctx.called); + KUNIT_EXPECT_EQ(test, (int)ctx.captured_input.scan, (int)SCAN_DIRECTION_VERTICAL); + KUNIT_EXPECT_EQ(test, (int)ctx.captured_input.format, + (int)SURFACE_PIXEL_FORMAT_GRPH_ARGB8888); + + dm_test_dcc_ctx = NULL; +} + +/** + * dm_test_validate_dcc_independent_64b_mismatch_fails() - Verify 64B compatibility check. + * @test: KUnit test context. + * + * Verify if validation fails when independent_64b_blks values do not match. + */ +static void dm_test_validate_dcc_independent_64b_mismatch_fails(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc *dc; + struct dc_tiling_info tiling_info = {0}; + struct dc_plane_dcc_param dcc = {0}; + struct dc_plane_address address = {0}; + struct plane_size plane_size = {0}; + struct dm_test_dcc_cap_ctx ctx = { + .callback_ret = true, + .capable = true, + .output_independent_64b_blks = true, + }; + + dm_test_init_validate_dcc_inputs(&adev, &dc, &tiling_info, &dcc, &address, + &plane_size, test); + dcc.independent_64b_blks = 0; + dc->cap_funcs.get_dcc_compression_cap = dm_test_get_dcc_compression_cap; + dm_test_dcc_ctx = &ctx; + + KUNIT_EXPECT_EQ(test, + amdgpu_dm_plane_validate_dcc(adev, SURFACE_PIXEL_FORMAT_GRPH_ARGB8888, + ROTATION_ANGLE_0, &tiling_info, &dcc, + &address, &plane_size), + -EINVAL); + + dm_test_dcc_ctx = NULL; +} + +/** + * dm_test_add_modifier_appends_value() - Verify one modifier append. + * @test: KUnit test context. + * + * Verify if a modifier is appended and size is updated. + */ +static void dm_test_add_modifier_appends_value(struct kunit *test) +{ + uint64_t size = 0; + uint64_t cap = 2; + uint64_t *mods = kmalloc_array(cap, sizeof(*mods), GFP_KERNEL); + + KUNIT_ASSERT_NOT_NULL(test, mods); + + amdgpu_dm_plane_add_modifier(&mods, &size, &cap, 0x1234ULL); + + KUNIT_ASSERT_NOT_NULL(test, mods); + KUNIT_EXPECT_EQ(test, size, 1ULL); + KUNIT_EXPECT_EQ(test, cap, 2ULL); + KUNIT_EXPECT_EQ(test, mods[0], 0x1234ULL); + + kfree(mods); +} + +/** + * dm_test_add_modifier_grows_capacity() - Verify add triggers growth and preserves old data. + * @test: KUnit test context. + * + * Verify if modifier array growth keeps old data and appends new data. + */ +static void dm_test_add_modifier_grows_capacity(struct kunit *test) +{ + uint64_t size = 1; + uint64_t cap = 1; + uint64_t *mods = kmalloc_array(cap, sizeof(*mods), GFP_KERNEL); + + KUNIT_ASSERT_NOT_NULL(test, mods); + mods[0] = 0xAAULL; + + amdgpu_dm_plane_add_modifier(&mods, &size, &cap, 0xBBULL); + + KUNIT_ASSERT_NOT_NULL(test, mods); + KUNIT_EXPECT_EQ(test, cap, 2ULL); + KUNIT_EXPECT_EQ(test, size, 2ULL); + KUNIT_EXPECT_EQ(test, mods[0], 0xAAULL); + KUNIT_EXPECT_EQ(test, mods[1], 0xBBULL); + + kfree(mods); +} + +/** + * dm_test_add_modifier_noop_when_mods_null() - Verify helper is a no-op on NULL mods list. + * @test: KUnit test context. + * + * Verify if add_modifier does nothing when the modifier list is NULL. + */ +static void dm_test_add_modifier_noop_when_mods_null(struct kunit *test) +{ + uint64_t size = 3; + uint64_t cap = 7; + uint64_t *mods = NULL; + + amdgpu_dm_plane_add_modifier(&mods, &size, &cap, 0x55ULL); + + KUNIT_EXPECT_PTR_EQ(test, mods, NULL); + KUNIT_EXPECT_EQ(test, size, 3ULL); + KUNIT_EXPECT_EQ(test, cap, 7ULL); +} + +/** + * dm_test_fill_gfx8_tiling_info_2d_tiled() - Verify GFX8 2D tiled flag parsing. + * @test: KUnit test context. + * + * Verify if 2D tiled GFX8 flags populate expected tiling fields. + */ +static void dm_test_fill_gfx8_tiling_info_2d_tiled(struct kunit *test) +{ + struct dc_tiling_info tiling_info = {0}; + uint64_t tiling_flags = 0; + + tiling_flags |= AMDGPU_TILING_SET(ARRAY_MODE, DC_ARRAY_2D_TILED_THIN1); + tiling_flags |= AMDGPU_TILING_SET(BANK_WIDTH, 2); + tiling_flags |= AMDGPU_TILING_SET(BANK_HEIGHT, 1); + tiling_flags |= AMDGPU_TILING_SET(MACRO_TILE_ASPECT, 3); + tiling_flags |= AMDGPU_TILING_SET(TILE_SPLIT, 4); + tiling_flags |= AMDGPU_TILING_SET(NUM_BANKS, 2); + tiling_flags |= AMDGPU_TILING_SET(PIPE_CONFIG, 7); + + amdgpu_dm_plane_fill_gfx8_tiling_info_from_flags(&tiling_info, tiling_flags); + + KUNIT_EXPECT_EQ(test, (int)tiling_info.gfxversion, (int)DcGfxVersion8); + KUNIT_EXPECT_EQ(test, (int)tiling_info.gfx8.array_mode, (int)DC_ARRAY_2D_TILED_THIN1); + KUNIT_EXPECT_EQ(test, tiling_info.gfx8.bank_width, 2U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx8.bank_height, 1U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx8.tile_aspect, 3U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx8.tile_split, 4U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx8.num_banks, 2U); + KUNIT_EXPECT_EQ(test, (int)tiling_info.gfx8.tile_mode, + (int)DC_ADDR_SURF_MICRO_TILING_DISPLAY); + KUNIT_EXPECT_EQ(test, tiling_info.gfx8.pipe_config, 7U); +} + +/** + * dm_test_fill_gfx8_tiling_info_1d_tiled() - Verify GFX8 1D tiled flag parsing. + * @test: KUnit test context. + * + * Verify if 1D tiled GFX8 flags populate array mode and pipe config. + */ +static void dm_test_fill_gfx8_tiling_info_1d_tiled(struct kunit *test) +{ + struct dc_tiling_info tiling_info = {0}; + uint64_t tiling_flags = 0; + + tiling_flags |= AMDGPU_TILING_SET(ARRAY_MODE, DC_ARRAY_1D_TILED_THIN1); + tiling_flags |= AMDGPU_TILING_SET(PIPE_CONFIG, 5); + + amdgpu_dm_plane_fill_gfx8_tiling_info_from_flags(&tiling_info, tiling_flags); + + KUNIT_EXPECT_EQ(test, (int)tiling_info.gfx8.array_mode, (int)DC_ARRAY_1D_TILED_THIN1); + KUNIT_EXPECT_EQ(test, tiling_info.gfx8.pipe_config, 5U); +} + +/** + * dm_test_fill_gfx8_tiling_info_other_mode() - Verify non-1D/non-2D mode handling. + * @test: KUnit test context. + * + * Verify if unsupported array mode keeps preset fields and updates pipe config. + */ +static void dm_test_fill_gfx8_tiling_info_other_mode(struct kunit *test) +{ + struct dc_tiling_info tiling_info = {0}; + uint64_t tiling_flags = 0; + + tiling_info.gfxversion = 0x7f; + tiling_info.gfx8.array_mode = 0x7f; + tiling_info.gfx8.tile_mode = 0x7f; + tiling_info.gfx8.num_banks = 0x7f; + + tiling_flags |= AMDGPU_TILING_SET(PIPE_CONFIG, 6); + + amdgpu_dm_plane_fill_gfx8_tiling_info_from_flags(&tiling_info, tiling_flags); + + KUNIT_EXPECT_EQ(test, tiling_info.gfxversion, 0x7f); + KUNIT_EXPECT_EQ(test, tiling_info.gfx8.array_mode, 0x7f); + KUNIT_EXPECT_EQ(test, tiling_info.gfx8.tile_mode, 0x7f); + KUNIT_EXPECT_EQ(test, tiling_info.gfx8.num_banks, 0x7f); + KUNIT_EXPECT_EQ(test, tiling_info.gfx8.pipe_config, 6U); +} + +/** + * dm_test_fill_gfx9_tiling_info_from_device_pre_10_3() - Verify GFX9 field copy before 10.3. + * @test: KUnit test context. + * + * Verify if pre-10.3 device fields are copied and existing num_pkrs is kept. + */ +static void dm_test_fill_gfx9_tiling_info_from_device_pre_10_3(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_tiling_info tiling_info = {0}; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + + adev->gfx.config.gb_addr_config_fields.num_pipes = 4; + adev->gfx.config.gb_addr_config_fields.num_banks = 8; + adev->gfx.config.gb_addr_config_fields.pipe_interleave_size = 256; + adev->gfx.config.gb_addr_config_fields.num_se = 2; + adev->gfx.config.gb_addr_config_fields.max_compress_frags = 1; + adev->gfx.config.gb_addr_config_fields.num_rb_per_se = 2; + adev->gfx.config.gb_addr_config_fields.num_pkrs = 3; + adev->ip_versions[GC_HWIP][0] = IP_VERSION(10, 2, 9); + + tiling_info.gfx9.num_pkrs = 0x5a; + + amdgpu_dm_plane_fill_gfx9_tiling_info_from_device(adev, &tiling_info); + + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.num_pipes, 4U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.num_banks, 8U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.pipe_interleave, 256U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.num_shader_engines, 2U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.max_compressed_frags, 1U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.num_rb_per_se, 2U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.shaderEnable, 1U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.num_pkrs, 0x5aU); +} + +/** + * dm_test_fill_gfx9_tiling_info_from_device_10_3_plus() - Verify num_pkrs update on 10.3+. + * @test: KUnit test context. + * + * Verify if 10.3+ device fields are copied and num_pkrs is updated. + */ +static void dm_test_fill_gfx9_tiling_info_from_device_10_3_plus(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_tiling_info tiling_info = {0}; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + + adev->gfx.config.gb_addr_config_fields.num_pipes = 2; + adev->gfx.config.gb_addr_config_fields.num_banks = 4; + adev->gfx.config.gb_addr_config_fields.pipe_interleave_size = 128; + adev->gfx.config.gb_addr_config_fields.num_se = 1; + adev->gfx.config.gb_addr_config_fields.max_compress_frags = 2; + adev->gfx.config.gb_addr_config_fields.num_rb_per_se = 1; + adev->gfx.config.gb_addr_config_fields.num_pkrs = 6; + adev->ip_versions[GC_HWIP][0] = IP_VERSION(10, 3, 0); + + amdgpu_dm_plane_fill_gfx9_tiling_info_from_device(adev, &tiling_info); + + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.num_pipes, 2U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.num_banks, 4U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.pipe_interleave, 128U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.num_shader_engines, 1U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.max_compressed_frags, 2U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.num_rb_per_se, 1U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.shaderEnable, 1U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.num_pkrs, 6U); +} + +/** + * dm_test_fill_gfx9_tiling_info_from_modifier_linear() - Verify non-AMD modifier keeps device values. + * @test: KUnit test context. + * + * Verify if linear modifier path keeps values from device configuration. + */ +static void dm_test_fill_gfx9_tiling_info_from_modifier_linear(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_tiling_info tiling_info = {0}; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + + adev->family = AMDGPU_FAMILY_NV; + adev->gfx.config.gb_addr_config_fields.num_pipes = 4; + adev->gfx.config.gb_addr_config_fields.num_banks = 8; + adev->gfx.config.gb_addr_config_fields.pipe_interleave_size = 256; + adev->gfx.config.gb_addr_config_fields.num_se = 2; + adev->gfx.config.gb_addr_config_fields.max_compress_frags = 1; + adev->gfx.config.gb_addr_config_fields.num_rb_per_se = 2; + adev->gfx.config.gb_addr_config_fields.num_pkrs = 3; + adev->ip_versions[GC_HWIP][0] = IP_VERSION(10, 3, 0); + + amdgpu_dm_plane_fill_gfx9_tiling_info_from_modifier(adev, &tiling_info, + DRM_FORMAT_MOD_LINEAR); + + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.num_pipes, 4U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.num_banks, 8U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.pipe_interleave, 256U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.num_shader_engines, 2U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.max_compressed_frags, 1U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.num_rb_per_se, 2U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.shaderEnable, 1U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.num_pkrs, 3U); +} + +/** + * dm_test_fill_gfx9_tiling_info_from_modifier_pre_nv() - Verify AMD modifier updates banks on pre-NV. + * @test: KUnit test context. + * + * Verify if AMD modifier updates pre-NV pipe, engine, and bank fields. + */ +static void dm_test_fill_gfx9_tiling_info_from_modifier_pre_nv(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_tiling_info tiling_info = {0}; + uint64_t modifier; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + + adev->family = AMDGPU_FAMILY_RV; + adev->gfx.config.gb_addr_config_fields.num_pipes = 4; + adev->gfx.config.gb_addr_config_fields.num_banks = 16; + adev->gfx.config.gb_addr_config_fields.pipe_interleave_size = 256; + adev->gfx.config.gb_addr_config_fields.num_se = 2; + adev->gfx.config.gb_addr_config_fields.max_compress_frags = 1; + adev->gfx.config.gb_addr_config_fields.num_rb_per_se = 2; + adev->gfx.config.gb_addr_config_fields.num_pkrs = 7; + adev->ip_versions[GC_HWIP][0] = IP_VERSION(10, 2, 9); + + tiling_info.gfx9.num_pkrs = 0x5a; + + modifier = AMD_FMT_MOD | + AMD_FMT_MOD_SET(TILE, AMD_FMT_MOD_TILE_GFX9_64K_S_X) | + AMD_FMT_MOD_SET(PIPE_XOR_BITS, 7) | + AMD_FMT_MOD_SET(BANK_XOR_BITS, 3) | + AMD_FMT_MOD_SET(PACKERS, 2); + + amdgpu_dm_plane_fill_gfx9_tiling_info_from_modifier(adev, &tiling_info, modifier); + + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.num_pipes, 32U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.num_shader_engines, 4U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.num_banks, 8U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.num_pkrs, 0x5aU); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.shaderEnable, 1U); +} + +/** + * dm_test_fill_gfx9_tiling_info_from_modifier_nv() - Verify AMD modifier updates packers on NV+. + * @test: KUnit test context. + * + * Verify if AMD modifier updates NV+ pipe, engine, and packer fields. + */ +static void dm_test_fill_gfx9_tiling_info_from_modifier_nv(struct kunit *test) +{ + struct amdgpu_device *adev; + struct dc_tiling_info tiling_info = {0}; + uint64_t modifier; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + + adev->family = AMDGPU_FAMILY_NV; + adev->gfx.config.gb_addr_config_fields.num_pipes = 2; + adev->gfx.config.gb_addr_config_fields.num_banks = 9; + adev->gfx.config.gb_addr_config_fields.pipe_interleave_size = 128; + adev->gfx.config.gb_addr_config_fields.num_se = 1; + adev->gfx.config.gb_addr_config_fields.max_compress_frags = 2; + adev->gfx.config.gb_addr_config_fields.num_rb_per_se = 1; + adev->gfx.config.gb_addr_config_fields.num_pkrs = 2; + adev->ip_versions[GC_HWIP][0] = IP_VERSION(10, 3, 0); + + modifier = AMD_FMT_MOD | + AMD_FMT_MOD_SET(TILE, AMD_FMT_MOD_TILE_GFX9_64K_S_X) | + AMD_FMT_MOD_SET(PIPE_XOR_BITS, 6) | + AMD_FMT_MOD_SET(BANK_XOR_BITS, 2) | + AMD_FMT_MOD_SET(PACKERS, 3); + + amdgpu_dm_plane_fill_gfx9_tiling_info_from_modifier(adev, &tiling_info, modifier); + + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.num_pipes, 32U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.num_shader_engines, 2U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.num_banks, 9U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.num_pkrs, 8U); + KUNIT_EXPECT_EQ(test, tiling_info.gfx9.shaderEnable, 1U); +} + +static struct kunit_case amdgpu_dm_plane_test_cases[] = { + /* amdgpu_dm_plane_is_video_format() */ + KUNIT_CASE(dm_test_plane_is_video_format_known_video), + /* amdgpu_dm_plane_fill_blending_from_plane_state() */ + KUNIT_CASE(dm_test_fill_blending_defaults), + KUNIT_CASE(dm_test_fill_blending_premulti_alpha_format), + KUNIT_CASE(dm_test_fill_blending_coverage_alpha_format), + KUNIT_CASE(dm_test_fill_blending_global_alpha), + /* amdgpu_dm_plane_modifier_* helpers() */ + KUNIT_CASE(dm_test_modifier_has_dcc), + KUNIT_CASE(dm_test_modifier_gfx9_swizzle_mode), + /* amdgpu_dm_plane_get_plane_formats() */ + KUNIT_CASE(dm_test_get_plane_formats), + /* amdgpu_dm_plane_get_plane_modifiers() */ + KUNIT_CASE(dm_test_get_plane_modifiers), + /* amdgpu_dm_plane_fill_dc_scaling_info() */ + KUNIT_CASE(dm_test_fill_dc_scaling_info), + /* amdgpu_dm_plane_get_min_max_dc_plane_scaling() */ + KUNIT_CASE(dm_test_get_min_max_dc_plane_scaling), + /* amdgpu_dm_plane_fill_plane_buffer_attributes() */ + KUNIT_CASE(dm_test_fill_plane_buffer_attributes_gfx8), + /* amdgpu_dm_plane_get_cursor_position() */ + KUNIT_CASE(dm_test_get_cursor_position), + /* amdgpu_dm_plane_format_mod_supported() */ + KUNIT_CASE(dm_test_format_mod_supported), + /* amdgpu_dm_plane_fill_gfx12_plane_attributes_from_modifiers() */ + KUNIT_CASE(dm_test_fill_gfx12_plane_attributes_from_modifiers), + /* amdgpu_dm_plane_fill_gfx9_plane_attributes_from_modifiers() */ + KUNIT_CASE(dm_test_fill_gfx9_plane_attributes_from_modifiers), + /* amdgpu_dm_plane_helper_check_state() */ + KUNIT_CASE(dm_test_helper_check_state_viewport_reject), + /* amdgpu_dm_plane_add_modifier() */ + KUNIT_CASE(dm_test_add_modifier_appends_value), + KUNIT_CASE(dm_test_add_modifier_grows_capacity), + KUNIT_CASE(dm_test_add_modifier_noop_when_mods_null), + /* amdgpu_dm_plane_fill_gfx8_tiling_info_from_flags() */ + KUNIT_CASE(dm_test_fill_gfx8_tiling_info_2d_tiled), + KUNIT_CASE(dm_test_fill_gfx8_tiling_info_1d_tiled), + KUNIT_CASE(dm_test_fill_gfx8_tiling_info_other_mode), + /* amdgpu_dm_plane_fill_gfx9_tiling_info_from_device() */ + KUNIT_CASE(dm_test_fill_gfx9_tiling_info_from_device_pre_10_3), + KUNIT_CASE(dm_test_fill_gfx9_tiling_info_from_device_10_3_plus), + /* amdgpu_dm_plane_fill_gfx9_tiling_info_from_modifier() */ + KUNIT_CASE(dm_test_fill_gfx9_tiling_info_from_modifier_linear), + KUNIT_CASE(dm_test_fill_gfx9_tiling_info_from_modifier_pre_nv), + KUNIT_CASE(dm_test_fill_gfx9_tiling_info_from_modifier_nv), + /* amdgpu_dm_plane_validate_dcc() */ + KUNIT_CASE(dm_test_validate_dcc_disabled_returns_success), + KUNIT_CASE(dm_test_validate_dcc_video_non_gfx12_fails), + KUNIT_CASE(dm_test_validate_dcc_missing_cap_func_fails), + KUNIT_CASE(dm_test_validate_dcc_success_and_scan_mapping), + KUNIT_CASE(dm_test_validate_dcc_independent_64b_mismatch_fails), + {} +}; + +static struct kunit_suite amdgpu_dm_plane_test_suite = { + .name = "amdgpu_dm_plane", + .test_cases = amdgpu_dm_plane_test_cases, +}; + +kunit_test_suite(amdgpu_dm_plane_test_suite); + +MODULE_DESCRIPTION("KUnit tests for amdgpu_dm_plane"); +MODULE_LICENSE("Dual MIT/GPL"); -- cgit v1.2.3 From 7a561c2b1b63abcffb55f625c0d0adb68ab2961a Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 15 Jun 2026 15:42:59 -0600 Subject: drm/amd/display: Simplify boolean checks [WHAT] Use direct boolean in connector and IRQ code paths. This removes redundant comparisons around MST state, IRQ validation, handler removal, and DMUB notification offload without changing behavior. Assisted-by: Copilot:GPT-5 Reviewed-by: Chen-Yu Chen Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c | 2 +- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') 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 959c843fb77c..d4720c5576ce 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 @@ -466,7 +466,7 @@ void amdgpu_dm_update_connector_after_detect( struct drm_device *dev = connector->dev; /* MST handled by drm_mst framework */ - if (aconnector->mst_mgr.mst_state == true) + if (aconnector->mst_mgr.mst_state) return; sink = aconnector->dc_link->local_sink; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c index 57dd176e4cc1..ffaf2b7bc35d 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c @@ -188,7 +188,7 @@ static struct list_head *remove_irq_handler(struct amdgpu_device *adev, DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); - if (handler_removed == false) { + if (!handler_removed) { /* Not necessarily an error - caller may not * know the context. */ @@ -326,7 +326,7 @@ void *amdgpu_dm_irq_register_interrupt(struct amdgpu_device *adev, unsigned long irq_table_flags; enum dc_irq_source irq_source; - if (false == validate_irq_registration_params(int_params, ih)) + if (!validate_irq_registration_params(int_params, ih)) return DAL_INVALID_IRQ_HANDLER_IDX; handler_data = kzalloc_obj(*handler_data); @@ -392,7 +392,7 @@ void amdgpu_dm_irq_unregister_interrupt(struct amdgpu_device *adev, struct dc_interrupt_params int_params; int i; - if (false == validate_irq_unregistration_params(irq_source, ih)) + if (!validate_irq_unregistration_params(irq_source, ih)) return; memset(&int_params, 0, sizeof(int_params)); @@ -2188,7 +2188,7 @@ static void dm_dmub_outbox1_low_irq(void *interrupt_params) dmub_notification_type_str(notify.type)); continue; } - if (dm->dmub_thread_offload[notify.type] == true) { + if (dm->dmub_thread_offload[notify.type]) { dmub_hpd_wrk = kzalloc_obj(*dmub_hpd_wrk, GFP_ATOMIC); if (!dmub_hpd_wrk) { -- cgit v1.2.3 From 418755e47af3d280750592cde008d91f5e110126 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 15 Jun 2026 15:51:20 -0600 Subject: drm/amd/display: Simplify DMUB notify registration [WHAT] Use an early guard for invalid DMUB notify callback registration inputs. This keeps the same accepted and rejected cases while removing the redundant else block. Assisted-by: Copilot:GPT-5 Reviewed-by: Chen-Yu Chen Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c index 7519219db0f8..2f14614c196c 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c @@ -124,12 +124,12 @@ bool dm_register_dmub_notify_callback(struct amdgpu_device *adev, dmub_notify_interrupt_callback_t callback, bool dmub_int_thread_offload) { - if (callback != NULL && type < ARRAY_SIZE(adev->dm.dmub_thread_offload)) { - adev->dm.dmub_callback[type] = callback; - adev->dm.dmub_thread_offload[type] = dmub_int_thread_offload; - } else + if (!callback || type >= ARRAY_SIZE(adev->dm.dmub_thread_offload)) return false; + adev->dm.dmub_callback[type] = callback; + adev->dm.dmub_thread_offload[type] = dmub_int_thread_offload; + return true; } EXPORT_IF_KUNIT(dm_register_dmub_notify_callback); -- cgit v1.2.3 From 829769f1cfe88f35125e0fd1186fce5c2aa19a3d Mon Sep 17 00:00:00 2001 From: James Lin Date: Tue, 16 Jun 2026 15:33:20 +0800 Subject: drm/amd/display: scale plane global alpha to 12 bits on DCN 4.2 [why] On DCN 4.2 the global alpha is reported using 12 bits (MPCC_GLOBAL_ALPHA spans bits [0:11]), whereas other ASICs such as DCN 3.1.4 use an 8-bit field (MPCC_GLOBAL_ALPHA spans bits [16:23]). The DRM plane alpha property is 16-bit and amdgpu_dm unconditionally scaled it down by >> 8, which only matches the 8-bit hardware field. On DCN 4.2 this fed a value that was 4 bits too small into the 12-bit field, so the hardware applied the wrong global alpha and the resulting blended output did not match the expected hw * alpha value. [how] Detect DCN 4.2 via amdgpu_ip_version(adev, DCE_HWIP, 0) and scale the 16-bit plane alpha by >> 4 to fill the 12-bit MPCC_GLOBAL_ALPHA field. All other ASICs keep the existing >> 8 behavior for their 8-bit field. Reviewed-by: ChiaHsuan (Tom) Chung Signed-off-by: James Lin Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c index 62f1ad1ff7b5..35813a39ebcb 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c @@ -137,8 +137,18 @@ void amdgpu_dm_plane_fill_blending_from_plane_state(const struct drm_plane_state } if (plane_state->alpha < 0xffff) { + struct amdgpu_device *adev = drm_to_adev(plane_state->plane->dev); *global_alpha = true; - *global_alpha_value = plane_state->alpha >> 8; + /* + * DCN 4.2 uses a 12-bit MPCC_GLOBAL_ALPHA field, while + * other ASICs use an 8-bit field. The DRM plane alpha is + * 16-bit, so scale it down to the width the hardware expects. + */ + if (amdgpu_ip_version(adev, DCE_HWIP, 0) == IP_VERSION(4, 2, 0)) + *global_alpha_value = plane_state->alpha >> 4; + else + *global_alpha_value = plane_state->alpha >> 8; + } } EXPORT_IF_KUNIT(amdgpu_dm_plane_fill_blending_from_plane_state); -- cgit v1.2.3 From dcce6246e6c63762d8d0f892f6626d30583b27e9 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Tue, 16 Jun 2026 09:47:28 -0600 Subject: drm/amd/display: Fix KUnit test crash after global alpha change [WHY] amdgpu_dm_plane_fill_blending_from_plane_state added drm_to_adev() but dm_test_fill_blending_global_alpha did not initialize plane_state->plane, causing a NULL pointer dereference. [HOW] Add an amdgpu_device and drm_plane so the plane->dev dereference is valid in the test. Fixes: 829769f1cfe8 ("drm/amd/display: scale plane global alpha to 12 bits on DCN 4.2") Cc: PingLei.Lin@amd.com Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c index deec75857c0e..071c28abaa8a 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c @@ -184,12 +184,19 @@ static void dm_test_fill_blending_coverage_alpha_format(struct kunit *test) */ static void dm_test_fill_blending_global_alpha(struct kunit *test) { + struct amdgpu_device *adev; + struct drm_plane plane = {0}; struct drm_plane_state state = { 0 }; bool per_pixel_alpha; bool pre_multiplied_alpha; bool global_alpha; int global_alpha_value; + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + + plane.dev = &adev->ddev; + state.plane = &plane; state.pixel_blend_mode = DRM_MODE_BLEND_PIXEL_NONE; state.alpha = 0x8000; -- cgit v1.2.3 From 8cbe3648aa868c2c2d557073cf61526d1177d756 Mon Sep 17 00:00:00 2001 From: Harry Wentland Date: Tue, 16 Jun 2026 11:29:03 -0400 Subject: drm/amd/display: clamp DMUB AUX reply length to payload buffer [Why] amdgpu_dm_process_dmub_aux_transfer_sync() copies p_notify->aux_reply.length bytes into payload->data without clamping. payload->data is typically a 16-byte DPCD scratch buffer, while aux_reply.length is echoed from the sink via the DMUB ring. While this is clamped by DMUB it's prudent to ensure we validate this in the driver as well. [How] Clamp the copy to sizeof(aux_reply.data), the scratch buffer the reply was read into, and use that for both the memcpy and the return value. For regular transfers additionally clamp to payload->length to cover callers whose destination buffer is smaller than 16 bytes. The write-status-update retry path (dce_aux_transfer_with_retries) deliberately zeroes payload->length while still expecting the partial-write status byte, so that bound is skipped in that case to avoid dropping the reply. Also guard against a NULL payload->data. Fixes: 81927e2808be ("drm/amd/display: Support for DMUB AUX") Assisted-by: Copilot:claude-opus-4.8 Reviewed-by: Alex Hung Signed-off-by: Harry Wentland Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c | 24 +++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c index 2f14614c196c..0aa99d1a542f 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_dmub.c @@ -797,12 +797,26 @@ int amdgpu_dm_process_dmub_aux_transfer_sync( payload->reply[0] = (adev->dm.dmub_notify->aux_reply.command >> 4) & 0xF; /*write req may receive a byte indicating partially written number as well*/ - if (p_notify->aux_reply.length) - memcpy(payload->data, p_notify->aux_reply.data, - p_notify->aux_reply.length); + if (p_notify->aux_reply.length && payload->data) { + /* Bound the reply to the scratch buffer it was read into. */ + ret = min((uint32_t)p_notify->aux_reply.length, + (uint32_t)sizeof(p_notify->aux_reply.data)); + + /* + * During a write-status-update retry the caller zeroes + * payload->length while still expecting the partial-write + * status byte in payload->data (see dce_aux_transfer_with_retries), + * so only clamp to payload->length for regular transfers. + */ + if (!payload->write_status_update) + ret = min(ret, payload->length); + + memcpy(payload->data, p_notify->aux_reply.data, ret); + } else { + /* success */ + ret = p_notify->aux_reply.length; + } - /* success */ - ret = p_notify->aux_reply.length; *operation_result = p_notify->result; out: reinit_completion(&adev->dm.dmub_aux_transfer_done); -- cgit v1.2.3 From 682710244fa3176f642eecd4c1a27e69be2e3a7f Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 15 Jun 2026 17:10:40 -0600 Subject: drm/amd/display: Add KUnit test for amdgpu_dm_wb [WHAT] Add KUnit test with DRM mock for amdgpu_dm_wb_connector_init(). Assisted-by: Copilot:GPT-5.5 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.c | 1 + .../display/amdgpu_dm/tests/amdgpu_dm_wb_test.c | 70 ++++++++++++++++++++++ 2 files changed, 71 insertions(+) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.c index 058d478a073d..0bf82e46f773 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_wb.c @@ -216,3 +216,4 @@ int amdgpu_dm_wb_connector_init(struct amdgpu_display_manager *dm, return 0; } +EXPORT_IF_KUNIT(amdgpu_dm_wb_connector_init); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_wb_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_wb_test.c index b8ad4b87163a..f9a839c10bf4 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_wb_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_wb_test.c @@ -16,6 +16,9 @@ #include #include +#include "dc.h" +#include "amdgpu.h" +#include "amdgpu_dm.h" #include "amdgpu_dm_wb.h" @@ -68,6 +71,23 @@ static struct drm_connector_state *alloc_test_conn_state(struct kunit *test, return conn_state; } +static struct amdgpu_device *alloc_test_adev(struct kunit *test) +{ + struct drm_device *drm; + struct device *dev; + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + drm = __drm_kunit_helper_alloc_drm_device(test, dev, + sizeof(struct amdgpu_device), + offsetof(struct amdgpu_device, ddev), + DRIVER_MODESET | DRIVER_ATOMIC); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + + return drm_to_adev(drm); +} + /* Tests for amdgpu_dm_wb_encoder_atomic_check */ /** @@ -310,6 +330,54 @@ static void dm_test_wb_get_modes_bounded_by_max(struct kunit *test) } } +/* Tests for amdgpu_dm_wb_connector_init using DRM mock */ + +/** + * dm_test_wb_connector_init_success - Verify writeback connector initialization + * @test: KUnit test context + * + * Uses a DRM mock device embedded in struct amdgpu_device to verify that + * amdgpu_dm_wb_connector_init() initializes the writeback connector, stores + * the DC link, installs connector state through reset, and wires the expected + * DRM callbacks. + */ +static void dm_test_wb_connector_init_success(struct kunit *test) +{ + struct amdgpu_dm_wb_connector *wbcon; + struct amdgpu_display_manager *dm; + struct amdgpu_device *adev; + struct dc_link *link; + struct dc *dc; + int ret; + + adev = alloc_test_adev(test); + adev->mode_info.num_crtc = 1; + dm = &adev->dm; + dm->adev = adev; + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc); + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + dc->links[0] = link; + dm->dc = dc; + + wbcon = kunit_kzalloc(test, sizeof(*wbcon), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, wbcon); + + ret = amdgpu_dm_wb_connector_init(dm, wbcon, 0); + + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_PTR_EQ(test, wbcon->link, link); + KUNIT_EXPECT_TRUE(test, wbcon->base.base.funcs != NULL); + KUNIT_EXPECT_TRUE(test, wbcon->base.base.helper_private != NULL); + KUNIT_EXPECT_TRUE(test, wbcon->base.base.state != NULL); + KUNIT_EXPECT_TRUE(test, wbcon->base.encoder.funcs != NULL); + KUNIT_EXPECT_EQ(test, wbcon->base.encoder.possible_crtcs, 0x1); +} + static struct kunit_case dm_wb_test_cases[] = { /* amdgpu_dm_wb_encoder_atomic_check */ KUNIT_CASE(dm_test_wb_atomic_check_no_job), @@ -322,6 +390,8 @@ static struct kunit_case dm_wb_test_cases[] = { /* amdgpu_dm_wb_connector_get_modes */ KUNIT_CASE(dm_test_wb_get_modes_returns_modes), KUNIT_CASE(dm_test_wb_get_modes_bounded_by_max), + /* amdgpu_dm_wb_connector_init */ + KUNIT_CASE(dm_test_wb_connector_init_success), {} }; -- cgit v1.2.3 From d99024d7d243aecec42b98ac000e720499cb3e92 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 15 Jun 2026 18:54:53 -0600 Subject: drm/amd/display: Add KUnit tests for amdgpu_dm_replay [WHAT] Add KUnit coverage for amdgpu_dm_set_replay_caps(), amdgpu_dm_link_setup_replay(), and amdgpu_dm_replay_set_event() including happy-path tests that exercise the configuration logic, coasting vtotal calculations, and early-return when replay events are already in the desired state. Assisted-by: Copilot:Claude-Opus-4.6 GPT-5.5 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../drm/amd/display/amdgpu_dm/amdgpu_dm_replay.c | 3 + .../amdgpu_dm/tests/amdgpu_dm_replay_test.c | 437 ++++++++++++++++++++- 2 files changed, 436 insertions(+), 4 deletions(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_replay.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_replay.c index f3cea2aba901..42e17119461d 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_replay.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_replay.c @@ -128,6 +128,7 @@ bool amdgpu_dm_set_replay_caps(struct dc_link *link, struct amdgpu_dm_connector return true; } +EXPORT_IF_KUNIT(amdgpu_dm_set_replay_caps); /* * amdgpu_dm_link_setup_replay() - config replay settings @@ -166,6 +167,7 @@ bool amdgpu_dm_link_setup_replay(struct dc_stream_state *stream, static_coasting_vtotal); return true; } +EXPORT_IF_KUNIT(amdgpu_dm_link_setup_replay); /* * amdgpu_dm_replay_set_event() - set or clear replay event for a stream @@ -205,3 +207,4 @@ bool amdgpu_dm_replay_set_event(struct amdgpu_display_manager *dm, return mod_power_set_replay_event(dm->power_module, stream, set_event, event, wait_for_disable); } +EXPORT_IF_KUNIT(amdgpu_dm_replay_set_event); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_replay_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_replay_test.c index 28ff8bbcc0f7..68f2f4d70407 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_replay_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_replay_test.c @@ -8,12 +8,12 @@ #include #include "dc.h" +#include "dc_dmub_srv.h" #include "amdgpu_mode.h" #include "amdgpu_dm.h" - -/* Extern declaration for the function under test */ -extern bool amdgpu_dm_link_supports_replay(struct dc_link *link, - struct amdgpu_dm_connector *aconnector); +#include "amdgpu_dm_replay.h" +#include "modules/power/power_helpers.h" +#include "dmub/dmub_srv.h" /* * Helper: allocate a dc_link, amdgpu_dm_connector, and dm_connector_state @@ -23,6 +23,9 @@ struct replay_test_ctx { struct dc_link *link; struct amdgpu_dm_connector *aconnector; struct dm_connector_state *dm_state; + struct dc *dc; + struct dc_context *dc_ctx; + struct dc_stream_state *stream; }; static struct replay_test_ctx *alloc_replay_ctx(struct kunit *test) @@ -41,8 +44,21 @@ static struct replay_test_ctx *alloc_replay_ctx(struct kunit *test) ctx->dm_state = kunit_kzalloc(test, sizeof(*ctx->dm_state), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, ctx->dm_state); + ctx->dc = kunit_kzalloc(test, sizeof(*ctx->dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->dc); + + ctx->dc_ctx = kunit_kzalloc(test, sizeof(*ctx->dc_ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->dc_ctx); + + ctx->stream = kunit_kzalloc(test, sizeof(*ctx->stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->stream); + /* Wire connector state so to_dm_connector_state() works */ ctx->aconnector->base.state = &ctx->dm_state->base; + ctx->link->ctx = ctx->dc_ctx; + ctx->dc_ctx->dc = ctx->dc; + ctx->dc->ctx = ctx->dc_ctx; + ctx->stream->link = ctx->link; return ctx; } @@ -55,6 +71,7 @@ static void set_all_replay_caps(struct replay_test_ctx *ctx) { ctx->dm_state->freesync_capable = true; ctx->aconnector->vsdb_info.replay_mode = true; + ctx->link->connector_signal = SIGNAL_TYPE_EDP; ctx->link->dpcd_caps.edp_rev = EDP_REVISION_13; ctx->link->dpcd_caps.alpm_caps.bits.AUX_WAKE_ALPM_CAP = 1; ctx->link->dpcd_caps.adaptive_sync_caps.dp_adap_sync_caps.bits.ADAPTIVE_SYNC_SDP_SUPPORT = 1; @@ -181,7 +198,398 @@ static void dm_test_replay_both_deviations_zero(struct kunit *test) /* End of tests for amdgpu_dm_link_supports_replay() */ +/* Tests for amdgpu_dm_set_replay_caps() */ + +/** + * dm_test_replay_set_caps_already_supported - Verify cached Replay support + * @test: KUnit test context + * + * When replay_supported is already set, amdgpu_dm_set_replay_caps() should + * return true without revalidating the link capabilities. + */ +static void dm_test_replay_set_caps_already_supported(struct kunit *test) +{ + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + + ctx->link->replay_settings.config.replay_supported = true; + + KUNIT_EXPECT_TRUE(test, amdgpu_dm_set_replay_caps(ctx->link, ctx->aconnector)); +} + +/** + * dm_test_replay_set_caps_non_embedded_signal - Verify non-eDP rejection + * @test: KUnit test context + * + * When the link signal is not embedded, amdgpu_dm_set_replay_caps() should + * reject Replay even if the sink capability fields are otherwise valid. + */ +static void dm_test_replay_set_caps_non_embedded_signal(struct kunit *test) +{ + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + + set_all_replay_caps(ctx); + ctx->link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_set_replay_caps(ctx->link, ctx->aconnector)); +} + +/** + * dm_test_replay_set_caps_disallowed_by_panel - Verify panel policy rejection + * @test: KUnit test context + * + * When the panel configuration disallows Replay, amdgpu_dm_set_replay_caps() + * should return false before accepting the capability set. + */ +static void dm_test_replay_set_caps_disallowed_by_panel(struct kunit *test) +{ + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + + set_all_replay_caps(ctx); + ctx->link->panel_config.psr.disallow_replay = true; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_set_replay_caps(ctx->link, ctx->aconnector)); +} + +/** + * dm_test_replay_set_caps_link_not_supported - Verify capability rejection + * @test: KUnit test context + * + * When amdgpu_dm_link_supports_replay() rejects the link, the higher-level + * Replay setup helper should also return false. + */ +static void dm_test_replay_set_caps_link_not_supported(struct kunit *test) +{ + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + + set_all_replay_caps(ctx); + ctx->dm_state->freesync_capable = false; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_set_replay_caps(ctx->link, ctx->aconnector)); +} + +/** + * dm_test_replay_set_caps_missing_dmub_srv - Verify missing DMUB rejection + * @test: KUnit test context + * + * When the link and connector support Replay but no DMUB service is available, + * amdgpu_dm_set_replay_caps() should return false. + */ +static void dm_test_replay_set_caps_missing_dmub_srv(struct kunit *test) +{ + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + + set_all_replay_caps(ctx); + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_set_replay_caps(ctx->link, ctx->aconnector)); +} + +/** + * dm_test_replay_set_caps_success - Verify successful Replay configuration + * @test: KUnit test context + * + * When all prerequisites are met (embedded signal, panel allows replay, link + * supports replay, DMUB present with replay support), amdgpu_dm_set_replay_caps() + * should configure the link replay settings and return true. + */ +static void dm_test_replay_set_caps_success(struct kunit *test) +{ + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + struct dc_dmub_srv *dmub_srv; + struct dmub_srv *dmub; + + set_all_replay_caps(ctx); + + dmub_srv = kunit_kzalloc(test, sizeof(*dmub_srv), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dmub_srv); + + dmub = kunit_kzalloc(test, sizeof(*dmub), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dmub); + + dmub->feature_caps.replay_supported = 1; + dmub_srv->dmub = dmub; + ctx->dc_ctx->dmub_srv = dmub_srv; + + KUNIT_EXPECT_TRUE(test, amdgpu_dm_set_replay_caps(ctx->link, ctx->aconnector)); + KUNIT_EXPECT_TRUE(test, ctx->link->replay_settings.config.replay_supported); +} + +/* Tests for amdgpu_dm_link_setup_replay() */ + +/** + * dm_test_replay_link_setup_null_stream - Verify NULL stream rejection + * @test: KUnit test context + * + * amdgpu_dm_link_setup_replay() should return false when no stream is provided. + */ +static void dm_test_replay_link_setup_null_stream(struct kunit *test) +{ + struct mod_vrr_params vrr_params = { 0 }; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_link_setup_replay(NULL, &vrr_params)); +} + +/** + * dm_test_replay_link_setup_null_link - Verify NULL stream link rejection + * @test: KUnit test context + * + * amdgpu_dm_link_setup_replay() should return false when the stream has no + * associated link. + */ +static void dm_test_replay_link_setup_null_link(struct kunit *test) +{ + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + struct mod_vrr_params vrr_params = { 0 }; + + ctx->stream->link = NULL; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_link_setup_replay(ctx->stream, &vrr_params)); +} + +/** + * dm_test_replay_link_setup_null_vrr_params - Verify NULL VRR params rejection + * @test: KUnit test context + * + * amdgpu_dm_link_setup_replay() should return false when VRR parameters are + * not supplied. + */ +static void dm_test_replay_link_setup_null_vrr_params(struct kunit *test) +{ + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_link_setup_replay(ctx->stream, NULL)); +} + +/** + * dm_test_replay_link_setup_not_supported - Verify unsupported Replay rejection + * @test: KUnit test context + * + * amdgpu_dm_link_setup_replay() should return false when Replay is not marked + * supported on the link configuration. + */ +static void dm_test_replay_link_setup_not_supported(struct kunit *test) +{ + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + struct mod_vrr_params vrr_params = { 0 }; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_link_setup_replay(ctx->stream, &vrr_params)); +} + +/** + * dm_test_replay_link_setup_already_enabled - Verify enabled Replay success + * @test: KUnit test context + * + * When Replay is already enabled, amdgpu_dm_link_setup_replay() should return + * true without recalculating coasting vtotal state. + */ +static void dm_test_replay_link_setup_already_enabled(struct kunit *test) +{ + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + struct mod_vrr_params vrr_params = { 0 }; + + ctx->link->replay_settings.config.replay_supported = true; + ctx->link->replay_settings.replay_feature_enabled = true; + + KUNIT_EXPECT_TRUE(test, amdgpu_dm_link_setup_replay(ctx->stream, &vrr_params)); +} + +/** + * dm_test_replay_link_setup_success - Verify coasting vtotal configuration + * @test: KUnit test context + * + * When Replay is supported but not yet enabled, amdgpu_dm_link_setup_replay() + * should calculate the link-off frame count and set the coasting vtotal values, + * then return true. + */ +static void dm_test_replay_link_setup_success(struct kunit *test) +{ + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + struct mod_vrr_params vrr_params = { 0 }; + + ctx->link->replay_settings.config.replay_supported = true; + ctx->link->replay_settings.config.replay_version = DC_FREESYNC_REPLAY; + + /* Set timing so calculate_replay_link_off_frame_count computes */ + ctx->stream->timing.v_total = 1125; + ctx->stream->timing.h_total = 2200; + ctx->stream->timing.pix_clk_100hz = 1485000; + ctx->link->dpcd_caps.pr_info.pixel_deviation_per_line = 4; + ctx->link->dpcd_caps.pr_info.max_deviation_line = 10; + + /* min_refresh_in_uhz = 0 makes calc return v_total directly */ + vrr_params.min_refresh_in_uhz = 0; + + KUNIT_EXPECT_TRUE(test, amdgpu_dm_link_setup_replay(ctx->stream, &vrr_params)); + + /* Verify coasting vtotal was set */ + KUNIT_EXPECT_EQ(test, + ctx->link->replay_settings.coasting_vtotal_table[PR_COASTING_TYPE_NOM], + (uint32_t)1125); + KUNIT_EXPECT_EQ(test, + ctx->link->replay_settings.coasting_vtotal_table[PR_COASTING_TYPE_STATIC], + (uint32_t)1125); + + /* Verify link_off_frame_count was calculated: 2200*10/(4*1125) = 4 */ + KUNIT_EXPECT_EQ(test, + ctx->link->replay_settings.link_off_frame_count, + (uint32_t)4); +} + +/* Tests for amdgpu_dm_replay_set_event() */ + +/** + * dm_test_replay_set_event_null_stream - Verify NULL stream rejection + * @test: KUnit test context + * + * amdgpu_dm_replay_set_event() should return false when no stream is provided. + */ +static void dm_test_replay_set_event_null_stream(struct kunit *test) +{ + struct amdgpu_display_manager *dm; + + dm = kunit_kzalloc(test, sizeof(*dm), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm); + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_replay_set_event(dm, NULL, true, + replay_event_vsync, false)); +} + +/** + * dm_test_replay_set_event_null_link - Verify NULL stream link rejection + * @test: KUnit test context + * + * amdgpu_dm_replay_set_event() should return false when the stream has no + * associated link. + */ +static void dm_test_replay_set_event_null_link(struct kunit *test) +{ + struct amdgpu_display_manager *dm; + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + + dm = kunit_kzalloc(test, sizeof(*dm), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm); + + ctx->stream->link = NULL; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_replay_set_event(dm, ctx->stream, true, + replay_event_vsync, false)); +} + +/** + * dm_test_replay_set_event_feature_disabled - Verify disabled Replay rejection + * @test: KUnit test context + * + * amdgpu_dm_replay_set_event() should return false when Replay is not enabled + * on the stream link. + */ +static void dm_test_replay_set_event_feature_disabled(struct kunit *test) +{ + struct amdgpu_display_manager *dm; + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + + dm = kunit_kzalloc(test, sizeof(*dm), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm); + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_replay_set_event(dm, ctx->stream, true, + replay_event_vsync, false)); +} + +/** + * dm_test_replay_set_event_missing_power_module - Verify missing power rejection + * @test: KUnit test context + * + * When Replay is enabled but no power module is available, the event helper + * should return false after failing to read the current Replay events. + */ +static void dm_test_replay_set_event_missing_power_module(struct kunit *test) +{ + struct amdgpu_display_manager *dm; + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + + dm = kunit_kzalloc(test, sizeof(*dm), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm); + + ctx->link->replay_settings.replay_feature_enabled = true; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_replay_set_event(dm, ctx->stream, true, + replay_event_vsync, false)); +} + +/** + * dm_test_replay_set_event_already_set - Verify no-op when event already active + * @test: KUnit test context + * + * When the requested event is already in the desired state, the function should + * return true without calling mod_power_set_replay_event(). + */ +static void dm_test_replay_set_event_already_set(struct kunit *test) +{ + struct amdgpu_display_manager *dm; + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + struct core_power *core_power; + struct power_entity *map; + + dm = kunit_kzalloc(test, sizeof(*dm), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm); + + core_power = kunit_kzalloc(test, sizeof(*core_power), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, core_power); + + map = kunit_kzalloc(test, sizeof(*map), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, map); + + /* Wire the power module so mod_power_get_replay_event() succeeds */ + map->stream = ctx->stream; + map->replay_events = replay_event_vsync; + core_power->map = map; + core_power->num_entities = 1; + dm->power_module = &core_power->mod_public; + + ctx->link->replay_settings.replay_feature_enabled = true; + + /* Event already set — should return true without calling set */ + KUNIT_EXPECT_TRUE(test, amdgpu_dm_replay_set_event(dm, ctx->stream, true, + replay_event_vsync, false)); +} + +/** + * dm_test_replay_set_event_already_clear - Verify no-op when event already cleared + * @test: KUnit test context + * + * When clearing an event that is not currently active, the function should + * return true without calling mod_power_set_replay_event(). + */ +static void dm_test_replay_set_event_already_clear(struct kunit *test) +{ + struct amdgpu_display_manager *dm; + struct replay_test_ctx *ctx = alloc_replay_ctx(test); + struct core_power *core_power; + struct power_entity *map; + + dm = kunit_kzalloc(test, sizeof(*dm), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm); + + core_power = kunit_kzalloc(test, sizeof(*core_power), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, core_power); + + map = kunit_kzalloc(test, sizeof(*map), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, map); + + /* Wire the power module — replay_events has NO vsync bit */ + map->stream = ctx->stream; + map->replay_events = 0; + core_power->map = map; + core_power->num_entities = 1; + dm->power_module = &core_power->mod_public; + + ctx->link->replay_settings.replay_feature_enabled = true; + + /* Clearing an event that's already clear — should return true */ + KUNIT_EXPECT_TRUE(test, amdgpu_dm_replay_set_event(dm, ctx->stream, false, + replay_event_vsync, false)); +} + static struct kunit_case dm_replay_test_cases[] = { + /* amdgpu_dm_link_supports_replay */ KUNIT_CASE(dm_test_replay_supports_all_caps), KUNIT_CASE(dm_test_replay_no_freesync), KUNIT_CASE(dm_test_replay_no_vsdb_replay_mode), @@ -191,6 +599,27 @@ static struct kunit_case dm_replay_test_cases[] = { KUNIT_CASE(dm_test_replay_zero_pixel_deviation), KUNIT_CASE(dm_test_replay_zero_max_deviation_line), KUNIT_CASE(dm_test_replay_both_deviations_zero), + /* amdgpu_dm_set_replay_caps */ + KUNIT_CASE(dm_test_replay_set_caps_already_supported), + KUNIT_CASE(dm_test_replay_set_caps_non_embedded_signal), + KUNIT_CASE(dm_test_replay_set_caps_disallowed_by_panel), + KUNIT_CASE(dm_test_replay_set_caps_link_not_supported), + KUNIT_CASE(dm_test_replay_set_caps_missing_dmub_srv), + KUNIT_CASE(dm_test_replay_set_caps_success), + /* amdgpu_dm_link_setup_replay */ + KUNIT_CASE(dm_test_replay_link_setup_null_stream), + KUNIT_CASE(dm_test_replay_link_setup_null_link), + KUNIT_CASE(dm_test_replay_link_setup_null_vrr_params), + KUNIT_CASE(dm_test_replay_link_setup_not_supported), + KUNIT_CASE(dm_test_replay_link_setup_already_enabled), + KUNIT_CASE(dm_test_replay_link_setup_success), + /* amdgpu_dm_replay_set_event */ + KUNIT_CASE(dm_test_replay_set_event_null_stream), + KUNIT_CASE(dm_test_replay_set_event_null_link), + KUNIT_CASE(dm_test_replay_set_event_feature_disabled), + KUNIT_CASE(dm_test_replay_set_event_missing_power_module), + KUNIT_CASE(dm_test_replay_set_event_already_set), + KUNIT_CASE(dm_test_replay_set_event_already_clear), {} }; -- cgit v1.2.3 From f1fa90c7a70966117c40ae01d5ae45f07eb73366 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Tue, 16 Jun 2026 19:42:06 -0600 Subject: drm/amd/display: Add KUnit tests for amdgpu_dm_psr [WHAT] Add Kunit tests for functions: - link_supports_psrsu() - amdgpu_dm_psr_fill_caps() - amdgpu_dm_set_psr_caps() - amdgpu_dm_psr_is_active_allowed() - amdgpu_dm_psr_set_event() Assisted-by: Copilot:GPT-5.5 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.c | 51 +- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.h | 5 + .../display/amdgpu_dm/tests/amdgpu_dm_psr_test.c | 538 +++++++++++++++++++++ 3 files changed, 592 insertions(+), 2 deletions(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.c index 0dadc0bb214f..f87de3d18ac0 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.c @@ -32,8 +32,8 @@ #include "modules/power/power_helpers.h" #include "amdgpu_dm_kunit_helpers.h" - -static bool link_supports_psrsu(struct dc_link *link) +STATIC_IFN_KUNIT +bool link_supports_psrsu(struct dc_link *link) { struct dc *dc = link->ctx->dc; @@ -60,6 +60,7 @@ static bool link_supports_psrsu(struct dc_link *link) /* Temporarily disable PSR-SU to avoid glitches */ return false; } +EXPORT_IF_KUNIT(link_supports_psrsu); STATIC_IFN_KUNIT void amdgpu_dm_psr_fill_caps(struct dc_link *link, struct psr_caps *caps) @@ -134,6 +135,7 @@ bool amdgpu_dm_set_psr_caps(struct dc_link *link, struct amdgpu_dm_connector *ac amdgpu_dm_psr_fill_caps(link, &aconnector->psr_caps); return true; } +EXPORT_IF_KUNIT(amdgpu_dm_set_psr_caps); /* * amdgpu_dm_psr_is_active_allowed() - check if psr is allowed on any stream @@ -157,6 +159,7 @@ bool amdgpu_dm_psr_is_active_allowed(struct amdgpu_display_manager *dm) } return false; } +EXPORT_IF_KUNIT(amdgpu_dm_psr_is_active_allowed); /* * amdgpu_dm_psr_set_event() - set or clear PSR event for stream @@ -190,3 +193,47 @@ bool amdgpu_dm_psr_set_event(struct amdgpu_display_manager *dm, struct dc_stream set_event, event, wait_for_disable); } EXPORT_IF_KUNIT(amdgpu_dm_psr_set_event); + +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +/** + * amdgpu_dm_psr_get_dc_feature_mask() - Get DC feature mask for KUnit tests. + * + * Return: Current value of amdgpu_dc_feature_mask. + */ +unsigned int amdgpu_dm_psr_get_dc_feature_mask(void) +{ + return amdgpu_dc_feature_mask; +} +EXPORT_IF_KUNIT(amdgpu_dm_psr_get_dc_feature_mask); + +/** + * amdgpu_dm_psr_set_dc_feature_mask() - Set DC feature mask for KUnit tests. + * @feature_mask: DC feature mask to set while testing amdgpu_dm_psr_fill_caps(). + */ +void amdgpu_dm_psr_set_dc_feature_mask(unsigned int feature_mask) +{ + amdgpu_dc_feature_mask = feature_mask; +} +EXPORT_IF_KUNIT(amdgpu_dm_psr_set_dc_feature_mask); + +/** + * amdgpu_dm_psr_get_dc_debug_mask() - Get DC debug mask for KUnit tests. + * + * Return: Current value of amdgpu_dc_debug_mask. + */ +unsigned int amdgpu_dm_psr_get_dc_debug_mask(void) +{ + return amdgpu_dc_debug_mask; +} +EXPORT_IF_KUNIT(amdgpu_dm_psr_get_dc_debug_mask); + +/** + * amdgpu_dm_psr_set_dc_debug_mask() - Set DC debug mask for KUnit tests. + * @debug_mask: DC debug mask to set while testing link_supports_psrsu(). + */ +void amdgpu_dm_psr_set_dc_debug_mask(unsigned int debug_mask) +{ + amdgpu_dc_debug_mask = debug_mask; +} +EXPORT_IF_KUNIT(amdgpu_dm_psr_set_dc_debug_mask); +#endif diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.h index 40a09b5dc606..e442e7ed82ec 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_psr.h @@ -43,7 +43,12 @@ bool amdgpu_dm_psr_set_event(struct amdgpu_display_manager *dm, bool wait_for_disable); #if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +bool link_supports_psrsu(struct dc_link *link); void amdgpu_dm_psr_fill_caps(struct dc_link *link, struct psr_caps *caps); +unsigned int amdgpu_dm_psr_get_dc_feature_mask(void); +void amdgpu_dm_psr_set_dc_feature_mask(unsigned int feature_mask); +unsigned int amdgpu_dm_psr_get_dc_debug_mask(void); +void amdgpu_dm_psr_set_dc_debug_mask(unsigned int debug_mask); #endif #endif /* AMDGPU_DM_AMDGPU_DM_PSR_H_ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_psr_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_psr_test.c index 09084f70a405..2dd870f650db 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_psr_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_psr_test.c @@ -7,7 +7,12 @@ #include +#include "dc.h" +#include "core_types.h" +#include "amdgpu_mode.h" +#include "amdgpu_dm.h" #include "amdgpu_dm_psr.h" +#include "power_helpers.h" /* * Helper: allocate and zero-initialise a dc_link sufficient for @@ -25,6 +30,365 @@ static struct dc_link *alloc_test_link(struct kunit *test) return link; } +/* + * Helper: allocate and wire the minimal DM/DC state needed for + * amdgpu_dm_psr_is_active_allowed() testing. + */ +static struct amdgpu_display_manager *alloc_test_dm(struct kunit *test) +{ + struct amdgpu_display_manager *dm; + struct dc *dc; + struct dc_state *state; + + dm = kunit_kzalloc(test, sizeof(*dm), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm); + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc); + + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state); + + dm->dc = dc; + dc->current_state = state; + + return dm; +} + +static void add_test_stream(struct kunit *test, struct dc_state *state, + unsigned int index, struct dc_link *link) +{ + struct dc_stream_state *stream; + + KUNIT_ASSERT_LT(test, index, (unsigned int)MAX_PIPES); + + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + stream->link = link; + state->streams[index] = stream; + if (state->stream_count <= index) + state->stream_count = index + 1; +} + +static struct dc_stream_state *alloc_test_psr_stream(struct kunit *test) +{ + struct dc_stream_state *stream; + struct dc_link *link; + + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + link = alloc_test_link(test); + link->psr_settings.psr_feature_enabled = true; + stream->link = link; + kref_init(&stream->refcount); + + return stream; +} + +static struct core_power *create_test_power_module(struct kunit *test, + struct dc_stream_state *stream, struct psr_caps *caps) +{ + struct core_power *core_power; + + core_power = kunit_kzalloc(test, sizeof(*core_power), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, core_power); + + core_power->map = kunit_kzalloc(test, sizeof(*core_power->map), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, core_power->map); + + core_power->map[0].stream = stream; + core_power->map[0].caps = caps; + core_power->map[0].psr_events = psr_event_vsync; + core_power->num_entities = 1; + + return core_power; +} + +static struct dc_link *alloc_test_psrsu_link(struct kunit *test) +{ + struct dc_link *link = alloc_test_link(test); + struct dc_context *ctx; + struct dc *dc; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc); + + link->ctx = ctx; + ctx->dc = dc; + dc->ctx = ctx; + dc->caps.dmcub_support = true; + ctx->dce_version = DCN_VERSION_3_1; + link->dpcd_caps.edp_rev = DP_EDP_14; + link->dpcd_caps.psr_info.psr_version = DP_PSR2_WITH_Y_COORD_ET_SUPPORTED; + link->dpcd_caps.alpm_caps.bits.AUX_WAKE_ALPM_CAP = 1; + link->dpcd_caps.psr_info.psr_dpcd_caps.bits.Y_COORDINATE_REQUIRED = 1; + + return link; +} + +static struct dc_link *alloc_test_psr_caps_link(struct kunit *test) +{ + struct dc_link *link = alloc_test_psrsu_link(test); + + link->ctx->dc->caps.dmub_caps.psr = true; + link->connector_signal = SIGNAL_TYPE_EDP; + link->type = dc_connection_single; + + return link; +} + +static struct amdgpu_dm_connector *alloc_test_aconnector(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + return aconnector; +} + +/* Tests for link_supports_psrsu() */ + +/** + * dm_test_link_supports_psrsu_no_dmcub() - DMCUB support is required. + * @test: KUnit test context. + */ +static void dm_test_link_supports_psrsu_no_dmcub(struct kunit *test) +{ + struct dc_link *link = alloc_test_psrsu_link(test); + + link->ctx->dc->caps.dmcub_support = false; + + KUNIT_EXPECT_FALSE(test, link_supports_psrsu(link)); +} + +/** + * dm_test_link_supports_psrsu_old_dcn() - DCN version 3.1 or newer is required. + * @test: KUnit test context. + */ +static void dm_test_link_supports_psrsu_old_dcn(struct kunit *test) +{ + struct dc_link *link = alloc_test_psrsu_link(test); + + link->ctx->dce_version = DCN_VERSION_3_0; + + KUNIT_EXPECT_FALSE(test, link_supports_psrsu(link)); +} + +/** + * dm_test_link_supports_psrsu_panel_unsupported() - Panel PSR-SU caps are required. + * @test: KUnit test context. + */ +static void dm_test_link_supports_psrsu_panel_unsupported(struct kunit *test) +{ + struct dc_link *link = alloc_test_psrsu_link(test); + + link->dpcd_caps.psr_info.psr_version = 0; + + KUNIT_EXPECT_FALSE(test, link_supports_psrsu(link)); +} + +/** + * dm_test_link_supports_psrsu_missing_alpm() - AUX wake ALPM is required. + * @test: KUnit test context. + */ +static void dm_test_link_supports_psrsu_missing_alpm(struct kunit *test) +{ + struct dc_link *link = alloc_test_psrsu_link(test); + + link->dpcd_caps.alpm_caps.bits.AUX_WAKE_ALPM_CAP = 0; + + KUNIT_EXPECT_FALSE(test, link_supports_psrsu(link)); +} + +/** + * dm_test_link_supports_psrsu_missing_y_coordinate() - Y coordinate support is required. + * @test: KUnit test context. + */ +static void dm_test_link_supports_psrsu_missing_y_coordinate(struct kunit *test) +{ + struct dc_link *link = alloc_test_psrsu_link(test); + + link->dpcd_caps.psr_info.psr_dpcd_caps.bits.Y_COORDINATE_REQUIRED = 0; + + KUNIT_EXPECT_FALSE(test, link_supports_psrsu(link)); +} + +/** + * dm_test_link_supports_psrsu_missing_granularity() - Required granularity must + * be reported by the panel. + * @test: KUnit test context. + */ +static void dm_test_link_supports_psrsu_missing_granularity(struct kunit *test) +{ + struct dc_link *link = alloc_test_psrsu_link(test); + + link->dpcd_caps.psr_info.psr_dpcd_caps.bits.SU_GRANULARITY_REQUIRED = 1; + link->dpcd_caps.psr_info.psr2_su_y_granularity_cap = 0; + + KUNIT_EXPECT_FALSE(test, link_supports_psrsu(link)); +} + +/** + * dm_test_link_supports_psrsu_debug_mask_disabled() - Debug mask disables PSR-SU. + * @test: KUnit test context. + */ +static void dm_test_link_supports_psrsu_debug_mask_disabled(struct kunit *test) +{ + struct dc_link *link = alloc_test_psrsu_link(test); + unsigned int old_debug_mask; + + old_debug_mask = amdgpu_dm_psr_get_dc_debug_mask(); + amdgpu_dm_psr_set_dc_debug_mask(old_debug_mask | DC_DISABLE_PSR_SU); + + KUNIT_EXPECT_FALSE(test, link_supports_psrsu(link)); + amdgpu_dm_psr_set_dc_debug_mask(old_debug_mask); +} + +/** + * dm_test_link_supports_psrsu_temporarily_disabled() - Supported panels still + * return false while PSR-SU is temporarily disabled. + * @test: KUnit test context. + */ +static void dm_test_link_supports_psrsu_temporarily_disabled(struct kunit *test) +{ + struct dc_link *link = alloc_test_psrsu_link(test); + unsigned int old_debug_mask; + + old_debug_mask = amdgpu_dm_psr_get_dc_debug_mask(); + amdgpu_dm_psr_set_dc_debug_mask(old_debug_mask & ~DC_DISABLE_PSR_SU); + + KUNIT_EXPECT_FALSE(test, link_supports_psrsu(link)); + amdgpu_dm_psr_set_dc_debug_mask(old_debug_mask); +} + +/* End of tests for link_supports_psrsu() */ + +/* Tests for amdgpu_dm_set_psr_caps() */ + +/** + * dm_test_set_psr_caps_null_link() - NULL link is rejected. + * @test: KUnit test context. + */ +static void dm_test_set_psr_caps_null_link(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector = alloc_test_aconnector(test); + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_set_psr_caps(NULL, aconnector)); +} + +/** + * dm_test_set_psr_caps_null_connector() - NULL connector is rejected. + * @test: KUnit test context. + */ +static void dm_test_set_psr_caps_null_connector(struct kunit *test) +{ + struct dc_link *link = alloc_test_psr_caps_link(test); + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_set_psr_caps(link, NULL)); +} + +/** + * dm_test_set_psr_caps_no_dmub_psr() - DMUB PSR capability is required. + * @test: KUnit test context. + */ +static void dm_test_set_psr_caps_no_dmub_psr(struct kunit *test) +{ + struct dc_link *link = alloc_test_psr_caps_link(test); + struct amdgpu_dm_connector *aconnector = alloc_test_aconnector(test); + + link->psr_settings.psr_version = DC_PSR_VERSION_1; + link->ctx->dc->caps.dmub_caps.psr = false; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_set_psr_caps(link, aconnector)); + KUNIT_EXPECT_EQ(test, link->psr_settings.psr_version, + DC_PSR_VERSION_UNSUPPORTED); +} + +/** + * dm_test_set_psr_caps_non_edp() - Only eDP links can enable PSR. + * @test: KUnit test context. + */ +static void dm_test_set_psr_caps_non_edp(struct kunit *test) +{ + struct dc_link *link = alloc_test_psr_caps_link(test); + struct amdgpu_dm_connector *aconnector = alloc_test_aconnector(test); + + link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_set_psr_caps(link, aconnector)); +} + +/** + * dm_test_set_psr_caps_disconnected() - Disconnected links cannot enable PSR. + * @test: KUnit test context. + */ +static void dm_test_set_psr_caps_disconnected(struct kunit *test) +{ + struct dc_link *link = alloc_test_psr_caps_link(test); + struct amdgpu_dm_connector *aconnector = alloc_test_aconnector(test); + + link->type = dc_connection_none; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_set_psr_caps(link, aconnector)); +} + +/** + * dm_test_set_psr_caps_no_dpcd_psr() - DPCD PSR version is required. + * @test: KUnit test context. + */ +static void dm_test_set_psr_caps_no_dpcd_psr(struct kunit *test) +{ + struct dc_link *link = alloc_test_psr_caps_link(test); + struct amdgpu_dm_connector *aconnector = alloc_test_aconnector(test); + + link->dpcd_caps.psr_info.psr_version = 0; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_set_psr_caps(link, aconnector)); +} + +/** + * dm_test_set_psr_caps_edp1_disabled() - eDP panel instance 1 is blocked. + * @test: KUnit test context. + */ +static void dm_test_set_psr_caps_edp1_disabled(struct kunit *test) +{ + struct dc_link *link = alloc_test_psr_caps_link(test); + struct dc_link *edp0 = alloc_test_link(test); + struct amdgpu_dm_connector *aconnector = alloc_test_aconnector(test); + struct dc *dc = link->ctx->dc; + + edp0->connector_signal = SIGNAL_TYPE_EDP; + dc->links[0] = edp0; + dc->links[1] = link; + dc->link_count = 2; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_set_psr_caps(link, aconnector)); +} + +/** + * dm_test_set_psr_caps_success_psr1() - Valid eDP link enables PSR1 caps. + * @test: KUnit test context. + */ +static void dm_test_set_psr_caps_success_psr1(struct kunit *test) +{ + struct dc_link *link = alloc_test_psr_caps_link(test); + struct amdgpu_dm_connector *aconnector = alloc_test_aconnector(test); + + KUNIT_EXPECT_TRUE(test, amdgpu_dm_set_psr_caps(link, aconnector)); + KUNIT_EXPECT_EQ(test, link->psr_settings.psr_version, DC_PSR_VERSION_1); + KUNIT_EXPECT_EQ(test, (int)aconnector->psr_caps.psr_version, 1); + KUNIT_EXPECT_EQ(test, (int)aconnector->psr_caps.support_ver, + DP_PSR2_WITH_Y_COORD_ET_SUPPORTED); +} + +/* End of tests for amdgpu_dm_set_psr_caps() */ + /* Tests for amdgpu_dm_psr_fill_caps() — PSR version mapping */ static void dm_test_psr_fill_caps_version_1(struct kunit *test) @@ -221,6 +585,24 @@ static void dm_test_psr_fill_caps_power_opts_z10_always_set(struct kunit *test) (caps.psr_power_opt_flag & psr_power_opt_z10_static_screen) != 0); } + +static void dm_test_psr_fill_caps_power_opts_smu_opt_set(struct kunit *test) +{ + struct dc_link *link = alloc_test_link(test); + struct psr_caps caps; + unsigned int old_feature_mask; + + memset(&caps, 0, sizeof(caps)); + old_feature_mask = amdgpu_dm_psr_get_dc_feature_mask(); + amdgpu_dm_psr_set_dc_feature_mask(old_feature_mask | DC_PSR_ALLOW_SMU_OPT); + + amdgpu_dm_psr_fill_caps(link, &caps); + amdgpu_dm_psr_set_dc_feature_mask(old_feature_mask); + + KUNIT_EXPECT_TRUE(test, + (caps.psr_power_opt_flag & + psr_power_opt_smu_opt_static_screen) != 0); +} /* End of tests for amdgpu_dm_psr_fill_caps() */ /* Tests for amdgpu_dm_psr_set_event() — early-exit validation guards */ @@ -258,9 +640,155 @@ static void dm_test_psr_set_event_psr_not_enabled(struct kunit *test) KUNIT_EXPECT_FALSE(test, amdgpu_dm_psr_set_event(NULL, stream, true, psr_event_vsync, false)); } + +/** + * dm_test_psr_set_event_get_event_fails() - Failed power event read returns false. + * @test: KUnit test context. + */ +static void dm_test_psr_set_event_get_event_fails(struct kunit *test) +{ + struct amdgpu_display_manager *dm = alloc_test_dm(test); + struct dc_stream_state *stream = alloc_test_psr_stream(test); + + dm->power_module = NULL; + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_psr_set_event(dm, stream, true, psr_event_vsync, false)); +} + +/** + * dm_test_psr_set_event_already_set() - Already set event returns true. + * @test: KUnit test context. + */ +static void dm_test_psr_set_event_already_set(struct kunit *test) +{ + struct amdgpu_display_manager *dm = alloc_test_dm(test); + struct dc_stream_state *stream = alloc_test_psr_stream(test); + struct psr_caps caps = {0}; + struct core_power *core_power; + + caps.psr_version = 1; + core_power = create_test_power_module(test, stream, &caps); + dm->power_module = &core_power->mod_public; + + KUNIT_EXPECT_TRUE(test, + amdgpu_dm_psr_set_event(dm, stream, true, psr_event_vsync, false)); + KUNIT_EXPECT_EQ(test, core_power->map[0].psr_events, + (unsigned int)psr_event_vsync); +} + +/** + * dm_test_psr_set_event_updates_event() - Changed event delegates to mod_power. + * @test: KUnit test context. + */ +static void dm_test_psr_set_event_updates_event(struct kunit *test) +{ + struct amdgpu_display_manager *dm = alloc_test_dm(test); + struct dc_stream_state *stream = alloc_test_psr_stream(test); + struct psr_caps caps = {0}; + struct core_power *core_power; + + caps.psr_version = 1; + core_power = create_test_power_module(test, stream, &caps); + dm->power_module = &core_power->mod_public; + + KUNIT_EXPECT_TRUE(test, + amdgpu_dm_psr_set_event(dm, stream, true, psr_event_full_screen, false)); + KUNIT_EXPECT_EQ(test, core_power->map[0].psr_events, + (unsigned int)(psr_event_vsync | psr_event_full_screen)); +} /* End of tests for amdgpu_dm_psr_set_event() */ +/* Tests for amdgpu_dm_psr_is_active_allowed() */ + +/** + * dm_test_psr_is_active_allowed_no_streams() - Empty DC state disallows PSR. + * @test: KUnit test context. + */ +static void dm_test_psr_is_active_allowed_no_streams(struct kunit *test) +{ + struct amdgpu_display_manager *dm = alloc_test_dm(test); + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_psr_is_active_allowed(dm)); +} + +/** + * dm_test_psr_is_active_allowed_null_link() - Streams without links are skipped. + * @test: KUnit test context. + */ +static void dm_test_psr_is_active_allowed_null_link(struct kunit *test) +{ + struct amdgpu_display_manager *dm = alloc_test_dm(test); + struct dc_state *state = dm->dc->current_state; + + add_test_stream(test, state, 0, NULL); + + KUNIT_EXPECT_FALSE(test, amdgpu_dm_psr_is_active_allowed(dm)); +} + +/** + * dm_test_psr_is_active_allowed_requires_enabled_and_allowed() - Both link flags + * must be set before PSR active is allowed. + * @test: KUnit test context. + */ +static void dm_test_psr_is_active_allowed_requires_enabled_and_allowed(struct kunit *test) +{ + struct amdgpu_display_manager *dm = alloc_test_dm(test); + struct dc_state *state = dm->dc->current_state; + struct dc_link *link = alloc_test_link(test); + + add_test_stream(test, state, 0, link); + link->psr_settings.psr_allow_active = true; + KUNIT_EXPECT_FALSE(test, amdgpu_dm_psr_is_active_allowed(dm)); + + link->psr_settings.psr_allow_active = false; + link->psr_settings.psr_feature_enabled = true; + KUNIT_EXPECT_FALSE(test, amdgpu_dm_psr_is_active_allowed(dm)); +} + +/** + * dm_test_psr_is_active_allowed_any_stream() - Any enabled and allowed stream + * permits active PSR. + * @test: KUnit test context. + */ +static void dm_test_psr_is_active_allowed_any_stream(struct kunit *test) +{ + struct amdgpu_display_manager *dm = alloc_test_dm(test); + struct dc_state *state = dm->dc->current_state; + struct dc_link *disabled_link = alloc_test_link(test); + struct dc_link *allowed_link = alloc_test_link(test); + + disabled_link->psr_settings.psr_allow_active = true; + allowed_link->psr_settings.psr_feature_enabled = true; + allowed_link->psr_settings.psr_allow_active = true; + + add_test_stream(test, state, 0, disabled_link); + add_test_stream(test, state, 1, allowed_link); + + KUNIT_EXPECT_TRUE(test, amdgpu_dm_psr_is_active_allowed(dm)); +} + +/* End of tests for amdgpu_dm_psr_is_active_allowed() */ + static struct kunit_case dm_psr_test_cases[] = { + /* link_supports_psrsu */ + KUNIT_CASE(dm_test_link_supports_psrsu_no_dmcub), + KUNIT_CASE(dm_test_link_supports_psrsu_old_dcn), + KUNIT_CASE(dm_test_link_supports_psrsu_panel_unsupported), + KUNIT_CASE(dm_test_link_supports_psrsu_missing_alpm), + KUNIT_CASE(dm_test_link_supports_psrsu_missing_y_coordinate), + KUNIT_CASE(dm_test_link_supports_psrsu_missing_granularity), + KUNIT_CASE(dm_test_link_supports_psrsu_debug_mask_disabled), + KUNIT_CASE(dm_test_link_supports_psrsu_temporarily_disabled), + /* amdgpu_dm_set_psr_caps */ + KUNIT_CASE(dm_test_set_psr_caps_null_link), + KUNIT_CASE(dm_test_set_psr_caps_null_connector), + KUNIT_CASE(dm_test_set_psr_caps_no_dmub_psr), + KUNIT_CASE(dm_test_set_psr_caps_non_edp), + KUNIT_CASE(dm_test_set_psr_caps_disconnected), + KUNIT_CASE(dm_test_set_psr_caps_no_dpcd_psr), + KUNIT_CASE(dm_test_set_psr_caps_edp1_disabled), + KUNIT_CASE(dm_test_set_psr_caps_success_psr1), + /* amdgpu_dm_psr_fill_caps */ KUNIT_CASE(dm_test_psr_fill_caps_version_1), KUNIT_CASE(dm_test_psr_fill_caps_version_su1), KUNIT_CASE(dm_test_psr_fill_caps_version_unsupported), @@ -273,9 +801,19 @@ static struct kunit_case dm_psr_test_cases[] = { KUNIT_CASE(dm_test_psr_fill_caps_dpcd_fields_unset), KUNIT_CASE(dm_test_psr_fill_caps_rate_control_always_zero), KUNIT_CASE(dm_test_psr_fill_caps_power_opts_z10_always_set), + KUNIT_CASE(dm_test_psr_fill_caps_power_opts_smu_opt_set), + /* amdgpu_dm_psr_set_event */ KUNIT_CASE(dm_test_psr_set_event_null_stream), KUNIT_CASE(dm_test_psr_set_event_null_link), KUNIT_CASE(dm_test_psr_set_event_psr_not_enabled), + KUNIT_CASE(dm_test_psr_set_event_get_event_fails), + KUNIT_CASE(dm_test_psr_set_event_already_set), + KUNIT_CASE(dm_test_psr_set_event_updates_event), + /* amdgpu_dm_psr_is_active_allowed */ + KUNIT_CASE(dm_test_psr_is_active_allowed_no_streams), + KUNIT_CASE(dm_test_psr_is_active_allowed_null_link), + KUNIT_CASE(dm_test_psr_is_active_allowed_requires_enabled_and_allowed), + KUNIT_CASE(dm_test_psr_is_active_allowed_any_stream), {} }; -- cgit v1.2.3 From b292f97d300f373e6de2acfa3a9fa8bd82e84c46 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Wed, 17 Jun 2026 14:04:48 -0600 Subject: drm/amd/display: Add KUnit tests for amdgpu_dm_pp_smu Add comprehensive KUnit test coverage for amdgpu_dm_pp_smu.c including: - Utility functions: dc_to_pp_clock_type, pp_to_dc_clock_levels, build_pm_display_cfg, get_default_clock_levels, build_wm_clock_ranges_soc15, cap_clock_levels_to_validation - DPM-backed functions: dm_pp_get_clock_levels_by_type, dm_pp_notify_wm_clock_changes, dm_pp_apply_clock_for_voltage_request, dm_pp_get_static_clocks - Raven pass-throughs: pp_rv_set_wm_ranges, pp_rv_set_pme_wa_enable, pp_rv_set_active_display_count, pp_rv_set_min_deep_sleep_dcfclk, pp_rv_set_hard_min_dcefclk_by_freq, pp_rv_set_hard_min_fclk_by_freq - Navi functions: pp_nv_set_wm_ranges, pp_nv_get_maximum_sustainable_clocks, pp_nv_get_uclk_dpm_states, pp_nv_get_dpm_clock_table - Renoir: pp_rn_get_dpm_clock_table - dm_pp_get_funcs ASIC family selection v2: squash in build fix for removed functions Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c | 52 +- .../drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.h | 23 + .../amdgpu_dm/tests/amdgpu_dm_pp_smu_test.c | 1481 +++++++++++++++++++- 3 files changed, 1538 insertions(+), 18 deletions(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c index e0fe4cb97f31..0d2e5294d062 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.c @@ -337,6 +337,7 @@ bool dm_pp_get_clock_levels_by_type( return true; } +EXPORT_IF_KUNIT(dm_pp_get_clock_levels_by_type); bool dm_pp_get_clock_levels_by_type_with_latency( const struct dc_context *ctx, @@ -357,6 +358,7 @@ bool dm_pp_get_clock_levels_by_type_with_latency( return true; } +EXPORT_IF_KUNIT(dm_pp_get_clock_levels_by_type_with_latency); bool dm_pp_get_clock_levels_by_type_with_voltage( const struct dc_context *ctx, @@ -377,6 +379,7 @@ bool dm_pp_get_clock_levels_by_type_with_voltage( return true; } +EXPORT_IF_KUNIT(dm_pp_get_clock_levels_by_type_with_voltage); bool dm_pp_notify_wm_clock_changes( const struct dc_context *ctx, @@ -396,6 +399,7 @@ bool dm_pp_notify_wm_clock_changes( return false; } +EXPORT_IF_KUNIT(dm_pp_notify_wm_clock_changes); bool dm_pp_apply_clock_for_voltage_request( const struct dc_context *ctx, @@ -464,7 +468,7 @@ STATIC_IFN_KUNIT void build_wm_clock_ranges_soc15( } EXPORT_IF_KUNIT(build_wm_clock_ranges_soc15); -static void pp_rv_set_wm_ranges(struct pp_smu *pp, +STATIC_IFN_KUNIT void pp_rv_set_wm_ranges(struct pp_smu *pp, struct pp_smu_wm_range_sets *ranges) { const struct dc_context *ctx = pp->dm; @@ -476,48 +480,54 @@ static void pp_rv_set_wm_ranges(struct pp_smu *pp, amdgpu_dpm_set_watermarks_for_clocks_ranges(adev, &wm_with_clock_ranges); } +EXPORT_IF_KUNIT(pp_rv_set_wm_ranges); -static void pp_rv_set_pme_wa_enable(struct pp_smu *pp) +STATIC_IFN_KUNIT void pp_rv_set_pme_wa_enable(struct pp_smu *pp) { const struct dc_context *ctx = pp->dm; struct amdgpu_device *adev = ctx->driver_context; amdgpu_dpm_notify_smu_enable_pwe(adev); } +EXPORT_IF_KUNIT(pp_rv_set_pme_wa_enable); -static void pp_rv_set_active_display_count(struct pp_smu *pp, int count) +STATIC_IFN_KUNIT void pp_rv_set_active_display_count(struct pp_smu *pp, int count) { const struct dc_context *ctx = pp->dm; struct amdgpu_device *adev = ctx->driver_context; amdgpu_dpm_set_active_display_count(adev, count); } +EXPORT_IF_KUNIT(pp_rv_set_active_display_count); -static void pp_rv_set_min_deep_sleep_dcfclk(struct pp_smu *pp, int clock) +STATIC_IFN_KUNIT void pp_rv_set_min_deep_sleep_dcfclk(struct pp_smu *pp, int clock) { const struct dc_context *ctx = pp->dm; struct amdgpu_device *adev = ctx->driver_context; amdgpu_dpm_set_min_deep_sleep_dcefclk(adev, clock); } +EXPORT_IF_KUNIT(pp_rv_set_min_deep_sleep_dcfclk); -static void pp_rv_set_hard_min_dcefclk_by_freq(struct pp_smu *pp, int clock) +STATIC_IFN_KUNIT void pp_rv_set_hard_min_dcefclk_by_freq(struct pp_smu *pp, int clock) { const struct dc_context *ctx = pp->dm; struct amdgpu_device *adev = ctx->driver_context; amdgpu_dpm_set_hard_min_dcefclk_by_freq(adev, clock); } +EXPORT_IF_KUNIT(pp_rv_set_hard_min_dcefclk_by_freq); -static void pp_rv_set_hard_min_fclk_by_freq(struct pp_smu *pp, int mhz) +STATIC_IFN_KUNIT void pp_rv_set_hard_min_fclk_by_freq(struct pp_smu *pp, int mhz) { const struct dc_context *ctx = pp->dm; struct amdgpu_device *adev = ctx->driver_context; amdgpu_dpm_set_hard_min_fclk_by_freq(adev, mhz); } +EXPORT_IF_KUNIT(pp_rv_set_hard_min_fclk_by_freq); -static enum pp_smu_status pp_nv_set_wm_ranges(struct pp_smu *pp, +STATIC_IFN_KUNIT enum pp_smu_status pp_nv_set_wm_ranges(struct pp_smu *pp, struct pp_smu_wm_range_sets *ranges) { const struct dc_context *ctx = pp->dm; @@ -527,8 +537,9 @@ static enum pp_smu_status pp_nv_set_wm_ranges(struct pp_smu *pp, return PP_SMU_RESULT_OK; } +EXPORT_IF_KUNIT(pp_nv_set_wm_ranges); -static enum pp_smu_status pp_nv_set_display_count(struct pp_smu *pp, int count) +STATIC_IFN_KUNIT enum pp_smu_status pp_nv_set_display_count(struct pp_smu *pp, int count) { const struct dc_context *ctx = pp->dm; struct amdgpu_device *adev = ctx->driver_context; @@ -543,8 +554,9 @@ static enum pp_smu_status pp_nv_set_display_count(struct pp_smu *pp, int count) return PP_SMU_RESULT_OK; } +EXPORT_IF_KUNIT(pp_nv_set_display_count); -static enum pp_smu_status +STATIC_IFN_KUNIT enum pp_smu_status pp_nv_set_min_deep_sleep_dcfclk(struct pp_smu *pp, int mhz) { const struct dc_context *ctx = pp->dm; @@ -560,8 +572,9 @@ pp_nv_set_min_deep_sleep_dcfclk(struct pp_smu *pp, int mhz) return PP_SMU_RESULT_OK; } +EXPORT_IF_KUNIT(pp_nv_set_min_deep_sleep_dcfclk); -static enum pp_smu_status pp_nv_set_hard_min_dcefclk_by_freq( +STATIC_IFN_KUNIT enum pp_smu_status pp_nv_set_hard_min_dcefclk_by_freq( struct pp_smu *pp, int mhz) { const struct dc_context *ctx = pp->dm; @@ -583,8 +596,9 @@ static enum pp_smu_status pp_nv_set_hard_min_dcefclk_by_freq( return PP_SMU_RESULT_OK; } +EXPORT_IF_KUNIT(pp_nv_set_hard_min_dcefclk_by_freq); -static enum pp_smu_status +STATIC_IFN_KUNIT enum pp_smu_status pp_nv_set_hard_min_uclk_by_freq(struct pp_smu *pp, int mhz) { const struct dc_context *ctx = pp->dm; @@ -606,8 +620,9 @@ pp_nv_set_hard_min_uclk_by_freq(struct pp_smu *pp, int mhz) return PP_SMU_RESULT_OK; } +EXPORT_IF_KUNIT(pp_nv_set_hard_min_uclk_by_freq); -static enum pp_smu_status pp_nv_set_pstate_handshake_support( +STATIC_IFN_KUNIT enum pp_smu_status pp_nv_set_pstate_handshake_support( struct pp_smu *pp, bool pstate_handshake_supported) { const struct dc_context *ctx = pp->dm; @@ -619,6 +634,7 @@ static enum pp_smu_status pp_nv_set_pstate_handshake_support( return PP_SMU_RESULT_OK; } +EXPORT_IF_KUNIT(pp_nv_set_pstate_handshake_support); STATIC_IFN_KUNIT bool pp_smu_nv_clock_id_to_pp(enum pp_smu_nv_clock_id clock_id, enum amd_pp_clock_type *clock_type) @@ -641,7 +657,7 @@ STATIC_IFN_KUNIT bool pp_smu_nv_clock_id_to_pp(enum pp_smu_nv_clock_id clock_id, } EXPORT_IF_KUNIT(pp_smu_nv_clock_id_to_pp); -static enum pp_smu_status pp_nv_set_voltage_by_freq(struct pp_smu *pp, +STATIC_IFN_KUNIT enum pp_smu_status pp_nv_set_voltage_by_freq(struct pp_smu *pp, enum pp_smu_nv_clock_id clock_id, int mhz) { const struct dc_context *ctx = pp->dm; @@ -665,8 +681,9 @@ static enum pp_smu_status pp_nv_set_voltage_by_freq(struct pp_smu *pp, return PP_SMU_RESULT_OK; } +EXPORT_IF_KUNIT(pp_nv_set_voltage_by_freq); -static enum pp_smu_status pp_nv_get_maximum_sustainable_clocks( +STATIC_IFN_KUNIT enum pp_smu_status pp_nv_get_maximum_sustainable_clocks( struct pp_smu *pp, struct pp_smu_nv_clock_table *max_clocks) { const struct dc_context *ctx = pp->dm; @@ -682,8 +699,9 @@ static enum pp_smu_status pp_nv_get_maximum_sustainable_clocks( return PP_SMU_RESULT_OK; } +EXPORT_IF_KUNIT(pp_nv_get_maximum_sustainable_clocks); -static enum pp_smu_status pp_nv_get_uclk_dpm_states(struct pp_smu *pp, +STATIC_IFN_KUNIT enum pp_smu_status pp_nv_get_uclk_dpm_states(struct pp_smu *pp, unsigned int *clock_values_in_khz, unsigned int *num_states) { const struct dc_context *ctx = pp->dm; @@ -700,8 +718,9 @@ static enum pp_smu_status pp_nv_get_uclk_dpm_states(struct pp_smu *pp, return PP_SMU_RESULT_OK; } +EXPORT_IF_KUNIT(pp_nv_get_uclk_dpm_states); -static enum pp_smu_status pp_rn_get_dpm_clock_table( +STATIC_IFN_KUNIT enum pp_smu_status pp_rn_get_dpm_clock_table( struct pp_smu *pp, struct dpm_clocks *clock_table) { const struct dc_context *ctx = pp->dm; @@ -716,6 +735,7 @@ static enum pp_smu_status pp_rn_get_dpm_clock_table( return PP_SMU_RESULT_OK; } +EXPORT_IF_KUNIT(pp_rn_get_dpm_clock_table); void dm_pp_get_funcs( struct dc_context *ctx, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.h index e851e3ee5b63..f918eb71f0d1 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_pp_smu.h @@ -33,6 +33,29 @@ void cap_clock_levels_to_validation(struct dm_pp_clock_levels *dc_clks, const struct amd_pp_simple_clock_info *validation_clks); bool pp_smu_nv_clock_id_to_pp(enum pp_smu_nv_clock_id clock_id, enum amd_pp_clock_type *clock_type); +void pp_rv_set_wm_ranges(struct pp_smu *pp, struct pp_smu_wm_range_sets *ranges); +void pp_rv_set_pme_wa_enable(struct pp_smu *pp); +void pp_rv_set_active_display_count(struct pp_smu *pp, int count); +void pp_rv_set_min_deep_sleep_dcfclk(struct pp_smu *pp, int clock); +void pp_rv_set_hard_min_dcefclk_by_freq(struct pp_smu *pp, int clock); +void pp_rv_set_hard_min_fclk_by_freq(struct pp_smu *pp, int mhz); +enum pp_smu_status pp_nv_set_wm_ranges(struct pp_smu *pp, + struct pp_smu_wm_range_sets *ranges); +enum pp_smu_status pp_nv_set_display_count(struct pp_smu *pp, int count); +enum pp_smu_status pp_nv_set_min_deep_sleep_dcfclk(struct pp_smu *pp, int mhz); +enum pp_smu_status pp_nv_set_hard_min_dcefclk_by_freq(struct pp_smu *pp, int mhz); +enum pp_smu_status pp_nv_set_hard_min_uclk_by_freq(struct pp_smu *pp, int mhz); +enum pp_smu_status pp_nv_set_pstate_handshake_support(struct pp_smu *pp, + bool pstate_handshake_supported); +enum pp_smu_status pp_nv_set_voltage_by_freq(struct pp_smu *pp, + enum pp_smu_nv_clock_id clock_id, int mhz); +enum pp_smu_status pp_nv_get_maximum_sustainable_clocks(struct pp_smu *pp, + struct pp_smu_nv_clock_table *max_clocks); +enum pp_smu_status pp_nv_get_uclk_dpm_states(struct pp_smu *pp, + unsigned int *clock_values_in_khz, + unsigned int *num_states); +enum pp_smu_status pp_rn_get_dpm_clock_table(struct pp_smu *pp, + struct dpm_clocks *clock_table); #endif #endif /* __AMDGPU_DM_PP_SMU_H__ */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_pp_smu_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_pp_smu_test.c index dbb6dfd5c284..8d1d26bfcc16 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_pp_smu_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_pp_smu_test.c @@ -7,6 +7,7 @@ #include #include +#include #include "dc.h" #include "dm_services.h" @@ -16,6 +17,201 @@ #include "amdgpu_dm.h" #include "amdgpu_dm_pp_smu.h" +/* ---- Stub DPM layer ---- */ + +/** + * struct stub_dpm_context - Tracks stub DPM callback invocations + * @ret_val: Return value for the next DPM callback + * @get_current_clocks_info: Clock info returned by stub get_current_clocks + * @get_clock_by_type_clocks: Clocks returned by stub get_clock_by_type + * @get_validation_clks: Validation clocks returned by stub + * @get_clock_by_type_with_latency_clks: Returned by stub with_latency + * @get_clock_by_type_with_voltage_clks: Returned by stub with_voltage + * @set_watermarks_ret: Return value for set_watermarks + * @display_clock_voltage_ret: Return value for display_clock_voltage_request + * @display_disable_memory_clock_switch_ret: Return for disable_memory_clock + * @get_max_sustainable_ret: Return for get_max_sustainable_clocks_by_dc + * @get_uclk_dpm_ret: Return for get_uclk_dpm_states + * @get_dpm_clock_table_ret: Return for get_dpm_clock_table + * @set_active_display_count_ret: Return for set_active_display_count + * @set_min_deep_sleep_dcefclk_ret: Return for set_min_deep_sleep_dcefclk + * @get_validation_clks_ret: Return for get_display_mode_validation_clocks + */ +struct stub_dpm_context { + int ret_val; + struct amd_pp_clock_info get_current_clocks_info; + struct amd_pp_clocks get_clock_by_type_clocks; + struct amd_pp_simple_clock_info get_validation_clks; + int get_validation_clks_ret; + struct pp_clock_levels_with_latency get_clock_by_type_with_latency_clks; + struct pp_clock_levels_with_voltage get_clock_by_type_with_voltage_clks; + int set_watermarks_ret; + int display_clock_voltage_ret; + int display_disable_memory_clock_switch_ret; + int get_max_sustainable_ret; + int get_uclk_dpm_ret; + int get_dpm_clock_table_ret; + int set_active_display_count_ret; + int set_min_deep_sleep_dcefclk_ret; +}; + +static struct stub_dpm_context *stub_dpm_ctx; + +static int stub_get_current_clocks(void *handle, struct amd_pp_clock_info *clocks) +{ + if (stub_dpm_ctx->ret_val) + return stub_dpm_ctx->ret_val; + *clocks = stub_dpm_ctx->get_current_clocks_info; + return 0; +} + +static int stub_get_clock_by_type(void *handle, enum amd_pp_clock_type type, + struct amd_pp_clocks *clocks) +{ + if (stub_dpm_ctx->ret_val) + return stub_dpm_ctx->ret_val; + *clocks = stub_dpm_ctx->get_clock_by_type_clocks; + return 0; +} + +static int stub_get_display_mode_validation_clocks(void *handle, + struct amd_pp_simple_clock_info *clocks) +{ + if (stub_dpm_ctx->get_validation_clks_ret) + return stub_dpm_ctx->get_validation_clks_ret; + *clocks = stub_dpm_ctx->get_validation_clks; + return 0; +} + +static int stub_get_clock_by_type_with_latency(void *handle, + enum amd_pp_clock_type type, + struct pp_clock_levels_with_latency *clocks) +{ + if (stub_dpm_ctx->ret_val) + return stub_dpm_ctx->ret_val; + *clocks = stub_dpm_ctx->get_clock_by_type_with_latency_clks; + return 0; +} + +static int stub_get_clock_by_type_with_voltage(void *handle, + enum amd_pp_clock_type type, + struct pp_clock_levels_with_voltage *clocks) +{ + if (stub_dpm_ctx->ret_val) + return stub_dpm_ctx->ret_val; + *clocks = stub_dpm_ctx->get_clock_by_type_with_voltage_clks; + return 0; +} + +static void stub_display_configuration_change(void *handle) +{ + /* No-op: satisfies display_configuration_changed callback */ +} + +static void stub_pm_compute_clocks(void *handle) +{ + /* No-op: satisfies pm_compute_clocks callback */ +} + +static int stub_set_watermarks_for_clocks_ranges(void *handle, void *clock_ranges) +{ + return stub_dpm_ctx->set_watermarks_ret; +} + +static int stub_display_clock_voltage_request(void *handle, + struct pp_display_clock_request *clock) +{ + return stub_dpm_ctx->display_clock_voltage_ret; +} + +static int stub_set_active_display_count(void *handle, uint32_t count) +{ + return stub_dpm_ctx->set_active_display_count_ret; +} + +static int stub_set_min_deep_sleep_dcefclk(void *handle, uint32_t clock) +{ + return stub_dpm_ctx->set_min_deep_sleep_dcefclk_ret; +} + +static int stub_set_hard_min_dcefclk_by_freq(void *handle, uint32_t clock) +{ + return 0; +} + +static int stub_set_hard_min_fclk_by_freq(void *handle, uint32_t clock) +{ + return 0; +} + +static int stub_notify_smu_enable_pwe(void *handle) +{ + return 0; +} + +static int stub_display_disable_memory_clock_switch(void *handle, + bool disable_memory_clock_switch) +{ + return stub_dpm_ctx->display_disable_memory_clock_switch_ret; +} + +static int stub_get_max_sustainable_clocks_by_dc(void *handle, + struct pp_smu_nv_clock_table *max_clocks) +{ + return stub_dpm_ctx->get_max_sustainable_ret; +} + +static int stub_get_uclk_dpm_states(void *handle, + unsigned int *clock_values_in_khz, + unsigned int *num_states) +{ + return stub_dpm_ctx->get_uclk_dpm_ret; +} + +static int stub_get_dpm_clock_table(void *handle, struct dpm_clocks *clock_table) +{ + return stub_dpm_ctx->get_dpm_clock_table_ret; +} + +static const struct amd_pm_funcs stub_pp_funcs = { + .get_current_clocks = stub_get_current_clocks, + .get_clock_by_type = stub_get_clock_by_type, + .get_display_mode_validation_clocks = stub_get_display_mode_validation_clocks, + .get_clock_by_type_with_latency = stub_get_clock_by_type_with_latency, + .get_clock_by_type_with_voltage = stub_get_clock_by_type_with_voltage, + .display_configuration_changed = stub_display_configuration_change, + .pm_compute_clocks = stub_pm_compute_clocks, + .set_watermarks_for_clocks_ranges = stub_set_watermarks_for_clocks_ranges, + .display_clock_voltage_request = stub_display_clock_voltage_request, + .set_active_display_count = stub_set_active_display_count, + .set_min_deep_sleep_dcefclk = stub_set_min_deep_sleep_dcefclk, + .set_hard_min_dcefclk_by_freq = stub_set_hard_min_dcefclk_by_freq, + .set_hard_min_fclk_by_freq = stub_set_hard_min_fclk_by_freq, + .notify_smu_enable_pwe = stub_notify_smu_enable_pwe, + .display_disable_memory_clock_switch = stub_display_disable_memory_clock_switch, + .get_max_sustainable_clocks_by_dc = stub_get_max_sustainable_clocks_by_dc, + .get_uclk_dpm_states = stub_get_uclk_dpm_states, + .get_dpm_clock_table = stub_get_dpm_clock_table, +}; + +/** + * setup_stub_dpm - Initialize a stub DPM environment for testing + * @test: KUnit test context + * @adev: Pointer to amdgpu_device to configure + * + * Sets up adev->powerplay.pp_funcs and initializes adev->pm.mutex so that + * amdgpu_dpm_* functions can be safely called with stub callbacks. + */ +static void setup_stub_dpm(struct kunit *test, struct amdgpu_device *adev) +{ + stub_dpm_ctx = kunit_kzalloc(test, sizeof(*stub_dpm_ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stub_dpm_ctx); + + adev->powerplay.pp_funcs = &stub_pp_funcs; + adev->powerplay.pp_handle = adev; + mutex_init(&adev->pm.mutex); +} + /* ---- Tests for get_default_clock_levels ---- */ /** @@ -706,23 +902,35 @@ static void dm_test_build_wm_clock_ranges_mcif(struct kunit *test) KUNIT_ASSERT_NOT_NULL(test, ranges); KUNIT_ASSERT_NOT_NULL(test, wm); - ranges->num_writer_wm_sets = 1; + ranges->num_writer_wm_sets = 2; ranges->writer_wm_sets[0].wm_inst = 1; ranges->writer_wm_sets[0].max_fill_clk_mhz = 1200; ranges->writer_wm_sets[0].min_fill_clk_mhz = 600; ranges->writer_wm_sets[0].max_drain_clk_mhz = 1000; ranges->writer_wm_sets[0].min_drain_clk_mhz = 500; + /* set 1: wm_inst > 3 -> clamped to WM_SET_A */ + ranges->writer_wm_sets[1].wm_inst = 5; + ranges->writer_wm_sets[1].max_fill_clk_mhz = 1400; + ranges->writer_wm_sets[1].min_fill_clk_mhz = 700; + ranges->writer_wm_sets[1].max_drain_clk_mhz = 1100; + ranges->writer_wm_sets[1].min_drain_clk_mhz = 550; build_wm_clock_ranges_soc15(ranges, wm); KUNIT_EXPECT_EQ(test, wm->num_wm_dmif_sets, 0U); - KUNIT_EXPECT_EQ(test, wm->num_wm_mcif_sets, 1U); + KUNIT_EXPECT_EQ(test, wm->num_wm_mcif_sets, 2U); KUNIT_EXPECT_EQ(test, wm->wm_mcif_clocks_ranges[0].wm_set_id, WM_SET_B); KUNIT_EXPECT_EQ(test, wm->wm_mcif_clocks_ranges[0].wm_max_socclk_clk_in_khz, 1200000U); KUNIT_EXPECT_EQ(test, wm->wm_mcif_clocks_ranges[0].wm_min_socclk_clk_in_khz, 600000U); KUNIT_EXPECT_EQ(test, wm->wm_mcif_clocks_ranges[0].wm_max_mem_clk_in_khz, 1000000U); KUNIT_EXPECT_EQ(test, wm->wm_mcif_clocks_ranges[0].wm_min_mem_clk_in_khz, 500000U); + + KUNIT_EXPECT_EQ(test, wm->wm_mcif_clocks_ranges[1].wm_set_id, WM_SET_A); + KUNIT_EXPECT_EQ(test, wm->wm_mcif_clocks_ranges[1].wm_max_socclk_clk_in_khz, 1400000U); + KUNIT_EXPECT_EQ(test, wm->wm_mcif_clocks_ranges[1].wm_min_socclk_clk_in_khz, 700000U); + KUNIT_EXPECT_EQ(test, wm->wm_mcif_clocks_ranges[1].wm_max_mem_clk_in_khz, 1100000U); + KUNIT_EXPECT_EQ(test, wm->wm_mcif_clocks_ranges[1].wm_min_mem_clk_in_khz, 550000U); } /* ---- Tests for cap_clock_levels_to_validation ---- */ @@ -911,6 +1119,1208 @@ static void dm_test_nv_clock_id_invalid(struct kunit *test) KUNIT_EXPECT_EQ(test, clock_type, amd_pp_dcef_clock); } +/* ---- Tests using stub DPM layer ---- */ + +/** + * dm_test_apply_display_requirements_dpm_enabled - Test DPM-enabled path + * @test: KUnit test context + * + * Verify that dm_pp_apply_display_requirements calls build_pm_display_cfg + * and the DPM callbacks when DPM is enabled, and returns true. + */ +static void dm_test_apply_display_requirements_dpm_enabled(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct dm_pp_display_configuration cfg = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + adev->pm.dpm_enabled = true; + + cfg.display_count = 1; + cfg.min_engine_clock_khz = 300000; + cfg.disp_configs[0].v_refresh = 60; + + KUNIT_EXPECT_TRUE(test, dm_pp_apply_display_requirements(ctx, &cfg)); + KUNIT_EXPECT_EQ(test, adev->pm.pm_display_cfg.min_core_set_clock, 30000); + KUNIT_EXPECT_EQ(test, adev->pm.pm_display_cfg.vrefresh, 60); +} + +/** + * dm_test_get_clock_levels_by_type_dpm_error - Test DPM error fallback + * @test: KUnit test context + * + * Verify that dm_pp_get_clock_levels_by_type falls back to default clock + * levels when amdgpu_dpm_get_clock_by_type returns an error. + */ +static void dm_test_get_clock_levels_by_type_dpm_error(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct dm_pp_clock_levels dc_clks = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + stub_dpm_ctx->ret_val = -EINVAL; + + KUNIT_EXPECT_TRUE(test, dm_pp_get_clock_levels_by_type(ctx, + DM_PP_CLOCK_TYPE_DISPLAY_CLK, &dc_clks)); + KUNIT_EXPECT_EQ(test, dc_clks.num_levels, 6U); + KUNIT_EXPECT_EQ(test, dc_clks.clocks_in_khz[0], 300000U); +} + +/** + * dm_test_get_clock_levels_by_type_success - Test successful clock query + * @test: KUnit test context + * + * Verify that dm_pp_get_clock_levels_by_type returns the queried clocks + * capped by validation clocks. + */ +static void dm_test_get_clock_levels_by_type_success(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct dm_pp_clock_levels dc_clks = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + + stub_dpm_ctx->get_clock_by_type_clocks.count = 3; + stub_dpm_ctx->get_clock_by_type_clocks.clock[0] = 300000; + stub_dpm_ctx->get_clock_by_type_clocks.clock[1] = 500000; + stub_dpm_ctx->get_clock_by_type_clocks.clock[2] = 700000; + + /* validation at 60000 * 10 = 600000 kHz → caps to 2 levels */ + stub_dpm_ctx->get_validation_clks.engine_max_clock = 60000; + stub_dpm_ctx->get_validation_clks.memory_max_clock = 80000; + + KUNIT_EXPECT_TRUE(test, dm_pp_get_clock_levels_by_type(ctx, + DM_PP_CLOCK_TYPE_ENGINE_CLK, &dc_clks)); + KUNIT_EXPECT_EQ(test, dc_clks.num_levels, 2U); + KUNIT_EXPECT_EQ(test, dc_clks.clocks_in_khz[0], 300000U); + KUNIT_EXPECT_EQ(test, dc_clks.clocks_in_khz[1], 500000U); +} + +/** + * dm_test_get_clock_levels_by_type_validation_fallback - Test validation error + * @test: KUnit test context + * + * Verify that dm_pp_get_clock_levels_by_type uses default validation clocks + * (engine=720000, memory=800000 kHz) when get_display_mode_validation_clocks + * returns an error, capping levels accordingly. + */ +static void dm_test_get_clock_levels_by_type_validation_fallback(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct dm_pp_clock_levels dc_clks = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + + /* get_clock_by_type succeeds with 3 engine clock levels */ + stub_dpm_ctx->get_clock_by_type_clocks.count = 3; + stub_dpm_ctx->get_clock_by_type_clocks.clock[0] = 300000; + stub_dpm_ctx->get_clock_by_type_clocks.clock[1] = 500000; + stub_dpm_ctx->get_clock_by_type_clocks.clock[2] = 800000; + + /* Force validation clocks to fail → triggers default path */ + stub_dpm_ctx->get_validation_clks_ret = -EINVAL; + + KUNIT_EXPECT_TRUE(test, dm_pp_get_clock_levels_by_type(ctx, + DM_PP_CLOCK_TYPE_ENGINE_CLK, &dc_clks)); + /* + * Default validation: engine_max_clock = 72000 * 10 = 720000 kHz. + * Clocks 300000 and 500000 are within limit, 800000 exceeds it, + * so num_levels is capped to 2. + */ + KUNIT_EXPECT_EQ(test, dc_clks.num_levels, 2U); + KUNIT_EXPECT_EQ(test, dc_clks.clocks_in_khz[0], 300000U); + KUNIT_EXPECT_EQ(test, dc_clks.clocks_in_khz[1], 500000U); +} + +/** + * dm_test_get_clock_levels_with_latency_success - Test latency clock query + * @test: KUnit test context + * + * Verify dm_pp_get_clock_levels_by_type_with_latency returns true and + * copies the clock/latency data from the DPM backend. + */ +static void dm_test_get_clock_levels_with_latency_success(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct dm_pp_clock_levels_with_latency info = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + + stub_dpm_ctx->get_clock_by_type_with_latency_clks.num_levels = 1; + stub_dpm_ctx->get_clock_by_type_with_latency_clks.data[0].clocks_in_khz = 600000; + stub_dpm_ctx->get_clock_by_type_with_latency_clks.data[0].latency_in_us = 15; + + KUNIT_EXPECT_TRUE(test, dm_pp_get_clock_levels_by_type_with_latency(ctx, + DM_PP_CLOCK_TYPE_ENGINE_CLK, &info)); + KUNIT_EXPECT_EQ(test, info.num_levels, 1U); + KUNIT_EXPECT_EQ(test, info.data[0].clocks_in_khz, 600000U); + KUNIT_EXPECT_EQ(test, info.data[0].latency_in_us, 15U); +} + +/** + * dm_test_get_clock_levels_with_latency_failure - Test latency query error + * @test: KUnit test context + * + * Verify dm_pp_get_clock_levels_by_type_with_latency returns false on DPM error. + */ +static void dm_test_get_clock_levels_with_latency_failure(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct dm_pp_clock_levels_with_latency info = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + stub_dpm_ctx->ret_val = -EINVAL; + + KUNIT_EXPECT_FALSE(test, dm_pp_get_clock_levels_by_type_with_latency(ctx, + DM_PP_CLOCK_TYPE_ENGINE_CLK, &info)); +} + +/** + * dm_test_get_clock_levels_with_voltage_success - Test voltage clock query + * @test: KUnit test context + * + * Verify dm_pp_get_clock_levels_by_type_with_voltage returns true and + * copies the clock/voltage data from the DPM backend. + */ +static void dm_test_get_clock_levels_with_voltage_success(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct dm_pp_clock_levels_with_voltage info = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + + stub_dpm_ctx->get_clock_by_type_with_voltage_clks.num_levels = 1; + stub_dpm_ctx->get_clock_by_type_with_voltage_clks.data[0].clocks_in_khz = 400000; + stub_dpm_ctx->get_clock_by_type_with_voltage_clks.data[0].voltage_in_mv = 900; + + KUNIT_EXPECT_TRUE(test, dm_pp_get_clock_levels_by_type_with_voltage(ctx, + DM_PP_CLOCK_TYPE_MEMORY_CLK, &info)); + KUNIT_EXPECT_EQ(test, info.num_levels, 1U); + KUNIT_EXPECT_EQ(test, info.data[0].clocks_in_khz, 400000U); + KUNIT_EXPECT_EQ(test, info.data[0].voltage_in_mv, 900U); +} + +/** + * dm_test_get_clock_levels_with_voltage_failure - Test voltage query error + * @test: KUnit test context + * + * Verify dm_pp_get_clock_levels_by_type_with_voltage returns false on DPM error. + */ +static void dm_test_get_clock_levels_with_voltage_failure(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct dm_pp_clock_levels_with_voltage info = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + stub_dpm_ctx->ret_val = -EINVAL; + + KUNIT_EXPECT_FALSE(test, dm_pp_get_clock_levels_by_type_with_voltage(ctx, + DM_PP_CLOCK_TYPE_MEMORY_CLK, &info)); +} + +/** + * dm_test_notify_wm_clock_changes_polaris - Test Polaris watermark path + * @test: KUnit test context + * + * Verify dm_pp_notify_wm_clock_changes returns true for Polaris ASICs + * when the DPM set_watermarks call succeeds. + */ +static void dm_test_notify_wm_clock_changes_polaris(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct dm_pp_wm_sets_with_clock_ranges wm = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + adev->asic_type = CHIP_POLARIS10; + stub_dpm_ctx->set_watermarks_ret = 0; + + KUNIT_EXPECT_TRUE(test, dm_pp_notify_wm_clock_changes(ctx, &wm)); +} + +/** + * dm_test_notify_wm_clock_changes_non_polaris - Test non-Polaris path + * @test: KUnit test context + * + * Verify dm_pp_notify_wm_clock_changes returns false for non-Polaris ASICs. + */ +static void dm_test_notify_wm_clock_changes_non_polaris(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct dm_pp_wm_sets_with_clock_ranges wm = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + adev->asic_type = CHIP_NAVI10; + + KUNIT_EXPECT_FALSE(test, dm_pp_notify_wm_clock_changes(ctx, &wm)); +} + +/** + * dm_test_apply_clock_for_voltage_success - Test successful voltage request + * @test: KUnit test context + * + * Verify dm_pp_apply_clock_for_voltage_request returns true when the DPM + * callback succeeds for a valid clock type. + */ +static void dm_test_apply_clock_for_voltage_success(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct dm_pp_clock_for_voltage_req req = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + stub_dpm_ctx->display_clock_voltage_ret = 0; + + req.clk_type = DM_PP_CLOCK_TYPE_ENGINE_CLK; + req.clocks_in_khz = 500000; + + KUNIT_EXPECT_TRUE(test, dm_pp_apply_clock_for_voltage_request(ctx, &req)); +} + +/** + * dm_test_apply_clock_for_voltage_eopnotsupp - Test EOPNOTSUPP treated as success + * @test: KUnit test context + * + * Verify dm_pp_apply_clock_for_voltage_request returns true when the DPM + * callback returns -EOPNOTSUPP (not supported is non-fatal). + */ +static void dm_test_apply_clock_for_voltage_eopnotsupp(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct dm_pp_clock_for_voltage_req req = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + stub_dpm_ctx->display_clock_voltage_ret = -EOPNOTSUPP; + + req.clk_type = DM_PP_CLOCK_TYPE_ENGINE_CLK; + req.clocks_in_khz = 500000; + + KUNIT_EXPECT_TRUE(test, dm_pp_apply_clock_for_voltage_request(ctx, &req)); +} + +/** + * dm_test_apply_clock_for_voltage_fail - Test DPM error returns false + * @test: KUnit test context + * + * Verify dm_pp_apply_clock_for_voltage_request returns false when the DPM + * callback fails with an error other than -EOPNOTSUPP. + */ +static void dm_test_apply_clock_for_voltage_fail(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct dm_pp_clock_for_voltage_req req = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + stub_dpm_ctx->display_clock_voltage_ret = -EIO; + + req.clk_type = DM_PP_CLOCK_TYPE_ENGINE_CLK; + req.clocks_in_khz = 500000; + + KUNIT_EXPECT_FALSE(test, dm_pp_apply_clock_for_voltage_request(ctx, &req)); +} + +/* ---- Tests for pp_nv_set_display_count ---- */ + +/** + * dm_test_nv_set_display_count_ok - Test successful display count set + * @test: KUnit test context + * + * Verify pp_nv_set_display_count returns PP_SMU_RESULT_OK on success. + */ +static void dm_test_nv_set_display_count_ok(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->set_active_display_count_ret = 0; + + KUNIT_EXPECT_EQ(test, (int)pp_nv_set_display_count(&pp_smu, 2), + (int)PP_SMU_RESULT_OK); +} + +/** + * dm_test_nv_set_display_count_unsupported - Test EOPNOTSUPP mapping + * @test: KUnit test context + * + * Verify pp_nv_set_display_count returns PP_SMU_RESULT_UNSUPPORTED when + * the DPM callback returns -EOPNOTSUPP. + */ +static void dm_test_nv_set_display_count_unsupported(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->set_active_display_count_ret = -EOPNOTSUPP; + + KUNIT_EXPECT_EQ(test, (int)pp_nv_set_display_count(&pp_smu, 2), + (int)PP_SMU_RESULT_UNSUPPORTED); +} + +/** + * dm_test_nv_set_display_count_fail - Test generic error mapping + * @test: KUnit test context + * + * Verify pp_nv_set_display_count returns PP_SMU_RESULT_FAIL on a generic + * DPM error. + */ +static void dm_test_nv_set_display_count_fail(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->set_active_display_count_ret = -EIO; + + KUNIT_EXPECT_EQ(test, (int)pp_nv_set_display_count(&pp_smu, 2), + (int)PP_SMU_RESULT_FAIL); +} + +/* ---- Tests for pp_nv_set_voltage_by_freq ---- */ + +/** + * dm_test_nv_set_voltage_by_freq_ok - Test successful voltage-by-freq + * @test: KUnit test context + * + * Verify pp_nv_set_voltage_by_freq returns PP_SMU_RESULT_OK on success. + */ +static void dm_test_nv_set_voltage_by_freq_ok(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->display_clock_voltage_ret = 0; + + KUNIT_EXPECT_EQ(test, (int)pp_nv_set_voltage_by_freq(&pp_smu, PP_SMU_NV_DISPCLK, 600), + (int)PP_SMU_RESULT_OK); +} + +/** + * dm_test_nv_set_voltage_by_freq_invalid_id - Test invalid clock id + * @test: KUnit test context + * + * Verify pp_nv_set_voltage_by_freq returns PP_SMU_RESULT_FAIL for an + * unrecognized clock id without calling DPM. + */ +static void dm_test_nv_set_voltage_by_freq_invalid_id(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + + KUNIT_EXPECT_EQ(test, + (int)pp_nv_set_voltage_by_freq(&pp_smu, (enum pp_smu_nv_clock_id)0xff, 600), + (int)PP_SMU_RESULT_FAIL); +} + +/* ---- Tests for pp_nv_set_pstate_handshake_support ---- */ + +/** + * dm_test_nv_pstate_handshake_ok - Test successful pstate handshake + * @test: KUnit test context + * + * Verify pp_nv_set_pstate_handshake_support returns PP_SMU_RESULT_OK + * when the DPM callback succeeds. + */ +static void dm_test_nv_pstate_handshake_ok(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->display_disable_memory_clock_switch_ret = 0; + + KUNIT_EXPECT_EQ(test, (int)pp_nv_set_pstate_handshake_support(&pp_smu, true), + (int)PP_SMU_RESULT_OK); +} + +/** + * dm_test_nv_pstate_handshake_fail - Test failed pstate handshake + * @test: KUnit test context + * + * Verify pp_nv_set_pstate_handshake_support returns PP_SMU_RESULT_FAIL + * when the DPM callback returns non-zero. + */ +static void dm_test_nv_pstate_handshake_fail(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->display_disable_memory_clock_switch_ret = -EIO; + + KUNIT_EXPECT_EQ(test, (int)pp_nv_set_pstate_handshake_support(&pp_smu, true), + (int)PP_SMU_RESULT_FAIL); +} + +/* ---- Tests for pp_rn_get_dpm_clock_table ---- */ + +/** + * dm_test_rn_get_dpm_clock_table_ok - Test successful DPM clock table + * @test: KUnit test context + * + * Verify pp_rn_get_dpm_clock_table returns PP_SMU_RESULT_OK on success. + */ +static void dm_test_rn_get_dpm_clock_table_ok(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + struct dpm_clocks clock_table = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->get_dpm_clock_table_ret = 0; + + KUNIT_EXPECT_EQ(test, (int)pp_rn_get_dpm_clock_table(&pp_smu, &clock_table), + (int)PP_SMU_RESULT_OK); +} + +/** + * dm_test_rn_get_dpm_clock_table_unsupported - Test EOPNOTSUPP mapping + * @test: KUnit test context + * + * Verify pp_rn_get_dpm_clock_table returns PP_SMU_RESULT_UNSUPPORTED. + */ +static void dm_test_rn_get_dpm_clock_table_unsupported(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + struct dpm_clocks clock_table = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->get_dpm_clock_table_ret = -EOPNOTSUPP; + + KUNIT_EXPECT_EQ(test, (int)pp_rn_get_dpm_clock_table(&pp_smu, &clock_table), + (int)PP_SMU_RESULT_UNSUPPORTED); +} + +/** + * dm_test_rn_get_dpm_clock_table_fail - Test generic error mapping + * @test: KUnit test context + * + * Verify pp_rn_get_dpm_clock_table returns PP_SMU_RESULT_FAIL. + */ +static void dm_test_rn_get_dpm_clock_table_fail(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + struct dpm_clocks clock_table = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->get_dpm_clock_table_ret = -EIO; + + KUNIT_EXPECT_EQ(test, (int)pp_rn_get_dpm_clock_table(&pp_smu, &clock_table), + (int)PP_SMU_RESULT_FAIL); +} + +/* ---- Tests for pp_rv_set_wm_ranges ---- */ + +/** + * dm_test_rv_set_wm_ranges - Test Raven watermark range forwarding + * @test: KUnit test context + * + * Verify pp_rv_set_wm_ranges converts watermark ranges via + * build_wm_clock_ranges_soc15 and forwards them to DPM without crashing. + */ +static void dm_test_rv_set_wm_ranges(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + struct pp_smu_wm_range_sets ranges = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + + ranges.num_reader_wm_sets = 1; + ranges.reader_wm_sets[0].wm_inst = 0; + ranges.reader_wm_sets[0].max_drain_clk_mhz = 600; + ranges.reader_wm_sets[0].min_drain_clk_mhz = 300; + + pp_rv_set_wm_ranges(&pp_smu, &ranges); + + /* Reaching here without crash confirms coverage */ + KUNIT_SUCCEED(test); +} + +/* ---- Tests for pp_rv_set_pme_wa_enable ---- */ + +/** + * dm_test_rv_set_pme_wa_enable - Test Raven PME workaround enable + * @test: KUnit test context + * + * Verify pp_rv_set_pme_wa_enable forwards the call to DPM without crashing. + */ +static void dm_test_rv_set_pme_wa_enable(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + + pp_rv_set_pme_wa_enable(&pp_smu); + + KUNIT_SUCCEED(test); +} + +/* ---- Tests for pp_rv_set_active_display_count ---- */ + +/** + * dm_test_rv_set_active_display_count - Test Raven display count forwarding + * @test: KUnit test context + * + * Verify pp_rv_set_active_display_count forwards the count to DPM without + * crashing. + */ +static void dm_test_rv_set_active_display_count(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + + pp_rv_set_active_display_count(&pp_smu, 2); + + KUNIT_SUCCEED(test); +} + +/* ---- Tests for pp_rv_set_min_deep_sleep_dcfclk ---- */ + +/** + * dm_test_rv_set_min_deep_sleep_dcfclk - Test Raven deep sleep clock + * @test: KUnit test context + * + * Verify pp_rv_set_min_deep_sleep_dcfclk forwards the clock value to DPM + * without crashing. + */ +static void dm_test_rv_set_min_deep_sleep_dcfclk(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + + pp_rv_set_min_deep_sleep_dcfclk(&pp_smu, 300); + + KUNIT_SUCCEED(test); +} + +/* ---- Tests for pp_rv_set_hard_min_dcefclk_by_freq ---- */ + +/** + * dm_test_rv_set_hard_min_dcefclk_by_freq - Test Raven hard min DCEFCLK + * @test: KUnit test context + * + * Verify pp_rv_set_hard_min_dcefclk_by_freq forwards the frequency to DPM + * without crashing. + */ +static void dm_test_rv_set_hard_min_dcefclk_by_freq(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + + pp_rv_set_hard_min_dcefclk_by_freq(&pp_smu, 600); + + KUNIT_SUCCEED(test); +} + +/* ---- Tests for pp_rv_set_hard_min_fclk_by_freq ---- */ + +/** + * dm_test_rv_set_hard_min_fclk_by_freq - Test Raven hard min FCLK + * @test: KUnit test context + * + * Verify pp_rv_set_hard_min_fclk_by_freq forwards the frequency to DPM + * without crashing. + */ +static void dm_test_rv_set_hard_min_fclk_by_freq(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + + pp_rv_set_hard_min_fclk_by_freq(&pp_smu, 800); + + KUNIT_SUCCEED(test); +} + +/* ---- Tests for pp_nv_set_wm_ranges ---- */ + +/** + * dm_test_nv_set_wm_ranges - Test Navi watermark range forwarding + * @test: KUnit test context + * + * Verify pp_nv_set_wm_ranges forwards ranges to DPM and unconditionally + * returns PP_SMU_RESULT_OK. + */ +static void dm_test_nv_set_wm_ranges(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + struct pp_smu_wm_range_sets ranges = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + + ranges.num_reader_wm_sets = 1; + ranges.reader_wm_sets[0].wm_inst = 0; + + KUNIT_EXPECT_EQ(test, (int)pp_nv_set_wm_ranges(&pp_smu, &ranges), + (int)PP_SMU_RESULT_OK); +} + +/* ---- Tests for pp_nv_set_min_deep_sleep_dcfclk ---- */ + +/** + * dm_test_nv_set_min_deep_sleep_dcfclk_ok - Test successful deep sleep set + * @test: KUnit test context + * + * Verify pp_nv_set_min_deep_sleep_dcfclk returns PP_SMU_RESULT_OK on success. + */ +static void dm_test_nv_set_min_deep_sleep_dcfclk_ok(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->set_min_deep_sleep_dcefclk_ret = 0; + + KUNIT_EXPECT_EQ(test, (int)pp_nv_set_min_deep_sleep_dcfclk(&pp_smu, 300), + (int)PP_SMU_RESULT_OK); +} + +/** + * dm_test_nv_set_min_deep_sleep_dcfclk_unsupported - Test EOPNOTSUPP mapping + * @test: KUnit test context + * + * Verify pp_nv_set_min_deep_sleep_dcfclk returns PP_SMU_RESULT_UNSUPPORTED. + */ +static void dm_test_nv_set_min_deep_sleep_dcfclk_unsupported(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->set_min_deep_sleep_dcefclk_ret = -EOPNOTSUPP; + + KUNIT_EXPECT_EQ(test, (int)pp_nv_set_min_deep_sleep_dcfclk(&pp_smu, 300), + (int)PP_SMU_RESULT_UNSUPPORTED); +} + +/** + * dm_test_nv_set_min_deep_sleep_dcfclk_fail - Test generic error mapping + * @test: KUnit test context + * + * Verify pp_nv_set_min_deep_sleep_dcfclk returns PP_SMU_RESULT_FAIL. + */ +static void dm_test_nv_set_min_deep_sleep_dcfclk_fail(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->set_min_deep_sleep_dcefclk_ret = -EIO; + + KUNIT_EXPECT_EQ(test, (int)pp_nv_set_min_deep_sleep_dcfclk(&pp_smu, 300), + (int)PP_SMU_RESULT_FAIL); +} + +/* ---- Tests for pp_nv_set_hard_min_dcefclk_by_freq ---- */ + +/** + * dm_test_nv_set_hard_min_dcefclk_ok - Test successful hard min DCEFCLK + * @test: KUnit test context + * + * Verify pp_nv_set_hard_min_dcefclk_by_freq returns PP_SMU_RESULT_OK. + */ +static void dm_test_nv_set_hard_min_dcefclk_ok(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->display_clock_voltage_ret = 0; + + KUNIT_EXPECT_EQ(test, (int)pp_nv_set_hard_min_dcefclk_by_freq(&pp_smu, 600), + (int)PP_SMU_RESULT_OK); +} + +/** + * dm_test_nv_set_hard_min_dcefclk_unsupported - Test EOPNOTSUPP mapping + * @test: KUnit test context + * + * Verify pp_nv_set_hard_min_dcefclk_by_freq returns PP_SMU_RESULT_UNSUPPORTED. + */ +static void dm_test_nv_set_hard_min_dcefclk_unsupported(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->display_clock_voltage_ret = -EOPNOTSUPP; + + KUNIT_EXPECT_EQ(test, (int)pp_nv_set_hard_min_dcefclk_by_freq(&pp_smu, 600), + (int)PP_SMU_RESULT_UNSUPPORTED); +} + +/** + * dm_test_nv_set_hard_min_dcefclk_fail - Test generic error mapping + * @test: KUnit test context + * + * Verify pp_nv_set_hard_min_dcefclk_by_freq returns PP_SMU_RESULT_FAIL. + */ +static void dm_test_nv_set_hard_min_dcefclk_fail(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->display_clock_voltage_ret = -EIO; + + KUNIT_EXPECT_EQ(test, (int)pp_nv_set_hard_min_dcefclk_by_freq(&pp_smu, 600), + (int)PP_SMU_RESULT_FAIL); +} + +/* ---- Tests for pp_nv_set_hard_min_uclk_by_freq ---- */ + +/** + * dm_test_nv_set_hard_min_uclk_ok - Test successful hard min UCLK + * @test: KUnit test context + * + * Verify pp_nv_set_hard_min_uclk_by_freq returns PP_SMU_RESULT_OK. + */ +static void dm_test_nv_set_hard_min_uclk_ok(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->display_clock_voltage_ret = 0; + + KUNIT_EXPECT_EQ(test, (int)pp_nv_set_hard_min_uclk_by_freq(&pp_smu, 800), + (int)PP_SMU_RESULT_OK); +} + +/** + * dm_test_nv_set_hard_min_uclk_unsupported - Test EOPNOTSUPP mapping + * @test: KUnit test context + * + * Verify pp_nv_set_hard_min_uclk_by_freq returns PP_SMU_RESULT_UNSUPPORTED. + */ +static void dm_test_nv_set_hard_min_uclk_unsupported(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->display_clock_voltage_ret = -EOPNOTSUPP; + + KUNIT_EXPECT_EQ(test, (int)pp_nv_set_hard_min_uclk_by_freq(&pp_smu, 800), + (int)PP_SMU_RESULT_UNSUPPORTED); +} + +/** + * dm_test_nv_set_hard_min_uclk_fail - Test generic error mapping + * @test: KUnit test context + * + * Verify pp_nv_set_hard_min_uclk_by_freq returns PP_SMU_RESULT_FAIL. + */ +static void dm_test_nv_set_hard_min_uclk_fail(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->display_clock_voltage_ret = -EIO; + + KUNIT_EXPECT_EQ(test, (int)pp_nv_set_hard_min_uclk_by_freq(&pp_smu, 800), + (int)PP_SMU_RESULT_FAIL); +} + +/* ---- Tests for pp_nv_get_maximum_sustainable_clocks ---- */ + +/** + * dm_test_nv_get_max_sustainable_clocks_ok - Test successful query + * @test: KUnit test context + * + * Verify pp_nv_get_maximum_sustainable_clocks returns PP_SMU_RESULT_OK. + */ +static void dm_test_nv_get_max_sustainable_clocks_ok(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + struct pp_smu_nv_clock_table max_clocks = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->get_max_sustainable_ret = 0; + + KUNIT_EXPECT_EQ(test, + (int)pp_nv_get_maximum_sustainable_clocks(&pp_smu, &max_clocks), + (int)PP_SMU_RESULT_OK); +} + +/** + * dm_test_nv_get_max_sustainable_clocks_unsupported - Test EOPNOTSUPP + * @test: KUnit test context + * + * Verify pp_nv_get_maximum_sustainable_clocks returns PP_SMU_RESULT_UNSUPPORTED. + */ +static void dm_test_nv_get_max_sustainable_clocks_unsupported(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + struct pp_smu_nv_clock_table max_clocks = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->get_max_sustainable_ret = -EOPNOTSUPP; + + KUNIT_EXPECT_EQ(test, + (int)pp_nv_get_maximum_sustainable_clocks(&pp_smu, &max_clocks), + (int)PP_SMU_RESULT_UNSUPPORTED); +} + +/** + * dm_test_nv_get_max_sustainable_clocks_fail - Test generic error + * @test: KUnit test context + * + * Verify pp_nv_get_maximum_sustainable_clocks returns PP_SMU_RESULT_FAIL. + */ +static void dm_test_nv_get_max_sustainable_clocks_fail(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + struct pp_smu_nv_clock_table max_clocks = {}; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->get_max_sustainable_ret = -EIO; + + KUNIT_EXPECT_EQ(test, + (int)pp_nv_get_maximum_sustainable_clocks(&pp_smu, &max_clocks), + (int)PP_SMU_RESULT_FAIL); +} + +/* ---- Tests for pp_nv_get_uclk_dpm_states ---- */ + +/** + * dm_test_nv_get_uclk_dpm_states_ok - Test successful DPM states query + * @test: KUnit test context + * + * Verify pp_nv_get_uclk_dpm_states returns PP_SMU_RESULT_OK. + */ +static void dm_test_nv_get_uclk_dpm_states_ok(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + unsigned int clock_values[4] = {}; + unsigned int num_states = 0; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->get_uclk_dpm_ret = 0; + + KUNIT_EXPECT_EQ(test, + (int)pp_nv_get_uclk_dpm_states(&pp_smu, clock_values, &num_states), + (int)PP_SMU_RESULT_OK); +} + +/** + * dm_test_nv_get_uclk_dpm_states_unsupported - Test EOPNOTSUPP mapping + * @test: KUnit test context + * + * Verify pp_nv_get_uclk_dpm_states returns PP_SMU_RESULT_UNSUPPORTED. + */ +static void dm_test_nv_get_uclk_dpm_states_unsupported(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + unsigned int clock_values[4] = {}; + unsigned int num_states = 0; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->get_uclk_dpm_ret = -EOPNOTSUPP; + + KUNIT_EXPECT_EQ(test, + (int)pp_nv_get_uclk_dpm_states(&pp_smu, clock_values, &num_states), + (int)PP_SMU_RESULT_UNSUPPORTED); +} + +/** + * dm_test_nv_get_uclk_dpm_states_fail - Test generic error mapping + * @test: KUnit test context + * + * Verify pp_nv_get_uclk_dpm_states returns PP_SMU_RESULT_FAIL. + */ +static void dm_test_nv_get_uclk_dpm_states_fail(struct kunit *test) +{ + struct amdgpu_device *adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + struct dc_context *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct pp_smu pp_smu = {}; + unsigned int clock_values[4] = {}; + unsigned int num_states = 0; + + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + setup_stub_dpm(test, adev); + ctx->driver_context = adev; + pp_smu.dm = ctx; + stub_dpm_ctx->get_uclk_dpm_ret = -EIO; + + KUNIT_EXPECT_EQ(test, + (int)pp_nv_get_uclk_dpm_states(&pp_smu, clock_values, &num_states), + (int)PP_SMU_RESULT_FAIL); +} + static struct kunit_case dm_pp_smu_test_cases[] = { /* get_default_clock_levels */ KUNIT_CASE(dm_test_default_clock_levels_display), @@ -963,6 +2373,73 @@ static struct kunit_case dm_pp_smu_test_cases[] = { KUNIT_CASE(dm_test_nv_clock_id_phyclk), KUNIT_CASE(dm_test_nv_clock_id_pixelclk), KUNIT_CASE(dm_test_nv_clock_id_invalid), + /* dm_pp_apply_display_requirements (DPM enabled) */ + KUNIT_CASE(dm_test_apply_display_requirements_dpm_enabled), + /* dm_pp_get_clock_levels_by_type */ + KUNIT_CASE(dm_test_get_clock_levels_by_type_dpm_error), + KUNIT_CASE(dm_test_get_clock_levels_by_type_success), + KUNIT_CASE(dm_test_get_clock_levels_by_type_validation_fallback), + /* dm_pp_get_clock_levels_by_type_with_latency */ + KUNIT_CASE(dm_test_get_clock_levels_with_latency_success), + KUNIT_CASE(dm_test_get_clock_levels_with_latency_failure), + /* dm_pp_get_clock_levels_by_type_with_voltage */ + KUNIT_CASE(dm_test_get_clock_levels_with_voltage_success), + KUNIT_CASE(dm_test_get_clock_levels_with_voltage_failure), + /* dm_pp_notify_wm_clock_changes */ + KUNIT_CASE(dm_test_notify_wm_clock_changes_polaris), + KUNIT_CASE(dm_test_notify_wm_clock_changes_non_polaris), + /* dm_pp_apply_clock_for_voltage_request (with DPM) */ + KUNIT_CASE(dm_test_apply_clock_for_voltage_success), + KUNIT_CASE(dm_test_apply_clock_for_voltage_eopnotsupp), + KUNIT_CASE(dm_test_apply_clock_for_voltage_fail), + /* pp_nv_set_display_count */ + KUNIT_CASE(dm_test_nv_set_display_count_ok), + KUNIT_CASE(dm_test_nv_set_display_count_unsupported), + KUNIT_CASE(dm_test_nv_set_display_count_fail), + /* pp_nv_set_voltage_by_freq */ + KUNIT_CASE(dm_test_nv_set_voltage_by_freq_ok), + KUNIT_CASE(dm_test_nv_set_voltage_by_freq_invalid_id), + /* pp_nv_set_pstate_handshake_support */ + KUNIT_CASE(dm_test_nv_pstate_handshake_ok), + KUNIT_CASE(dm_test_nv_pstate_handshake_fail), + /* pp_rn_get_dpm_clock_table */ + KUNIT_CASE(dm_test_rn_get_dpm_clock_table_ok), + KUNIT_CASE(dm_test_rn_get_dpm_clock_table_unsupported), + KUNIT_CASE(dm_test_rn_get_dpm_clock_table_fail), + /* pp_rv_set_wm_ranges */ + KUNIT_CASE(dm_test_rv_set_wm_ranges), + /* pp_rv_set_pme_wa_enable */ + KUNIT_CASE(dm_test_rv_set_pme_wa_enable), + /* pp_rv_set_active_display_count */ + KUNIT_CASE(dm_test_rv_set_active_display_count), + /* pp_rv_set_min_deep_sleep_dcfclk */ + KUNIT_CASE(dm_test_rv_set_min_deep_sleep_dcfclk), + /* pp_rv_set_hard_min_dcefclk_by_freq */ + KUNIT_CASE(dm_test_rv_set_hard_min_dcefclk_by_freq), + /* pp_rv_set_hard_min_fclk_by_freq */ + KUNIT_CASE(dm_test_rv_set_hard_min_fclk_by_freq), + /* pp_nv_set_wm_ranges */ + KUNIT_CASE(dm_test_nv_set_wm_ranges), + /* pp_nv_set_min_deep_sleep_dcfclk */ + KUNIT_CASE(dm_test_nv_set_min_deep_sleep_dcfclk_ok), + KUNIT_CASE(dm_test_nv_set_min_deep_sleep_dcfclk_unsupported), + KUNIT_CASE(dm_test_nv_set_min_deep_sleep_dcfclk_fail), + /* pp_nv_set_hard_min_dcefclk_by_freq */ + KUNIT_CASE(dm_test_nv_set_hard_min_dcefclk_ok), + KUNIT_CASE(dm_test_nv_set_hard_min_dcefclk_unsupported), + KUNIT_CASE(dm_test_nv_set_hard_min_dcefclk_fail), + /* pp_nv_set_hard_min_uclk_by_freq */ + KUNIT_CASE(dm_test_nv_set_hard_min_uclk_ok), + KUNIT_CASE(dm_test_nv_set_hard_min_uclk_unsupported), + KUNIT_CASE(dm_test_nv_set_hard_min_uclk_fail), + /* pp_nv_get_maximum_sustainable_clocks */ + KUNIT_CASE(dm_test_nv_get_max_sustainable_clocks_ok), + KUNIT_CASE(dm_test_nv_get_max_sustainable_clocks_unsupported), + KUNIT_CASE(dm_test_nv_get_max_sustainable_clocks_fail), + /* pp_nv_get_uclk_dpm_states */ + KUNIT_CASE(dm_test_nv_get_uclk_dpm_states_ok), + KUNIT_CASE(dm_test_nv_get_uclk_dpm_states_unsupported), + KUNIT_CASE(dm_test_nv_get_uclk_dpm_states_fail), {} }; -- cgit v1.2.3 From 04bed7922fa92ad037ff8c5dbafd5cbc7e4f8db0 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Wed, 17 Jun 2026 18:11:42 -0600 Subject: drm/amd/display: Add KUnit tests for mst_types Add KUnit coverage for the following MST functions: - dm_dp_aux_transfer(): native read/write, partial write, error result remapping, and HPD disconnect quirk via fake DC link service - dm_dp_aux_transfer_result(): error code translation - dm_dp_aux_fill_payload_flags(): request flag decoding - dm_mst_msg_ready_mask(): ESI mask selection - dm_mst_select_esi_dpcd(): DPCD address/length selection - dm_mst_atomic_best_encoder(): encoder selection by CRTC ID - dm_dp_mst_detect(): unregistered connector early return - dm_dp_mst_atomic_check(): no-old-CRTC early return - dm_dp_create_fake_mst_encoders(): encoder init and CRTC mask - dm_handle_mst_sideband_msg_ready_event(): idle no-ready-bits - retrieve_branch_specific_data(): branch OUI parsing - retrieve_downstream_port_device(): downstream port present - needs_dsc_aux_workaround(): DSC workaround matching - dm_mst_get_pbn_divider(): null link guard - amdgpu_dm_mst_reset_mst_connector_setting(): field reset - dm_dp_mst_is_port_support_mode(): FP-off fallback Assisted-by: Copilot:GPT-5.5 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_mst_types.c | 19 +- .../amd/display/amdgpu_dm/amdgpu_dm_mst_types.h | 9 +- .../amdgpu_dm/tests/amdgpu_dm_mst_types_test.c | 572 +++++++++++++++++++++ 3 files changed, 593 insertions(+), 7 deletions(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c index b6bfe56eeb68..0546efea5de1 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c @@ -99,8 +99,8 @@ EXPORT_IF_KUNIT(dm_dp_aux_fill_payload_flags); /* * This function handles both native AUX and I2C-Over-AUX transactions. */ -static ssize_t dm_dp_aux_transfer(struct drm_dp_aux *aux, - struct drm_dp_aux_msg *msg) +STATIC_IFN_KUNIT ssize_t dm_dp_aux_transfer(struct drm_dp_aux *aux, + struct drm_dp_aux_msg *msg) { ssize_t result = 0; struct aux_payload payload; @@ -167,6 +167,7 @@ static ssize_t dm_dp_aux_transfer(struct drm_dp_aux *aux, return result; } +EXPORT_IF_KUNIT(dm_dp_aux_transfer); static void dm_dp_mst_connector_destroy(struct drm_connector *connector) @@ -518,7 +519,7 @@ static int dm_dp_mst_get_modes(struct drm_connector *connector) return ret; } -static struct drm_encoder * +STATIC_IFN_KUNIT struct drm_encoder * dm_mst_atomic_best_encoder(struct drm_connector *connector, struct drm_atomic_commit *state) { @@ -529,8 +530,9 @@ dm_mst_atomic_best_encoder(struct drm_connector *connector, return &adev->dm.mst_encoders[acrtc->crtc_id].base; } +EXPORT_IF_KUNIT(dm_mst_atomic_best_encoder); -static int +STATIC_IFN_KUNIT int dm_dp_mst_detect(struct drm_connector *connector, struct drm_modeset_acquire_ctx *ctx, bool force) { @@ -600,9 +602,10 @@ dm_dp_mst_detect(struct drm_connector *connector, return connection_status; } +EXPORT_IF_KUNIT(dm_dp_mst_detect); -static int dm_dp_mst_atomic_check(struct drm_connector *connector, - struct drm_atomic_commit *state) +STATIC_IFN_KUNIT int dm_dp_mst_atomic_check(struct drm_connector *connector, + struct drm_atomic_commit *state) { struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector); struct drm_dp_mst_topology_mgr *mst_mgr = &aconnector->mst_root->mst_mgr; @@ -610,6 +613,7 @@ static int dm_dp_mst_atomic_check(struct drm_connector *connector, return drm_dp_atomic_release_time_slots(state, mst_mgr, mst_port); } +EXPORT_IF_KUNIT(dm_dp_mst_atomic_check); static const struct drm_connector_helper_funcs dm_dp_mst_connector_helper_funcs = { .get_modes = dm_dp_mst_get_modes, @@ -650,6 +654,7 @@ dm_dp_create_fake_mst_encoders(struct amdgpu_device *adev) drm_encoder_helper_add(encoder, &amdgpu_dm_encoder_helper_funcs); } } +EXPORT_IF_KUNIT(dm_dp_create_fake_mst_encoders); static struct drm_connector * dm_dp_add_mst_connector(struct drm_dp_mst_topology_mgr *mgr, @@ -855,6 +860,7 @@ void dm_handle_mst_sideband_msg_ready_event( if (process_count == max_process_count) DRM_DEBUG_DRIVER("Loop exceeded max iterations\n"); } +EXPORT_IF_KUNIT(dm_handle_mst_sideband_msg_ready_event); static void dm_handle_mst_down_rep_msg_ready(struct drm_dp_mst_topology_mgr *mgr) { @@ -2108,3 +2114,4 @@ enum dc_status dm_dp_mst_is_port_support_mode( #endif return DC_OK; } +EXPORT_IF_KUNIT(dm_dp_mst_is_port_support_mode); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.h index 2aefab5264d0..fecf108a9216 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.h @@ -64,7 +64,7 @@ struct aux_payload; struct dc_state; struct dc_stream_state; struct dm_atomic_state; -struct drm_atomic_state; +struct drm_atomic_commit; struct drm_dp_mst_topology_mgr; uint32_t dm_mst_get_pbn_divider(struct dc_link *link); @@ -108,8 +108,15 @@ bool retrieve_branch_specific_data(struct amdgpu_dm_connector *aconnector); ssize_t dm_dp_aux_transfer_result(ssize_t result, enum aux_return_code_type operation_result); void dm_dp_aux_fill_payload_flags(u8 request, struct aux_payload *payload); +ssize_t dm_dp_aux_transfer(struct drm_dp_aux *aux, struct drm_dp_aux_msg *msg); u8 dm_mst_msg_ready_mask(enum mst_msg_ready_type msg_rdy_type); void dm_mst_select_esi_dpcd(u8 dpcd_rev, int *dpcd_addr, u8 *dpcd_bytes_to_read); +struct drm_encoder *dm_mst_atomic_best_encoder(struct drm_connector *connector, + struct drm_atomic_commit *state); +int dm_dp_mst_atomic_check(struct drm_connector *connector, + struct drm_atomic_commit *state); +int dm_dp_mst_detect(struct drm_connector *connector, + struct drm_modeset_acquire_ctx *ctx, bool force); #endif #endif diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c index e3b171992be1..d40ed83d8685 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c @@ -7,6 +7,8 @@ #include +#include +#include #include #include #include @@ -18,12 +20,67 @@ #include "amdgpu_mode.h" #include "amdgpu_dm.h" #include "amdgpu_dm_mst_types.h" +#include "inc/link_service.h" /* * Minimal mock DPCD backing store and AUX transfer callback used to exercise * the DPCD read paths without real hardware. */ static u8 dm_mst_test_dpcd[0x10]; +static u8 dm_mst_test_desc_dpcd[0x10]; +static struct aux_payload dm_mst_test_last_payload; +static int dm_mst_test_aux_transfer_raw_result; +static enum aux_return_code_type dm_mst_test_aux_transfer_raw_operation_result; + +static int dm_mst_test_aux_transfer_raw(struct ddc_service *ddc, + struct aux_payload *payload, + enum aux_return_code_type *operation_result) +{ + size_t i; + + dm_mst_test_last_payload = *payload; + *operation_result = dm_mst_test_aux_transfer_raw_operation_result; + + if (dm_mst_test_aux_transfer_raw_result) + return dm_mst_test_aux_transfer_raw_result; + + if (payload->write) + return 0; + + for (i = 0; i < payload->length; i++) + payload->data[i] = dm_mst_test_dpcd[(payload->address + i) & 0xf]; + + return payload->length; +} + +static void dm_mst_test_setup_dm_aux(struct amdgpu_dm_dp_aux *dm_aux, + struct ddc_service *ddc, + struct dc_link *link, + struct dc *dc, + struct link_service *link_srv, + struct dc_context *ctx, + struct amdgpu_device *adev) +{ + memset(&dm_mst_test_last_payload, 0, sizeof(dm_mst_test_last_payload)); + dm_mst_test_aux_transfer_raw_result = 0; + dm_mst_test_aux_transfer_raw_operation_result = AUX_RET_SUCCESS; + link_srv->aux_transfer_raw = dm_mst_test_aux_transfer_raw; + dc->link_srv = link_srv; + link->dc = dc; + ctx->driver_context = adev; + ddc->link = link; + ddc->ctx = ctx; + dm_aux->ddc_service = ddc; + dm_aux->aux.name = "dm_mst_test_dm_aux"; + dm_aux->aux.transfer = dm_dp_aux_transfer; + drm_dp_aux_init(&dm_aux->aux); + drm_dp_dpcd_set_probe(&dm_aux->aux, false); +} + +static const struct dc_link_status *dm_mst_test_get_status(const struct dc_link *link) +{ + return &link->link_status; +} static ssize_t dm_mst_test_aux_transfer(struct drm_dp_aux *aux, struct drm_dp_aux_msg *msg) @@ -45,6 +102,21 @@ static ssize_t dm_mst_test_aux_transfer(struct drm_dp_aux *aux, } } +static ssize_t dm_mst_test_desc_aux_transfer(struct drm_dp_aux *aux, + struct drm_dp_aux_msg *msg) +{ + size_t i; + + if ((msg->request & ~DP_AUX_I2C_MOT) != DP_AUX_NATIVE_READ) + return -EINVAL; + + for (i = 0; i < msg->size; i++) + ((u8 *)msg->buffer)[i] = dm_mst_test_desc_dpcd[msg->address + i - DP_BRANCH_OUI]; + + msg->reply = DP_AUX_NATIVE_REPLY_ACK; + return msg->size; +} + /* Tests for needs_dsc_aux_workaround */ /** @@ -285,6 +357,51 @@ static void dm_mst_test_retrieve_branch_no_parent(struct kunit *test) KUNIT_EXPECT_FALSE(test, retrieve_branch_specific_data(aconnector)); } +/** + * dm_mst_test_retrieve_branch_reads_oui - Test branch OUI parsing + * @test: KUnit test context + * + * Verify that retrieve_branch_specific_data() reads the immediate upstream + * branch descriptor and caches its IEEE OUI value on the connector. + */ +static void dm_mst_test_retrieve_branch_reads_oui(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct drm_dp_mst_topology_mgr *mgr; + struct drm_dp_mst_branch *branch; + struct drm_dp_mst_port *port; + struct drm_dp_aux *aux; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + mgr = kunit_kzalloc(test, sizeof(*mgr), GFP_KERNEL); + branch = kunit_kzalloc(test, sizeof(*branch), GFP_KERNEL); + port = kunit_kzalloc(test, sizeof(*port), GFP_KERNEL); + aux = kunit_kzalloc(test, sizeof(*aux), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + KUNIT_ASSERT_NOT_NULL(test, mgr); + KUNIT_ASSERT_NOT_NULL(test, branch); + KUNIT_ASSERT_NOT_NULL(test, port); + KUNIT_ASSERT_NOT_NULL(test, aux); + + memset(dm_mst_test_desc_dpcd, 0, sizeof(dm_mst_test_desc_dpcd)); + dm_mst_test_desc_dpcd[0] = 0x12; + dm_mst_test_desc_dpcd[1] = 0x34; + dm_mst_test_desc_dpcd[2] = 0x56; + + aux->name = "dm_mst_test_desc_aux"; + aux->transfer = dm_mst_test_desc_aux_transfer; + drm_dp_aux_init(aux); + drm_dp_dpcd_set_probe(aux, false); + mgr->aux = aux; + port->parent = branch; + port->mgr = mgr; + port->aux.drm_dev = NULL; + aconnector->mst_output_port = port; + + KUNIT_EXPECT_TRUE(test, retrieve_branch_specific_data(aconnector)); + KUNIT_EXPECT_EQ(test, aconnector->branch_ieee_oui, 0x123456U); +} + /** * dm_mst_test_aux_result_success - AUX_RET_SUCCESS preserves the input result. * @test: KUnit test context. @@ -340,6 +457,246 @@ static void dm_mst_test_aux_result_timeout(struct kunit *test) (ssize_t)-ETIMEDOUT); } +/** + * dm_mst_test_aux_transfer_native_read - native AUX read through DM callback. + * @test: KUnit test context. + * + * The DM AUX transfer callback should build a read payload, call the DC link + * service, and return the number of bytes provided by the fake backend. + */ +static void dm_mst_test_aux_transfer_native_read(struct kunit *test) +{ + struct amdgpu_dm_dp_aux *dm_aux; + struct amdgpu_device *adev; + struct ddc_service *ddc; + struct dc_link *link; + struct dc *dc; + struct link_service *link_srv; + struct dc_context *ctx; + u8 buffer[3] = { 0 }; + ssize_t ret; + + dm_aux = kunit_kzalloc(test, sizeof(*dm_aux), GFP_KERNEL); + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + ddc = kunit_kzalloc(test, sizeof(*ddc), GFP_KERNEL); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_aux); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ddc); + KUNIT_ASSERT_NOT_NULL(test, link); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, link_srv); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + memset(dm_mst_test_dpcd, 0, sizeof(dm_mst_test_dpcd)); + dm_mst_test_dpcd[4] = 0xaa; + dm_mst_test_dpcd[5] = 0xbb; + dm_mst_test_dpcd[6] = 0xcc; + dm_mst_test_setup_dm_aux(dm_aux, ddc, link, dc, link_srv, ctx, adev); + + ret = drm_dp_dpcd_read(&dm_aux->aux, 4, buffer, sizeof(buffer)); + + KUNIT_EXPECT_EQ(test, ret, (ssize_t)sizeof(buffer)); + KUNIT_EXPECT_EQ(test, buffer[0], (u8)0xaa); + KUNIT_EXPECT_EQ(test, buffer[1], (u8)0xbb); + KUNIT_EXPECT_EQ(test, buffer[2], (u8)0xcc); + KUNIT_EXPECT_FALSE(test, dm_mst_test_last_payload.write); + KUNIT_EXPECT_FALSE(test, dm_mst_test_last_payload.i2c_over_aux); + KUNIT_EXPECT_EQ(test, dm_mst_test_last_payload.address, 4U); +} + +/** + * dm_mst_test_aux_transfer_native_write - native AUX write through DM callback. + * @test: KUnit test context. + * + * A successful write with an ACK reply should report the requested write size + * and pass a write payload into the fake DC link service. + */ +static void dm_mst_test_aux_transfer_native_write(struct kunit *test) +{ + struct amdgpu_dm_dp_aux *dm_aux; + struct amdgpu_device *adev; + struct ddc_service *ddc; + struct dc_link *link; + struct dc *dc; + struct link_service *link_srv; + struct dc_context *ctx; + u8 buffer[2] = { 0x11, 0x22 }; + ssize_t ret; + + dm_aux = kunit_kzalloc(test, sizeof(*dm_aux), GFP_KERNEL); + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + ddc = kunit_kzalloc(test, sizeof(*ddc), GFP_KERNEL); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_aux); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ddc); + KUNIT_ASSERT_NOT_NULL(test, link); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, link_srv); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + dm_mst_test_setup_dm_aux(dm_aux, ddc, link, dc, link_srv, ctx, adev); + + ret = drm_dp_dpcd_write(&dm_aux->aux, 7, buffer, sizeof(buffer)); + + KUNIT_EXPECT_EQ(test, ret, (ssize_t)sizeof(buffer)); + KUNIT_EXPECT_TRUE(test, dm_mst_test_last_payload.write); + KUNIT_EXPECT_FALSE(test, dm_mst_test_last_payload.i2c_over_aux); + KUNIT_EXPECT_EQ(test, dm_mst_test_last_payload.address, 7U); + KUNIT_EXPECT_EQ(test, dm_mst_test_last_payload.length, + (u32)sizeof(buffer)); +} + +/** + * dm_mst_test_aux_transfer_partial_write - partial write reports byte count. + * @test: KUnit test context. + * + * A positive write result from the DC link service should be interpreted as a + * partial write and replaced with the first payload byte. + */ +static void dm_mst_test_aux_transfer_partial_write(struct kunit *test) +{ + struct amdgpu_dm_dp_aux *dm_aux; + struct amdgpu_device *adev; + struct ddc_service *ddc; + struct dc_link *link; + struct dc *dc; + struct link_service *link_srv; + struct dc_context *ctx; + u8 buffer[2] = { 1, 0xaa }; + struct drm_dp_aux_msg msg = { + .address = 7, + .request = DP_AUX_NATIVE_WRITE, + .buffer = buffer, + .size = sizeof(buffer), + }; + ssize_t ret; + + dm_aux = kunit_kzalloc(test, sizeof(*dm_aux), GFP_KERNEL); + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + ddc = kunit_kzalloc(test, sizeof(*ddc), GFP_KERNEL); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_aux); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ddc); + KUNIT_ASSERT_NOT_NULL(test, link); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, link_srv); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + dm_mst_test_setup_dm_aux(dm_aux, ddc, link, dc, link_srv, ctx, adev); + dm_mst_test_aux_transfer_raw_result = 1; + + ret = dm_dp_aux_transfer(&dm_aux->aux, &msg); + + KUNIT_EXPECT_EQ(test, ret, (ssize_t)buffer[0]); + KUNIT_EXPECT_TRUE(test, dm_mst_test_last_payload.write); + KUNIT_EXPECT_EQ(test, dm_mst_test_last_payload.address, 7U); +} + +/** + * dm_mst_test_aux_transfer_error_result - transfer errors are remapped. + * @test: KUnit test context. + * + * A negative DC link service result should be converted through + * dm_dp_aux_transfer_result() using the returned AUX operation result. + */ +static void dm_mst_test_aux_transfer_error_result(struct kunit *test) +{ + struct amdgpu_dm_dp_aux *dm_aux; + struct amdgpu_device *adev; + struct ddc_service *ddc; + struct dc_link *link; + struct dc *dc; + struct link_service *link_srv; + struct dc_context *ctx; + u8 buffer[2] = { 0 }; + ssize_t ret; + + dm_aux = kunit_kzalloc(test, sizeof(*dm_aux), GFP_KERNEL); + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + ddc = kunit_kzalloc(test, sizeof(*ddc), GFP_KERNEL); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_aux); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ddc); + KUNIT_ASSERT_NOT_NULL(test, link); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, link_srv); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + dm_mst_test_setup_dm_aux(dm_aux, ddc, link, dc, link_srv, ctx, adev); + dm_mst_test_aux_transfer_raw_result = -EIO; + dm_mst_test_aux_transfer_raw_operation_result = AUX_RET_ERROR_TIMEOUT; + + ret = drm_dp_dpcd_read(&dm_aux->aux, 4, buffer, sizeof(buffer)); + + KUNIT_EXPECT_EQ(test, ret, (ssize_t)-ETIMEDOUT); + KUNIT_EXPECT_FALSE(test, dm_mst_test_last_payload.write); + KUNIT_EXPECT_EQ(test, dm_mst_test_last_payload.address, 4U); +} + +/** + * dm_mst_test_aux_transfer_hpd_discon_quirk - HPD disconnect quirk succeeds. + * @test: KUnit test context. + * + * AUX_RET_ERROR_HPD_DISCON on the sideband down request address should be + * treated as a successful transfer when the platform quirk is enabled. + */ +static void dm_mst_test_aux_transfer_hpd_discon_quirk(struct kunit *test) +{ + struct amdgpu_dm_dp_aux *dm_aux; + struct amdgpu_device *adev; + struct ddc_service *ddc; + struct dc_link *link; + struct dc *dc; + struct link_service *link_srv; + struct dc_context *ctx; + u8 buffer[2] = { 2, 0 }; + ssize_t ret; + + dm_aux = kunit_kzalloc(test, sizeof(*dm_aux), GFP_KERNEL); + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + ddc = kunit_kzalloc(test, sizeof(*ddc), GFP_KERNEL); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm_aux); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, ddc); + KUNIT_ASSERT_NOT_NULL(test, link); + KUNIT_ASSERT_NOT_NULL(test, dc); + KUNIT_ASSERT_NOT_NULL(test, link_srv); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + dm_mst_test_setup_dm_aux(dm_aux, ddc, link, dc, link_srv, ctx, adev); + adev->dm.aux_hpd_discon_quirk = true; + dm_mst_test_aux_transfer_raw_result = -EIO; + dm_mst_test_aux_transfer_raw_operation_result = AUX_RET_ERROR_HPD_DISCON; + + ret = drm_dp_dpcd_write(&dm_aux->aux, DP_SIDEBAND_MSG_DOWN_REQ_BASE, + buffer, sizeof(buffer)); + + KUNIT_EXPECT_EQ(test, ret, (ssize_t)sizeof(buffer)); + KUNIT_EXPECT_TRUE(test, dm_mst_test_last_payload.write); + KUNIT_EXPECT_EQ(test, dm_mst_test_last_payload.address, + DP_SIDEBAND_MSG_DOWN_REQ_BASE); +} + /** * dm_mst_test_fill_payload_flags_native_write - native write request decode. * @test: KUnit test context. @@ -463,6 +820,203 @@ static void dm_mst_test_select_esi_dpcd_esi(struct kunit *test) (int)(DP_PSR_ERROR_STATUS - DP_SINK_COUNT_ESI)); } +/** + * dm_mst_test_sideband_msg_ready_no_ready_bits - Test idle sideband event + * @test: KUnit test context + * + * Verify that dm_handle_mst_sideband_msg_ready_event() returns cleanly when + * the ESI read succeeds but no DOWN_REP/UP_REQ ready bits are set. + */ +static void dm_mst_test_sideband_msg_ready_no_ready_bits(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + struct link_service *link_srv; + struct dc_link *link; + struct dc *dc; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL); + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + KUNIT_ASSERT_NOT_NULL(test, link_srv); + KUNIT_ASSERT_NOT_NULL(test, link); + KUNIT_ASSERT_NOT_NULL(test, dc); + + mutex_init(&aconnector->handle_mst_msg_ready); + link_srv->get_status = dm_mst_test_get_status; + dc->link_srv = link_srv; + link->dc = dc; + link->dpcd_caps.dpcd_rev.raw = DPCD_REV_14; + link->link_status.dpcd_caps = &link->dpcd_caps; + aconnector->dc_link = link; + aconnector->dm_dp_aux.aux.name = "dm_mst_test_sideband_aux"; + aconnector->dm_dp_aux.aux.transfer = dm_mst_test_aux_transfer; + drm_dp_aux_init(&aconnector->dm_dp_aux.aux); + drm_dp_dpcd_set_probe(&aconnector->dm_dp_aux.aux, false); + memset(dm_mst_test_dpcd, 0, sizeof(dm_mst_test_dpcd)); + + dm_handle_mst_sideband_msg_ready_event(&aconnector->mst_mgr, + DOWN_REP_MSG_RDY_EVENT); + + KUNIT_EXPECT_EQ(test, dm_mst_test_dpcd[1], (u8)0); +} + +/** + * dm_mst_test_atomic_best_encoder - Test MST encoder selection + * @test: KUnit test context + * + * Verify that dm_mst_atomic_best_encoder() selects the MST encoder indexed by + * the CRTC ID in the connector's new atomic state. This uses structural DRM + * mocks only; registering connector/CRTC objects is unnecessary for this helper. + */ +static void dm_mst_test_atomic_best_encoder(struct kunit *test) +{ + struct drm_connector_state connector_state = { 0 }; + struct drm_atomic_commit state = { 0 }; + struct amdgpu_dm_connector *aconnector; + struct amdgpu_device *adev; + struct amdgpu_crtc *acrtc; + unsigned int connector_index = 3; + + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + KUNIT_ASSERT_NOT_NULL(test, acrtc); + + aconnector->base.dev = &adev->ddev; + aconnector->base.index = connector_index; + acrtc->crtc_id = 2; + connector_state.connector = &aconnector->base; + connector_state.crtc = &acrtc->base; + state.num_connector = connector_index + 1; + state.connectors = kunit_kzalloc(test, + sizeof(*state.connectors) * state.num_connector, + GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state.connectors); + state.connectors[connector_index].ptr = &aconnector->base; + state.connectors[connector_index].new_state = &connector_state; + + KUNIT_EXPECT_PTR_EQ(test, dm_mst_atomic_best_encoder(&aconnector->base, &state), + &adev->dm.mst_encoders[2].base); +} + +/** + * dm_mst_test_create_fake_mst_encoders - Test fake MST encoder setup + * @test: KUnit test context + * + * Verify that dm_dp_create_fake_mst_encoders() initializes the requested MST + * encoders as DPMST encoders with the CRTC mask derived from the device state. + */ +static void dm_mst_test_create_fake_mst_encoders(struct kunit *test) +{ + struct amdgpu_device *adev; + struct drm_device *drm; + struct device *dev; + int i; + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + drm = __drm_kunit_helper_alloc_drm_device(test, dev, + sizeof(*adev), + offsetof(struct amdgpu_device, ddev), + DRIVER_MODESET | DRIVER_ATOMIC); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + adev = drm_to_adev(drm); + adev->dm.display_indexes_num = 3; + adev->mode_info.num_crtc = 3; + + dm_dp_create_fake_mst_encoders(adev); + + for (i = 0; i < adev->dm.display_indexes_num; i++) { + struct drm_encoder *encoder = &adev->dm.mst_encoders[i].base; + + KUNIT_EXPECT_PTR_EQ(test, encoder->dev, drm); + KUNIT_EXPECT_EQ(test, encoder->encoder_type, DRM_MODE_ENCODER_DPMST); + KUNIT_EXPECT_EQ(test, encoder->possible_crtcs, 0x7U); + KUNIT_EXPECT_TRUE(test, encoder->helper_private != NULL); + } +} + +/** + * dm_mst_test_atomic_check_no_old_crtc - Test atomic check no-op path + * @test: KUnit test context + * + * Verify that dm_dp_mst_atomic_check() returns success when the MST port's old + * connector state has no CRTC, before MST topology state is required. + */ +static void dm_mst_test_atomic_check_no_old_crtc(struct kunit *test) +{ + struct drm_connector_state old_conn_state = { 0 }; + struct drm_connector_state new_conn_state = { 0 }; + struct drm_atomic_commit state = { 0 }; + struct amdgpu_dm_connector *aconnector; + struct amdgpu_dm_connector *root; + struct drm_dp_mst_port *port; + unsigned int connector_index = 2; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + root = kunit_kzalloc(test, sizeof(*root), GFP_KERNEL); + port = kunit_kzalloc(test, sizeof(*port), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + KUNIT_ASSERT_NOT_NULL(test, root); + KUNIT_ASSERT_NOT_NULL(test, port); + + aconnector->base.index = connector_index; + aconnector->mst_root = root; + aconnector->mst_output_port = port; + port->connector = &aconnector->base; + old_conn_state.connector = &aconnector->base; + new_conn_state.connector = &aconnector->base; + state.num_connector = connector_index + 1; + state.connectors = kunit_kzalloc(test, + sizeof(*state.connectors) * state.num_connector, + GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state.connectors); + state.connectors[connector_index].ptr = &aconnector->base; + state.connectors[connector_index].old_state = &old_conn_state; + state.connectors[connector_index].new_state = &new_conn_state; + + KUNIT_EXPECT_EQ(test, dm_dp_mst_atomic_check(&aconnector->base, &state), 0); +} + +/** + * dm_mst_test_detect_unregistered - Test detect skips unregistered connector + * @test: KUnit test context + * + * Verify that dm_dp_mst_detect() returns disconnected for an unregistered + * connector before calling into the MST topology helper. + */ +static void dm_mst_test_detect_unregistered(struct kunit *test) +{ + struct amdgpu_dm_connector *aconnector; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + aconnector->base.registration_state = DRM_CONNECTOR_UNREGISTERED; + + KUNIT_EXPECT_EQ(test, + dm_dp_mst_detect(&aconnector->base, NULL, false), + (int)connector_status_disconnected); +} + +/** + * dm_mst_test_fp_guarded_public_stubs - Test FP-off public fallbacks + * @test: KUnit test context + * + * When CONFIG_DRM_AMD_DC_FP is disabled, the public DSC validation helper + * has no FP body and must return DC_OK without touching its arguments. + */ +static void dm_mst_test_fp_guarded_public_stubs(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, dm_dp_mst_is_port_support_mode(NULL, NULL), + (enum dc_status)DC_OK); +} + static struct kunit_case dm_mst_types_test_cases[] = { /* needs_dsc_aux_workaround tests */ KUNIT_CASE(dm_mst_test_needs_dsc_aux_workaround_match), @@ -480,11 +1034,17 @@ static struct kunit_case dm_mst_types_test_cases[] = { KUNIT_CASE(dm_mst_test_retrieve_downstream_present), /* retrieve_branch_specific_data tests */ KUNIT_CASE(dm_mst_test_retrieve_branch_no_parent), + KUNIT_CASE(dm_mst_test_retrieve_branch_reads_oui), /* dm_dp_aux_transfer_result tests */ KUNIT_CASE(dm_mst_test_aux_result_success), KUNIT_CASE(dm_mst_test_aux_result_eio), KUNIT_CASE(dm_mst_test_aux_result_ebusy), KUNIT_CASE(dm_mst_test_aux_result_timeout), + KUNIT_CASE(dm_mst_test_aux_transfer_native_read), + KUNIT_CASE(dm_mst_test_aux_transfer_native_write), + KUNIT_CASE(dm_mst_test_aux_transfer_partial_write), + KUNIT_CASE(dm_mst_test_aux_transfer_error_result), + KUNIT_CASE(dm_mst_test_aux_transfer_hpd_discon_quirk), /* dm_dp_aux_fill_payload_flags tests */ KUNIT_CASE(dm_mst_test_fill_payload_flags_native_write), KUNIT_CASE(dm_mst_test_fill_payload_flags_native_read), @@ -495,6 +1055,18 @@ static struct kunit_case dm_mst_types_test_cases[] = { /* dm_mst_select_esi_dpcd tests */ KUNIT_CASE(dm_mst_test_select_esi_dpcd_legacy), KUNIT_CASE(dm_mst_test_select_esi_dpcd_esi), + /* dm_handle_mst_sideband_msg_ready_event tests */ + KUNIT_CASE(dm_mst_test_sideband_msg_ready_no_ready_bits), + /* dm_mst_atomic_best_encoder tests */ + KUNIT_CASE(dm_mst_test_atomic_best_encoder), + /* dm_dp_create_fake_mst_encoders tests */ + KUNIT_CASE(dm_mst_test_create_fake_mst_encoders), + /* dm_dp_mst_atomic_check tests */ + KUNIT_CASE(dm_mst_test_atomic_check_no_old_crtc), + /* dm_dp_mst_detect tests */ + KUNIT_CASE(dm_mst_test_detect_unregistered), + /* CONFIG_DRM_AMD_DC_FP disabled public paths */ + KUNIT_CASE(dm_mst_test_fp_guarded_public_stubs), {} }; -- cgit v1.2.3 From 9bcf6af12bacb046b712359a770a9302b8856ae6 Mon Sep 17 00:00:00 2001 From: Harry Wentland Date: Wed, 17 Jun 2026 15:00:40 -0400 Subject: drm/amd/display: hold a vblank ref while writeback is pending Writeback completion is detected in dm_crtc_high_irq(), the CRTC vblank IRQ handler. The arm path (dm_set_writeback) never took a vblank reference, so the interrupt was only enabled incidentally (by a pageflip on the same commit, fbcon, or a previous vblank's off-delay window). A writeback-only commit right after a fresh drm_crtc_vblank_on() (e.g. a writeback connector detached and re-attached) therefore has no vblank reference: the IRQ never fires, wb_pending is never cleared and the out fence times out. This is reproducible with IGT kms_writeback and was seen via kms_colorop on writeback-capable hardware. The relevant IGT branch is at https://gitlab.freedesktop.org/hwentland/igt-gpu-tools/-/tree/yuv-fm-colorop Take a vblank reference when arming the writeback and release it once completion is signalled. The get is done before arming wb_pending so the completion IRQ cannot drop the reference before it is taken. Factor the shared completion bookkeeping into amdgpu_dm_crtc_complete_writeback() and also call it from the teardown path, so a writeback torn down while still pending signals its out fence and releases the reference instead of leaking both. Fixes: c81e13b929df ("drm/amd/display: Hande writeback request from userspace") Assisted-by: Copilot:claude-opus-4.8 Reviewed-by: Alex Hung Signed-off-by: Harry Wentland Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 64 +++++++++++++++++++++- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 2 + .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c | 45 ++++++--------- 3 files changed, 82 insertions(+), 29 deletions(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') 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 6bcd447f4f5d..67b825cbb88f 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -4498,10 +4498,55 @@ static void amdgpu_dm_crtc_copy_transient_flags(struct drm_crtc_state *crtc_stat stream_state->mode_changed = drm_atomic_crtc_needs_modeset(crtc_state); } +/** + * amdgpu_dm_crtc_complete_writeback - finish a pending writeback job + * @acrtc: the CRTC whose pending writeback should be completed + * + * Clears the pending state, signals the writeback out fence and releases the + * vblank reference taken in dm_set_writeback() while the writeback was armed. + * The pending flag is tested and cleared under the writeback job lock, so this + * is safe to call concurrently from the completion vblank IRQ + * (dm_crtc_high_irq()) and from the writeback teardown path + * (dm_clear_writeback()); only the caller that observes the pending job + * performs the completion. + * + * Return: true if a pending writeback job was completed by this call. + */ +bool amdgpu_dm_crtc_complete_writeback(struct amdgpu_crtc *acrtc) +{ + unsigned long flags; + bool pending; + + if (!acrtc->wb_conn) + return false; + + spin_lock_irqsave(&acrtc->wb_conn->job_lock, flags); + pending = acrtc->wb_pending; + acrtc->wb_pending = false; + spin_unlock_irqrestore(&acrtc->wb_conn->job_lock, flags); + + if (!pending) + return false; + + drm_writeback_signal_completion(acrtc->wb_conn, 0); + drm_crtc_vblank_put(&acrtc->base); + + return true; +} + static void dm_clear_writeback(struct amdgpu_display_manager *dm, + struct amdgpu_crtc *acrtc, struct dm_crtc_state *crtc_state) { dc_stream_remove_writeback(dm->dc, crtc_state->stream, 0); + + /* + * If the writeback is still pending when it is torn down (its + * completion vblank IRQ never fired), signal the out fence so a + * waiting client does not stall and release the vblank reference + * taken in dm_set_writeback(). + */ + amdgpu_dm_crtc_complete_writeback(acrtc); } /** @@ -4654,7 +4699,7 @@ static void amdgpu_dm_commit_streams(struct drm_atomic_commit *state, dm_old_crtc_state = to_dm_crtc_state(old_crtc_state); - dm_clear_writeback(dm, dm_old_crtc_state); + dm_clear_writeback(dm, acrtc, dm_old_crtc_state); acrtc->wb_enabled = false; } @@ -4928,9 +4973,24 @@ static void dm_set_writeback(struct amdgpu_display_manager *dm, dc_stream_add_writeback(dm->dc, crtc_state->stream, wb_info); - acrtc->wb_pending = true; acrtc->wb_conn = wb_conn; drm_writeback_queue_job(wb_conn, new_con_state); + + /* + * Writeback completion is detected in the CRTC vblank IRQ + * (dm_crtc_high_irq()). Take a vblank reference so the vblank interrupt + * stays enabled while the writeback is pending; otherwise a + * writeback-only commit right after drm_crtc_vblank_on() (e.g. + * re-enabling a CRTC that was disabled) has no other vblank reference, + * the IRQ never fires and the out fence times out. The matching put + * happens once completion is signalled in dm_crtc_high_irq(), or when + * the writeback is torn down in dm_clear_writeback(). + * + * Arm wb_pending only after the reference is held so the completion IRQ + * cannot run its matching vblank_put before this get. + */ + WARN_ON(drm_crtc_vblank_get(&acrtc->base)); + acrtc->wb_pending = true; } static void amdgpu_dm_update_hdcp(struct drm_atomic_commit *state) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h index 2ace3abe15e5..91affbdb2d6c 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h @@ -1120,6 +1120,8 @@ void dm_free_gpu_mem(struct amdgpu_device *adev, bool amdgpu_dm_is_headless(struct amdgpu_device *adev); +bool amdgpu_dm_crtc_complete_writeback(struct amdgpu_crtc *acrtc); + void retrieve_dmi_info(struct amdgpu_display_manager *dm); void amdgpu_dm_emulated_link_detect(struct dc_link *link); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c index ffaf2b7bc35d..551901c7598a 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c @@ -1965,7 +1965,6 @@ static void dm_crtc_high_irq(void *interrupt_params) { struct common_irq_params *irq_params = interrupt_params; struct amdgpu_device *adev = irq_params->adev; - struct drm_writeback_job *job; struct amdgpu_crtc *acrtc; unsigned long flags; int vrr_active; @@ -1974,32 +1973,24 @@ static void dm_crtc_high_irq(void *interrupt_params) if (!acrtc) return; - if (acrtc->wb_conn) { - spin_lock_irqsave(&acrtc->wb_conn->job_lock, flags); - - if (acrtc->wb_pending) { - job = list_first_entry_or_null(&acrtc->wb_conn->job_queue, - struct drm_writeback_job, - list_entry); - acrtc->wb_pending = false; - spin_unlock_irqrestore(&acrtc->wb_conn->job_lock, flags); - - if (job) { - unsigned int v_total, refresh_hz; - struct dc_stream_state *stream = acrtc->dm_irq_params.stream; - - v_total = stream->adjust.v_total_max ? - stream->adjust.v_total_max : stream->timing.v_total; - refresh_hz = div_u64((uint64_t) stream->timing.pix_clk_100hz * - 100LL, (v_total * stream->timing.h_total)); - mdelay(1000 / refresh_hz); - - drm_writeback_signal_completion(acrtc->wb_conn, 0); - dc_stream_fc_disable_writeback(adev->dm.dc, - acrtc->dm_irq_params.stream, 0); - } - } else - spin_unlock_irqrestore(&acrtc->wb_conn->job_lock, flags); + if (acrtc->wb_conn && acrtc->wb_pending) { + struct dc_stream_state *stream = acrtc->dm_irq_params.stream; + unsigned int v_total, refresh_hz; + + v_total = stream->adjust.v_total_max ? + stream->adjust.v_total_max : stream->timing.v_total; + refresh_hz = div_u64((uint64_t) stream->timing.pix_clk_100hz * + 100LL, (v_total * stream->timing.h_total)); + mdelay(1000 / refresh_hz); + + /* + * Completion (signalling the out fence and releasing the vblank + * reference taken in dm_set_writeback()) is handled by the shared + * helper, which is also used by the teardown path. + */ + if (amdgpu_dm_crtc_complete_writeback(acrtc)) + dc_stream_fc_disable_writeback(adev->dm.dc, + acrtc->dm_irq_params.stream, 0); } vrr_active = amdgpu_dm_crtc_vrr_active_irq(acrtc); -- cgit v1.2.3 From a532f8d7e4c96a3244e75539665637df9f33d8db Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Thu, 18 Jun 2026 14:22:27 -0600 Subject: drm/amd/display: Extract shared KUnit test helpers Extract common allocation and setup patterns from KUnit test files into a dedicated helpers module to reduce duplication. Add tests/amdgpu_dm_kunit_helpers.c with shared helpers: - dm_kunit_alloc_adev: allocate amdgpu_device via DRM mock - dm_kunit_alloc_link: allocate zeroed dc_link - dm_kunit_alloc_link_with_ctx: allocate dc_link with dc_context - dm_kunit_alloc_dm: allocate display_manager with DC state - dm_kunit_alloc_stream: allocate dc_stream_state with link - dm_kunit_add_stream_to_state: wire stream into dc_state - dm_kunit_alloc_connector: allocate connector wired to device Update 10 test files to use the shared helpers, removing duplicated local alloc_test_adev, alloc_test_link, alloc_test_dm, alloc_test_stream, and add_test_stream functions. Add missing MODULE_DESCRIPTION() macro to suppress modpost warning: WARNING: modpost: missing MODULE_DESCRIPTION() in amdgpu_dm_kunit_helpers.o Assisted-by: Copilot:Claude-Opus-4.6 Reviewed-by: Bhawanpreet Lakha Signed-off-by: Alex Hung Signed-off-by: George Zhang Signed-off-by: Alex Deucher --- .../gpu/drm/amd/display/amdgpu_dm/tests/Makefile | 1 + .../amdgpu_dm/tests/amdgpu_dm_backlight_test.c | 21 +-- .../amdgpu_dm/tests/amdgpu_dm_colorop_test.c | 13 +- .../display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c | 15 +-- .../amdgpu_dm/tests/amdgpu_dm_helpers_test.c | 19 +-- .../display/amdgpu_dm/tests/amdgpu_dm_irq_test.c | 37 +----- .../display/amdgpu_dm/tests/amdgpu_dm_ism_test.c | 39 ++---- .../amdgpu_dm/tests/amdgpu_dm_kunit_helpers.c | 142 +++++++++++++++++++++ .../amdgpu_dm/tests/amdgpu_dm_kunit_test_helpers.h | 32 +++++ .../amdgpu_dm/tests/amdgpu_dm_mst_types_test.c | 13 +- .../display/amdgpu_dm/tests/amdgpu_dm_psr_test.c | 126 +++++------------- .../amdgpu_dm/tests/amdgpu_dm_replay_test.c | 19 +-- .../display/amdgpu_dm/tests/amdgpu_dm_wb_test.c | 18 +-- 13 files changed, 252 insertions(+), 243 deletions(-) create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_helpers.c create mode 100644 drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_test_helpers.h (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile index 4d89ad8a6df6..1592e8dae1a9 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/Makefile @@ -11,6 +11,7 @@ ccflags-y += -I$(src)/../../../amdgpu ccflags-y += -I$(src)/../../../amdkfd ccflags-y += -I$(src)/../../../include +obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_kunit_helpers.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_crc_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_hdcp_test.o obj-$(CONFIG_DRM_AMD_DC_KUNIT_TEST) += amdgpu_dm_audio_test.o diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c index 0e9de940e5a8..fff50c1325c6 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c @@ -13,6 +13,7 @@ #include "amdgpu_mode.h" #include "amdgpu_dm.h" #include "amdgpu_dm_backlight.h" +#include "amdgpu_dm_kunit_test_helpers.h" #include "amd_shared.h" #include "dc/inc/hw/panel_cntl.h" @@ -22,16 +23,6 @@ struct dm_backlight_connector_fixture { struct dc_link *link; }; -static struct amdgpu_display_manager *alloc_test_dm(struct kunit *test) -{ - struct amdgpu_display_manager *dm; - - dm = kunit_kzalloc(test, sizeof(*dm), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, dm); - - return dm; -} - static void setup_test_connector(struct kunit *test, struct dm_backlight_connector_fixture *fixture, int bl_idx, enum signal_type signal) @@ -57,7 +48,7 @@ static void setup_test_connector(struct kunit *test, */ static void dm_test_backlight_device_index_matches_second(struct kunit *test) { - struct amdgpu_display_manager *dm = alloc_test_dm(test); + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); struct backlight_device *bd0; struct backlight_device *bd1; @@ -79,7 +70,7 @@ static void dm_test_backlight_device_index_matches_second(struct kunit *test) */ static void dm_test_backlight_device_index_missing_fallback(struct kunit *test) { - struct amdgpu_display_manager *dm = alloc_test_dm(test); + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); struct backlight_device *known_bd; struct backlight_device *unknown_bd; @@ -102,7 +93,7 @@ static void dm_test_backlight_device_index_missing_fallback(struct kunit *test) */ static void dm_test_backlight_caps_valid_short_circuit(struct kunit *test) { - struct amdgpu_display_manager *dm = alloc_test_dm(test); + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); struct amdgpu_dm_backlight_caps *caps = &dm->backlight_caps[0]; caps->caps_valid = true; @@ -125,7 +116,7 @@ static void dm_test_backlight_caps_valid_short_circuit(struct kunit *test) */ static void dm_test_backlight_caps_aux_support_noop(struct kunit *test) { - struct amdgpu_display_manager *dm = alloc_test_dm(test); + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); struct amdgpu_dm_backlight_caps *caps = &dm->backlight_caps[0]; caps->caps_valid = false; @@ -146,7 +137,7 @@ static void dm_test_backlight_caps_aux_support_noop(struct kunit *test) */ static void dm_test_backlight_caps_non_aux_sets_defaults(struct kunit *test) { - struct amdgpu_display_manager *dm = alloc_test_dm(test); + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); struct amdgpu_dm_backlight_caps *caps = &dm->backlight_caps[0]; caps->caps_valid = false; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_colorop_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_colorop_test.c index b28a165b213e..2e557ff66818 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_colorop_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_colorop_test.c @@ -12,6 +12,7 @@ #include "dc.h" #include "amdgpu.h" #include "amdgpu_dm_colorop.h" +#include "amdgpu_dm_kunit_test_helpers.h" /* Tests for amdgpu_dm_supported_degam_tfs */ @@ -222,19 +223,11 @@ static void dm_test_initialize_default_pipeline_caps(struct kunit *test, struct amdgpu_device *adev; struct drm_device *drm; struct drm_plane *plane; - struct device *dev; struct dc *dc; int ret; - dev = drm_kunit_helper_alloc_device(test); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); - - drm = __drm_kunit_helper_alloc_drm_device(test, dev, - sizeof(*adev), - offsetof(struct amdgpu_device, ddev), - DRIVER_MODESET); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); - adev = drm_to_adev(drm); + adev = dm_kunit_alloc_adev(test); + drm = &adev->ddev; dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c index c83bd3e074f1..0edaf969f16b 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c @@ -15,6 +15,7 @@ #include "amdgpu_mode.h" #include "amdgpu_dm.h" #include "amdgpu_dm_crtc.h" +#include "amdgpu_dm_kunit_test_helpers.h" #include "amdgpu_dm_irq_params.h" /* Tests for amdgpu_dm_crtc_modeset_required() */ @@ -435,23 +436,13 @@ static void dm_test_crtc_set_vupdate_irq_no_otg(struct kunit *test) { struct amdgpu_crtc *acrtc; struct amdgpu_device *adev; - struct drm_device *drm; - struct device *dev; - dev = drm_kunit_helper_alloc_device(test); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); - - drm = __drm_kunit_helper_alloc_drm_device(test, dev, - sizeof(*adev), - offsetof(struct amdgpu_device, ddev), - DRIVER_MODESET); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); - adev = drm_to_adev(drm); + adev = dm_kunit_alloc_adev(test); acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); - acrtc->base.dev = drm; + acrtc->base.dev = &adev->ddev; acrtc->otg_inst = -1; KUNIT_EXPECT_EQ(test, amdgpu_dm_crtc_set_vupdate_irq(&acrtc->base, true), 0); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c index 14004ff87c9b..33014a2d2222 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c @@ -16,6 +16,7 @@ #include "dm_helpers.h" #include "ddc_service_types.h" #include "amdgpu_dm_helpers.h" +#include "amdgpu_dm_kunit_test_helpers.h" /* Tests for edid_extract_panel_id() */ @@ -552,26 +553,14 @@ static void dm_test_mst_start_top_mgr_boot(struct kunit *test) { struct amdgpu_dm_connector *aconnector; struct amdgpu_device *adev; - struct drm_device *drm; - struct device *dev; struct dc_link *link; - dev = drm_kunit_helper_alloc_device(test); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + adev = dm_kunit_alloc_adev(test); - drm = __drm_kunit_helper_alloc_drm_device(test, dev, - sizeof(*adev), - offsetof(struct amdgpu_device, ddev), - DRIVER_MODESET); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); - adev = drm_to_adev(drm); + link = dm_kunit_alloc_link(test); - aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, aconnector); - aconnector->base.dev = drm; + aconnector = dm_kunit_alloc_connector(test, adev, NULL); - link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, link); link->priv = aconnector; KUNIT_EXPECT_TRUE(test, dm_helpers_dp_mst_start_top_mgr(NULL, link, true)); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c index 525caa0b1f6a..a73a6dd146d6 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c @@ -13,6 +13,7 @@ #include "amdgpu_mode.h" #include "amdgpu_dm.h" #include "amdgpu_dm_irq.h" +#include "amdgpu_dm_kunit_test_helpers.h" #include "dmub/dmub_srv.h" static void dm_test_irq_handler(void *arg) @@ -778,17 +779,9 @@ static void dm_test_get_crtc_by_otg_inst_returns_match(struct kunit *test) struct amdgpu_crtc *acrtc_a, *acrtc_b; struct amdgpu_device *adev; struct drm_device *drm; - struct device *dev; - dev = drm_kunit_helper_alloc_device(test); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); - - drm = __drm_kunit_helper_alloc_drm_device(test, dev, - sizeof(*adev), - offsetof(struct amdgpu_device, ddev), - DRIVER_MODESET); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); - adev = drm_to_adev(drm); + adev = dm_kunit_alloc_adev(test); + drm = &adev->ddev; acrtc_a = kunit_kzalloc(test, sizeof(*acrtc_a), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc_a); @@ -819,17 +812,9 @@ static void dm_test_get_crtc_by_otg_inst_returns_null(struct kunit *test) struct amdgpu_crtc *acrtc; struct amdgpu_device *adev; struct drm_device *drm; - struct device *dev; - - dev = drm_kunit_helper_alloc_device(test); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); - drm = __drm_kunit_helper_alloc_drm_device(test, dev, - sizeof(*adev), - offsetof(struct amdgpu_device, ddev), - DRIVER_MODESET); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); - adev = drm_to_adev(drm); + adev = dm_kunit_alloc_adev(test); + drm = &adev->ddev; acrtc = kunit_kzalloc(test, sizeof(*acrtc), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); @@ -851,18 +836,8 @@ static void dm_test_get_crtc_by_otg_inst_returns_null(struct kunit *test) static void dm_test_get_crtc_by_otg_inst_empty_list(struct kunit *test) { struct amdgpu_device *adev; - struct drm_device *drm; - struct device *dev; - - dev = drm_kunit_helper_alloc_device(test); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); - drm = __drm_kunit_helper_alloc_drm_device(test, dev, - sizeof(*adev), - offsetof(struct amdgpu_device, ddev), - DRIVER_MODESET); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); - adev = drm_to_adev(drm); + adev = dm_kunit_alloc_adev(test); KUNIT_EXPECT_NULL(test, amdgpu_dm_get_crtc_by_otg_inst(adev, 0)); } diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_ism_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_ism_test.c index f3b3f77aafd5..7dfb3b351d20 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_ism_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_ism_test.c @@ -9,20 +9,7 @@ #include "dc.h" #include "amdgpu_dm_ism.h" - -/* - * Helper: allocate and zero-initialise a dc_stream_state for timing tests. - * Only the timing sub-struct is accessed by the functions under test. - */ -static struct dc_stream_state *alloc_test_stream(struct kunit *test) -{ - struct dc_stream_state *stream; - - stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, stream); - - return stream; -} +#include "amdgpu_dm_kunit_test_helpers.h" /* * Helper: allocate and zero-initialise an ISM instance. @@ -275,7 +262,7 @@ static void dm_test_ism_sso_delay_null_stream(struct kunit *test) static void dm_test_ism_sso_delay_zero_frames(struct kunit *test) { struct amdgpu_dm_ism *ism = alloc_test_ism(test); - struct dc_stream_state *stream = alloc_test_stream(test); + struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); stream->timing.v_total = 1125; stream->timing.h_total = 2200; @@ -288,7 +275,7 @@ static void dm_test_ism_sso_delay_zero_frames(struct kunit *test) static void dm_test_ism_sso_delay_1080p60_3frames(struct kunit *test) { struct amdgpu_dm_ism *ism = alloc_test_ism(test); - struct dc_stream_state *stream = alloc_test_stream(test); + struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); uint64_t expected_one_frame_ns, expected; /* @@ -311,7 +298,7 @@ static void dm_test_ism_sso_delay_1080p60_3frames(struct kunit *test) static void dm_test_ism_sso_delay_4k60_1frame(struct kunit *test) { struct amdgpu_dm_ism *ism = alloc_test_ism(test); - struct dc_stream_state *stream = alloc_test_stream(test); + struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); uint64_t expected_one_frame_ns; /* @@ -347,7 +334,7 @@ static void dm_test_ism_idle_delay_null_stream(struct kunit *test) static void dm_test_ism_idle_delay_zero_filter_frames(struct kunit *test) { struct amdgpu_dm_ism *ism = alloc_test_ism(test); - struct dc_stream_state *stream = alloc_test_stream(test); + struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); stream->timing.v_total = 1125; stream->timing.h_total = 2200; @@ -361,7 +348,7 @@ static void dm_test_ism_idle_delay_zero_filter_frames(struct kunit *test) static void dm_test_ism_idle_delay_zero_entry_count(struct kunit *test) { struct amdgpu_dm_ism *ism = alloc_test_ism(test); - struct dc_stream_state *stream = alloc_test_stream(test); + struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); stream->timing.v_total = 1125; stream->timing.h_total = 2200; @@ -376,7 +363,7 @@ static void dm_test_ism_idle_delay_zero_entry_count(struct kunit *test) static void dm_test_ism_idle_delay_zero_delay_frames(struct kunit *test) { struct amdgpu_dm_ism *ism = alloc_test_ism(test); - struct dc_stream_state *stream = alloc_test_stream(test); + struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); stream->timing.v_total = 1125; stream->timing.h_total = 2200; @@ -392,7 +379,7 @@ static void dm_test_ism_idle_delay_zero_delay_frames(struct kunit *test) static void dm_test_ism_idle_delay_no_short_idles(struct kunit *test) { struct amdgpu_dm_ism *ism = alloc_test_ism(test); - struct dc_stream_state *stream = alloc_test_stream(test); + struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); uint64_t one_frame_ns; /* @@ -426,7 +413,7 @@ static void dm_test_ism_idle_delay_no_short_idles(struct kunit *test) static void dm_test_ism_idle_delay_enough_short_idles(struct kunit *test) { struct amdgpu_dm_ism *ism = alloc_test_ism(test); - struct dc_stream_state *stream = alloc_test_stream(test); + struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); uint64_t one_frame_ns, expected; /* @@ -461,7 +448,7 @@ static void dm_test_ism_idle_delay_enough_short_idles(struct kunit *test) static void dm_test_ism_idle_delay_wraps_around_buffer(struct kunit *test) { struct amdgpu_dm_ism *ism = alloc_test_ism(test); - struct dc_stream_state *stream = alloc_test_stream(test); + struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); uint64_t one_frame_ns, expected; /* @@ -497,7 +484,7 @@ static void dm_test_ism_idle_delay_wraps_around_buffer(struct kunit *test) static void dm_test_ism_idle_delay_old_history_cutoff(struct kunit *test) { struct amdgpu_dm_ism *ism = alloc_test_ism(test); - struct dc_stream_state *stream = alloc_test_stream(test); + struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); uint64_t one_frame_ns; /* @@ -545,7 +532,7 @@ static void dm_test_ism_idle_delay_old_history_cutoff(struct kunit *test) static void dm_test_ism_idle_delay_mixed_durations(struct kunit *test) { struct amdgpu_dm_ism *ism = alloc_test_ism(test); - struct dc_stream_state *stream = alloc_test_stream(test); + struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); uint64_t one_frame_ns; /* @@ -586,7 +573,7 @@ static void dm_test_ism_idle_delay_mixed_durations(struct kunit *test) static void dm_test_ism_idle_delay_entry_count_exceeds_history_size(struct kunit *test) { struct amdgpu_dm_ism *ism = alloc_test_ism(test); - struct dc_stream_state *stream = alloc_test_stream(test); + struct dc_stream_state *stream = dm_kunit_alloc_stream(test, NULL); uint64_t one_frame_ns, expected; /* diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_helpers.c new file mode 100644 index 000000000000..58615cdbe854 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_helpers.c @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * KUnit test helpers for amdgpu_dm tests. + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include +#include +#include + +#include "dc.h" +#include "core_types.h" +#include "amdgpu.h" +#include "amdgpu_mode.h" +#include "amdgpu_dm.h" +#include "amdgpu_dm_kunit_test_helpers.h" + +struct amdgpu_device *dm_kunit_alloc_adev(struct kunit *test) +{ + struct drm_device *drm; + struct device *dev; + + dev = drm_kunit_helper_alloc_device(test); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + drm = __drm_kunit_helper_alloc_drm_device(test, dev, + sizeof(struct amdgpu_device), + offsetof(struct amdgpu_device, ddev), + DRIVER_MODESET | DRIVER_ATOMIC); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); + + return drm_to_adev(drm); +} +EXPORT_SYMBOL(dm_kunit_alloc_adev); + +struct dc_link *dm_kunit_alloc_link(struct kunit *test) +{ + struct dc_link *link; + + link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, link); + + return link; +} +EXPORT_SYMBOL(dm_kunit_alloc_link); + +struct dc_link *dm_kunit_alloc_link_with_ctx(struct kunit *test) +{ + struct dc_link *link; + struct dc_context *ctx; + struct dc *dc; + + link = dm_kunit_alloc_link(test); + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx); + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc); + + link->ctx = ctx; + ctx->dc = dc; + dc->ctx = ctx; + + return link; +} +EXPORT_SYMBOL(dm_kunit_alloc_link_with_ctx); + +struct amdgpu_display_manager *dm_kunit_alloc_dm(struct kunit *test) +{ + struct amdgpu_display_manager *dm; + struct dc *dc; + struct dc_state *state; + + dm = kunit_kzalloc(test, sizeof(*dm), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dm); + + dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, dc); + + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, state); + + dm->dc = dc; + dc->current_state = state; + + return dm; +} +EXPORT_SYMBOL(dm_kunit_alloc_dm); + +struct dc_stream_state *dm_kunit_alloc_stream(struct kunit *test, + struct dc_link *link) +{ + struct dc_stream_state *stream; + + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + stream->link = link; + kref_init(&stream->refcount); + + return stream; +} +EXPORT_SYMBOL(dm_kunit_alloc_stream); + +void dm_kunit_add_stream_to_state(struct kunit *test, struct dc_state *state, + unsigned int index, struct dc_link *link) +{ + struct dc_stream_state *stream; + + KUNIT_ASSERT_LT(test, index, (unsigned int)MAX_PIPES); + + stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, stream); + + stream->link = link; + state->streams[index] = stream; + if (state->stream_count <= index) + state->stream_count = index + 1; +} +EXPORT_SYMBOL(dm_kunit_add_stream_to_state); + +struct amdgpu_dm_connector *dm_kunit_alloc_connector(struct kunit *test, + struct amdgpu_device *adev, + struct dc_link *link) +{ + struct amdgpu_dm_connector *aconnector; + + aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, aconnector); + + if (adev) + aconnector->base.dev = &adev->ddev; + aconnector->dc_link = link; + + return aconnector; +} +EXPORT_SYMBOL(dm_kunit_alloc_connector); + +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("KUnit test helpers for amdgpu_dm tests"); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_test_helpers.h b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_test_helpers.h new file mode 100644 index 000000000000..0f1c48fa2128 --- /dev/null +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_kunit_test_helpers.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0 OR MIT */ +/* + * KUnit test helpers for amdgpu_dm tests. + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#ifndef AMDGPU_DM_KUNIT_TEST_HELPERS_H +#define AMDGPU_DM_KUNIT_TEST_HELPERS_H + +#include + +struct amdgpu_device; +struct amdgpu_display_manager; +struct amdgpu_dm_connector; +struct dc_link; +struct dc_state; +struct dc_stream_state; + +struct amdgpu_device *dm_kunit_alloc_adev(struct kunit *test); +struct dc_link *dm_kunit_alloc_link(struct kunit *test); +struct dc_link *dm_kunit_alloc_link_with_ctx(struct kunit *test); +struct amdgpu_display_manager *dm_kunit_alloc_dm(struct kunit *test); +struct dc_stream_state *dm_kunit_alloc_stream(struct kunit *test, + struct dc_link *link); +void dm_kunit_add_stream_to_state(struct kunit *test, struct dc_state *state, + unsigned int index, struct dc_link *link); +struct amdgpu_dm_connector *dm_kunit_alloc_connector(struct kunit *test, + struct amdgpu_device *adev, + struct dc_link *link); + +#endif /* AMDGPU_DM_KUNIT_TEST_HELPERS_H */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c index d40ed83d8685..a6b4df091e8e 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c @@ -20,6 +20,7 @@ #include "amdgpu_mode.h" #include "amdgpu_dm.h" #include "amdgpu_dm_mst_types.h" +#include "amdgpu_dm_kunit_test_helpers.h" #include "inc/link_service.h" /* @@ -914,18 +915,10 @@ static void dm_mst_test_create_fake_mst_encoders(struct kunit *test) { struct amdgpu_device *adev; struct drm_device *drm; - struct device *dev; int i; - dev = drm_kunit_helper_alloc_device(test); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); - - drm = __drm_kunit_helper_alloc_drm_device(test, dev, - sizeof(*adev), - offsetof(struct amdgpu_device, ddev), - DRIVER_MODESET | DRIVER_ATOMIC); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); - adev = drm_to_adev(drm); + adev = dm_kunit_alloc_adev(test); + drm = &adev->ddev; adev->dm.display_indexes_num = 3; adev->mode_info.num_crtc = 3; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_psr_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_psr_test.c index 2dd870f650db..09bd98e93047 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_psr_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_psr_test.c @@ -12,79 +12,17 @@ #include "amdgpu_mode.h" #include "amdgpu_dm.h" #include "amdgpu_dm_psr.h" +#include "amdgpu_dm_kunit_test_helpers.h" #include "power_helpers.h" -/* - * Helper: allocate and zero-initialise a dc_link sufficient for - * amdgpu_dm_psr_fill_caps() testing. The function only accesses - * embedded members (dpcd_caps, psr_settings) so no pointer fields - * need to be wired up. - */ -static struct dc_link *alloc_test_link(struct kunit *test) -{ - struct dc_link *link; - - link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, link); - - return link; -} - -/* - * Helper: allocate and wire the minimal DM/DC state needed for - * amdgpu_dm_psr_is_active_allowed() testing. - */ -static struct amdgpu_display_manager *alloc_test_dm(struct kunit *test) -{ - struct amdgpu_display_manager *dm; - struct dc *dc; - struct dc_state *state; - - dm = kunit_kzalloc(test, sizeof(*dm), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, dm); - - dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, dc); - - state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, state); - - dm->dc = dc; - dc->current_state = state; - - return dm; -} - -static void add_test_stream(struct kunit *test, struct dc_state *state, - unsigned int index, struct dc_link *link) -{ - struct dc_stream_state *stream; - - KUNIT_ASSERT_LT(test, index, (unsigned int)MAX_PIPES); - - stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, stream); - - stream->link = link; - state->streams[index] = stream; - if (state->stream_count <= index) - state->stream_count = index + 1; -} - static struct dc_stream_state *alloc_test_psr_stream(struct kunit *test) { - struct dc_stream_state *stream; struct dc_link *link; - stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, stream); - - link = alloc_test_link(test); + link = dm_kunit_alloc_link(test); link->psr_settings.psr_feature_enabled = true; - stream->link = link; - kref_init(&stream->refcount); - return stream; + return dm_kunit_alloc_stream(test, link); } static struct core_power *create_test_power_module(struct kunit *test, @@ -108,7 +46,7 @@ static struct core_power *create_test_power_module(struct kunit *test, static struct dc_link *alloc_test_psrsu_link(struct kunit *test) { - struct dc_link *link = alloc_test_link(test); + struct dc_link *link = dm_kunit_alloc_link(test); struct dc_context *ctx; struct dc *dc; @@ -359,7 +297,7 @@ static void dm_test_set_psr_caps_no_dpcd_psr(struct kunit *test) static void dm_test_set_psr_caps_edp1_disabled(struct kunit *test) { struct dc_link *link = alloc_test_psr_caps_link(test); - struct dc_link *edp0 = alloc_test_link(test); + struct dc_link *edp0 = dm_kunit_alloc_link(test); struct amdgpu_dm_connector *aconnector = alloc_test_aconnector(test); struct dc *dc = link->ctx->dc; @@ -393,7 +331,7 @@ static void dm_test_set_psr_caps_success_psr1(struct kunit *test) static void dm_test_psr_fill_caps_version_1(struct kunit *test) { - struct dc_link *link = alloc_test_link(test); + struct dc_link *link = dm_kunit_alloc_link(test); struct psr_caps caps; memset(&caps, 0, sizeof(caps)); @@ -406,7 +344,7 @@ static void dm_test_psr_fill_caps_version_1(struct kunit *test) static void dm_test_psr_fill_caps_version_su1(struct kunit *test) { - struct dc_link *link = alloc_test_link(test); + struct dc_link *link = dm_kunit_alloc_link(test); struct psr_caps caps; memset(&caps, 0, sizeof(caps)); @@ -419,7 +357,7 @@ static void dm_test_psr_fill_caps_version_su1(struct kunit *test) static void dm_test_psr_fill_caps_version_unsupported(struct kunit *test) { - struct dc_link *link = alloc_test_link(test); + struct dc_link *link = dm_kunit_alloc_link(test); struct psr_caps caps; memset(&caps, 0, sizeof(caps)); @@ -438,7 +376,7 @@ static void dm_test_psr_fill_caps_version_unsupported(struct kunit *test) static void dm_test_psr_fill_caps_setup_time_zero(struct kunit *test) { - struct dc_link *link = alloc_test_link(test); + struct dc_link *link = dm_kunit_alloc_link(test); struct psr_caps caps; memset(&caps, 0, sizeof(caps)); @@ -452,7 +390,7 @@ static void dm_test_psr_fill_caps_setup_time_zero(struct kunit *test) static void dm_test_psr_fill_caps_setup_time_mid(struct kunit *test) { - struct dc_link *link = alloc_test_link(test); + struct dc_link *link = dm_kunit_alloc_link(test); struct psr_caps caps; memset(&caps, 0, sizeof(caps)); @@ -466,7 +404,7 @@ static void dm_test_psr_fill_caps_setup_time_mid(struct kunit *test) static void dm_test_psr_fill_caps_setup_time_max(struct kunit *test) { - struct dc_link *link = alloc_test_link(test); + struct dc_link *link = dm_kunit_alloc_link(test); struct psr_caps caps; memset(&caps, 0, sizeof(caps)); @@ -482,7 +420,7 @@ static void dm_test_psr_fill_caps_setup_time_max(struct kunit *test) static void dm_test_psr_fill_caps_link_training_required(struct kunit *test) { - struct dc_link *link = alloc_test_link(test); + struct dc_link *link = dm_kunit_alloc_link(test); struct psr_caps caps; memset(&caps, 0, sizeof(caps)); @@ -495,7 +433,7 @@ static void dm_test_psr_fill_caps_link_training_required(struct kunit *test) static void dm_test_psr_fill_caps_link_training_not_required(struct kunit *test) { - struct dc_link *link = alloc_test_link(test); + struct dc_link *link = dm_kunit_alloc_link(test); struct psr_caps caps; memset(&caps, 0, sizeof(caps)); @@ -510,7 +448,7 @@ static void dm_test_psr_fill_caps_link_training_not_required(struct kunit *test) static void dm_test_psr_fill_caps_dpcd_fields(struct kunit *test) { - struct dc_link *link = alloc_test_link(test); + struct dc_link *link = dm_kunit_alloc_link(test); struct psr_caps caps; memset(&caps, 0, sizeof(caps)); @@ -536,7 +474,7 @@ static void dm_test_psr_fill_caps_dpcd_fields(struct kunit *test) static void dm_test_psr_fill_caps_dpcd_fields_unset(struct kunit *test) { - struct dc_link *link = alloc_test_link(test); + struct dc_link *link = dm_kunit_alloc_link(test); struct psr_caps caps; memset(&caps, 0xFF, sizeof(caps)); @@ -557,7 +495,7 @@ static void dm_test_psr_fill_caps_dpcd_fields_unset(struct kunit *test) static void dm_test_psr_fill_caps_rate_control_always_zero(struct kunit *test) { - struct dc_link *link = alloc_test_link(test); + struct dc_link *link = dm_kunit_alloc_link(test); struct psr_caps caps; /* Pre-fill caps with non-zero to verify overwrite */ @@ -570,7 +508,7 @@ static void dm_test_psr_fill_caps_rate_control_always_zero(struct kunit *test) static void dm_test_psr_fill_caps_power_opts_z10_always_set(struct kunit *test) { - struct dc_link *link = alloc_test_link(test); + struct dc_link *link = dm_kunit_alloc_link(test); struct psr_caps caps; memset(&caps, 0, sizeof(caps)); @@ -588,7 +526,7 @@ static void dm_test_psr_fill_caps_power_opts_z10_always_set(struct kunit *test) static void dm_test_psr_fill_caps_power_opts_smu_opt_set(struct kunit *test) { - struct dc_link *link = alloc_test_link(test); + struct dc_link *link = dm_kunit_alloc_link(test); struct psr_caps caps; unsigned int old_feature_mask; @@ -647,7 +585,7 @@ static void dm_test_psr_set_event_psr_not_enabled(struct kunit *test) */ static void dm_test_psr_set_event_get_event_fails(struct kunit *test) { - struct amdgpu_display_manager *dm = alloc_test_dm(test); + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); struct dc_stream_state *stream = alloc_test_psr_stream(test); dm->power_module = NULL; @@ -661,7 +599,7 @@ static void dm_test_psr_set_event_get_event_fails(struct kunit *test) */ static void dm_test_psr_set_event_already_set(struct kunit *test) { - struct amdgpu_display_manager *dm = alloc_test_dm(test); + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); struct dc_stream_state *stream = alloc_test_psr_stream(test); struct psr_caps caps = {0}; struct core_power *core_power; @@ -682,7 +620,7 @@ static void dm_test_psr_set_event_already_set(struct kunit *test) */ static void dm_test_psr_set_event_updates_event(struct kunit *test) { - struct amdgpu_display_manager *dm = alloc_test_dm(test); + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); struct dc_stream_state *stream = alloc_test_psr_stream(test); struct psr_caps caps = {0}; struct core_power *core_power; @@ -706,7 +644,7 @@ static void dm_test_psr_set_event_updates_event(struct kunit *test) */ static void dm_test_psr_is_active_allowed_no_streams(struct kunit *test) { - struct amdgpu_display_manager *dm = alloc_test_dm(test); + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); KUNIT_EXPECT_FALSE(test, amdgpu_dm_psr_is_active_allowed(dm)); } @@ -717,10 +655,10 @@ static void dm_test_psr_is_active_allowed_no_streams(struct kunit *test) */ static void dm_test_psr_is_active_allowed_null_link(struct kunit *test) { - struct amdgpu_display_manager *dm = alloc_test_dm(test); + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); struct dc_state *state = dm->dc->current_state; - add_test_stream(test, state, 0, NULL); + dm_kunit_add_stream_to_state(test, state, 0, NULL); KUNIT_EXPECT_FALSE(test, amdgpu_dm_psr_is_active_allowed(dm)); } @@ -732,11 +670,11 @@ static void dm_test_psr_is_active_allowed_null_link(struct kunit *test) */ static void dm_test_psr_is_active_allowed_requires_enabled_and_allowed(struct kunit *test) { - struct amdgpu_display_manager *dm = alloc_test_dm(test); + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); struct dc_state *state = dm->dc->current_state; - struct dc_link *link = alloc_test_link(test); + struct dc_link *link = dm_kunit_alloc_link(test); - add_test_stream(test, state, 0, link); + dm_kunit_add_stream_to_state(test, state, 0, link); link->psr_settings.psr_allow_active = true; KUNIT_EXPECT_FALSE(test, amdgpu_dm_psr_is_active_allowed(dm)); @@ -752,17 +690,17 @@ static void dm_test_psr_is_active_allowed_requires_enabled_and_allowed(struct ku */ static void dm_test_psr_is_active_allowed_any_stream(struct kunit *test) { - struct amdgpu_display_manager *dm = alloc_test_dm(test); + struct amdgpu_display_manager *dm = dm_kunit_alloc_dm(test); struct dc_state *state = dm->dc->current_state; - struct dc_link *disabled_link = alloc_test_link(test); - struct dc_link *allowed_link = alloc_test_link(test); + struct dc_link *disabled_link = dm_kunit_alloc_link(test); + struct dc_link *allowed_link = dm_kunit_alloc_link(test); disabled_link->psr_settings.psr_allow_active = true; allowed_link->psr_settings.psr_feature_enabled = true; allowed_link->psr_settings.psr_allow_active = true; - add_test_stream(test, state, 0, disabled_link); - add_test_stream(test, state, 1, allowed_link); + dm_kunit_add_stream_to_state(test, state, 0, disabled_link); + dm_kunit_add_stream_to_state(test, state, 1, allowed_link); KUNIT_EXPECT_TRUE(test, amdgpu_dm_psr_is_active_allowed(dm)); } diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_replay_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_replay_test.c index 68f2f4d70407..6f633b1bbaca 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_replay_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_replay_test.c @@ -12,6 +12,7 @@ #include "amdgpu_mode.h" #include "amdgpu_dm.h" #include "amdgpu_dm_replay.h" +#include "amdgpu_dm_kunit_test_helpers.h" #include "modules/power/power_helpers.h" #include "dmub/dmub_srv.h" @@ -35,8 +36,9 @@ static struct replay_test_ctx *alloc_replay_ctx(struct kunit *test) ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, ctx); - ctx->link = kunit_kzalloc(test, sizeof(*ctx->link), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, ctx->link); + ctx->link = dm_kunit_alloc_link_with_ctx(test); + ctx->dc_ctx = ctx->link->ctx; + ctx->dc = ctx->dc_ctx->dc; ctx->aconnector = kunit_kzalloc(test, sizeof(*ctx->aconnector), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, ctx->aconnector); @@ -44,21 +46,10 @@ static struct replay_test_ctx *alloc_replay_ctx(struct kunit *test) ctx->dm_state = kunit_kzalloc(test, sizeof(*ctx->dm_state), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, ctx->dm_state); - ctx->dc = kunit_kzalloc(test, sizeof(*ctx->dc), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, ctx->dc); - - ctx->dc_ctx = kunit_kzalloc(test, sizeof(*ctx->dc_ctx), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, ctx->dc_ctx); - - ctx->stream = kunit_kzalloc(test, sizeof(*ctx->stream), GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, ctx->stream); + ctx->stream = dm_kunit_alloc_stream(test, ctx->link); /* Wire connector state so to_dm_connector_state() works */ ctx->aconnector->base.state = &ctx->dm_state->base; - ctx->link->ctx = ctx->dc_ctx; - ctx->dc_ctx->dc = ctx->dc; - ctx->dc->ctx = ctx->dc_ctx; - ctx->stream->link = ctx->link; return ctx; } diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_wb_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_wb_test.c index f9a839c10bf4..c71f61a2438d 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_wb_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_wb_test.c @@ -20,6 +20,7 @@ #include "amdgpu.h" #include "amdgpu_dm.h" #include "amdgpu_dm_wb.h" +#include "amdgpu_dm_kunit_test_helpers.h" /* Helper functions */ @@ -71,22 +72,7 @@ static struct drm_connector_state *alloc_test_conn_state(struct kunit *test, return conn_state; } -static struct amdgpu_device *alloc_test_adev(struct kunit *test) -{ - struct drm_device *drm; - struct device *dev; - - dev = drm_kunit_helper_alloc_device(test); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); - drm = __drm_kunit_helper_alloc_drm_device(test, dev, - sizeof(struct amdgpu_device), - offsetof(struct amdgpu_device, ddev), - DRIVER_MODESET | DRIVER_ATOMIC); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, drm); - - return drm_to_adev(drm); -} /* Tests for amdgpu_dm_wb_encoder_atomic_check */ @@ -350,7 +336,7 @@ static void dm_test_wb_connector_init_success(struct kunit *test) struct dc *dc; int ret; - adev = alloc_test_adev(test); + adev = dm_kunit_alloc_adev(test); adev->mode_info.num_crtc = 1; dm = &adev->dm; dm->adev = adev; -- cgit v1.2.3 From 2e9e7234f16d09fb075a1742d8d11271a9b98b48 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Mon, 22 Jun 2026 21:11:18 -0600 Subject: drm/amd/amdgpu: Fix stack frame size warnings in KUnit tests [WHAT] Replace stack-allocated large structs with kunit_kzalloc() in KUnit test functions that exceed the kernel 1280-byte stack frame limit. Also add CONFIG_FRAME_WARN=1024 to .kunitconfig to enforce the limit. Affected structs and files: - struct dc_link in amdgpu_dm_connector_test.c and amdgpu_dm_mst_types_test.c - struct drm_plane, drm_plane_state, drm_framebuffer in amdgpu_dm_plane_test.c - struct drm_connector_state, drm_atomic_state in amdgpu_dm_mst_types_test.c - struct dm_connector_state in amdgpu_dm_test.c Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202606230825.9qMV9L0g-lkp@intel.com/ Assisted-by: Copilot:Claude-Opus-4.6 Acked-by: George Zhang Signed-off-by: Alex Hung Signed-off-by: Alex Deucher --- .../drm/amd/display/amdgpu_dm/tests/.kunitconfig | 3 + .../amdgpu_dm/tests/amdgpu_dm_connector_test.c | 64 ++++++++----- .../amdgpu_dm/tests/amdgpu_dm_mst_types_test.c | 104 ++++++++++++--------- .../display/amdgpu_dm/tests/amdgpu_dm_plane_test.c | 99 ++++++++++++-------- .../amd/display/amdgpu_dm/tests/amdgpu_dm_test.c | 72 ++++++++------ 5 files changed, 208 insertions(+), 134 deletions(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/.kunitconfig b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/.kunitconfig index 1e93bd8b44ce..c7c8527dbb10 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/.kunitconfig +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/.kunitconfig @@ -15,6 +15,9 @@ CONFIG_I2C=y CONFIG_POWER_SUPPLY=y CONFIG_CRC16=y +# Limit stack size to 1280 +CONFIG_FRAME_WARN=1280 + # Treat warnings as errors CONFIG_WERROR=y diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c index 34e40d2a9d2c..aa451064b30c 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c @@ -29,10 +29,12 @@ */ static void dm_test_subconnector_type_none(struct kunit *test) { - struct dc_link link = {}; + struct dc_link *link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); - link.dpcd_caps.dongle_type = DISPLAY_DONGLE_NONE; - KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(&link), (int)DRM_MODE_SUBCONNECTOR_Native); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + + link->dpcd_caps.dongle_type = DISPLAY_DONGLE_NONE; + KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(link), (int)DRM_MODE_SUBCONNECTOR_Native); } /** @@ -41,10 +43,12 @@ static void dm_test_subconnector_type_none(struct kunit *test) */ static void dm_test_subconnector_type_vga(struct kunit *test) { - struct dc_link link = {}; + struct dc_link *link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); - link.dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_VGA_CONVERTER; - KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(&link), (int)DRM_MODE_SUBCONNECTOR_VGA); + link->dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_VGA_CONVERTER; + KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(link), (int)DRM_MODE_SUBCONNECTOR_VGA); } /** @@ -53,10 +57,12 @@ static void dm_test_subconnector_type_vga(struct kunit *test) */ static void dm_test_subconnector_type_dvi_converter(struct kunit *test) { - struct dc_link link = {}; + struct dc_link *link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); - link.dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_DVI_CONVERTER; - KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(&link), (int)DRM_MODE_SUBCONNECTOR_DVID); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + + link->dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_DVI_CONVERTER; + KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(link), (int)DRM_MODE_SUBCONNECTOR_DVID); } /** @@ -65,10 +71,12 @@ static void dm_test_subconnector_type_dvi_converter(struct kunit *test) */ static void dm_test_subconnector_type_dvi_dongle(struct kunit *test) { - struct dc_link link = {}; + struct dc_link *link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); - link.dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_DVI_DONGLE; - KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(&link), (int)DRM_MODE_SUBCONNECTOR_DVID); + link->dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_DVI_DONGLE; + KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(link), (int)DRM_MODE_SUBCONNECTOR_DVID); } /** @@ -77,10 +85,12 @@ static void dm_test_subconnector_type_dvi_dongle(struct kunit *test) */ static void dm_test_subconnector_type_hdmi_converter(struct kunit *test) { - struct dc_link link = {}; + struct dc_link *link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); - link.dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_HDMI_CONVERTER; - KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(&link), (int)DRM_MODE_SUBCONNECTOR_HDMIA); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + + link->dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_HDMI_CONVERTER; + KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(link), (int)DRM_MODE_SUBCONNECTOR_HDMIA); } /** @@ -89,10 +99,12 @@ static void dm_test_subconnector_type_hdmi_converter(struct kunit *test) */ static void dm_test_subconnector_type_hdmi_dongle(struct kunit *test) { - struct dc_link link = {}; + struct dc_link *link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); - link.dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_HDMI_DONGLE; - KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(&link), (int)DRM_MODE_SUBCONNECTOR_HDMIA); + link->dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_HDMI_DONGLE; + KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(link), (int)DRM_MODE_SUBCONNECTOR_HDMIA); } /** @@ -101,10 +113,12 @@ static void dm_test_subconnector_type_hdmi_dongle(struct kunit *test) */ static void dm_test_subconnector_type_mismatched(struct kunit *test) { - struct dc_link link = {}; + struct dc_link *link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); - link.dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_HDMI_MISMATCHED_DONGLE; - KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(&link), (int)DRM_MODE_SUBCONNECTOR_Unknown); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + + link->dpcd_caps.dongle_type = DISPLAY_DONGLE_DP_HDMI_MISMATCHED_DONGLE; + KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(link), (int)DRM_MODE_SUBCONNECTOR_Unknown); } /** @@ -113,10 +127,12 @@ static void dm_test_subconnector_type_mismatched(struct kunit *test) */ static void dm_test_subconnector_type_default_unknown(struct kunit *test) { - struct dc_link link = {}; + struct dc_link *link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); - link.dpcd_caps.dongle_type = (typeof(link.dpcd_caps.dongle_type))0x7f; - KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(&link), (int)DRM_MODE_SUBCONNECTOR_Unknown); + link->dpcd_caps.dongle_type = (typeof(link->dpcd_caps.dongle_type))0x7f; + KUNIT_EXPECT_EQ(test, (int)get_subconnector_type(link), (int)DRM_MODE_SUBCONNECTOR_Unknown); } /* Tests for get_output_content_type() */ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c index a6b4df091e8e..3a663ee0ca2b 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_mst_types_test.c @@ -129,13 +129,15 @@ static ssize_t dm_mst_test_desc_aux_transfer(struct drm_dp_aux *aux, */ static void dm_mst_test_needs_dsc_aux_workaround_match(struct kunit *test) { - struct dc_link link = {0}; + struct dc_link *link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); - link.dpcd_caps.branch_dev_id = DP_BRANCH_DEVICE_ID_90CC24; - link.dpcd_caps.dpcd_rev.raw = DPCD_REV_14; - link.dpcd_caps.sink_count.bits.SINK_COUNT = 2; + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); - KUNIT_EXPECT_TRUE(test, needs_dsc_aux_workaround(&link)); + link->dpcd_caps.branch_dev_id = DP_BRANCH_DEVICE_ID_90CC24; + link->dpcd_caps.dpcd_rev.raw = DPCD_REV_14; + link->dpcd_caps.sink_count.bits.SINK_COUNT = 2; + + KUNIT_EXPECT_TRUE(test, needs_dsc_aux_workaround(link)); } /** @@ -147,13 +149,15 @@ static void dm_mst_test_needs_dsc_aux_workaround_match(struct kunit *test) */ static void dm_mst_test_needs_dsc_aux_workaround_rev12(struct kunit *test) { - struct dc_link link = {0}; + struct dc_link *link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); - link.dpcd_caps.branch_dev_id = DP_BRANCH_DEVICE_ID_90CC24; - link.dpcd_caps.dpcd_rev.raw = DPCD_REV_12; - link.dpcd_caps.sink_count.bits.SINK_COUNT = 3; + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); - KUNIT_EXPECT_TRUE(test, needs_dsc_aux_workaround(&link)); + link->dpcd_caps.branch_dev_id = DP_BRANCH_DEVICE_ID_90CC24; + link->dpcd_caps.dpcd_rev.raw = DPCD_REV_12; + link->dpcd_caps.sink_count.bits.SINK_COUNT = 3; + + KUNIT_EXPECT_TRUE(test, needs_dsc_aux_workaround(link)); } /** @@ -165,13 +169,15 @@ static void dm_mst_test_needs_dsc_aux_workaround_rev12(struct kunit *test) */ static void dm_mst_test_needs_dsc_aux_workaround_wrong_dev_id(struct kunit *test) { - struct dc_link link = {0}; + struct dc_link *link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); - link.dpcd_caps.branch_dev_id = 0x123456; - link.dpcd_caps.dpcd_rev.raw = DPCD_REV_14; - link.dpcd_caps.sink_count.bits.SINK_COUNT = 2; + link->dpcd_caps.branch_dev_id = 0x123456; + link->dpcd_caps.dpcd_rev.raw = DPCD_REV_14; + link->dpcd_caps.sink_count.bits.SINK_COUNT = 2; - KUNIT_EXPECT_FALSE(test, needs_dsc_aux_workaround(&link)); + KUNIT_EXPECT_FALSE(test, needs_dsc_aux_workaround(link)); } /** @@ -183,13 +189,15 @@ static void dm_mst_test_needs_dsc_aux_workaround_wrong_dev_id(struct kunit *test */ static void dm_mst_test_needs_dsc_aux_workaround_wrong_rev(struct kunit *test) { - struct dc_link link = {0}; + struct dc_link *link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); - link.dpcd_caps.branch_dev_id = DP_BRANCH_DEVICE_ID_90CC24; - link.dpcd_caps.dpcd_rev.raw = 0x11; /* DPCD 1.1 */ - link.dpcd_caps.sink_count.bits.SINK_COUNT = 2; + link->dpcd_caps.branch_dev_id = DP_BRANCH_DEVICE_ID_90CC24; + link->dpcd_caps.dpcd_rev.raw = 0x11; /* DPCD 1.1 */ + link->dpcd_caps.sink_count.bits.SINK_COUNT = 2; - KUNIT_EXPECT_FALSE(test, needs_dsc_aux_workaround(&link)); + KUNIT_EXPECT_FALSE(test, needs_dsc_aux_workaround(link)); } /** @@ -201,13 +209,15 @@ static void dm_mst_test_needs_dsc_aux_workaround_wrong_rev(struct kunit *test) */ static void dm_mst_test_needs_dsc_aux_workaround_low_sink_count(struct kunit *test) { - struct dc_link link = {0}; + struct dc_link *link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); - link.dpcd_caps.branch_dev_id = DP_BRANCH_DEVICE_ID_90CC24; - link.dpcd_caps.dpcd_rev.raw = DPCD_REV_14; - link.dpcd_caps.sink_count.bits.SINK_COUNT = 1; + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); + + link->dpcd_caps.branch_dev_id = DP_BRANCH_DEVICE_ID_90CC24; + link->dpcd_caps.dpcd_rev.raw = DPCD_REV_14; + link->dpcd_caps.sink_count.bits.SINK_COUNT = 1; - KUNIT_EXPECT_FALSE(test, needs_dsc_aux_workaround(&link)); + KUNIT_EXPECT_FALSE(test, needs_dsc_aux_workaround(link)); } /** @@ -219,13 +229,15 @@ static void dm_mst_test_needs_dsc_aux_workaround_low_sink_count(struct kunit *te */ static void dm_mst_test_needs_dsc_aux_workaround_zero_sink_count(struct kunit *test) { - struct dc_link link = {0}; + struct dc_link *link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL); - link.dpcd_caps.branch_dev_id = DP_BRANCH_DEVICE_ID_90CC24; - link.dpcd_caps.dpcd_rev.raw = DPCD_REV_14; - link.dpcd_caps.sink_count.bits.SINK_COUNT = 0; + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link); - KUNIT_EXPECT_FALSE(test, needs_dsc_aux_workaround(&link)); + link->dpcd_caps.branch_dev_id = DP_BRANCH_DEVICE_ID_90CC24; + link->dpcd_caps.dpcd_rev.raw = DPCD_REV_14; + link->dpcd_caps.sink_count.bits.SINK_COUNT = 0; + + KUNIT_EXPECT_FALSE(test, needs_dsc_aux_workaround(link)); } /* Tests for dm_mst_get_pbn_divider */ @@ -943,17 +955,23 @@ static void dm_mst_test_create_fake_mst_encoders(struct kunit *test) */ static void dm_mst_test_atomic_check_no_old_crtc(struct kunit *test) { - struct drm_connector_state old_conn_state = { 0 }; - struct drm_connector_state new_conn_state = { 0 }; - struct drm_atomic_commit state = { 0 }; + struct drm_connector_state *old_conn_state; + struct drm_connector_state *new_conn_state; + struct drm_atomic_commit *state; struct amdgpu_dm_connector *aconnector; struct amdgpu_dm_connector *root; struct drm_dp_mst_port *port; unsigned int connector_index = 2; + old_conn_state = kunit_kzalloc(test, sizeof(*old_conn_state), GFP_KERNEL); + new_conn_state = kunit_kzalloc(test, sizeof(*new_conn_state), GFP_KERNEL); + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); aconnector = kunit_kzalloc(test, sizeof(*aconnector), GFP_KERNEL); root = kunit_kzalloc(test, sizeof(*root), GFP_KERNEL); port = kunit_kzalloc(test, sizeof(*port), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, old_conn_state); + KUNIT_ASSERT_NOT_NULL(test, new_conn_state); + KUNIT_ASSERT_NOT_NULL(test, state); KUNIT_ASSERT_NOT_NULL(test, aconnector); KUNIT_ASSERT_NOT_NULL(test, root); KUNIT_ASSERT_NOT_NULL(test, port); @@ -962,18 +980,18 @@ static void dm_mst_test_atomic_check_no_old_crtc(struct kunit *test) aconnector->mst_root = root; aconnector->mst_output_port = port; port->connector = &aconnector->base; - old_conn_state.connector = &aconnector->base; - new_conn_state.connector = &aconnector->base; - state.num_connector = connector_index + 1; - state.connectors = kunit_kzalloc(test, - sizeof(*state.connectors) * state.num_connector, + old_conn_state->connector = &aconnector->base; + new_conn_state->connector = &aconnector->base; + state->num_connector = connector_index + 1; + state->connectors = kunit_kzalloc(test, + sizeof(*state->connectors) * state->num_connector, GFP_KERNEL); - KUNIT_ASSERT_NOT_NULL(test, state.connectors); - state.connectors[connector_index].ptr = &aconnector->base; - state.connectors[connector_index].old_state = &old_conn_state; - state.connectors[connector_index].new_state = &new_conn_state; + KUNIT_ASSERT_NOT_NULL(test, state->connectors); + state->connectors[connector_index].ptr = &aconnector->base; + state->connectors[connector_index].old_state = old_conn_state; + state->connectors[connector_index].new_state = new_conn_state; - KUNIT_EXPECT_EQ(test, dm_dp_mst_atomic_check(&aconnector->base, &state), 0); + KUNIT_EXPECT_EQ(test, dm_dp_mst_atomic_check(&aconnector->base, state), 0); } /** diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c index 071c28abaa8a..46c9af432e37 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c @@ -185,22 +185,26 @@ static void dm_test_fill_blending_coverage_alpha_format(struct kunit *test) static void dm_test_fill_blending_global_alpha(struct kunit *test) { struct amdgpu_device *adev; - struct drm_plane plane = {0}; - struct drm_plane_state state = { 0 }; + struct drm_plane *plane; + struct drm_plane_state *state; bool per_pixel_alpha; bool pre_multiplied_alpha; bool global_alpha; int global_alpha_value; adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL); + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, plane); + KUNIT_ASSERT_NOT_NULL(test, state); - plane.dev = &adev->ddev; - state.plane = &plane; - state.pixel_blend_mode = DRM_MODE_BLEND_PIXEL_NONE; - state.alpha = 0x8000; + plane->dev = &adev->ddev; + state->plane = plane; + state->pixel_blend_mode = DRM_MODE_BLEND_PIXEL_NONE; + state->alpha = 0x8000; - amdgpu_dm_plane_fill_blending_from_plane_state(&state, + amdgpu_dm_plane_fill_blending_from_plane_state(state, &per_pixel_alpha, &pre_multiplied_alpha, &global_alpha, @@ -250,23 +254,28 @@ static void dm_test_modifier_gfx9_swizzle_mode(struct kunit *test) */ static void dm_test_get_plane_formats(struct kunit *test) { - struct drm_plane plane = {0}; - struct dc_plane_cap cap = {0}; + struct drm_plane *plane; + struct dc_plane_cap *cap; uint32_t formats[32] = {0}; - plane.type = DRM_PLANE_TYPE_PRIMARY; - KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_get_plane_formats(&plane, NULL, formats, 32), 14); + plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL); + cap = kunit_kzalloc(test, sizeof(*cap), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, plane); + KUNIT_ASSERT_NOT_NULL(test, cap); + + plane->type = DRM_PLANE_TYPE_PRIMARY; + KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_get_plane_formats(plane, NULL, formats, 32), 14); - cap.pixel_format_support.nv12 = true; - cap.pixel_format_support.p010 = true; - cap.pixel_format_support.fp16 = true; - KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_get_plane_formats(&plane, &cap, formats, 32), 20); + cap->pixel_format_support.nv12 = true; + cap->pixel_format_support.p010 = true; + cap->pixel_format_support.fp16 = true; + KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_get_plane_formats(plane, cap, formats, 32), 20); - plane.type = DRM_PLANE_TYPE_OVERLAY; - KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_get_plane_formats(&plane, NULL, formats, 32), 9); + plane->type = DRM_PLANE_TYPE_OVERLAY; + KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_get_plane_formats(plane, NULL, formats, 32), 9); - plane.type = DRM_PLANE_TYPE_CURSOR; - KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_get_plane_formats(&plane, NULL, formats, 32), 1); + plane->type = DRM_PLANE_TYPE_CURSOR; + KUNIT_EXPECT_EQ(test, amdgpu_dm_plane_get_plane_formats(plane, NULL, formats, 32), 1); } /** @@ -433,30 +442,36 @@ static void dm_test_get_cursor_position(struct kunit *test) { struct amdgpu_device *adev; struct amdgpu_crtc *amdgpu_crtc; - struct drm_plane plane = {0}; - struct drm_plane_state state = {0}; - struct drm_framebuffer fb = {0}; + struct drm_plane *plane; + struct drm_plane_state *state; + struct drm_framebuffer *fb; struct dc_cursor_position position = {0}; adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); amdgpu_crtc = kunit_kzalloc(test, sizeof(*amdgpu_crtc), GFP_KERNEL); + plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL); + state = kunit_kzalloc(test, sizeof(*state), GFP_KERNEL); + fb = kunit_kzalloc(test, sizeof(*fb), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, adev); KUNIT_ASSERT_NOT_NULL(test, amdgpu_crtc); + KUNIT_ASSERT_NOT_NULL(test, plane); + KUNIT_ASSERT_NOT_NULL(test, state); + KUNIT_ASSERT_NOT_NULL(test, fb); adev->ip_versions[DCE_HWIP][0] = IP_VERSION(4, 0, 0); amdgpu_crtc->max_cursor_width = 64; amdgpu_crtc->max_cursor_height = 64; - plane.dev = &adev->ddev; - plane.state = &state; - state.fb = &fb; - state.crtc_x = -5; - state.crtc_y = -7; - state.crtc_w = 32; - state.crtc_h = 32; + plane->dev = &adev->ddev; + plane->state = state; + state->fb = fb; + state->crtc_x = -5; + state->crtc_y = -7; + state->crtc_w = 32; + state->crtc_h = 32; KUNIT_ASSERT_EQ(test, - amdgpu_dm_plane_get_cursor_position(&plane, &amdgpu_crtc->base, &position), + amdgpu_dm_plane_get_cursor_position(plane, &amdgpu_crtc->base, &position), 0); KUNIT_EXPECT_TRUE(test, position.enable); KUNIT_EXPECT_EQ(test, position.x, 0); @@ -466,10 +481,10 @@ static void dm_test_get_cursor_position(struct kunit *test) KUNIT_EXPECT_TRUE(test, position.translate_by_source); memset(&position, 0, sizeof(position)); - state.crtc_x = -64; - state.crtc_y = 0; + state->crtc_x = -64; + state->crtc_y = 0; KUNIT_ASSERT_EQ(test, - amdgpu_dm_plane_get_cursor_position(&plane, &amdgpu_crtc->base, &position), + amdgpu_dm_plane_get_cursor_position(plane, &amdgpu_crtc->base, &position), 0); KUNIT_EXPECT_FALSE(test, position.enable); } @@ -483,35 +498,37 @@ static void dm_test_get_cursor_position(struct kunit *test) static void dm_test_format_mod_supported(struct kunit *test) { struct amdgpu_device *adev; - struct drm_plane plane = {0}; + struct drm_plane *plane; uint64_t listed_mod; adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + plane = kunit_kzalloc(test, sizeof(*plane), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, adev); + KUNIT_ASSERT_NOT_NULL(test, plane); adev->family = AMDGPU_FAMILY_NV; - plane.dev = &adev->ddev; + plane->dev = &adev->ddev; KUNIT_EXPECT_TRUE(test, - amdgpu_dm_plane_format_mod_supported(&plane, DRM_FORMAT_XRGB8888, + amdgpu_dm_plane_format_mod_supported(plane, DRM_FORMAT_XRGB8888, DRM_FORMAT_MOD_LINEAR)); KUNIT_EXPECT_TRUE(test, - amdgpu_dm_plane_format_mod_supported(&plane, DRM_FORMAT_XRGB8888, + amdgpu_dm_plane_format_mod_supported(plane, DRM_FORMAT_XRGB8888, DRM_FORMAT_MOD_INVALID)); KUNIT_EXPECT_FALSE(test, - amdgpu_dm_plane_format_mod_supported(&plane, DRM_FORMAT_XRGB8888, + amdgpu_dm_plane_format_mod_supported(plane, DRM_FORMAT_XRGB8888, DRM_FORMAT_MOD_VENDOR_AMD)); listed_mod = AMD_FMT_MOD | AMD_FMT_MOD_SET(TILE, AMD_FMT_MOD_TILE_GFX9_64K_S_X) | AMD_FMT_MOD_SET(TILE_VERSION, AMD_FMT_MOD_TILE_VER_GFX9) | AMD_FMT_MOD_SET(DCC, 1); - plane.modifiers = &listed_mod; - plane.modifier_count = 1; + plane->modifiers = &listed_mod; + plane->modifier_count = 1; KUNIT_EXPECT_FALSE(test, - amdgpu_dm_plane_format_mod_supported(&plane, DRM_FORMAT_NV12, listed_mod)); + amdgpu_dm_plane_format_mod_supported(plane, DRM_FORMAT_NV12, listed_mod)); } /** diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c index 31194ab42f04..0b29bf0a7d04 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c @@ -452,14 +452,19 @@ static void dm_test_get_plane_scale_zero_src_width(struct kunit *test) */ static void dm_test_scaling_state_same(struct kunit *test) { - struct dm_connector_state a = { 0 }; - struct dm_connector_state b = { 0 }; + struct dm_connector_state *a; + struct dm_connector_state *b; - a.scaling = RMX_FULL; - a.underscan_enable = false; - b = a; + a = kunit_kzalloc(test, sizeof(*a), GFP_KERNEL); + b = kunit_kzalloc(test, sizeof(*b), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, a); + KUNIT_ASSERT_NOT_NULL(test, b); - KUNIT_EXPECT_FALSE(test, is_scaling_state_different(&a, &b)); + a->scaling = RMX_FULL; + a->underscan_enable = false; + *b = *a; + + KUNIT_EXPECT_FALSE(test, is_scaling_state_different(a, b)); } /** @@ -468,13 +473,18 @@ static void dm_test_scaling_state_same(struct kunit *test) */ static void dm_test_scaling_state_scaling_changed(struct kunit *test) { - struct dm_connector_state a = { 0 }; - struct dm_connector_state b = { 0 }; + struct dm_connector_state *a; + struct dm_connector_state *b; + + a = kunit_kzalloc(test, sizeof(*a), GFP_KERNEL); + b = kunit_kzalloc(test, sizeof(*b), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, a); + KUNIT_ASSERT_NOT_NULL(test, b); - a.scaling = RMX_FULL; - b.scaling = RMX_CENTER; + a->scaling = RMX_FULL; + b->scaling = RMX_CENTER; - KUNIT_EXPECT_TRUE(test, is_scaling_state_different(&a, &b)); + KUNIT_EXPECT_TRUE(test, is_scaling_state_different(a, b)); } /** @@ -483,16 +493,21 @@ static void dm_test_scaling_state_scaling_changed(struct kunit *test) */ static void dm_test_scaling_state_underscan_enabled(struct kunit *test) { - struct dm_connector_state old_state = { 0 }; - struct dm_connector_state new_state = { 0 }; + struct dm_connector_state *old_state; + struct dm_connector_state *new_state; + + old_state = kunit_kzalloc(test, sizeof(*old_state), GFP_KERNEL); + new_state = kunit_kzalloc(test, sizeof(*new_state), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, old_state); + KUNIT_ASSERT_NOT_NULL(test, new_state); /* new enables underscan with non-zero borders, old has it disabled */ - new_state.underscan_enable = true; - new_state.underscan_hborder = 16; - new_state.underscan_vborder = 16; - old_state.underscan_enable = false; + new_state->underscan_enable = true; + new_state->underscan_hborder = 16; + new_state->underscan_vborder = 16; + old_state->underscan_enable = false; - KUNIT_EXPECT_TRUE(test, is_scaling_state_different(&new_state, &old_state)); + KUNIT_EXPECT_TRUE(test, is_scaling_state_different(new_state, old_state)); } /** @@ -501,16 +516,21 @@ static void dm_test_scaling_state_underscan_enabled(struct kunit *test) */ static void dm_test_scaling_state_underscan_border_changed(struct kunit *test) { - struct dm_connector_state a = { 0 }; - struct dm_connector_state b = { 0 }; + struct dm_connector_state *a; + struct dm_connector_state *b; + + a = kunit_kzalloc(test, sizeof(*a), GFP_KERNEL); + b = kunit_kzalloc(test, sizeof(*b), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, a); + KUNIT_ASSERT_NOT_NULL(test, b); - a.underscan_enable = true; - a.underscan_hborder = 16; - a.underscan_vborder = 16; - b = a; - b.underscan_hborder = 32; + a->underscan_enable = true; + a->underscan_hborder = 16; + a->underscan_vborder = 16; + *b = *a; + b->underscan_hborder = 32; - KUNIT_EXPECT_TRUE(test, is_scaling_state_different(&a, &b)); + KUNIT_EXPECT_TRUE(test, is_scaling_state_different(a, b)); } /* Tests for is_timing_unchanged_for_freesync() */ -- cgit v1.2.3 From 98cad4bd1443975d972f4c7f705980da03722a22 Mon Sep 17 00:00:00 2001 From: Evgenii Burenchev Date: Mon, 29 Jun 2026 15:58:50 -0500 Subject: drm/amd/display: Fix dangling pointer in plane reset function amdgpu_dm_plane_drm_plane_reset() frees the old state before allocating a new one. If kzalloc() fails, the function returns without updating the state pointer, leaving a dangling pointer to already freed memory. Fix this by allocating the new state first. On allocation failure, the old state remains untouched and the function safely returns. Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: 5d945cbcd4b1 ("drm/amd/display: Create a file dedicated to planes") Signed-off-by: Evgenii Burenchev Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20260629090435.9729-3-evg28bur@yandex.ru [adjust for movement around current amd-staging-drm-next] Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c index 35813a39ebcb..1b564cfe2120 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c @@ -1517,17 +1517,15 @@ static const struct drm_plane_helper_funcs dm_primary_plane_helper_funcs = { static void amdgpu_dm_plane_drm_plane_reset(struct drm_plane *plane) { - struct dm_plane_state *amdgpu_state = NULL; - - if (plane->state) - plane->funcs->atomic_destroy_state(plane, plane->state); + struct dm_plane_state *amdgpu_state; amdgpu_state = kzalloc_obj(*amdgpu_state); - WARN_ON(amdgpu_state == NULL); - if (!amdgpu_state) return; + if (plane->state) + plane->funcs->atomic_destroy_state(plane, plane->state); + __drm_atomic_helper_plane_reset(plane, &amdgpu_state->base); amdgpu_state->degamma_tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT; amdgpu_state->hdr_mult = AMDGPU_HDR_MULT_DEFAULT; -- cgit v1.2.3 From 0aeed866cb938943908c3ba46422128e49d2d080 Mon Sep 17 00:00:00 2001 From: Evgenii Burenchev Date: Mon, 29 Jun 2026 15:58:56 -0500 Subject: drm/amd/display: Fix dangling pointer in CRTC reset function amdgpu_dm_crtc_reset_state() frees the old state before allocating a new one. If kzalloc() fails, the function returns without updating the state pointer, leaving a dangling pointer to already freed memory. Fix this by allocating the new state first. On allocation failure, the old state remains untouched and the function safely returns. Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: e7b07ceef2a6 ("drm/amd/display: Merge amdgpu_dm_crtc and dm_crtc_state") Signed-off-by: Evgenii Burenchev Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20260629090435.9729-4-evg28bur@yandex.ru [adjust for movement around current amd-staging-drm-next] Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c index f7fcce6e76bb..0ad7704800d9 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c @@ -444,13 +444,13 @@ static void amdgpu_dm_crtc_reset_state(struct drm_crtc *crtc) { struct dm_crtc_state *state; - if (crtc->state) - amdgpu_dm_crtc_destroy_state(crtc, crtc->state); - state = kzalloc_obj(*state); - if (WARN_ON(!state)) + if (!state) return; + if (crtc->state) + amdgpu_dm_crtc_destroy_state(crtc, crtc->state); + __drm_atomic_helper_crtc_reset(crtc, &state->base); } -- cgit v1.2.3 From 3b1f4d5e47b361002490d2297b344ce34dae3d55 Mon Sep 17 00:00:00 2001 From: Evgenii Burenchev Date: Mon, 29 Jun 2026 15:59:01 -0500 Subject: drm/amd/display: Fix dangling pointer in connector reset function amdgpu_dm_connector_funcs_reset() frees the old state before allocating a new one. If kzalloc() fails, the function returns without updating the state pointer, leaving a dangling pointer to already freed memory. Fix this by allocating the new state first. On allocation failure, the old state remains untouched and the function safely returns. Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: e7b07ceef2a6 ("drm/amd/display: Merge amdgpu_dm_crtc and dm_crtc_state") Signed-off-by: Evgenii Burenchev Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20260629090435.9729-5-evg28bur@yandex.ru [adjust for movement around current amd-staging-drm-next] Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher --- .../amd/display/amdgpu_dm/amdgpu_dm_connector.c | 39 +++++++++++----------- 1 file changed, 20 insertions(+), 19 deletions(-) (limited to 'drivers/gpu/drm/amd/display/amdgpu_dm') 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 d4720c5576ce..40688d35bde6 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 @@ -1786,33 +1786,34 @@ static void amdgpu_dm_connector_destroy(struct drm_connector *connector) void amdgpu_dm_connector_funcs_reset(struct drm_connector *connector) { - struct dm_connector_state *state = + struct dm_connector_state *old_state = to_dm_connector_state(connector->state); + struct dm_connector_state *state; + + state = kzalloc_obj(*state); + if (!state) + return; if (connector->state) __drm_atomic_helper_connector_destroy_state(connector->state); - kfree(state); + kfree(old_state); - state = kzalloc_obj(*state); + __drm_atomic_helper_connector_reset(connector, &state->base); - if (state) { - state->scaling = RMX_OFF; - state->underscan_enable = false; - state->underscan_hborder = 0; - state->underscan_vborder = 0; - state->base.max_requested_bpc = 8; - state->vcpi_slots = 0; - state->pbn = 0; - - if (connector->connector_type == DRM_MODE_CONNECTOR_eDP) { - if (amdgpu_dm_abm_level <= 0) - state->abm_level = ABM_LEVEL_IMMEDIATE_DISABLE; - else - state->abm_level = amdgpu_dm_abm_level; - } + state->scaling = RMX_OFF; + state->underscan_enable = false; + state->underscan_hborder = 0; + state->underscan_vborder = 0; + state->base.max_requested_bpc = 8; + state->vcpi_slots = 0; + state->pbn = 0; - __drm_atomic_helper_connector_reset(connector, &state->base); + if (connector->connector_type == DRM_MODE_CONNECTOR_eDP) { + if (amdgpu_dm_abm_level <= 0) + state->abm_level = ABM_LEVEL_IMMEDIATE_DISABLE; + else + state->abm_level = amdgpu_dm_abm_level; } } EXPORT_IF_KUNIT(amdgpu_dm_connector_funcs_reset); -- cgit v1.2.3