]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
drm/amd/display: Implement DCE analog link encoders (v2)
authorTimur Kristóf <timur.kristof@gmail.com>
Fri, 26 Sep 2025 18:01:52 +0000 (20:01 +0200)
committerAlex Deucher <alexander.deucher@amd.com>
Tue, 28 Oct 2025 14:09:12 +0000 (10:09 -0400)
We support two kinds of analog connections:

1. DVI-I, which allows both digital and analog signals:
The DC code base only allows 1 encoder per connector, and the
preferred engine type is still going to be digital. So, for DVI-I
to work, we need to make sure the pre-existing link encoder can
also work with analog signals.

1. VGA, which only supports analog signals:
For VGA, we need to create a link encoder that only works with the
DAC without perturbing any digital transmitter functionality.
Since dce110_link_encoder already supports analog DVI-I,
just reuse that code for VGA as well.

v2:
Reduce code churn by reusing same link encoder for VGA and DVI-I.

Signed-off-by: Timur Kristóf <timur.kristof@gmail.com>
Reviewed-by: Harry Wentland <harry.wentland@amd.com>
Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
drivers/gpu/drm/amd/display/dc/dce/dce_link_encoder.c
drivers/gpu/drm/amd/display/dc/dce/dce_link_encoder.h
drivers/gpu/drm/amd/display/dc/resource/dce100/dce100_resource.c
drivers/gpu/drm/amd/display/dc/resource/dce60/dce60_resource.c
drivers/gpu/drm/amd/display/dc/resource/dce80/dce80_resource.c

index 0c50fe266c8a16432ddc247f5a58088f4c6e2898..87dbb8d7ed27d87c0309534064a033014259d73d 100644 (file)
@@ -302,6 +302,10 @@ static void setup_panel_mode(
        if (ctx->dc->caps.psp_setup_panel_mode)
                return;
 
+       /* The code below is only applicable to encoders with a digital transmitter. */
+       if (enc110->base.transmitter == TRANSMITTER_UNKNOWN)
+               return;
+
        ASSERT(REG(DP_DPHY_INTERNAL_CTRL));
        value = REG_READ(DP_DPHY_INTERNAL_CTRL);
 
@@ -804,6 +808,33 @@ bool dce110_link_encoder_validate_dp_output(
        return true;
 }
 
+static bool dce110_link_encoder_validate_rgb_output(
+       const struct dce110_link_encoder *enc110,
+       const struct dc_crtc_timing *crtc_timing)
+{
+       /* When the VBIOS doesn't specify any limits, use 400 MHz.
+        * The value comes from amdgpu_atombios_get_clock_info.
+        */
+       uint32_t max_pixel_clock_khz = 400000;
+
+       if (enc110->base.ctx->dc_bios->fw_info_valid &&
+           enc110->base.ctx->dc_bios->fw_info.max_pixel_clock) {
+               max_pixel_clock_khz =
+                       enc110->base.ctx->dc_bios->fw_info.max_pixel_clock;
+       }
+
+       if (crtc_timing->pix_clk_100hz > max_pixel_clock_khz * 10)
+               return false;
+
+       if (crtc_timing->display_color_depth != COLOR_DEPTH_888)
+               return false;
+
+       if (crtc_timing->pixel_encoding != PIXEL_ENCODING_RGB)
+               return false;
+
+       return true;
+}
+
 void dce110_link_encoder_construct(
        struct dce110_link_encoder *enc110,
        const struct encoder_init_data *init_data,
@@ -824,6 +855,7 @@ void dce110_link_encoder_construct(
        enc110->base.connector = init_data->connector;
 
        enc110->base.preferred_engine = ENGINE_ID_UNKNOWN;
+       enc110->base.analog_engine = init_data->analog_engine;
 
        enc110->base.features = *enc_features;
 
@@ -847,6 +879,11 @@ void dce110_link_encoder_construct(
                SIGNAL_TYPE_EDP |
                SIGNAL_TYPE_HDMI_TYPE_A;
 
+       if ((enc110->base.connector.id == CONNECTOR_ID_DUAL_LINK_DVII ||
+            enc110->base.connector.id == CONNECTOR_ID_SINGLE_LINK_DVII) &&
+               enc110->base.analog_engine != ENGINE_ID_UNKNOWN)
+               enc110->base.output_signals |= SIGNAL_TYPE_RGB;
+
        /* For DCE 8.0 and 8.1, by design, UNIPHY is hardwired to DIG_BE.
         * SW always assign DIG_FE 1:1 mapped to DIG_FE for non-MST UNIPHY.
         * SW assign DIG_FE to non-MST UNIPHY first and MST last. So prefer
@@ -885,6 +922,13 @@ void dce110_link_encoder_construct(
                enc110->base.preferred_engine = ENGINE_ID_DIGG;
        break;
        default:
+               if (init_data->analog_engine != ENGINE_ID_UNKNOWN) {
+                       /* The connector is analog-only, ie. VGA */
+                       enc110->base.preferred_engine = init_data->analog_engine;
+                       enc110->base.output_signals = SIGNAL_TYPE_RGB;
+                       enc110->base.transmitter = TRANSMITTER_UNKNOWN;
+                       break;
+               }
                ASSERT_CRITICAL(false);
                enc110->base.preferred_engine = ENGINE_ID_UNKNOWN;
        }
@@ -939,6 +983,10 @@ bool dce110_link_encoder_validate_output_with_stream(
                is_valid = dce110_link_encoder_validate_dp_output(
                                        enc110, &stream->timing);
        break;
+       case SIGNAL_TYPE_RGB:
+               is_valid = dce110_link_encoder_validate_rgb_output(
+                                       enc110, &stream->timing);
+       break;
        case SIGNAL_TYPE_EDP:
        case SIGNAL_TYPE_LVDS:
                is_valid = stream->timing.pixel_encoding == PIXEL_ENCODING_RGB;
@@ -969,6 +1017,10 @@ void dce110_link_encoder_hw_init(
        cntl.coherent = false;
        cntl.hpd_sel = enc110->base.hpd_source;
 
+       /* The code below is only applicable to encoders with a digital transmitter. */
+       if (enc110->base.transmitter == TRANSMITTER_UNKNOWN)
+               return;
+
        if (enc110->base.connector.id == CONNECTOR_ID_EDP)
                cntl.signal = SIGNAL_TYPE_EDP;
 
@@ -1034,6 +1086,8 @@ void dce110_link_encoder_setup(
                /* DP MST */
                REG_UPDATE(DIG_BE_CNTL, DIG_MODE, 5);
                break;
+       case SIGNAL_TYPE_RGB:
+               break;
        default:
                ASSERT_CRITICAL(false);
                /* invalid mode ! */
@@ -1282,6 +1336,24 @@ void dce110_link_encoder_disable_output(
        struct bp_transmitter_control cntl = { 0 };
        enum bp_result result;
 
+       switch (enc->analog_engine) {
+       case ENGINE_ID_DACA:
+               REG_UPDATE(DAC_ENABLE, DAC_ENABLE, 0);
+               break;
+       case ENGINE_ID_DACB:
+               /* DACB doesn't seem to be present on DCE6+,
+                * although there are references to it in the register file.
+                */
+               DC_LOG_ERROR("%s DACB is unsupported\n", __func__);
+               break;
+       default:
+               break;
+       }
+
+       /* The code below only applies to connectors that support digital signals. */
+       if (enc->transmitter == TRANSMITTER_UNKNOWN)
+               return;
+
        if (!dce110_is_dig_enabled(enc)) {
                /* OF_SKIP_POWER_DOWN_INACTIVE_ENCODER */
                return;
@@ -1726,6 +1798,7 @@ void dce60_link_encoder_construct(
        enc110->base.connector = init_data->connector;
 
        enc110->base.preferred_engine = ENGINE_ID_UNKNOWN;
+       enc110->base.analog_engine = init_data->analog_engine;
 
        enc110->base.features = *enc_features;
 
@@ -1749,6 +1822,11 @@ void dce60_link_encoder_construct(
                SIGNAL_TYPE_EDP |
                SIGNAL_TYPE_HDMI_TYPE_A;
 
+       if ((enc110->base.connector.id == CONNECTOR_ID_DUAL_LINK_DVII ||
+            enc110->base.connector.id == CONNECTOR_ID_SINGLE_LINK_DVII) &&
+               enc110->base.analog_engine != ENGINE_ID_UNKNOWN)
+               enc110->base.output_signals |= SIGNAL_TYPE_RGB;
+
        /* For DCE 8.0 and 8.1, by design, UNIPHY is hardwired to DIG_BE.
         * SW always assign DIG_FE 1:1 mapped to DIG_FE for non-MST UNIPHY.
         * SW assign DIG_FE to non-MST UNIPHY first and MST last. So prefer
@@ -1787,6 +1865,13 @@ void dce60_link_encoder_construct(
                enc110->base.preferred_engine = ENGINE_ID_DIGG;
        break;
        default:
+               if (init_data->analog_engine != ENGINE_ID_UNKNOWN) {
+                       /* The connector is analog-only, ie. VGA */
+                       enc110->base.preferred_engine = init_data->analog_engine;
+                       enc110->base.output_signals = SIGNAL_TYPE_RGB;
+                       enc110->base.transmitter = TRANSMITTER_UNKNOWN;
+                       break;
+               }
                ASSERT_CRITICAL(false);
                enc110->base.preferred_engine = ENGINE_ID_UNKNOWN;
        }
index 261c70e01e331386fc466c88cf8a04681188fc60..c58b69bc319b733c1eff2f923d2424265ffa64a5 100644 (file)
        SRI(DP_SEC_CNTL, DP, id), \
        SRI(DP_VID_STREAM_CNTL, DP, id), \
        SRI(DP_DPHY_FAST_TRAINING, DP, id), \
-       SRI(DP_SEC_CNTL1, DP, id)
+       SRI(DP_SEC_CNTL1, DP, id), \
+       SR(DAC_ENABLE)
 #endif
 
 #define LE_DCE80_REG_LIST(id)\
        SRI(DP_DPHY_INTERNAL_CTRL, DP, id), \
-       LE_COMMON_REG_LIST_BASE(id)
+       LE_COMMON_REG_LIST_BASE(id), \
+       SR(DAC_ENABLE)
 
 #define LE_DCE100_REG_LIST(id)\
        LE_COMMON_REG_LIST_BASE(id), \
        SRI(DP_DPHY_BS_SR_SWAP_CNTL, DP, id), \
        SRI(DP_DPHY_INTERNAL_CTRL, DP, id), \
-       SR(DCI_MEM_PWR_STATUS)
+       SR(DCI_MEM_PWR_STATUS), \
+       SR(DAC_ENABLE)
 
 #define LE_DCE110_REG_LIST(id)\
        LE_COMMON_REG_LIST_BASE(id), \
@@ -181,6 +184,9 @@ struct dce110_link_enc_registers {
        uint32_t DP_DPHY_BS_SR_SWAP_CNTL;
        uint32_t DP_DPHY_HBR2_PATTERN_CONTROL;
        uint32_t DP_SEC_CNTL1;
+
+       /* DAC registers */
+       uint32_t DAC_ENABLE;
 };
 
 struct dce110_link_encoder {
@@ -215,10 +221,6 @@ bool dce110_link_encoder_validate_dvi_output(
        enum signal_type signal,
        const struct dc_crtc_timing *crtc_timing);
 
-bool dce110_link_encoder_validate_rgb_output(
-       const struct dce110_link_encoder *enc110,
-       const struct dc_crtc_timing *crtc_timing);
-
 bool dce110_link_encoder_validate_dp_output(
        const struct dce110_link_encoder *enc110,
        const struct dc_crtc_timing *crtc_timing);
index 3856477981c626b0adc46ad6f7a2dfa207e16675..85ea219e4fd4fd323b640ddd6c1a5ca226c9e960 100644 (file)
@@ -225,6 +225,7 @@ static const struct dce110_link_enc_registers link_enc_regs[] = {
        link_regs(4),
        link_regs(5),
        link_regs(6),
+       { .DAC_ENABLE = mmDAC_ENABLE },
 };
 
 #define stream_enc_regs(id)\
@@ -632,7 +633,20 @@ static struct link_encoder *dce100_link_encoder_create(
                kzalloc(sizeof(struct dce110_link_encoder), GFP_KERNEL);
        int link_regs_id;
 
-       if (!enc110 || enc_init_data->hpd_source >= ARRAY_SIZE(link_enc_hpd_regs))
+       if (!enc110)
+               return NULL;
+
+       if (enc_init_data->connector.id == CONNECTOR_ID_VGA) {
+               dce110_link_encoder_construct(enc110,
+                       enc_init_data,
+                       &link_enc_feature,
+                       &link_enc_regs[ENGINE_ID_DACA],
+                       NULL,
+                       NULL);
+               return &enc110->base;
+       }
+
+       if (enc_init_data->hpd_source >= ARRAY_SIZE(link_enc_hpd_regs))
                return NULL;
 
        link_regs_id =
index 7195f435e3f76e6ce344700c2b66e54a547dec20..6e89b7020b61293357c3d1703a8fae31e7c5a629 100644 (file)
@@ -240,7 +240,9 @@ static const struct dce110_link_enc_registers link_enc_regs[] = {
        link_regs(2),
        link_regs(3),
        link_regs(4),
-       link_regs(5)
+       link_regs(5),
+       {0},
+       { .DAC_ENABLE = mmDAC_ENABLE },
 };
 
 #define stream_enc_regs(id)\
@@ -726,7 +728,20 @@ static struct link_encoder *dce60_link_encoder_create(
                kzalloc(sizeof(struct dce110_link_encoder), GFP_KERNEL);
        int link_regs_id;
 
-       if (!enc110 || enc_init_data->hpd_source >= ARRAY_SIZE(link_enc_hpd_regs))
+       if (!enc110)
+               return NULL;
+
+       if (enc_init_data->connector.id == CONNECTOR_ID_VGA) {
+               dce110_link_encoder_construct(enc110,
+                       enc_init_data,
+                       &link_enc_feature,
+                       &link_enc_regs[ENGINE_ID_DACA],
+                       NULL,
+                       NULL);
+               return &enc110->base;
+       }
+
+       if (enc_init_data->hpd_source >= ARRAY_SIZE(link_enc_hpd_regs))
                return NULL;
 
        link_regs_id =
index e9b79db37d1be942c21faa72f24cc98915de2b7a..a1ad7398e39f7331f70da6aeaed463dabf427ad7 100644 (file)
@@ -241,6 +241,7 @@ static const struct dce110_link_enc_registers link_enc_regs[] = {
        link_regs(4),
        link_regs(5),
        link_regs(6),
+       { .DAC_ENABLE = mmDAC_ENABLE },
 };
 
 #define stream_enc_regs(id)\
@@ -734,7 +735,20 @@ static struct link_encoder *dce80_link_encoder_create(
                kzalloc(sizeof(struct dce110_link_encoder), GFP_KERNEL);
        int link_regs_id;
 
-       if (!enc110 || enc_init_data->hpd_source >= ARRAY_SIZE(link_enc_hpd_regs))
+       if (!enc110)
+               return NULL;
+
+       if (enc_init_data->connector.id == CONNECTOR_ID_VGA) {
+               dce110_link_encoder_construct(enc110,
+                       enc_init_data,
+                       &link_enc_feature,
+                       &link_enc_regs[ENGINE_ID_DACA],
+                       NULL,
+                       NULL);
+               return &enc110->base;
+       }
+
+       if (enc_init_data->hpd_source >= ARRAY_SIZE(link_enc_hpd_regs))
                return NULL;
 
        link_regs_id =