From 539fd979b625be20b72dfcf55b031d18a3d29b79 Mon Sep 17 00:00:00 2001 From: Zhang Yubing Date: Wed, 17 Jan 2024 10:35:00 +0800 Subject: [PATCH] drm/rockchip: dw-dp: support more feature active protocol converter adapters According to DP1.4a 5.3.3, To support more feature active protocol converte adapters. All the adapters are branch device, Read the Detailed Capabilities Info to get the branch type and the feature it support. For Example, the max dot clock for VGA, max TDMS clock for HDMI, the YCbCr420 support or not for HDMI 2.0, conversion to YCbCr420 from YCbCr444 or not for HDMI 2.0. According the feature of branch device, filter the resolution, color format and color depth that the branch device can't support. Change-Id: I640ce0e6324811875a9dabea846514bc3e42f915 Signed-off-by: Zhang Yubing --- drivers/gpu/drm/rockchip/dw-dp.c | 107 +++++++++++++++++++++++++++++-- 1 file changed, 103 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/rockchip/dw-dp.c b/drivers/gpu/drm/rockchip/dw-dp.c index a29212f42f78..1955bd7ed545 100644 --- a/drivers/gpu/drm/rockchip/dw-dp.c +++ b/drivers/gpu/drm/rockchip/dw-dp.c @@ -322,6 +322,7 @@ struct drm_dp_link_train { struct dw_dp_link { u8 dpcd[DP_RECEIVER_CAP_SIZE]; + u8 downstream_ports[DP_MAX_DOWNSTREAM_PORTS]; unsigned char revision; unsigned int max_rate; unsigned int rate; @@ -439,6 +440,14 @@ struct dw_dp_mst_enc { bool active; }; +struct dw_dp_dfp { + int min_tmds_clock; + int max_tmds_clock; + int max_dotclock; + u8 max_bpc; + bool ycbcr_444_to_420; +}; + struct dw_dp { const struct dw_dp_chip_data *chip_data; struct device *dev; @@ -473,12 +482,14 @@ struct dw_dp { struct dw_dp_video video; struct dw_dp_audio *audio; struct dw_dp_compliance compliance; + struct dw_dp_dfp dfp; DECLARE_BITMAP(sdp_reg_bank, SDP_REG_BANK_SIZE); bool split_mode; bool dual_connector_split; bool left_display; + bool branch_ycbcr_444_to_422; struct dw_dp *left; struct dw_dp *right; @@ -1661,6 +1672,35 @@ static int dw_dp_update_hdr_property(struct drm_connector *connector) return ret; } +static void dw_dp_update_dfp(struct dw_dp *dp, struct edid *edid) +{ + struct dw_dp_link *link = &dp->link; + struct dw_dp_dfp *dfp = &dp->dfp; + bool ycbcr_420_passthrough, ycbcr_444_to_420; + + memset(&dp->dfp, 0, sizeof(dp->dfp)); + + dfp->max_bpc = drm_dp_downstream_max_bpc(link->dpcd, link->downstream_ports, edid); + + dfp->max_dotclock = drm_dp_downstream_max_dotclock(link->dpcd, link->downstream_ports); + + dfp->min_tmds_clock = drm_dp_downstream_min_tmds_clock(link->dpcd, link->downstream_ports, + edid); + dfp->max_tmds_clock = drm_dp_downstream_max_tmds_clock(link->dpcd, link->downstream_ports, + edid); + ycbcr_420_passthrough = drm_dp_downstream_420_passthrough(link->dpcd, + link->downstream_ports); + ycbcr_444_to_420 = drm_dp_downstream_444_to_420_conversion(link->dpcd, + link->downstream_ports); + /* Prefer 4:2:0 passthrough over 4:4:4->4:2:0 conversion */ + dfp->ycbcr_444_to_420 = ycbcr_444_to_420 && !ycbcr_420_passthrough; + + dw_dp_dbg(dp, + "dfp max bpc:%d, max dot:%d, min tmds:%d, max tmds:%d, ycbcr 444 to 420:%d\n", + dfp->max_bpc, dfp->max_dotclock, dfp->min_tmds_clock, dfp->max_tmds_clock, + dfp->ycbcr_444_to_420); +} + static int dw_dp_connector_get_modes(struct drm_connector *connector) { struct dw_dp *dp = connector_to_dp(connector); @@ -1689,6 +1729,7 @@ static int dw_dp_connector_get_modes(struct drm_connector *connector) drm_connector_update_edid_property(connector, edid); num_modes = drm_add_edid_modes(connector, edid); dw_dp_update_hdr_property(connector); + dw_dp_update_dfp(dp, edid); kfree(edid); } } @@ -1922,6 +1963,9 @@ static int dw_dp_link_probe(struct dw_dp *dp) } link->sink_support_mst = drm_dp_read_mst_cap(&dp->aux, dp->link.dpcd); + ret = drm_dp_read_downstream_info(&dp->aux, link->dpcd, link->downstream_ports); + if (ret) + return ret; ret = drm_dp_dpcd_readb(&dp->aux, DP_DPRX_FEATURE_ENUMERATION_LIST, &dpcd); @@ -3016,6 +3060,9 @@ static int dw_dp_video_enable(struct dw_dp *dp, struct dw_dp_video *video, int s FIELD_PREP(HBLANK_INTERVAL_EN, 1) | FIELD_PREP(HBLANK_INTERVAL, hblank_interval)); + if (dp->branch_ycbcr_444_to_422) + drm_dp_dpcd_writeb(&dp->aux, DP_PROTOCOL_CONVERTER_CONTROL_1, + DP_CONVERSION_TO_YCBCR420_ENABLE); /* Video stream enable */ regmap_update_bits(dp->regmap, DPTX_VSAMPLE_CTRL_N(stream_id), VIDEO_STREAM_ENABLE, FIELD_PREP(VIDEO_STREAM_ENABLE, 1)); @@ -3419,6 +3466,39 @@ static ssize_t dw_dp_sim_aux_transfer(struct drm_dp_aux *aux, return dw_dp_aux_transfer(aux, msg); } +static int dw_dp_hdmi_tmds_clock(int clock, int bpc, bool ycbcr420_output) +{ + if (ycbcr420_output) + clock /= 2; + + return DIV_ROUND_CLOSEST(clock * bpc, 8); +} + +static enum drm_mode_status +dw_dp_tmds_clock_valid(struct dw_dp *dp, int bpc, + const struct drm_display_mode *mode, + const struct drm_display_info *info) +{ + int tmds_clock, min_tmds_clock, max_tmds_clock; + bool ycbcr_420_output; + + if (dp->dfp.max_dotclock && mode->clock > dp->dfp.max_dotclock) + return MODE_CLOCK_HIGH; + + ycbcr_420_output = drm_mode_is_420_only(info, mode); + tmds_clock = dw_dp_hdmi_tmds_clock(mode->clock, bpc, ycbcr_420_output); + min_tmds_clock = dp->dfp.min_tmds_clock; + max_tmds_clock = min(dp->dfp.max_tmds_clock, info->max_tmds_clock); + + if (min_tmds_clock && tmds_clock < min_tmds_clock) + return MODE_CLOCK_LOW; + + if (max_tmds_clock && tmds_clock > max_tmds_clock) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + static enum drm_mode_status dw_dp_bridge_mode_valid(struct drm_bridge *bridge, const struct drm_display_info *info, @@ -3446,6 +3526,7 @@ dw_dp_bridge_mode_valid(struct drm_bridge *bridge, min_bpp = 24; if (!link->vsc_sdp_extension_for_colorimetry_supported && + !dp->dfp.ycbcr_444_to_420 && drm_mode_is_420_only(info, &m)) return MODE_NO_420; @@ -3455,7 +3536,8 @@ dw_dp_bridge_mode_valid(struct drm_bridge *bridge, if (m.flags & DRM_MODE_FLAG_DBLCLK) return MODE_H_ILLEGAL; - return MODE_OK; + /* Assume 8bpc for the HDMI/DVI TMDS clock check */ + return dw_dp_tmds_clock_valid(dp, 8, mode, info); } static void _dw_dp_loader_protect(struct dw_dp *dp, bool on) @@ -4799,6 +4881,9 @@ out: } } + if (status == connector_status_disconnected) + memset(&dp->dfp, 0, sizeof(dp->dfp)); + return status; } @@ -4852,6 +4937,7 @@ static u32 *dw_dp_bridge_atomic_get_output_bus_fmts(struct drm_bridge *bridge, unsigned int i, j = 0; dp->eotf_type = dw_dp_get_eotf(conn_state); + dp->branch_ycbcr_444_to_422 = false; if (dp->split_mode || dp->dual_connector_split) drm_mode_convert_to_origin_mode(&mode); @@ -4895,9 +4981,16 @@ static u32 *dw_dp_bridge_atomic_get_output_bus_fmts(struct drm_bridge *bridge, fmt->color_format == DRM_COLOR_FORMAT_YCBCR420) continue; - if (drm_mode_is_420_only(di, &mode) && - fmt->color_format != DRM_COLOR_FORMAT_YCBCR420) - continue; + if (drm_mode_is_420_only(di, &mode)) { + if (dp->dfp.ycbcr_444_to_420) { + dp->branch_ycbcr_444_to_422 = true; + if (fmt->color_format != DRM_COLOR_FORMAT_YCBCR444) + continue; + } else { + if (fmt->color_format != DRM_COLOR_FORMAT_YCBCR420) + continue; + } + } if (!dw_dp_bandwidth_ok(dp, &mode, fmt->bpp, link->lanes, link->max_rate)) continue; @@ -4905,6 +4998,12 @@ static u32 *dw_dp_bridge_atomic_get_output_bus_fmts(struct drm_bridge *bridge, if (dw_dp_is_hdr_eotf(dp->eotf_type) && fmt->bpc < 8) continue; + if (dp->dfp.max_bpc && fmt->bpc > dp->dfp.max_bpc) + continue; + + if (dp->dfp.max_tmds_clock && fmt->bpc > 8) + continue; + output_fmts[j++] = fmt->bus_format; }