The HDMI PHY PLL can only be used as DCLK source while the HDMI link operates in TMDS mode, i.e. for TMDS character rates up to 600 MHz. Beyond that, HDMI 2.1 FRL is required, in which case the PLL would run at the link rate and VOP2 must keep using the system CRU.
Currently, this is decided by reconstructing the TMDS character rate from the DCLK rate and the color depth, i.e. DCLK * bpc / 8, which does not hold for all possible output configurations: - YUV422 always transmits two 12-bit components per clock cycle, regardless of the color depth, which from a clock-rate perspective is equivalent to three 8-bit RGB components. For example, 4K@60Hz 10-bit YUV422 requires the same bandwidth as 4K@60Hz 8-bit RGB, typically 594 MHz. However, VOP2 wrongly assumes it needs 742.5 MHz (594 * 10 / 8) and ends up switching the DCLK source. As a consequence, modes requiring uncommon pixel clocks, such as those corresponding to fractional refresh rates, will fail. An example is [email protected], which would likely rely on a 593.407 MHz clock rate, unsupported by the system CRU. - When VOP2 processes multiple pixels per clock cycle, DCLK is only a fraction of the pixel clock, e.g. RK3576 halves it for pixel clocks above 600 MHz. Hence, an FRL mode such as 4K@120Hz 8-bit RGB, with a pixel clock of 1188 MHz, ends up with a 594 MHz DCLK, which slips under the limit and wrongly selects the HDMI PHY PLL as DCLK source. In preparation for supporting the use cases above, rework the DCLK source switch logic to check the TMDS character rate computed by the HDMI connector state helpers, which already accounts for the output color format and depth. Keep using the system CRU when the rate is not available. While at it, drop the VOP2_MAX_DCLK_RATE define in favor of the generic HDMI_2_0_TMDS_CHAR_RATE_MAX_HZ, since the 600 MHz limit applies to the TMDS character rate rather than to DCLK. Signed-off-by: Cristian Ciocaltea <[email protected]> --- drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 50 ++++++++++++++++++---------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c index ab6ced49ae40..7c35d73c82cb 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c @@ -7,6 +7,7 @@ #include <linux/clk.h> #include <linux/component.h> #include <linux/delay.h> +#include <linux/hdmi.h> #include <linux/iopoll.h> #include <linux/kernel.h> #include <linux/media-bus-format.h> @@ -103,8 +104,6 @@ enum vop2_afbc_format { VOP2_AFBC_FMT_INVALID = -1, }; -#define VOP2_MAX_DCLK_RATE 600000000ULL - /* * bus-format types. */ @@ -1800,6 +1799,23 @@ static int us_to_vertical_line(struct drm_display_mode *mode, int us) return us * mode->clock / mode->htotal / 1000; } +static bool vop2_hdmi_phy_pll_usable(struct drm_atomic_commit *state, + struct drm_encoder *encoder) +{ + struct drm_connector_state *conn_state; + struct drm_connector *connector; + unsigned long long rate; + + connector = drm_atomic_get_new_connector_for_encoder(state, encoder); + if (!connector) + return false; + + conn_state = drm_atomic_get_new_connector_state(state, connector); + rate = conn_state->hdmi.tmds_char_rate; + + return rate && rate <= HDMI_2_0_TMDS_CHAR_RATE_MAX_HZ; +} + static void vop2_crtc_atomic_enable(struct drm_crtc *crtc, struct drm_atomic_commit *state) { @@ -1950,28 +1966,26 @@ static void vop2_crtc_atomic_enable(struct drm_crtc *crtc, vop2_vp_write(vp, RK3568_VP_MIPI_CTRL, 0); /* - * Switch to HDMI PHY PLL as DCLK source for display modes up - * to 4K@60Hz, if available, otherwise keep using the system CRU. + * Switch to HDMI PHY PLL as DCLK source while the HDMI link operates + * in TMDS mode, if available, otherwise keep using the system CRU. */ if (vop2->pll_hdmiphy0 || vop2->pll_hdmiphy1) { - unsigned int bpc = vcstate->output_bpc ?: 8; - unsigned long max_dclk = DIV_ROUND_CLOSEST_ULL(VOP2_MAX_DCLK_RATE * 8, bpc); struct clk *pll_hdmiphy = NULL; - if (clock <= max_dclk) { - drm_for_each_encoder_mask(encoder, crtc->dev, crtc_state->encoder_mask) { - struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder); + drm_for_each_encoder_mask(encoder, crtc->dev, crtc_state->encoder_mask) { + struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder); - if (rkencoder->crtc_endpoint_id == ROCKCHIP_VOP2_EP_HDMI0) { - pll_hdmiphy = vop2->pll_hdmiphy0; - break; - } + if (rkencoder->crtc_endpoint_id == ROCKCHIP_VOP2_EP_HDMI0) + pll_hdmiphy = vop2->pll_hdmiphy0; + else if (rkencoder->crtc_endpoint_id == ROCKCHIP_VOP2_EP_HDMI1) + pll_hdmiphy = vop2->pll_hdmiphy1; + else + continue; - if (rkencoder->crtc_endpoint_id == ROCKCHIP_VOP2_EP_HDMI1) { - pll_hdmiphy = vop2->pll_hdmiphy1; - break; - } - } + if (pll_hdmiphy && !vop2_hdmi_phy_pll_usable(state, encoder)) + pll_hdmiphy = NULL; + + break; } if (pll_hdmiphy) { -- 2.55.0
