]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
drm/amd/display: Add DCE HWSS support for external DP bridge encoders
authorTimur Kristóf <timur.kristof@gmail.com>
Mon, 26 Jan 2026 21:08:33 +0000 (22:08 +0100)
committerAlex Deucher <alexander.deucher@amd.com>
Mon, 23 Feb 2026 19:28:32 +0000 (14:28 -0500)
Some GPUs use external DP bridge encoders NUTMEG and TRAVIS
to implement analog and/or LVDS connections. Typically found in
CIK APU based laptops or on FM2 motherboards that have analog
connectors. These were necessary at the time because Kaveri
didn't have a built-in DAC nor LVDS support.

These devices sadly don't work transparently and need to be
controlled by the driver. Implement the necessary control for
the NUTMEG and TRAVIS encoders in the DCE HWSS.

For reference, see the legacy non-DC amdgpu display code:
amdgpu_atombios_encoder_setup_external_encoder()
amdgpu_atombios_encoder_setup_dig()
amdgpu_atombios_encoder_setup_ext_encoder_ddc()

- Prepare DDC before using it:
  Call the EXTERNAL_ENCODER_CONTROL_DDC_SETUP action so that
  the encoder knows to set up DDC over the AUX channel.

- When a stream is enabled or disabled:
  Call the EXTERNAL_ENCODER_CONTROL_ENABLE/DISABLE actions.

- Before enabling the DP link:
  Call the EXTERNAL_ENCODER_CONTROL_SETUP action.

This commit just hooks up the HWSS support.
Detecting the external DP bridge encoders will be done in
a subsequent commit.

Signed-off-by: Timur Kristóf <timur.kristof@gmail.com>
Reviewed-by: Alex Hung <alex.hung@amd.com>
Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
drivers/gpu/drm/amd/display/dc/dc.h
drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c

index 71f2812acf599b66baae68f4e6131f44317fec3d..eb9de0e48c63cc6b5b788c539e7e3ae7ebe94753 100644 (file)
@@ -1682,6 +1682,10 @@ struct dc_scratch_space {
        struct panel_cntl *panel_cntl;
        struct link_encoder *link_enc;
        struct graphics_object_id link_id;
+
+       /* External encoder eg. NUTMEG or TRAVIS used on CIK APUs. */
+       struct graphics_object_id ext_enc_id;
+
        /* Endpoint type distinguishes display endpoints which do not have entries
         * in the BIOS connector table from those that do. Helps when tracking link
         * encoder to display endpoint assignments.
index cbc8c2f1b2ab73cf7f87586c696ce96c60922e81..3313acf5755350b8a66e5c2571af1d7a89a99a12 100644 (file)
@@ -660,6 +660,48 @@ void dce110_update_info_frame(struct pipe_ctx *pipe_ctx)
        }
 }
 
+static void
+dce110_external_encoder_control(enum bp_external_encoder_control_action action,
+                               struct dc_link *link,
+                               struct dc_crtc_timing *timing)
+{
+       struct dc *dc = link->ctx->dc;
+       struct dc_bios *bios = link->ctx->dc_bios;
+       const struct dc_link_settings *link_settings = &link->cur_link_settings;
+       enum bp_result bp_result = BP_RESULT_OK;
+       struct bp_external_encoder_control ext_cntl = {
+               .action = action,
+               .connector_obj_id = link->link_enc->connector,
+               .encoder_id = link->ext_enc_id,
+               .lanes_number = link_settings->lane_count,
+               .link_rate = link_settings->link_rate,
+
+               /* Use signal type of the real link encoder, ie. DP */
+               .signal = link->connector_signal,
+
+               /* We don't know the timing yet when executing the SETUP action,
+                * so use a reasonably high default value. It seems that ENABLE
+                * can change the actual pixel clock but doesn't work with higher
+                * pixel clocks than what SETUP was called with.
+                */
+               .pixel_clock = timing ? timing->pix_clk_100hz / 10 : 300000,
+               .color_depth = timing ? timing->display_color_depth : COLOR_DEPTH_888,
+       };
+       DC_LOGGER_INIT();
+
+       bp_result = bios->funcs->external_encoder_control(bios, &ext_cntl);
+
+       if (bp_result != BP_RESULT_OK)
+               DC_LOG_ERROR("Failed to execute external encoder action: 0x%x\n", action);
+}
+
+static void
+dce110_prepare_ddc(struct dc_link *link)
+{
+       if (link->ext_enc_id.id)
+               dce110_external_encoder_control(EXTERNAL_ENCODER_CONTROL_DDC_SETUP, link, NULL);
+}
+
 static bool
 dce110_dac_load_detect(struct dc_link *link)
 {
@@ -701,6 +743,8 @@ void dce110_enable_stream(struct pipe_ctx *pipe_ctx)
 
        tg->funcs->set_early_control(tg, early_control);
 
+       if (link->ext_enc_id.id)
+               dce110_external_encoder_control(EXTERNAL_ENCODER_CONTROL_ENABLE, link, timing);
 }
 
 static enum bp_result link_transmitter_control(
@@ -1194,6 +1238,9 @@ void dce110_disable_stream(struct pipe_ctx *pipe_ctx)
                dccg->funcs->disable_symclk_se(dccg, stream_enc->stream_enc_inst,
                                               link_enc->transmitter - TRANSMITTER_UNIPHY_A);
        }
+
+       if (link->ext_enc_id.id)
+               dce110_external_encoder_control(EXTERNAL_ENCODER_CONTROL_DISABLE, link, NULL);
 }
 
 void dce110_unblank_stream(struct pipe_ctx *pipe_ctx,
@@ -3328,6 +3375,11 @@ void dce110_enable_dp_link_output(
                }
        }
 
+       if (link->ext_enc_id.id) {
+               dce110_external_encoder_control(EXTERNAL_ENCODER_CONTROL_INIT, link, NULL);
+               dce110_external_encoder_control(EXTERNAL_ENCODER_CONTROL_SETUP, link, NULL);
+       }
+
        if (dc->link_srv->dp_get_encoding_format(link_settings) == DP_8b_10b_ENCODING) {
                if (dc->clk_mgr->funcs->notify_link_rate_change)
                        dc->clk_mgr->funcs->notify_link_rate_change(dc->clk_mgr, link);
@@ -3421,6 +3473,7 @@ static const struct hw_sequencer_funcs dce110_funcs = {
        .enable_analog_link_output = dce110_enable_analog_link_output,
        .disable_link_output = dce110_disable_link_output,
        .dac_load_detect = dce110_dac_load_detect,
+       .prepare_ddc = dce110_prepare_ddc,
 };
 
 static const struct hwseq_private_funcs dce110_private_funcs = {