drm/rockchip: vop: add rk3366 vop lit support
[firefly-linux-kernel-4.4.55.git] / drivers / gpu / drm / rockchip / rockchip_drm_vop.c
index 9219d9672d5429105c74d227215ec00fa8e8c914..7608a02d72f0255456476713f613c947b5840219 100644 (file)
@@ -17,6 +17,7 @@
 #include <drm/drm_atomic.h>
 #include <drm/drm_crtc.h>
 #include <drm/drm_crtc_helper.h>
+#include <drm/drm_flip_work.h>
 #include <drm/drm_plane_helper.h>
 
 #include <linux/devfreq.h>
@@ -25,6 +26,7 @@
 #include <linux/module.h>
 #include <linux/platform_device.h>
 #include <linux/clk.h>
+#include <linux/iopoll.h>
 #include <linux/of.h>
 #include <linux/of_device.h>
 #include <linux/pm_runtime.h>
@@ -39,6 +41,7 @@
 #include "rockchip_drm_gem.h"
 #include "rockchip_drm_fb.h"
 #include "rockchip_drm_vop.h"
+#include "rockchip_drm_backlight.h"
 
 #define VOP_REG_SUPPORT(vop, reg) \
                (!reg.major || (reg.major == VOP_MAJOR(vop->data->version) && \
@@ -129,10 +132,15 @@ struct vop_zpos {
        int zpos;
 };
 
+enum vop_pending {
+       VOP_PENDING_FB_UNREF,
+};
+
 struct vop_plane_state {
        struct drm_plane_state base;
        int format;
        int zpos;
+       unsigned int logo_ymirror;
        struct drm_rect src;
        struct drm_rect dest;
        dma_addr_t yrgb_mst;
@@ -177,9 +185,13 @@ struct vop {
        bool vsync_work_pending;
        bool loader_protect;
        struct completion dsp_hold_completion;
-       struct completion wait_update_complete;
+
+       /* protected by dev->event_lock */
        struct drm_pending_vblank_event *event;
 
+       struct drm_flip_work fb_unref_work;
+       unsigned long pending;
+
        struct completion line_flag_completion;
 
        const struct vop_data *data;
@@ -195,6 +207,8 @@ struct vop {
        u32 *lut;
        u32 lut_len;
        bool lut_active;
+       void __iomem *cabc_lut_regs;
+       u32 cabc_lut_len;
 
        /* one time only one process allowed to config the register */
        spinlock_t reg_lock;
@@ -211,6 +225,8 @@ struct vop {
        struct clk *dclk;
        /* vop share memory frequency */
        struct clk *aclk;
+       /* vop source handling, optional */
+       struct clk *dclk_source;
 
        /* vop dclk reset */
        struct reset_control *dclk_rst;
@@ -340,6 +356,11 @@ static inline uint32_t vop_read_lut(struct vop *vop, uint32_t offset)
        return readl(vop->lut_regs + offset);
 }
 
+static inline void vop_write_cabc_lut(struct vop *vop, uint32_t offset, uint32_t v)
+{
+       writel(v, vop->cabc_lut_regs + offset);
+}
+
 static bool has_rb_swapped(uint32_t format)
 {
        switch (format) {
@@ -387,6 +408,8 @@ static bool is_yuv_output(uint32_t bus_format)
        switch (bus_format) {
        case MEDIA_BUS_FMT_YUV8_1X24:
        case MEDIA_BUS_FMT_YUV10_1X30:
+       case MEDIA_BUS_FMT_UYYVYY8_0_5X24:
+       case MEDIA_BUS_FMT_UYYVYY10_0_5X30:
                return true;
        default:
                return false;
@@ -916,6 +939,7 @@ err_disable_hclk:
 static void vop_initial(struct drm_crtc *crtc)
 {
        struct vop *vop = to_vop(crtc);
+       uint32_t irqs;
        int i;
 
        vop_power_enable(crtc);
@@ -949,6 +973,12 @@ static void vop_initial(struct drm_crtc *crtc)
                VOP_WIN_SET(vop, win, gate, 1);
        }
        VOP_CTRL_SET(vop, afbdc_en, 0);
+
+       irqs = BUS_ERROR_INTR | WIN0_EMPTY_INTR | WIN1_EMPTY_INTR |
+               WIN2_EMPTY_INTR | WIN3_EMPTY_INTR | HWC_EMPTY_INTR |
+               POST_BUF_EMPTY_INTR;
+       VOP_INTR_SET_TYPE(vop, clear, irqs, 1);
+       VOP_INTR_SET_TYPE(vop, enable, irqs, 1);
 }
 
 static void vop_crtc_disable(struct drm_crtc *crtc)
@@ -1108,7 +1138,8 @@ static int vop_plane_atomic_check(struct drm_plane *plane,
        }
 
        offset = (src->x1 >> 16) * drm_format_plane_bpp(fb->pixel_format, 0) / 8;
-       if (state->rotation & BIT(DRM_REFLECT_Y))
+       if (state->rotation & BIT(DRM_REFLECT_Y) ||
+           (rockchip_fb_is_logo(fb) && vop_plane_state->logo_ymirror))
                offset += ((src->y2 >> 16) - 1) * fb->pitches[0];
        else
                offset += (src->y1 >> 16) * fb->pitches[0];
@@ -1210,7 +1241,8 @@ static void vop_plane_atomic_update(struct drm_plane *plane,
        dsp_sty = dest->y1 + crtc->mode.vtotal - crtc->mode.vsync_start;
        dsp_st = dsp_sty << 16 | (dsp_stx & 0xffff);
 
-       ymirror = !!(state->rotation & BIT(DRM_REFLECT_Y));
+       ymirror = state->rotation & BIT(DRM_REFLECT_Y) ||
+                 (rockchip_fb_is_logo(fb) && vop_plane_state->logo_ymirror);
        xmirror = !!(state->rotation & BIT(DRM_REFLECT_X));
 
        vop = to_vop(state->crtc);
@@ -1333,6 +1365,7 @@ static int vop_atomic_plane_set_property(struct drm_plane *plane,
                                         struct drm_property *property,
                                         uint64_t val)
 {
+       struct rockchip_drm_private *private = plane->dev->dev_private;
        struct vop_win *win = to_vop_win(plane);
        struct vop_plane_state *plane_state = to_vop_plane_state(state);
 
@@ -1346,6 +1379,12 @@ static int vop_atomic_plane_set_property(struct drm_plane *plane,
                return 0;
        }
 
+       if (property == private->logo_ymirror_prop) {
+               WARN_ON(!rockchip_fb_is_logo(state->fb));
+               plane_state->logo_ymirror = val;
+               return 0;
+       }
+
        DRM_ERROR("failed to set vop plane property\n");
        return -EINVAL;
 }
@@ -1377,6 +1416,7 @@ static const struct drm_plane_funcs vop_plane_funcs = {
        .disable_plane  = drm_atomic_helper_disable_plane,
        .destroy = vop_plane_destroy,
        .reset = vop_atomic_plane_reset,
+       .set_property = drm_atomic_helper_plane_set_property,
        .atomic_duplicate_state = vop_atomic_plane_duplicate_state,
        .atomic_destroy_state = vop_atomic_plane_destroy_state,
        .atomic_set_property = vop_atomic_plane_set_property,
@@ -1416,15 +1456,6 @@ static void vop_crtc_disable_vblank(struct drm_crtc *crtc)
        spin_unlock_irqrestore(&vop->irq_lock, flags);
 }
 
-static void vop_crtc_wait_for_update(struct drm_crtc *crtc)
-{
-       struct vop *vop = to_vop(crtc);
-
-       reinit_completion(&vop->wait_update_complete);
-       WARN_ON(!wait_for_completion_timeout(&vop->wait_update_complete,
-                                            msecs_to_jiffies(1000)));
-}
-
 static void vop_crtc_cancel_pending_vblank(struct drm_crtc *crtc,
                                           struct drm_file *file_priv)
 {
@@ -1465,6 +1496,14 @@ static int vop_crtc_loader_protect(struct drm_crtc *crtc, bool on)
        return 0;
 }
 
+#define DEBUG_PRINT(args...) \
+               do { \
+                       if (s) \
+                               seq_printf(s, args); \
+                       else \
+                               printk(args); \
+               } while (0)
+
 static int vop_plane_info_dump(struct seq_file *s, struct drm_plane *plane)
 {
        struct vop_win *win = to_vop_win(plane);
@@ -1474,27 +1513,27 @@ static int vop_plane_info_dump(struct seq_file *s, struct drm_plane *plane)
        struct drm_framebuffer *fb = state->fb;
        int i;
 
-       seq_printf(s, "    win%d-%d: %s\n", win->win_id, win->area_id,
-                  pstate->enable ? "ACTIVE" : "DISABLED");
+       DEBUG_PRINT("    win%d-%d: %s\n", win->win_id, win->area_id,
+                   pstate->enable ? "ACTIVE" : "DISABLED");
        if (!fb)
                return 0;
 
        src = &pstate->src;
        dest = &pstate->dest;
 
-       seq_printf(s, "\tformat: %s%s\n", drm_get_format_name(fb->pixel_format),
-                  fb->modifier[0] == DRM_FORMAT_MOD_ARM_AFBC ? "[AFBC]" : "");
-       seq_printf(s, "\tzpos: %d\n", pstate->zpos);
-       seq_printf(s, "\tsrc: pos[%dx%d] rect[%dx%d]\n", src->x1 >> 16,
-                  src->y1 >> 16, drm_rect_width(src) >> 16,
-                  drm_rect_height(src) >> 16);
-       seq_printf(s, "\tdst: pos[%dx%d] rect[%dx%d]\n", dest->x1, dest->y1,
-                  drm_rect_width(dest), drm_rect_height(dest));
+       DEBUG_PRINT("\tformat: %s%s\n", drm_get_format_name(fb->pixel_format),
+                   fb->modifier[0] == DRM_FORMAT_MOD_ARM_AFBC ? "[AFBC]" : "");
+       DEBUG_PRINT("\tzpos: %d\n", pstate->zpos);
+       DEBUG_PRINT("\tsrc: pos[%dx%d] rect[%dx%d]\n", src->x1 >> 16,
+                   src->y1 >> 16, drm_rect_width(src) >> 16,
+                   drm_rect_height(src) >> 16);
+       DEBUG_PRINT("\tdst: pos[%dx%d] rect[%dx%d]\n", dest->x1, dest->y1,
+                   drm_rect_width(dest), drm_rect_height(dest));
 
        for (i = 0; i < drm_format_num_planes(fb->pixel_format); i++) {
                dma_addr_t fb_addr = rockchip_fb_get_dma_addr(fb, i);
-               seq_printf(s, "\tbuf[%d]: addr: %pad pitch: %d offset: %d\n",
-                          i, &fb_addr, fb->pitches[i], fb->offsets[i]);
+               DEBUG_PRINT("\tbuf[%d]: addr: %pad pitch: %d offset: %d\n",
+                           i, &fb_addr, fb->pitches[i], fb->offsets[i]);
        }
 
        return 0;
@@ -1510,25 +1549,25 @@ static int vop_crtc_debugfs_dump(struct drm_crtc *crtc, struct seq_file *s)
        struct drm_plane *plane;
        int i;
 
-       seq_printf(s, "VOP [%s]: %s\n", dev_name(vop->dev),
-                  crtc_state->active ? "ACTIVE" : "DISABLED");
+       DEBUG_PRINT("VOP [%s]: %s\n", dev_name(vop->dev),
+                   crtc_state->active ? "ACTIVE" : "DISABLED");
 
        if (!crtc_state->active)
                return 0;
 
-       seq_printf(s, "    Connector: %s\n",
-                  drm_get_connector_name(state->output_type));
-       seq_printf(s, "\tbus_format[%x] output_mode[%x]\n",
-                  state->bus_format, state->output_mode);
-       seq_printf(s, "    Display mode: %dx%d%s%d\n",
-                  mode->hdisplay, mode->vdisplay, interlaced ? "i" : "p",
-                  drm_mode_vrefresh(mode));
-       seq_printf(s, "\tclk[%d] real_clk[%d] type[%x] flag[%x]\n",
-                  mode->clock, mode->crtc_clock, mode->type, mode->flags);
-       seq_printf(s, "\tH: %d %d %d %d\n", mode->hdisplay, mode->hsync_start,
-                  mode->hsync_end, mode->htotal);
-       seq_printf(s, "\tV: %d %d %d %d\n", mode->vdisplay, mode->vsync_start,
-                  mode->vsync_end, mode->vtotal);
+       DEBUG_PRINT("    Connector: %s\n",
+                   drm_get_connector_name(state->output_type));
+       DEBUG_PRINT("\tbus_format[%x] output_mode[%x]\n",
+                   state->bus_format, state->output_mode);
+       DEBUG_PRINT("    Display mode: %dx%d%s%d\n",
+                   mode->hdisplay, mode->vdisplay, interlaced ? "i" : "p",
+                   drm_mode_vrefresh(mode));
+       DEBUG_PRINT("\tclk[%d] real_clk[%d] type[%x] flag[%x]\n",
+                   mode->clock, mode->crtc_clock, mode->type, mode->flags);
+       DEBUG_PRINT("\tH: %d %d %d %d\n", mode->hdisplay, mode->hsync_start,
+                   mode->hsync_end, mode->htotal);
+       DEBUG_PRINT("\tV: %d %d %d %d\n", mode->vdisplay, mode->vsync_start,
+                   mode->vsync_end, mode->vtotal);
 
        for (i = 0; i < vop->num_wins; i++) {
                plane = &vop->win[i].base;
@@ -1538,6 +1577,25 @@ static int vop_crtc_debugfs_dump(struct drm_crtc *crtc, struct seq_file *s)
        return 0;
 }
 
+static void vop_crtc_regs_dump(struct drm_crtc *crtc, struct seq_file *s)
+{
+       struct vop *vop = to_vop(crtc);
+       struct drm_crtc_state *crtc_state = crtc->state;
+       int dump_len = vop->len > 0x400 ? 0x400 : vop->len;
+       int i;
+
+       if (!crtc_state->active)
+               return;
+
+       for (i = 0; i < dump_len; i += 4) {
+               if (i % 16 == 0)
+                       DEBUG_PRINT("\n0x%08x: ", i);
+               DEBUG_PRINT("%08x ", vop_readl(vop, i));
+       }
+}
+
+#undef DEBUG_PRINT
+
 static enum drm_mode_status
 vop_crtc_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode *mode,
                    int output_type)
@@ -1549,8 +1607,11 @@ vop_crtc_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode *mode,
 
        if (mode->hdisplay > vop_data->max_output.width)
                return MODE_BAD_HVALUE;
-       if (mode->vdisplay > vop_data->max_output.height)
-               return MODE_BAD_VVALUE;
+
+       if ((mode->flags & DRM_MODE_FLAG_INTERLACE) &&
+           VOP_MAJOR(vop->data->version) == 3 &&
+           VOP_MINOR(vop->data->version) <= 2)
+               return MODE_BAD;
 
        if (mode->flags & DRM_MODE_FLAG_DBLCLK)
                request_clock *= 2;
@@ -1571,9 +1632,9 @@ static const struct rockchip_crtc_funcs private_crtc_funcs = {
        .loader_protect = vop_crtc_loader_protect,
        .enable_vblank = vop_crtc_enable_vblank,
        .disable_vblank = vop_crtc_disable_vblank,
-       .wait_for_update = vop_crtc_wait_for_update,
        .cancel_pending_vblank = vop_crtc_cancel_pending_vblank,
        .debugfs_dump = vop_crtc_debugfs_dump,
+       .regs_dump = vop_crtc_regs_dump,
        .mode_valid = vop_crtc_mode_valid,
 };
 
@@ -1584,8 +1645,7 @@ static bool vop_crtc_mode_fixup(struct drm_crtc *crtc,
        struct vop *vop = to_vop(crtc);
        const struct vop_data *vop_data = vop->data;
 
-       if (mode->hdisplay > vop_data->max_output.width ||
-           mode->vdisplay > vop_data->max_output.height)
+       if (mode->hdisplay > vop_data->max_output.width)
                return false;
 
        drm_mode_set_crtcinfo(adj_mode,
@@ -1621,34 +1681,62 @@ static void vop_crtc_enable(struct drm_crtc *crtc)
        mutex_lock(&vop->vop_lock);
        vop_initial(crtc);
 
-       val = BIT(DCLK_INVERT);
-       val |= (adjusted_mode->flags & DRM_MODE_FLAG_NHSYNC) ?
+       VOP_CTRL_SET(vop, dclk_pol, 1);
+       val = (adjusted_mode->flags & DRM_MODE_FLAG_NHSYNC) ?
                   0 : BIT(HSYNC_POSITIVE);
        val |= (adjusted_mode->flags & DRM_MODE_FLAG_NVSYNC) ?
                   0 : BIT(VSYNC_POSITIVE);
        VOP_CTRL_SET(vop, pin_pol, val);
+
+       if (vop->dclk_source && s->pll && s->pll->pll) {
+               if (clk_set_parent(vop->dclk_source, s->pll->pll))
+                       DRM_DEV_ERROR(vop->dev,
+                                     "failed to set dclk's parents\n");
+       }
+
        switch (s->output_type) {
        case DRM_MODE_CONNECTOR_LVDS:
                VOP_CTRL_SET(vop, rgb_en, 1);
                VOP_CTRL_SET(vop, rgb_pin_pol, val);
+               VOP_CTRL_SET(vop, rgb_dclk_pol, 1);
+               VOP_CTRL_SET(vop, lvds_en, 1);
+               VOP_CTRL_SET(vop, lvds_pin_pol, val);
+               VOP_CTRL_SET(vop, lvds_dclk_pol, 1);
                break;
        case DRM_MODE_CONNECTOR_eDP:
                VOP_CTRL_SET(vop, edp_en, 1);
                VOP_CTRL_SET(vop, edp_pin_pol, val);
+               VOP_CTRL_SET(vop, edp_dclk_pol, 1);
                break;
        case DRM_MODE_CONNECTOR_HDMIA:
                VOP_CTRL_SET(vop, hdmi_en, 1);
                VOP_CTRL_SET(vop, hdmi_pin_pol, val);
+               VOP_CTRL_SET(vop, hdmi_dclk_pol, 1);
                break;
        case DRM_MODE_CONNECTOR_DSI:
                VOP_CTRL_SET(vop, mipi_en, 1);
                VOP_CTRL_SET(vop, mipi_pin_pol, val);
+               VOP_CTRL_SET(vop, mipi_dclk_pol, 1);
                break;
        case DRM_MODE_CONNECTOR_DisplayPort:
-               val &= ~BIT(DCLK_INVERT);
+               VOP_CTRL_SET(vop, dp_dclk_pol, 0);
                VOP_CTRL_SET(vop, dp_pin_pol, val);
                VOP_CTRL_SET(vop, dp_en, 1);
                break;
+       case DRM_MODE_CONNECTOR_TV:
+               if (vdisplay == CVBS_PAL_VDISPLAY)
+                       VOP_CTRL_SET(vop, tve_sw_mode, 1);
+               else
+                       VOP_CTRL_SET(vop, tve_sw_mode, 0);
+
+               VOP_CTRL_SET(vop, tve_dclk_pol, 1);
+               VOP_CTRL_SET(vop, tve_dclk_en, 1);
+               /* use the same pol reg with hdmi */
+               VOP_CTRL_SET(vop, hdmi_pin_pol, val);
+               VOP_CTRL_SET(vop, sw_genlock, 1);
+               VOP_CTRL_SET(vop, sw_uv_offset_en, 1);
+               VOP_CTRL_SET(vop, dither_up, 1);
+               break;
        default:
                DRM_ERROR("unsupport connector_type[%d]\n", s->output_type);
        }
@@ -1667,9 +1755,11 @@ static void vop_crtc_enable(struct drm_crtc *crtc)
                val = DITHER_DOWN_EN(1) | DITHER_DOWN_MODE(RGB888_TO_RGB666);
                break;
        case MEDIA_BUS_FMT_YUV8_1X24:
+       case MEDIA_BUS_FMT_UYYVYY8_0_5X24:
                val = DITHER_DOWN_EN(0) | PRE_DITHER_DOWN_EN(1);
                break;
        case MEDIA_BUS_FMT_YUV10_1X30:
+       case MEDIA_BUS_FMT_UYYVYY10_0_5X30:
                val = DITHER_DOWN_EN(0) | PRE_DITHER_DOWN_EN(0);
                break;
        case MEDIA_BUS_FMT_RGB888_1X24:
@@ -1688,9 +1778,18 @@ static void vop_crtc_enable(struct drm_crtc *crtc)
                     s->output_mode == ROCKCHIP_OUT_MODE_YUV420 ? 1 : 0);
        VOP_CTRL_SET(vop, overlay_mode, is_yuv_output(s->bus_format));
        VOP_CTRL_SET(vop, dsp_out_yuv, is_yuv_output(s->bus_format));
-       VOP_CTRL_SET(vop, dsp_background,
-                    is_yuv_output(s->bus_format) ? 0x20010200 : 0);
 
+       /*
+        * Background color is 10bit depth if vop version >= 3.5
+        */
+       if (!is_yuv_output(s->bus_format))
+               val = 0;
+       else if (VOP_MAJOR(vop->data->version) == 3 &&
+                VOP_MINOR(vop->data->version) >= 5)
+               val = 0x20010200;
+       else
+               val = 0x801080;
+       VOP_CTRL_SET(vop, dsp_background, val);
        VOP_CTRL_SET(vop, htotal_pw, (htotal << 16) | hsync_len);
        val = hact_st << 16;
        val |= hact_end;
@@ -1701,6 +1800,10 @@ static void vop_crtc_enable(struct drm_crtc *crtc)
        val |= vact_end;
        VOP_CTRL_SET(vop, vact_st_end, val);
        VOP_CTRL_SET(vop, vpost_st_end, val);
+
+       VOP_INTR_SET(vop, line_flag_num[0], vact_end);
+       VOP_INTR_SET(vop, line_flag_num[1],
+                    vact_end - us_to_vertical_line(adjusted_mode, 1000));
        if (adjusted_mode->flags & DRM_MODE_FLAG_INTERLACE) {
                u16 vact_st_f1 = vtotal + vact_st + 1;
                u16 vact_end_f1 = vact_st_f1 + vdisplay;
@@ -1723,6 +1826,13 @@ static void vop_crtc_enable(struct drm_crtc *crtc)
        VOP_CTRL_SET(vop, core_dclk_div,
                     !!(adjusted_mode->flags & DRM_MODE_FLAG_DBLCLK));
 
+       VOP_CTRL_SET(vop, cabc_total_num, hdisplay * vdisplay);
+       VOP_CTRL_SET(vop, cabc_config_mode, STAGE_BY_STAGE);
+       VOP_CTRL_SET(vop, cabc_stage_up_mode, MUL_MODE);
+       VOP_CTRL_SET(vop, cabc_scale_cfg_value, 1);
+       VOP_CTRL_SET(vop, cabc_scale_cfg_enable, 0);
+       VOP_CTRL_SET(vop, cabc_global_dn_limit_en, 1);
+
        clk_set_rate(vop->dclk, adjusted_mode->crtc_clock * 1000);
 
        vop_cfg_done(vop);
@@ -1816,6 +1926,40 @@ static int vop_afbdc_atomic_check(struct drm_crtc *crtc,
        return 0;
 }
 
+static void vop_dclk_source_generate(struct drm_crtc *crtc,
+                                    struct drm_crtc_state *crtc_state)
+{
+       struct rockchip_drm_private *private = crtc->dev->dev_private;
+       struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state);
+       struct rockchip_crtc_state *old_s = to_rockchip_crtc_state(crtc->state);
+       struct vop *vop = to_vop(crtc);
+
+       if (!vop->dclk_source)
+               return;
+
+       if (crtc_state->active) {
+               WARN_ON(s->pll && !s->pll->use_count);
+               if (!s->pll || s->pll->use_count > 1 ||
+                   s->output_type != old_s->output_type) {
+                       if (s->pll)
+                               s->pll->use_count--;
+
+                       if (s->output_type != DRM_MODE_CONNECTOR_HDMIA &&
+                           !private->default_pll.use_count)
+                               s->pll = &private->default_pll;
+                       else
+                               s->pll = &private->hdmi_pll;
+
+                       s->pll->use_count++;
+               }
+       } else if (s->pll) {
+               s->pll->use_count--;
+               s->pll = NULL;
+       }
+       if (s->pll && s->pll != old_s->pll)
+               crtc_state->mode_changed = true;
+}
+
 static int vop_crtc_atomic_check(struct drm_crtc *crtc,
                                 struct drm_crtc_state *crtc_state)
 {
@@ -1890,6 +2034,8 @@ static int vop_crtc_atomic_check(struct drm_crtc *crtc,
 
        s->dsp_layer_sel = dsp_layer_sel;
 
+       vop_dclk_source_generate(crtc, crtc_state);
+
 err_free_pzpos:
        kfree(pzpos);
        return ret;
@@ -1923,7 +2069,12 @@ static void vop_post_config(struct drm_crtc *crtc)
        val = scl_cal_scale2(vdisplay, vsize) << 16;
        val |= scl_cal_scale2(hdisplay, hsize);
        VOP_CTRL_SET(vop, post_scl_factor, val);
-       VOP_CTRL_SET(vop, post_scl_ctrl, 0x3);
+
+#define POST_HORIZONTAL_SCALEDOWN_EN(x)                ((x) << 0)
+#define POST_VERTICAL_SCALEDOWN_EN(x)          ((x) << 1)
+       VOP_CTRL_SET(vop, post_scl_ctrl,
+                    POST_HORIZONTAL_SCALEDOWN_EN(hdisplay != hsize) ||
+                    POST_VERTICAL_SCALEDOWN_EN(vdisplay != vsize));
        if (mode->flags & DRM_MODE_FLAG_INTERLACE) {
                u16 vact_st_f1 = vtotal + vact_st + 1;
                u16 vact_end_f1 = vact_st_f1 + vsize;
@@ -1933,6 +2084,86 @@ static void vop_post_config(struct drm_crtc *crtc)
        }
 }
 
+static void vop_update_cabc_lut(struct drm_crtc *crtc,
+                           struct drm_crtc_state *old_crtc_state)
+{
+       struct rockchip_crtc_state *s =
+                       to_rockchip_crtc_state(crtc->state);
+       struct rockchip_crtc_state *old_s =
+                       to_rockchip_crtc_state(old_crtc_state);
+       struct drm_property_blob *cabc_lut = s->cabc_lut;
+       struct drm_property_blob *old_cabc_lut = old_s->cabc_lut;
+       struct vop *vop = to_vop(crtc);
+       int lut_size;
+       u32 *lut;
+       u32 lut_len = vop->cabc_lut_len;
+       int i, dle;
+
+       if (!cabc_lut && old_cabc_lut) {
+               VOP_CTRL_SET(vop, cabc_lut_en, 0);
+               return;
+       }
+       if (!cabc_lut)
+               return;
+
+       if (old_cabc_lut && old_cabc_lut->base.id == cabc_lut->base.id)
+               return;
+
+       lut = (u32 *)cabc_lut->data;
+       lut_size = cabc_lut->length / sizeof(u32);
+       if (WARN(lut_size != lut_len, "Unexpect cabc lut size not match\n"))
+               return;
+
+#define CTRL_GET(name) VOP_CTRL_GET(vop, name)
+       if (CTRL_GET(cabc_lut_en)) {
+               VOP_CTRL_SET(vop, cabc_lut_en, 0);
+               vop_cfg_done(vop);
+               readx_poll_timeout(CTRL_GET, cabc_lut_en, dle, !dle, 5, 33333);
+       }
+
+       for (i = 0; i < lut_len; i++)
+               vop_write_cabc_lut(vop, (i << 2), lut[i]);
+#undef CTRL_GET
+       VOP_CTRL_SET(vop, cabc_lut_en, 1);
+}
+
+static void vop_update_cabc(struct drm_crtc *crtc,
+                           struct drm_crtc_state *old_crtc_state)
+{
+       struct rockchip_crtc_state *s =
+                       to_rockchip_crtc_state(crtc->state);
+       struct vop *vop = to_vop(crtc);
+       struct drm_display_mode *mode = &crtc->state->adjusted_mode;
+       int pixel_total = mode->hdisplay * mode->vdisplay;
+
+       if (!vop->cabc_lut_regs)
+               return;
+
+       vop_update_cabc_lut(crtc, old_crtc_state);
+
+       if (s->cabc_mode != ROCKCHIP_DRM_CABC_MODE_DISABLE) {
+               VOP_CTRL_SET(vop, cabc_en, 1);
+               VOP_CTRL_SET(vop, cabc_handle_en, 1);
+               VOP_CTRL_SET(vop, cabc_stage_up, s->cabc_stage_up);
+               VOP_CTRL_SET(vop, cabc_stage_down, s->cabc_stage_down);
+               VOP_CTRL_SET(vop, cabc_global_dn, s->cabc_global_dn);
+               VOP_CTRL_SET(vop, cabc_calc_pixel_num,
+                            s->cabc_calc_pixel_num * pixel_total / 1000);
+       } else {
+               /*
+                * There are some hardware issues on cabc disabling:
+                *   1: if cabc auto gating enable, cabc disabling will cause
+                *      vop die
+                *   2: cabc disabling always would make timing several
+                *      pixel cycle abnormal, cause some panel abnormal.
+                *
+                * So just keep cabc enable, and make it no work with max
+                * cabc_calc_pixel_num, it only has little power consume.
+                */
+               VOP_CTRL_SET(vop, cabc_calc_pixel_num, pixel_total);
+       }
+}
+
 static void vop_cfg_update(struct drm_crtc *crtc,
                           struct drm_crtc_state *old_crtc_state)
 {
@@ -1961,10 +2192,40 @@ static void vop_cfg_update(struct drm_crtc *crtc,
        spin_unlock(&vop->reg_lock);
 }
 
+static bool vop_fs_irq_is_pending(struct vop *vop)
+{
+       return VOP_INTR_GET_TYPE(vop, status, FS_INTR);
+}
+
+static void vop_wait_for_irq_handler(struct vop *vop)
+{
+       bool pending;
+       int ret;
+
+       /*
+        * Spin until frame start interrupt status bit goes low, which means
+        * that interrupt handler was invoked and cleared it. The timeout of
+        * 10 msecs is really too long, but it is just a safety measure if
+        * something goes really wrong. The wait will only happen in the very
+        * unlikely case of a vblank happening exactly at the same time and
+        * shouldn't exceed microseconds range.
+        */
+       ret = readx_poll_timeout_atomic(vop_fs_irq_is_pending, vop, pending,
+                                       !pending, 0, 10 * 1000);
+       if (ret)
+               DRM_DEV_ERROR(vop->dev, "VOP vblank IRQ stuck for 10 ms\n");
+
+       synchronize_irq(vop->irq);
+}
+
 static void vop_crtc_atomic_flush(struct drm_crtc *crtc,
                                  struct drm_crtc_state *old_crtc_state)
 {
+       struct drm_atomic_state *old_state = old_crtc_state->state;
+       struct drm_plane_state *old_plane_state;
        struct vop *vop = to_vop(crtc);
+       struct drm_plane *plane;
+       int i;
 
        vop_cfg_update(crtc, old_crtc_state);
 
@@ -2009,7 +2270,29 @@ static void vop_crtc_atomic_flush(struct drm_crtc *crtc,
                vop->is_iommu_enabled = true;
        }
 
+       vop_update_cabc(crtc, old_crtc_state);
+
        vop_cfg_done(vop);
+
+       /*
+        * There is a (rather unlikely) possiblity that a vblank interrupt
+        * fired before we set the cfg_done bit. To avoid spuriously
+        * signalling flip completion we need to wait for it to finish.
+        */
+       vop_wait_for_irq_handler(vop);
+
+       for_each_plane_in_state(old_state, plane, old_plane_state, i) {
+               if (!old_plane_state->fb)
+                       continue;
+
+               if (old_plane_state->fb == plane->state->fb)
+                       continue;
+
+               drm_framebuffer_reference(old_plane_state->fb);
+               drm_flip_work_queue(&vop->fb_unref_work, old_plane_state->fb);
+               set_bit(VOP_PENDING_FB_UNREF, &vop->pending);
+               WARN_ON(drm_crtc_vblank_get(crtc) != 0);
+       }
 }
 
 static void vop_crtc_atomic_begin(struct drm_crtc *crtc,
@@ -2043,6 +2326,8 @@ static void vop_crtc_destroy(struct drm_crtc *crtc)
 static void vop_crtc_reset(struct drm_crtc *crtc)
 {
        struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc->state);
+       struct rockchip_drm_private *private = crtc->dev->dev_private;
+       struct vop *vop = to_vop(crtc);
 
        if (crtc->state) {
                __drm_atomic_helper_crtc_destroy_state(crtc, crtc->state);
@@ -2054,6 +2339,20 @@ static void vop_crtc_reset(struct drm_crtc *crtc)
                return;
        crtc->state = &s->base;
        crtc->state->crtc = crtc;
+
+       if (vop->dclk_source) {
+               struct clk *parent;
+
+               parent = clk_get_parent(vop->dclk_source);
+               if (parent) {
+                       if (clk_is_match(private->default_pll.pll, parent))
+                               s->pll = &private->default_pll;
+                       else if (clk_is_match(private->hdmi_pll.pll, parent))
+                               s->pll = &private->hdmi_pll;
+                       if (s->pll)
+                               s->pll->use_count++;
+               }
+       }
        s->left_margin = 100;
        s->right_margin = 100;
        s->top_margin = 100;
@@ -2088,6 +2387,7 @@ static int vop_crtc_atomic_get_property(struct drm_crtc *crtc,
                                        uint64_t *val)
 {
        struct drm_device *drm_dev = crtc->dev;
+       struct rockchip_drm_private *private = drm_dev->dev_private;
        struct drm_mode_config *mode_config = &drm_dev->mode_config;
        struct rockchip_crtc_state *s = to_rockchip_crtc_state(state);
 
@@ -2111,6 +2411,36 @@ static int vop_crtc_atomic_get_property(struct drm_crtc *crtc,
                return 0;
        }
 
+       if (property == private->cabc_mode_property) {
+               *val = s->cabc_mode;
+               return 0;
+       }
+
+       if (property == private->cabc_stage_up_property) {
+               *val = s->cabc_stage_up;
+               return 0;
+       }
+
+       if (property == private->cabc_stage_down_property) {
+               *val = s->cabc_stage_down;
+               return 0;
+       }
+
+       if (property == private->cabc_global_dn_property) {
+               *val = s->cabc_global_dn;
+               return 0;
+       }
+
+       if (property == private->cabc_calc_pixel_num_property) {
+               *val = s->cabc_calc_pixel_num;
+               return 0;
+       }
+
+       if (property == private->cabc_lut_property) {
+               *val = s->cabc_lut ? s->cabc_lut->base.id : 0;
+               return 0;
+       }
+
        DRM_ERROR("failed to get vop crtc property\n");
        return -EINVAL;
 }
@@ -2121,8 +2451,10 @@ static int vop_crtc_atomic_set_property(struct drm_crtc *crtc,
                                        uint64_t val)
 {
        struct drm_device *drm_dev = crtc->dev;
+       struct rockchip_drm_private *private = drm_dev->dev_private;
        struct drm_mode_config *mode_config = &drm_dev->mode_config;
        struct rockchip_crtc_state *s = to_rockchip_crtc_state(state);
+       struct vop *vop = to_vop(crtc);
 
        if (property == mode_config->tv_left_margin_property) {
                s->left_margin = val;
@@ -2144,6 +2476,57 @@ static int vop_crtc_atomic_set_property(struct drm_crtc *crtc,
                return 0;
        }
 
+       if (property == private->cabc_mode_property) {
+               s->cabc_mode = val;
+               /*
+                * Pre-define lowpower and normal mode to make cabc
+                * easier to use.
+                */
+               if (s->cabc_mode == ROCKCHIP_DRM_CABC_MODE_NORMAL) {
+                       s->cabc_stage_up = 257;
+                       s->cabc_stage_down = 255;
+                       s->cabc_global_dn = 192;
+                       s->cabc_calc_pixel_num = 995;
+               } else if (s->cabc_mode == ROCKCHIP_DRM_CABC_MODE_LOWPOWER) {
+                       s->cabc_stage_up = 260;
+                       s->cabc_stage_down = 252;
+                       s->cabc_global_dn = 180;
+                       s->cabc_calc_pixel_num = 992;
+               }
+               return 0;
+       }
+
+       if (property == private->cabc_stage_up_property) {
+               s->cabc_stage_up = val;
+               return 0;
+       }
+
+       if (property == private->cabc_stage_down_property) {
+               s->cabc_stage_down = val;
+               return 0;
+       }
+
+       if (property == private->cabc_calc_pixel_num_property) {
+               s->cabc_calc_pixel_num = val;
+               return 0;
+       }
+
+       if (property == private->cabc_global_dn_property) {
+               s->cabc_global_dn = val;
+               return 0;
+       }
+
+       if (property == private->cabc_lut_property) {
+               bool replaced;
+               ssize_t size = vop->cabc_lut_len * 4;
+
+               return drm_atomic_replace_property_blob_from_id(crtc,
+                                                               &s->cabc_lut,
+                                                               val,
+                                                               size,
+                                                               &replaced);
+       }
+
        DRM_ERROR("failed to set vop crtc property\n");
        return -EINVAL;
 }
@@ -2171,12 +2554,22 @@ static const struct drm_crtc_funcs vop_crtc_funcs = {
        .page_flip = drm_atomic_helper_page_flip,
        .destroy = vop_crtc_destroy,
        .reset = vop_crtc_reset,
+       .set_property = drm_atomic_helper_crtc_set_property,
        .atomic_get_property = vop_crtc_atomic_get_property,
        .atomic_set_property = vop_crtc_atomic_set_property,
        .atomic_duplicate_state = vop_crtc_duplicate_state,
        .atomic_destroy_state = vop_crtc_destroy_state,
 };
 
+static void vop_fb_unref_worker(struct drm_flip_work *work, void *val)
+{
+       struct vop *vop = container_of(work, struct vop, fb_unref_work);
+       struct drm_framebuffer *fb = val;
+
+       drm_crtc_vblank_put(&vop->crtc);
+       drm_framebuffer_unreference(fb);
+}
+
 static void vop_handle_vblank(struct vop *vop)
 {
        struct drm_device *drm = vop->drm_dev;
@@ -2195,8 +2588,9 @@ static void vop_handle_vblank(struct vop *vop)
 
                spin_unlock_irqrestore(&drm->event_lock, flags);
        }
-       if (!completion_done(&vop->wait_update_complete))
-               complete(&vop->wait_update_complete);
+
+       if (test_and_clear_bit(VOP_PENDING_FB_UNREF, &vop->pending))
+               drm_flip_work_commit(&vop->fb_unref_work, system_unbound_wq);
 }
 
 static irqreturn_t vop_isr(int irq, void *data)
@@ -2243,6 +2637,23 @@ static irqreturn_t vop_isr(int irq, void *data)
                ret = IRQ_HANDLED;
        }
 
+#define ERROR_HANDLER(x) \
+       do { \
+               if (active_irqs & x##_INTR) {\
+                       DRM_DEV_ERROR_RATELIMITED(vop->dev, #x " irq err\n"); \
+                       active_irqs &= ~x##_INTR; \
+                       ret = IRQ_HANDLED; \
+               } \
+       } while (0)
+
+       ERROR_HANDLER(BUS_ERROR);
+       ERROR_HANDLER(WIN0_EMPTY);
+       ERROR_HANDLER(WIN1_EMPTY);
+       ERROR_HANDLER(WIN2_EMPTY);
+       ERROR_HANDLER(WIN3_EMPTY);
+       ERROR_HANDLER(HWC_EMPTY);
+       ERROR_HANDLER(POST_BUF_EMPTY);
+
        /* Unhandled irqs are spurious. */
        if (active_irqs)
                DRM_ERROR("Unknown VOP IRQs: %#02x\n", active_irqs);
@@ -2253,6 +2664,7 @@ static irqreturn_t vop_isr(int irq, void *data)
 static int vop_plane_init(struct vop *vop, struct vop_win *win,
                          unsigned long possible_crtcs)
 {
+       struct rockchip_drm_private *private = vop->drm_dev->dev_private;
        struct drm_plane *share = NULL;
        unsigned int rotations = 0;
        struct drm_property *prop;
@@ -2276,9 +2688,17 @@ static int vop_plane_init(struct vop *vop, struct vop_win *win,
        if (VOP_WIN_SUPPORT(vop, win, xmirror))
                rotations |= BIT(DRM_REFLECT_X);
 
-       if (VOP_WIN_SUPPORT(vop, win, ymirror))
+       if (VOP_WIN_SUPPORT(vop, win, ymirror)) {
                rotations |= BIT(DRM_REFLECT_Y);
 
+               prop = drm_property_create_bool(vop->drm_dev,
+                                               DRM_MODE_PROP_ATOMIC,
+                                               "LOGO_YMIRROR");
+               if (!prop)
+                       return -ENOMEM;
+               private->logo_ymirror_prop = prop;
+       }
+
        if (rotations) {
                rotations |= BIT(DRM_ROTATE_0);
                prop = drm_mode_create_rotation_property(vop->drm_dev,
@@ -2308,6 +2728,7 @@ static int vop_create_crtc(struct vop *vop)
        struct device *dev = vop->dev;
        const struct vop_data *vop_data = vop->data;
        struct drm_device *drm_dev = vop->drm_dev;
+       struct rockchip_drm_private *private = drm_dev->dev_private;
        struct drm_plane *primary = NULL, *cursor = NULL, *plane, *tmp;
        struct drm_crtc *crtc = &vop->crtc;
        struct device_node *port;
@@ -2370,8 +2791,10 @@ static int vop_create_crtc(struct vop *vop)
                goto err_cleanup_crtc;
        }
 
+       drm_flip_work_init(&vop->fb_unref_work, "fb_unref",
+                          vop_fb_unref_worker);
+
        init_completion(&vop->dsp_hold_completion);
-       init_completion(&vop->wait_update_complete);
        init_completion(&vop->line_flag_completion);
        crtc->port = port;
        rockchip_register_crtc_funcs(crtc, &private_crtc_funcs);
@@ -2386,8 +2809,16 @@ static int vop_create_crtc(struct vop *vop)
        VOP_ATTACH_MODE_CONFIG_PROP(tv_right_margin_property, 100);
        VOP_ATTACH_MODE_CONFIG_PROP(tv_top_margin_property, 100);
        VOP_ATTACH_MODE_CONFIG_PROP(tv_bottom_margin_property, 100);
+
 #undef VOP_ATTACH_MODE_CONFIG_PROP
 
+       drm_object_attach_property(&crtc->base, private->cabc_lut_property, 0);
+       drm_object_attach_property(&crtc->base, private->cabc_mode_property, 0);
+       drm_object_attach_property(&crtc->base, private->cabc_stage_up_property, 0);
+       drm_object_attach_property(&crtc->base, private->cabc_stage_down_property, 0);
+       drm_object_attach_property(&crtc->base, private->cabc_global_dn_property, 0);
+       drm_object_attach_property(&crtc->base, private->cabc_calc_pixel_num_property, 0);
+
        if (vop_data->feature & VOP_FEATURE_AFBDC)
                feature |= BIT(ROCKCHIP_DRM_CRTC_FEATURE_AFBDC);
        drm_object_attach_property(&crtc->base, vop->feature_prop,
@@ -2453,6 +2884,7 @@ static void vop_destroy_crtc(struct vop *vop)
         * references the CRTC.
         */
        drm_crtc_cleanup(crtc);
+       drm_flip_work_cleanup(&vop->fb_unref_work);
 }
 
 /*
@@ -2617,6 +3049,27 @@ int rockchip_drm_register_notifier_to_dmc(struct devfreq *devfreq)
 }
 EXPORT_SYMBOL(rockchip_drm_register_notifier_to_dmc);
 
+static void vop_backlight_config_done(struct device *dev, bool async)
+{
+       struct vop *vop = dev_get_drvdata(dev);
+
+       if (vop && vop->is_enabled) {
+               int dle;
+
+               vop_cfg_done(vop);
+               if (!async) {
+                       #define CTRL_GET(name) VOP_CTRL_GET(vop, name)
+                       readx_poll_timeout(CTRL_GET, cfg_done,
+                                          dle, !dle, 5, 33333);
+                       #undef CTRL_GET
+               }
+       }
+}
+
+static const struct rockchip_sub_backlight_ops rockchip_sub_backlight_ops = {
+       .config_done = vop_backlight_config_done,
+};
+
 static int vop_bind(struct device *dev, struct device *master, void *data)
 {
        struct platform_device *pdev = to_platform_device(dev);
@@ -2654,17 +3107,21 @@ static int vop_bind(struct device *dev, struct device *master, void *data)
        if (ret)
                return ret;
 
-       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
-       vop->len = resource_size(res);
+       res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "regs");
+       if (!res) {
+               dev_warn(vop->dev, "failed to get vop register byname\n");
+               res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       }
        vop->regs = devm_ioremap_resource(dev, res);
        if (IS_ERR(vop->regs))
                return PTR_ERR(vop->regs);
+       vop->len = resource_size(res);
 
        vop->regsbak = devm_kzalloc(dev, vop->len, GFP_KERNEL);
        if (!vop->regsbak)
                return -ENOMEM;
 
-       res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+       res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "gamma_lut");
        vop->lut_regs = devm_ioremap_resource(dev, res);
        if (IS_ERR(vop->lut_regs)) {
                dev_warn(vop->dev, "failed to get vop lut registers\n");
@@ -2679,6 +3136,22 @@ static int vop_bind(struct device *dev, struct device *master, void *data)
                }
        }
 
+       res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "cabc_lut");
+       vop->cabc_lut_regs = devm_ioremap_resource(dev, res);
+       if (IS_ERR(vop->cabc_lut_regs)) {
+               dev_warn(vop->dev, "failed to get vop cabc lut registers\n");
+               vop->cabc_lut_regs = NULL;
+       }
+
+       if (vop->cabc_lut_regs) {
+               vop->cabc_lut_len = resource_size(res) >> 2;
+               if (vop->cabc_lut_len != 128) {
+                       dev_err(vop->dev, "unsupport cabc lut sizes %d\n",
+                               vop->cabc_lut_len);
+                       return -EINVAL;
+               }
+       }
+
        vop->hclk = devm_clk_get(vop->dev, "hclk_vop");
        if (IS_ERR(vop->hclk)) {
                dev_err(vop->dev, "failed to get hclk source\n");
@@ -2695,6 +3168,16 @@ static int vop_bind(struct device *dev, struct device *master, void *data)
                return PTR_ERR(vop->dclk);
        }
 
+       vop->dclk_source = devm_clk_get(vop->dev, "dclk_source");
+       if (PTR_ERR(vop->dclk_source) == -ENOENT) {
+               vop->dclk_source = NULL;
+       } else if (PTR_ERR(vop->dclk_source) == -EPROBE_DEFER) {
+               return -EPROBE_DEFER;
+       } else if (IS_ERR(vop->dclk_source)) {
+               dev_err(vop->dev, "failed to get dclk source parent\n");
+               return PTR_ERR(vop->dclk_source);
+       }
+
        irq = platform_get_irq(pdev, 0);
        if (irq < 0) {
                dev_err(dev, "cannot find irq for vop\n");
@@ -2722,6 +3205,9 @@ static int vop_bind(struct device *dev, struct device *master, void *data)
 
        pm_runtime_enable(&pdev->dev);
 
+       of_rockchip_drm_sub_backlight_register(dev, &vop->crtc,
+                                              &rockchip_sub_backlight_ops);
+
        dmc_vop = vop;
 
        return 0;