spin_unlock_irqrestore(&vop->irq_lock, flags);
 }
 
+static enum drm_mode_status vop_crtc_mode_valid(struct drm_crtc *crtc,
+                                               const struct drm_display_mode *mode)
+{
+       struct vop *vop = to_vop(crtc);
+
+       if (vop->data->max_output.width && mode->hdisplay > vop->data->max_output.width)
+               return MODE_BAD_HVALUE;
+
+       return MODE_OK;
+}
+
 static bool vop_crtc_mode_fixup(struct drm_crtc *crtc,
                                const struct drm_display_mode *mode,
                                struct drm_display_mode *adjusted_mode)
 }
 
 static const struct drm_crtc_helper_funcs vop_crtc_helper_funcs = {
+       .mode_valid = vop_crtc_mode_valid,
        .mode_fixup = vop_crtc_mode_fixup,
        .atomic_check = vop_crtc_atomic_check,
        .atomic_begin = vop_crtc_atomic_begin,
 
        .output = &rk3036_output,
        .win = rk3036_vop_win_data,
        .win_size = ARRAY_SIZE(rk3036_vop_win_data),
+       .max_output = { 1920, 1080 },
 };
 
 static const struct vop_win_phy rk3126_win1_data = {
        .output = &rk3036_output,
        .win = rk3126_vop_win_data,
        .win_size = ARRAY_SIZE(rk3126_vop_win_data),
+       .max_output = { 1920, 1080 },
 };
 
 static const int px30_vop_intrs[] = {
        .output = &px30_output,
        .win = px30_vop_big_win_data,
        .win_size = ARRAY_SIZE(px30_vop_big_win_data),
+       .max_output = { 1920, 1080 },
 };
 
 static const struct vop_win_data px30_vop_lit_win_data[] = {
        .output = &px30_output,
        .win = px30_vop_lit_win_data,
        .win_size = ARRAY_SIZE(px30_vop_lit_win_data),
+       .max_output = { 1920, 1080 },
 };
 
 static const struct vop_scl_regs rk3066_win_scl = {
        .output = &rk3066_output,
        .win = rk3066_vop_win_data,
        .win_size = ARRAY_SIZE(rk3066_vop_win_data),
+       .max_output = { 1920, 1080 },
 };
 
 static const struct vop_scl_regs rk3188_win_scl = {
        .win = rk3188_vop_win_data,
        .win_size = ARRAY_SIZE(rk3188_vop_win_data),
        .feature = VOP_FEATURE_INTERNAL_RGB,
+       .max_output = { 2048, 1536 },
 };
 
 static const struct vop_scl_extension rk3288_win_full_scl_ext = {
        .win = rk3288_vop_win_data,
        .win_size = ARRAY_SIZE(rk3288_vop_win_data),
        .lut_size = 1024,
+       /*
+        * This is the maximum resolution for the VOPB, the VOPL can only do
+        * 2560x1600, but we can't distinguish them as they have the same
+        * compatible.
+        */
+       .max_output = { 3840, 2160 },
 };
 
 static const int rk3368_vop_intrs[] = {
        .misc = &rk3368_misc,
        .win = rk3368_vop_win_data,
        .win_size = ARRAY_SIZE(rk3368_vop_win_data),
+       .max_output = { 4096, 2160 },
 };
 
 static const struct vop_intr rk3366_vop_intr = {
        .misc = &rk3368_misc,
        .win = rk3368_vop_win_data,
        .win_size = ARRAY_SIZE(rk3368_vop_win_data),
+       .max_output = { 4096, 2160 },
 };
 
 static const struct vop_output rk3399_output = {
        .win_size = ARRAY_SIZE(rk3399_vop_win_data),
        .win_yuv2yuv = rk3399_vop_big_win_yuv2yuv_data,
        .lut_size = 1024,
+       .max_output = { 4096, 2160 },
 };
 
 static const struct vop_win_data rk3399_vop_lit_win_data[] = {
        .win_size = ARRAY_SIZE(rk3399_vop_lit_win_data),
        .win_yuv2yuv = rk3399_vop_lit_win_yuv2yuv_data,
        .lut_size = 256,
+       .max_output = { 2560, 1600 },
 };
 
 static const struct vop_win_data rk3228_vop_win_data[] = {
        .misc = &rk3368_misc,
        .win = rk3228_vop_win_data,
        .win_size = ARRAY_SIZE(rk3228_vop_win_data),
+       .max_output = { 4096, 2160 },
 };
 
 static const struct vop_modeset rk3328_modeset = {
        .misc = &rk3328_misc,
        .win = rk3328_vop_win_data,
        .win_size = ARRAY_SIZE(rk3328_vop_win_data),
+       .max_output = { 4096, 2160 },
 };
 
 static const struct of_device_id vop_driver_dt_match[] = {