Merge drm/drm-next into drm-misc-next
[linux-2.6-microblaze.git] / drivers / gpu / drm / vc4 / vc4_hdmi.c
index 6c58b0f..8b01b1e 100644 (file)
 
 #define HDMI_14_MAX_TMDS_CLK   (340 * 1000 * 1000)
 
-static bool vc4_hdmi_mode_needs_scrambling(const struct drm_display_mode *mode)
+static const char * const output_format_str[] = {
+       [VC4_HDMI_OUTPUT_RGB]           = "RGB",
+       [VC4_HDMI_OUTPUT_YUV420]        = "YUV 4:2:0",
+       [VC4_HDMI_OUTPUT_YUV422]        = "YUV 4:2:2",
+       [VC4_HDMI_OUTPUT_YUV444]        = "YUV 4:4:4",
+};
+
+static const char *vc4_hdmi_output_fmt_str(enum vc4_hdmi_output_format fmt)
 {
-       return (mode->clock * 1000) > HDMI_14_MAX_TMDS_CLK;
+       if (fmt >= ARRAY_SIZE(output_format_str))
+               return "invalid";
+
+       return output_format_str[fmt];
+}
+
+static unsigned long long
+vc4_hdmi_encoder_compute_mode_clock(const struct drm_display_mode *mode,
+                                   unsigned int bpc, enum vc4_hdmi_output_format fmt);
+
+static bool vc4_hdmi_mode_needs_scrambling(const struct drm_display_mode *mode,
+                                          unsigned int bpc,
+                                          enum vc4_hdmi_output_format fmt)
+{
+       unsigned long long clock = vc4_hdmi_encoder_compute_mode_clock(mode, bpc, fmt);
+
+       return clock > HDMI_14_MAX_TMDS_CLK;
 }
 
 static bool vc4_hdmi_is_full_range_rgb(struct vc4_hdmi *vc4_hdmi,
@@ -266,7 +289,7 @@ static int vc4_hdmi_connector_get_modes(struct drm_connector *connector)
                struct drm_display_mode *mode;
 
                list_for_each_entry(mode, &connector->probed_modes, head) {
-                       if (vc4_hdmi_mode_needs_scrambling(mode)) {
+                       if (vc4_hdmi_mode_needs_scrambling(mode, 8, VC4_HDMI_OUTPUT_RGB)) {
                                drm_warn_once(drm, "The core clock cannot reach frequencies high enough to support 4k @ 60Hz.");
                                drm_warn_once(drm, "Please change your config.txt file to add hdmi_enable_4kp60.");
                        }
@@ -323,6 +346,7 @@ static void vc4_hdmi_connector_reset(struct drm_connector *connector)
 
        new_state->base.max_bpc = 8;
        new_state->base.max_requested_bpc = 8;
+       new_state->output_format = VC4_HDMI_OUTPUT_RGB;
        drm_atomic_helper_connector_tv_reset(connector);
 }
 
@@ -337,7 +361,9 @@ vc4_hdmi_connector_duplicate_state(struct drm_connector *connector)
        if (!new_state)
                return NULL;
 
-       new_state->pixel_rate = vc4_state->pixel_rate;
+       new_state->tmds_char_rate = vc4_state->tmds_char_rate;
+       new_state->output_bpc = vc4_state->output_bpc;
+       new_state->output_format = vc4_state->output_format;
        __drm_atomic_helper_connector_duplicate_state(connector, &new_state->base);
 
        return &new_state->base;
@@ -481,11 +507,38 @@ static void vc4_hdmi_write_infoframe(struct drm_encoder *encoder,
                DRM_ERROR("Failed to wait for infoframe to start: %d\n", ret);
 }
 
+static void vc4_hdmi_avi_infoframe_colorspace(struct hdmi_avi_infoframe *frame,
+                                             enum vc4_hdmi_output_format fmt)
+{
+       switch (fmt) {
+       case VC4_HDMI_OUTPUT_RGB:
+               frame->colorspace = HDMI_COLORSPACE_RGB;
+               break;
+
+       case VC4_HDMI_OUTPUT_YUV420:
+               frame->colorspace = HDMI_COLORSPACE_YUV420;
+               break;
+
+       case VC4_HDMI_OUTPUT_YUV422:
+               frame->colorspace = HDMI_COLORSPACE_YUV422;
+               break;
+
+       case VC4_HDMI_OUTPUT_YUV444:
+               frame->colorspace = HDMI_COLORSPACE_YUV444;
+               break;
+
+       default:
+               break;
+       }
+}
+
 static void vc4_hdmi_set_avi_infoframe(struct drm_encoder *encoder)
 {
        struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
        struct drm_connector *connector = &vc4_hdmi->connector;
        struct drm_connector_state *cstate = connector->state;
+       struct vc4_hdmi_connector_state *vc4_state =
+               conn_state_to_vc4_hdmi_conn_state(cstate);
        const struct drm_display_mode *mode = &vc4_hdmi->saved_adjusted_mode;
        union hdmi_infoframe frame;
        int ret;
@@ -505,6 +558,7 @@ static void vc4_hdmi_set_avi_infoframe(struct drm_encoder *encoder)
                                           HDMI_QUANTIZATION_RANGE_FULL :
                                           HDMI_QUANTIZATION_RANGE_LIMITED);
        drm_hdmi_avi_infoframe_colorimetry(&frame.avi, cstate);
+       vc4_hdmi_avi_infoframe_colorspace(&frame.avi, vc4_state->output_format);
        drm_hdmi_avi_infoframe_bars(&frame.avi, cstate);
 
        vc4_hdmi_write_infoframe(encoder, &frame);
@@ -607,7 +661,9 @@ static void vc4_hdmi_enable_scrambling(struct drm_encoder *encoder)
        if (!vc4_hdmi_supports_scrambling(encoder, mode))
                return;
 
-       if (!vc4_hdmi_mode_needs_scrambling(mode))
+       if (!vc4_hdmi_mode_needs_scrambling(mode,
+                                           vc4_hdmi->output_bpc,
+                                           vc4_hdmi->output_format))
                return;
 
        drm_scdc_set_high_tmds_clock_ratio(vc4_hdmi->ddc, true);
@@ -802,6 +858,39 @@ static const u16 vc5_hdmi_csc_full_rgb_to_limited_rgb[3][4] = {
        { 0x0000, 0x0000, 0x1b80, 0x0400 },
 };
 
+/*
+ * Conversion between Full Range RGB and Full Range YUV422 using the
+ * BT.709 Colorspace
+ *
+ *
+ * [  0.181906  0.611804  0.061758  16  ]
+ * [ -0.100268 -0.337232  0.437500  128 ]
+ * [  0.437500 -0.397386 -0.040114  128 ]
+ *
+ * Matrix is signed 2p13 fixed point, with signed 9p6 offsets
+ */
+static const u16 vc5_hdmi_csc_full_rgb_to_limited_yuv422_bt709[3][4] = {
+       { 0x05d2, 0x1394, 0x01fa, 0x0400 },
+       { 0xfccc, 0xf536, 0x0e00, 0x2000 },
+       { 0x0e00, 0xf34a, 0xfeb8, 0x2000 },
+};
+
+/*
+ * Conversion between Full Range RGB and Full Range YUV444 using the
+ * BT.709 Colorspace
+ *
+ * [ -0.100268 -0.337232  0.437500  128 ]
+ * [  0.437500 -0.397386 -0.040114  128 ]
+ * [  0.181906  0.611804  0.061758  16  ]
+ *
+ * Matrix is signed 2p13 fixed point, with signed 9p6 offsets
+ */
+static const u16 vc5_hdmi_csc_full_rgb_to_limited_yuv444_bt709[3][4] = {
+       { 0xfccc, 0xf536, 0x0e00, 0x2000 },
+       { 0x0e00, 0xf34a, 0xfeb8, 0x2000 },
+       { 0x05d2, 0x1394, 0x01fa, 0x0400 },
+};
+
 static void vc5_hdmi_set_csc_coeffs(struct vc4_hdmi *vc4_hdmi,
                                    const u16 coeffs[3][4])
 {
@@ -819,19 +908,53 @@ static void vc5_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi,
                               struct drm_connector_state *state,
                               const struct drm_display_mode *mode)
 {
+       struct vc4_hdmi_connector_state *vc4_state =
+               conn_state_to_vc4_hdmi_conn_state(state);
        unsigned long flags;
+       u32 if_cfg = 0;
+       u32 if_xbar = 0x543210;
+       u32 csc_chan_ctl = 0;
        u32 csc_ctl = VC5_MT_CP_CSC_CTL_ENABLE | VC4_SET_FIELD(VC4_HD_CSC_CTL_MODE_CUSTOM,
                                                               VC5_MT_CP_CSC_CTL_MODE);
 
        spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
 
-       HDMI_WRITE(HDMI_VEC_INTERFACE_XBAR, 0x354021);
+       switch (vc4_state->output_format) {
+       case VC4_HDMI_OUTPUT_YUV444:
+               vc5_hdmi_set_csc_coeffs(vc4_hdmi, vc5_hdmi_csc_full_rgb_to_limited_yuv444_bt709);
+               break;
+
+       case VC4_HDMI_OUTPUT_YUV422:
+               csc_ctl |= VC4_SET_FIELD(VC5_MT_CP_CSC_CTL_FILTER_MODE_444_TO_422_STANDARD,
+                                        VC5_MT_CP_CSC_CTL_FILTER_MODE_444_TO_422) |
+                       VC5_MT_CP_CSC_CTL_USE_444_TO_422 |
+                       VC5_MT_CP_CSC_CTL_USE_RNG_SUPPRESSION;
 
-       if (!vc4_hdmi_is_full_range_rgb(vc4_hdmi, mode))
-               vc5_hdmi_set_csc_coeffs(vc4_hdmi, vc5_hdmi_csc_full_rgb_to_limited_rgb);
-       else
-               vc5_hdmi_set_csc_coeffs(vc4_hdmi, vc5_hdmi_csc_full_rgb_unity);
+               csc_chan_ctl |= VC4_SET_FIELD(VC5_MT_CP_CHANNEL_CTL_OUTPUT_REMAP_LEGACY_STYLE,
+                                             VC5_MT_CP_CHANNEL_CTL_OUTPUT_REMAP);
+
+               if_cfg |= VC4_SET_FIELD(VC5_DVP_HT_VEC_INTERFACE_CFG_SEL_422_FORMAT_422_LEGACY,
+                                       VC5_DVP_HT_VEC_INTERFACE_CFG_SEL_422);
+
+               vc5_hdmi_set_csc_coeffs(vc4_hdmi, vc5_hdmi_csc_full_rgb_to_limited_yuv422_bt709);
+               break;
 
+       case VC4_HDMI_OUTPUT_RGB:
+               if_xbar = 0x354021;
+
+               if (!vc4_hdmi_is_full_range_rgb(vc4_hdmi, mode))
+                       vc5_hdmi_set_csc_coeffs(vc4_hdmi, vc5_hdmi_csc_full_rgb_to_limited_rgb);
+               else
+                       vc5_hdmi_set_csc_coeffs(vc4_hdmi, vc5_hdmi_csc_full_rgb_unity);
+               break;
+
+       default:
+               break;
+       }
+
+       HDMI_WRITE(HDMI_VEC_INTERFACE_CFG, if_cfg);
+       HDMI_WRITE(HDMI_VEC_INTERFACE_XBAR, if_xbar);
+       HDMI_WRITE(HDMI_CSC_CHANNEL_CTL, csc_chan_ctl);
        HDMI_WRITE(HDMI_CSC_CTL, csc_ctl);
 
        spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
@@ -892,6 +1015,8 @@ static void vc5_hdmi_set_timings(struct vc4_hdmi *vc4_hdmi,
                                 struct drm_connector_state *state,
                                 struct drm_display_mode *mode)
 {
+       const struct vc4_hdmi_connector_state *vc4_state =
+               conn_state_to_vc4_hdmi_conn_state(state);
        bool hsync_pos = mode->flags & DRM_MODE_FLAG_PHSYNC;
        bool vsync_pos = mode->flags & DRM_MODE_FLAG_PVSYNC;
        bool interlaced = mode->flags & DRM_MODE_FLAG_INTERLACE;
@@ -939,7 +1064,7 @@ static void vc5_hdmi_set_timings(struct vc4_hdmi *vc4_hdmi,
        HDMI_WRITE(HDMI_VERTB0, vertb_even);
        HDMI_WRITE(HDMI_VERTB1, vertb);
 
-       switch (state->max_bpc) {
+       switch (vc4_state->output_bpc) {
        case 12:
                gcp = 6;
                gcp_en = true;
@@ -955,6 +1080,15 @@ static void vc5_hdmi_set_timings(struct vc4_hdmi *vc4_hdmi,
                break;
        }
 
+       /*
+        * YCC422 is always 36-bit and not considered deep colour so
+        * doesn't signal in GCP.
+        */
+       if (vc4_state->output_format == VC4_HDMI_OUTPUT_YUV422) {
+               gcp = 4;
+               gcp_en = false;
+       }
+
        reg = HDMI_READ(HDMI_DEEP_COLOR_CONFIG_1);
        reg &= ~(VC5_HDMI_DEEP_COLOR_CONFIG_1_INIT_PACK_PHASE_MASK |
                 VC5_HDMI_DEEP_COLOR_CONFIG_1_COLOR_DEPTH_MASK);
@@ -1022,7 +1156,7 @@ static void vc4_hdmi_encoder_pre_crtc_configure(struct drm_encoder *encoder,
        struct vc4_hdmi_connector_state *vc4_conn_state =
                conn_state_to_vc4_hdmi_conn_state(conn_state);
        struct drm_display_mode *mode = &vc4_hdmi->saved_adjusted_mode;
-       unsigned long pixel_rate = vc4_conn_state->pixel_rate;
+       unsigned long tmds_char_rate = vc4_conn_state->tmds_char_rate;
        unsigned long bvb_rate, hsm_rate;
        unsigned long flags;
        int ret;
@@ -1045,7 +1179,7 @@ static void vc4_hdmi_encoder_pre_crtc_configure(struct drm_encoder *encoder,
         * Additionally, the AXI clock needs to be at least 25% of
         * pixel clock, but HSM ends up being the limiting factor.
         */
-       hsm_rate = max_t(unsigned long, 120000000, (pixel_rate / 100) * 101);
+       hsm_rate = max_t(unsigned long, 120000000, (tmds_char_rate / 100) * 101);
        ret = clk_set_min_rate(vc4_hdmi->hsm_clock, hsm_rate);
        if (ret) {
                DRM_ERROR("Failed to set HSM clock rate: %d\n", ret);
@@ -1058,7 +1192,7 @@ static void vc4_hdmi_encoder_pre_crtc_configure(struct drm_encoder *encoder,
                goto out;
        }
 
-       ret = clk_set_rate(vc4_hdmi->pixel_clock, pixel_rate);
+       ret = clk_set_rate(vc4_hdmi->pixel_clock, tmds_char_rate);
        if (ret) {
                DRM_ERROR("Failed to set pixel clock rate: %d\n", ret);
                goto err_put_runtime_pm;
@@ -1073,9 +1207,9 @@ static void vc4_hdmi_encoder_pre_crtc_configure(struct drm_encoder *encoder,
 
        vc4_hdmi_cec_update_clk_div(vc4_hdmi);
 
-       if (pixel_rate > 297000000)
+       if (tmds_char_rate > 297000000)
                bvb_rate = 300000000;
-       else if (pixel_rate > 148500000)
+       else if (tmds_char_rate > 148500000)
                bvb_rate = 150000000;
        else
                bvb_rate = 75000000;
@@ -1232,13 +1366,234 @@ static void vc4_hdmi_encoder_atomic_mode_set(struct drm_encoder *encoder,
                                             struct drm_connector_state *conn_state)
 {
        struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
+       struct vc4_hdmi_connector_state *vc4_state =
+               conn_state_to_vc4_hdmi_conn_state(conn_state);
 
        mutex_lock(&vc4_hdmi->mutex);
        drm_mode_copy(&vc4_hdmi->saved_adjusted_mode,
                      &crtc_state->adjusted_mode);
+       vc4_hdmi->output_bpc = vc4_state->output_bpc;
+       vc4_hdmi->output_format = vc4_state->output_format;
        mutex_unlock(&vc4_hdmi->mutex);
 }
 
+static bool
+vc4_hdmi_sink_supports_format_bpc(const struct vc4_hdmi *vc4_hdmi,
+                                 const struct drm_display_info *info,
+                                 const struct drm_display_mode *mode,
+                                 unsigned int format, unsigned int bpc)
+{
+       struct drm_device *dev = vc4_hdmi->connector.dev;
+       u8 vic = drm_match_cea_mode(mode);
+
+       if (vic == 1 && bpc != 8) {
+               drm_dbg(dev, "VIC1 requires a bpc of 8, got %u\n", bpc);
+               return false;
+       }
+
+       if (!info->is_hdmi &&
+           (format != VC4_HDMI_OUTPUT_RGB || bpc != 8)) {
+               drm_dbg(dev, "DVI Monitors require an RGB output at 8 bpc\n");
+               return false;
+       }
+
+       switch (format) {
+       case VC4_HDMI_OUTPUT_RGB:
+               drm_dbg(dev, "RGB Format, checking the constraints.\n");
+
+               if (!(info->color_formats & DRM_COLOR_FORMAT_RGB444))
+                       return false;
+
+               if (bpc == 10 && !(info->edid_hdmi_rgb444_dc_modes & DRM_EDID_HDMI_DC_30)) {
+                       drm_dbg(dev, "10 BPC but sink doesn't support Deep Color 30.\n");
+                       return false;
+               }
+
+               if (bpc == 12 && !(info->edid_hdmi_rgb444_dc_modes & DRM_EDID_HDMI_DC_36)) {
+                       drm_dbg(dev, "12 BPC but sink doesn't support Deep Color 36.\n");
+                       return false;
+               }
+
+               drm_dbg(dev, "RGB format supported in that configuration.\n");
+
+               return true;
+
+       case VC4_HDMI_OUTPUT_YUV422:
+               drm_dbg(dev, "YUV422 format, checking the constraints.\n");
+
+               if (!(info->color_formats & DRM_COLOR_FORMAT_YCBCR422)) {
+                       drm_dbg(dev, "Sink doesn't support YUV422.\n");
+                       return false;
+               }
+
+               if (bpc != 12) {
+                       drm_dbg(dev, "YUV422 only supports 12 bpc.\n");
+                       return false;
+               }
+
+               drm_dbg(dev, "YUV422 format supported in that configuration.\n");
+
+               return true;
+
+       case VC4_HDMI_OUTPUT_YUV444:
+               drm_dbg(dev, "YUV444 format, checking the constraints.\n");
+
+               if (!(info->color_formats & DRM_COLOR_FORMAT_YCBCR444)) {
+                       drm_dbg(dev, "Sink doesn't support YUV444.\n");
+                       return false;
+               }
+
+               if (bpc == 10 && !(info->edid_hdmi_ycbcr444_dc_modes & DRM_EDID_HDMI_DC_30)) {
+                       drm_dbg(dev, "10 BPC but sink doesn't support Deep Color 30.\n");
+                       return false;
+               }
+
+               if (bpc == 12 && !(info->edid_hdmi_ycbcr444_dc_modes & DRM_EDID_HDMI_DC_36)) {
+                       drm_dbg(dev, "12 BPC but sink doesn't support Deep Color 36.\n");
+                       return false;
+               }
+
+               drm_dbg(dev, "YUV444 format supported in that configuration.\n");
+
+               return true;
+       }
+
+       return false;
+}
+
+static enum drm_mode_status
+vc4_hdmi_encoder_clock_valid(const struct vc4_hdmi *vc4_hdmi,
+                            unsigned long long clock)
+{
+       const struct drm_connector *connector = &vc4_hdmi->connector;
+       const struct drm_display_info *info = &connector->display_info;
+
+       if (clock > vc4_hdmi->variant->max_pixel_clock)
+               return MODE_CLOCK_HIGH;
+
+       if (vc4_hdmi->disable_4kp60 && clock > HDMI_14_MAX_TMDS_CLK)
+               return MODE_CLOCK_HIGH;
+
+       if (info->max_tmds_clock && clock > (info->max_tmds_clock * 1000))
+               return MODE_CLOCK_HIGH;
+
+       return MODE_OK;
+}
+
+static unsigned long long
+vc4_hdmi_encoder_compute_mode_clock(const struct drm_display_mode *mode,
+                                   unsigned int bpc,
+                                   enum vc4_hdmi_output_format fmt)
+{
+       unsigned long long clock = mode->clock * 1000;
+
+       if (mode->flags & DRM_MODE_FLAG_DBLCLK)
+               clock = clock * 2;
+
+       if (fmt == VC4_HDMI_OUTPUT_YUV422)
+               bpc = 8;
+
+       clock = clock * bpc;
+       do_div(clock, 8);
+
+       return clock;
+}
+
+static int
+vc4_hdmi_encoder_compute_clock(const struct vc4_hdmi *vc4_hdmi,
+                              struct vc4_hdmi_connector_state *vc4_state,
+                              const struct drm_display_mode *mode,
+                              unsigned int bpc, unsigned int fmt)
+{
+       unsigned long long clock;
+
+       clock = vc4_hdmi_encoder_compute_mode_clock(mode, bpc, fmt);
+       if (vc4_hdmi_encoder_clock_valid(vc4_hdmi, clock) != MODE_OK)
+               return -EINVAL;
+
+       vc4_state->tmds_char_rate = clock;
+
+       return 0;
+}
+
+static int
+vc4_hdmi_encoder_compute_format(const struct vc4_hdmi *vc4_hdmi,
+                               struct vc4_hdmi_connector_state *vc4_state,
+                               const struct drm_display_mode *mode,
+                               unsigned int bpc)
+{
+       struct drm_device *dev = vc4_hdmi->connector.dev;
+       const struct drm_connector *connector = &vc4_hdmi->connector;
+       const struct drm_display_info *info = &connector->display_info;
+       unsigned int format;
+
+       drm_dbg(dev, "Trying with an RGB output\n");
+
+       format = VC4_HDMI_OUTPUT_RGB;
+       if (vc4_hdmi_sink_supports_format_bpc(vc4_hdmi, info, mode, format, bpc)) {
+               int ret;
+
+               ret = vc4_hdmi_encoder_compute_clock(vc4_hdmi, vc4_state,
+                                                    mode, bpc, format);
+               if (!ret) {
+                       vc4_state->output_format = format;
+                       return 0;
+               }
+       }
+
+       drm_dbg(dev, "Failed, Trying with an YUV422 output\n");
+
+       format = VC4_HDMI_OUTPUT_YUV422;
+       if (vc4_hdmi_sink_supports_format_bpc(vc4_hdmi, info, mode, format, bpc)) {
+               int ret;
+
+               ret = vc4_hdmi_encoder_compute_clock(vc4_hdmi, vc4_state,
+                                                    mode, bpc, format);
+               if (!ret) {
+                       vc4_state->output_format = format;
+                       return 0;
+               }
+       }
+
+       drm_dbg(dev, "Failed. No Format Supported for that bpc count.\n");
+
+       return -EINVAL;
+}
+
+static int
+vc4_hdmi_encoder_compute_config(const struct vc4_hdmi *vc4_hdmi,
+                               struct vc4_hdmi_connector_state *vc4_state,
+                               const struct drm_display_mode *mode)
+{
+       struct drm_device *dev = vc4_hdmi->connector.dev;
+       struct drm_connector_state *conn_state = &vc4_state->base;
+       unsigned int max_bpc = clamp_t(unsigned int, conn_state->max_bpc, 8, 12);
+       unsigned int bpc;
+       int ret;
+
+       for (bpc = max_bpc; bpc >= 8; bpc -= 2) {
+               drm_dbg(dev, "Trying with a %d bpc output\n", bpc);
+
+               ret = vc4_hdmi_encoder_compute_format(vc4_hdmi, vc4_state,
+                                                     mode, bpc);
+               if (ret)
+                       continue;
+
+               vc4_state->output_bpc = bpc;
+
+               drm_dbg(dev,
+                       "Mode %ux%u @ %uHz: Found configuration: bpc: %u, fmt: %s, clock: %llu\n",
+                       mode->hdisplay, mode->vdisplay, drm_mode_vrefresh(mode),
+                       vc4_state->output_bpc,
+                       vc4_hdmi_output_fmt_str(vc4_state->output_format),
+                       vc4_state->tmds_char_rate);
+
+               break;
+       }
+
+       return ret;
+}
+
 #define WIFI_2_4GHz_CH1_MIN_FREQ       2400000000ULL
 #define WIFI_2_4GHz_CH1_MAX_FREQ       2422000000ULL
 
@@ -1249,8 +1604,9 @@ static int vc4_hdmi_encoder_atomic_check(struct drm_encoder *encoder,
        struct vc4_hdmi_connector_state *vc4_state = conn_state_to_vc4_hdmi_conn_state(conn_state);
        struct drm_display_mode *mode = &crtc_state->adjusted_mode;
        struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
-       unsigned long long pixel_rate = mode->clock * 1000;
-       unsigned long long tmds_rate;
+       unsigned long long tmds_char_rate = mode->clock * 1000;
+       unsigned long long tmds_bit_rate;
+       int ret;
 
        if (vc4_hdmi->variant->unsupported_odd_h_timings &&
            !(mode->flags & DRM_MODE_FLAG_DBLCLK) &&
@@ -1264,32 +1620,17 @@ static int vc4_hdmi_encoder_atomic_check(struct drm_encoder *encoder,
         * bandwidth). Slightly lower the frequency to bring it out of
         * the WiFi range.
         */
-       tmds_rate = pixel_rate * 10;
+       tmds_bit_rate = tmds_char_rate * 10;
        if (vc4_hdmi->disable_wifi_frequencies &&
-           (tmds_rate >= WIFI_2_4GHz_CH1_MIN_FREQ &&
-            tmds_rate <= WIFI_2_4GHz_CH1_MAX_FREQ)) {
+           (tmds_bit_rate >= WIFI_2_4GHz_CH1_MIN_FREQ &&
+            tmds_bit_rate <= WIFI_2_4GHz_CH1_MAX_FREQ)) {
                mode->clock = 238560;
-               pixel_rate = mode->clock * 1000;
+               tmds_char_rate = mode->clock * 1000;
        }
 
-       if (conn_state->max_bpc == 12) {
-               pixel_rate = pixel_rate * 150;
-               do_div(pixel_rate, 100);
-       } else if (conn_state->max_bpc == 10) {
-               pixel_rate = pixel_rate * 125;
-               do_div(pixel_rate, 100);
-       }
-
-       if (mode->flags & DRM_MODE_FLAG_DBLCLK)
-               pixel_rate = pixel_rate * 2;
-
-       if (pixel_rate > vc4_hdmi->variant->max_pixel_clock)
-               return -EINVAL;
-
-       if (vc4_hdmi->disable_4kp60 && (pixel_rate > HDMI_14_MAX_TMDS_CLK))
-               return -EINVAL;
-
-       vc4_state->pixel_rate = pixel_rate;
+       ret = vc4_hdmi_encoder_compute_config(vc4_hdmi, vc4_state, mode);
+       if (ret)
+               return ret;
 
        return 0;
 }
@@ -1306,13 +1647,7 @@ vc4_hdmi_encoder_mode_valid(struct drm_encoder *encoder,
             (mode->hsync_end % 2) || (mode->htotal % 2)))
                return MODE_H_ILLEGAL;
 
-       if ((mode->clock * 1000) > vc4_hdmi->variant->max_pixel_clock)
-               return MODE_CLOCK_HIGH;
-
-       if (vc4_hdmi->disable_4kp60 && vc4_hdmi_mode_needs_scrambling(mode))
-               return MODE_CLOCK_HIGH;
-
-       return MODE_OK;
+       return vc4_hdmi_encoder_clock_valid(vc4_hdmi, mode->clock * 1000);
 }
 
 static const struct drm_encoder_helper_funcs vc4_hdmi_encoder_helper_funcs = {