drm/rockchip: vop: fixup post scale configure
[firefly-linux-kernel-4.4.55.git] / drivers / gpu / drm / rockchip / rockchip_drm_vop.c
index a31833260aceea4b8e5e89ec884c411b3329656c..8d7cba2e7b79223a54e0471fe9c2c49426410f12 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,9 @@
 #include "rockchip_drm_gem.h"
 #include "rockchip_drm_fb.h"
 #include "rockchip_drm_vop.h"
+#include "rockchip_drm_backlight.h"
+
+#define MAX_VOPS       2
 
 #define VOP_REG_SUPPORT(vop, reg) \
                (!reg.major || (reg.major == VOP_MAJOR(vop->data->version) && \
@@ -129,6 +134,10 @@ struct vop_zpos {
        int zpos;
 };
 
+enum vop_pending {
+       VOP_PENDING_FB_UNREF,
+};
+
 struct vop_plane_state {
        struct drm_plane_state base;
        int format;
@@ -178,9 +187,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;
@@ -196,6 +209,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;
@@ -218,13 +233,16 @@ struct vop {
        /* vop dclk reset */
        struct reset_control *dclk_rst;
 
-       struct devfreq *devfreq;
        struct notifier_block dmc_nb;
 
+       struct rockchip_dclk_pll *pll;
+
        struct vop_win win[];
 };
 
-struct vop *dmc_vop;
+static struct vop *dmc_vop[MAX_VOPS];
+static struct devfreq *devfreq_vop;
+static DEFINE_MUTEX(register_devfreq_lock);
 
 static inline void vop_writel(struct vop *vop, uint32_t offset, uint32_t v)
 {
@@ -318,11 +336,6 @@ static bool vop_is_allwin_disabled(struct vop *vop)
        return true;
 }
 
-static bool vop_is_cfg_done_complete(struct vop *vop)
-{
-       return VOP_CTRL_GET(vop, cfg_done) ? false : true;
-}
-
 static bool vop_fs_irq_is_active(struct vop *vop)
 {
        return VOP_INTR_GET_TYPE(vop, status, FS_INTR);
@@ -343,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) {
@@ -921,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);
@@ -954,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)
@@ -1391,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,
@@ -1430,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)
 {
@@ -1460,12 +1477,27 @@ static void vop_crtc_cancel_pending_vblank(struct drm_crtc *crtc,
 
 static int vop_crtc_loader_protect(struct drm_crtc *crtc, bool on)
 {
+       struct rockchip_drm_private *private = crtc->dev->dev_private;
        struct vop *vop = to_vop(crtc);
 
        if (on == vop->loader_protect)
                return 0;
 
        if (on) {
+               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))
+                                       vop->pll = &private->default_pll;
+                               else if (clk_is_match(private->hdmi_pll.pll, parent))
+                                       vop->pll = &private->hdmi_pll;
+                               if (vop->pll)
+                                       vop->pll->use_count++;
+                       }
+               }
+
                vop_power_enable(crtc);
                enable_irq(vop->irq);
                drm_crtc_vblank_on(crtc);
@@ -1473,6 +1505,10 @@ static int vop_crtc_loader_protect(struct drm_crtc *crtc, bool on)
        } else {
                vop_crtc_disable(crtc);
 
+               if (vop->dclk_source && vop->pll) {
+                       vop->pll->use_count--;
+                       vop->pll = NULL;
+               }
                vop->loader_protect = false;
        }
 
@@ -1591,6 +1627,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->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;
        clock = clk_round_rate(vop->dclk, request_clock * 1000) / 1000;
@@ -1610,7 +1651,6 @@ 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,
@@ -1660,15 +1700,15 @@ 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))
+       if (vop->dclk_source && vop->pll && vop->pll->pll) {
+               if (clk_set_parent(vop->dclk_source, vop->pll->pll))
                        DRM_DEV_ERROR(vop->dev,
                                      "failed to set dclk's parents\n");
        }
@@ -1677,21 +1717,32 @@ static void vop_crtc_enable(struct drm_crtc *crtc)
        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);
+               VOP_CTRL_SET(vop, mipi_dual_channel_en,
+                       !!(s->output_flags & ROCKCHIP_OUTPUT_DSI_DUAL_CHANNEL));
+               VOP_CTRL_SET(vop, data01_swap,
+                       !!(s->output_flags & ROCKCHIP_OUTPUT_DSI_DUAL_LINK));
                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;
@@ -1750,9 +1801,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;
@@ -1789,6 +1849,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);
@@ -1889,30 +1956,31 @@ static void vop_dclk_source_generate(struct drm_crtc *crtc,
        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);
+       struct rockchip_dclk_pll *old_pll = vop->pll;
 
        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 ||
+               WARN_ON(vop->pll && !vop->pll->use_count);
+               if (!vop->pll || vop->pll->use_count > 1 ||
                    s->output_type != old_s->output_type) {
-                       if (s->pll)
-                               s->pll->use_count--;
+                       if (vop->pll)
+                               vop->pll->use_count--;
 
                        if (s->output_type != DRM_MODE_CONNECTOR_HDMIA &&
                            !private->default_pll.use_count)
-                               s->pll = &private->default_pll;
+                               vop->pll = &private->default_pll;
                        else
-                               s->pll = &private->hdmi_pll;
+                               vop->pll = &private->hdmi_pll;
 
-                       s->pll->use_count++;
+                       vop->pll->use_count++;
                }
-       } else if (s->pll) {
-               s->pll->use_count--;
-               s->pll = NULL;
+       } else if (vop->pll) {
+               vop->pll->use_count--;
+               vop->pll = NULL;
        }
-       if (s->pll && s->pll != old_s->pll)
+       if (vop->pll != old_pll)
                crtc_state->mode_changed = true;
 }
 
@@ -2025,7 +2093,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;
@@ -2035,6 +2108,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)
 {
@@ -2063,10 +2216,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);
 
@@ -2111,20 +2294,44 @@ 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);
-}
 
-static void vop_crtc_atomic_begin(struct drm_crtc *crtc,
-                                 struct drm_crtc_state *old_crtc_state)
-{
-       struct vop *vop = to_vop(crtc);
+       /*
+        * 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);
 
+       spin_lock_irq(&crtc->dev->event_lock);
        if (crtc->state->event) {
                WARN_ON(drm_crtc_vblank_get(crtc) != 0);
+               WARN_ON(vop->event);
 
                vop->event = crtc->state->event;
                crtc->state->event = NULL;
        }
+       spin_unlock_irq(&crtc->dev->event_lock);
+
+       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);
+               WARN_ON(drm_crtc_vblank_get(crtc) != 0);
+               drm_flip_work_queue(&vop->fb_unref_work, old_plane_state->fb);
+               set_bit(VOP_PENDING_FB_UNREF, &vop->pending);
+       }
+}
+
+static void vop_crtc_atomic_begin(struct drm_crtc *crtc,
+                                 struct drm_crtc_state *old_crtc_state)
+{
 }
 
 static const struct drm_crtc_helper_funcs vop_crtc_helper_funcs = {
@@ -2145,8 +2352,6 @@ 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);
@@ -2159,19 +2364,6 @@ static void vop_crtc_reset(struct drm_crtc *crtc)
        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;
@@ -2206,6 +2398,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);
 
@@ -2229,6 +2422,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;
 }
@@ -2239,8 +2462,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;
@@ -2262,6 +2487,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;
 }
@@ -2289,21 +2565,28 @@ 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;
        struct drm_crtc *crtc = &vop->crtc;
        unsigned long flags;
 
-       if (!vop_is_cfg_done_complete(vop))
-               return;
-
        if (vop->event) {
                spin_lock_irqsave(&drm->event_lock, flags);
 
@@ -2313,8 +2596,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)
@@ -2361,6 +2645,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);
@@ -2435,6 +2736,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;
@@ -2497,8 +2799,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);
@@ -2513,8 +2817,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,
@@ -2580,6 +2892,7 @@ static void vop_destroy_crtc(struct vop *vop)
         * references the CRTC.
         */
        drm_crtc_cleanup(crtc);
+       drm_flip_work_cleanup(&vop->fb_unref_work);
 }
 
 /*
@@ -2723,27 +3036,63 @@ EXPORT_SYMBOL(rockchip_drm_wait_line_flag);
 static int dmc_notifier_call(struct notifier_block *nb, unsigned long event,
                             void *data)
 {
+       struct vop *vop = container_of(nb, struct vop, dmc_nb);
+
        if (event == DEVFREQ_PRECHANGE)
-               mutex_lock(&dmc_vop->vop_lock);
+               mutex_lock(&vop->vop_lock);
        else if (event == DEVFREQ_POSTCHANGE)
-               mutex_unlock(&dmc_vop->vop_lock);
+               mutex_unlock(&vop->vop_lock);
 
        return NOTIFY_OK;
 }
 
 int rockchip_drm_register_notifier_to_dmc(struct devfreq *devfreq)
 {
-       if (!dmc_vop)
+       int i, j = 0;
+
+       mutex_lock(&register_devfreq_lock);
+
+       devfreq_vop = devfreq;
+
+       for (i = 0; i < ARRAY_SIZE(dmc_vop); i++) {
+               if (!dmc_vop[i])
+                       continue;
+               dmc_vop[i]->dmc_nb.notifier_call = dmc_notifier_call;
+               devfreq_register_notifier(devfreq_vop, &dmc_vop[i]->dmc_nb,
+                                         DEVFREQ_TRANSITION_NOTIFIER);
+               j++;
+       }
+
+       mutex_unlock(&register_devfreq_lock);
+
+       if (j == 0)
                return -ENOMEM;
 
-       dmc_vop->devfreq = devfreq;
-       dmc_vop->dmc_nb.notifier_call = dmc_notifier_call;
-       devfreq_register_notifier(dmc_vop->devfreq, &dmc_vop->dmc_nb,
-                                 DEVFREQ_TRANSITION_NOTIFIER);
        return 0;
 }
 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);
@@ -2781,17 +3130,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");
@@ -2806,6 +3159,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");
@@ -2859,7 +3228,25 @@ static int vop_bind(struct device *dev, struct device *master, void *data)
 
        pm_runtime_enable(&pdev->dev);
 
-       dmc_vop = vop;
+       of_rockchip_drm_sub_backlight_register(dev, &vop->crtc,
+                                              &rockchip_sub_backlight_ops);
+
+       mutex_lock(&register_devfreq_lock);
+
+       for (i = 0; i < ARRAY_SIZE(dmc_vop); i++) {
+               if (dmc_vop[i])
+                       continue;
+               if (devfreq_vop) {
+                       vop->dmc_nb.notifier_call = dmc_notifier_call;
+                       devfreq_register_notifier(devfreq_vop,
+                                                 &vop->dmc_nb,
+                                                 DEVFREQ_TRANSITION_NOTIFIER);
+               }
+               dmc_vop[i] = vop;
+               break;
+       }
+
+       mutex_unlock(&register_devfreq_lock);
 
        return 0;
 }
@@ -2867,6 +3254,24 @@ static int vop_bind(struct device *dev, struct device *master, void *data)
 static void vop_unbind(struct device *dev, struct device *master, void *data)
 {
        struct vop *vop = dev_get_drvdata(dev);
+       int i;
+
+       mutex_lock(&register_devfreq_lock);
+
+       for (i = 0; i < ARRAY_SIZE(dmc_vop); i++) {
+               if (dmc_vop[i] != vop)
+                       continue;
+               dmc_vop[i] = NULL;
+
+               if (!devfreq_vop)
+                       break;
+               devfreq_unregister_notifier(devfreq_vop,
+                                           &vop->dmc_nb,
+                                           DEVFREQ_TRANSITION_NOTIFIER);
+               break;
+       }
+
+       mutex_unlock(&register_devfreq_lock);
 
        pm_runtime_disable(dev);
        vop_destroy_crtc(vop);