OSDN Git Service

drm/armada: remove temporary crtc state
[uclinux-h8/linux.git] / drivers / gpu / drm / armada / armada_crtc.c
index 03eeee1..0566739 100644 (file)
@@ -11,6 +11,7 @@
 #include <linux/of_device.h>
 #include <linux/platform_device.h>
 #include <drm/drmP.h>
+#include <drm/drm_atomic.h>
 #include <drm/drm_crtc_helper.h>
 #include <drm/drm_plane_helper.h>
 #include <drm/drm_atomic_helper.h>
@@ -294,19 +295,6 @@ static void armada_drm_crtc_complete_frame_work(struct armada_crtc *dcrtc,
        spin_unlock_irqrestore(&dcrtc->irq_lock, flags);
 }
 
-static void armada_drm_crtc_complete_disable_work(struct armada_crtc *dcrtc,
-       struct armada_plane_work *work)
-{
-       unsigned long flags;
-
-       if (dcrtc->plane == work->plane)
-               dcrtc->plane = NULL;
-
-       spin_lock_irqsave(&dcrtc->irq_lock, flags);
-       armada_drm_crtc_update_regs(dcrtc, work->regs);
-       spin_unlock_irqrestore(&dcrtc->irq_lock, flags);
-}
-
 static struct armada_plane_work *
 armada_drm_crtc_alloc_plane_work(struct drm_plane *plane)
 {
@@ -325,38 +313,6 @@ armada_drm_crtc_alloc_plane_work(struct drm_plane *plane)
        return work;
 }
 
-static void armada_drm_crtc_finish_fb(struct armada_crtc *dcrtc,
-       struct drm_framebuffer *fb, bool force)
-{
-       struct armada_plane_work *work;
-
-       if (!fb)
-               return;
-
-       if (force) {
-               /* Display is disabled, so just drop the old fb */
-               drm_framebuffer_put(fb);
-               return;
-       }
-
-       work = armada_drm_crtc_alloc_plane_work(dcrtc->crtc.primary);
-       if (work) {
-               work->old_fb = fb;
-
-               if (armada_drm_plane_work_queue(dcrtc, work) == 0)
-                       return;
-
-               kfree(work);
-       }
-
-       /*
-        * Oops - just drop the reference immediately and hope for
-        * the best.  The worst that will happen is the buffer gets
-        * reused before it has finished being displayed.
-        */
-       drm_framebuffer_put(fb);
-}
-
 static void armada_drm_vblank_off(struct armada_crtc *dcrtc)
 {
        /*
@@ -399,6 +355,7 @@ static void armada_drm_crtc_prepare(struct drm_crtc *crtc)
 {
        struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
        struct drm_plane *plane;
+       u32 val;
 
        /*
         * If we have an overlay plane associated with this CRTC, disable
@@ -411,6 +368,18 @@ static void armada_drm_crtc_prepare(struct drm_crtc *crtc)
                WARN_ON(!armada_drm_plane_work_wait(drm_to_armada_plane(plane),
                                                    HZ));
        }
+
+       /* Wait for pending flips to complete */
+       armada_drm_plane_work_wait(drm_to_armada_plane(dcrtc->crtc.primary),
+                                  MAX_SCHEDULE_TIMEOUT);
+
+       drm_crtc_vblank_off(crtc);
+
+       val = dcrtc->dumb_ctrl & ~CFG_DUMB_ENA;
+       if (val != dcrtc->dumb_ctrl) {
+               dcrtc->dumb_ctrl = val;
+               writel_relaxed(val, dcrtc->base + LCD_SPU_DUMB_CTRL);
+       }
 }
 
 /* The mode_config.mutex will be held for this call */
@@ -418,10 +387,9 @@ static void armada_drm_crtc_commit(struct drm_crtc *crtc)
 {
        struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
 
-       if (dcrtc->dpms != DRM_MODE_DPMS_ON) {
-               dcrtc->dpms = DRM_MODE_DPMS_ON;
-               armada_drm_crtc_update(dcrtc);
-       }
+       dcrtc->dpms = DRM_MODE_DPMS_ON;
+       armada_drm_crtc_update(dcrtc);
+       drm_crtc_vblank_on(crtc);
 }
 
 /* The mode_config.mutex will be held for this call */
@@ -519,8 +487,9 @@ static irqreturn_t armada_drm_irq(int irq, void *arg)
        u32 v, stat = readl_relaxed(dcrtc->base + LCD_SPU_IRQ_ISR);
 
        /*
-        * This is rediculous - rather than writing bits to clear, we
-        * have to set the actual status register value.  This is racy.
+        * Reading the ISR appears to clear bits provided CLEAN_SPU_IRQ_ISR
+        * is set.  Writing has some other effect to acknowledge the IRQ -
+        * without this, we only get a single IRQ.
         */
        writel_relaxed(0, dcrtc->base + LCD_SPU_IRQ_ISR);
 
@@ -572,71 +541,16 @@ static uint32_t armada_drm_crtc_calculate_csc(struct armada_crtc *dcrtc)
        return val;
 }
 
-static void armada_drm_gra_plane_regs(struct armada_regs *regs,
-       struct drm_framebuffer *fb, struct armada_plane_state *state,
-       int x, int y, bool interlaced)
-{
-       unsigned int i;
-       u32 ctrl0;
-
-       i = armada_drm_crtc_calc_fb(fb, x, y, regs, interlaced);
-       armada_reg_queue_set(regs, i, state->dst_yx, LCD_SPU_GRA_OVSA_HPXL_VLN);
-       armada_reg_queue_set(regs, i, state->src_hw, LCD_SPU_GRA_HPXL_VLN);
-       armada_reg_queue_set(regs, i, state->dst_hw, LCD_SPU_GZM_HPXL_VLN);
-
-       ctrl0 = state->ctrl0;
-       if (interlaced)
-               ctrl0 |= CFG_GRA_FTOGGLE;
-
-       armada_reg_queue_mod(regs, i, ctrl0, CFG_GRAFORMAT |
-                            CFG_GRA_MOD(CFG_SWAPRB | CFG_SWAPUV |
-                                        CFG_SWAPYU | CFG_YUV2RGB) |
-                            CFG_PALETTE_ENA | CFG_GRA_FTOGGLE |
-                            CFG_GRA_HSMOOTH | CFG_GRA_ENA,
-                            LCD_SPU_DMA_CTRL0);
-       armada_reg_queue_end(regs, i);
-}
-
-static void armada_drm_primary_set(struct drm_crtc *crtc,
-       struct drm_plane *plane, int x, int y)
-{
-       struct armada_plane_state *state = &drm_to_armada_plane(plane)->state;
-       struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
-       struct armada_regs regs[8];
-       bool interlaced = dcrtc->interlaced;
-
-       armada_drm_gra_plane_regs(regs, plane->fb, state, x, y, interlaced);
-       armada_drm_crtc_update_regs(dcrtc, regs);
-}
-
 /* The mode_config.mutex will be held for this call */
-static int armada_drm_crtc_mode_set(struct drm_crtc *crtc,
-       struct drm_display_mode *mode, struct drm_display_mode *adj,
-       int x, int y, struct drm_framebuffer *old_fb)
+static void armada_drm_crtc_mode_set_nofb(struct drm_crtc *crtc)
 {
+       struct drm_display_mode *adj = &crtc->state->adjusted_mode;
        struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
        struct armada_regs regs[17];
        uint32_t lm, rm, tm, bm, val, sclk;
        unsigned long flags;
        unsigned i;
-       bool interlaced;
-
-       drm_framebuffer_get(crtc->primary->fb);
-
-       interlaced = !!(adj->flags & DRM_MODE_FLAG_INTERLACE);
-
-       val = CFG_GRA_ENA;
-       val |= CFG_GRA_FMT(drm_fb_to_armada_fb(dcrtc->crtc.primary->fb)->fmt);
-       val |= CFG_GRA_MOD(drm_fb_to_armada_fb(dcrtc->crtc.primary->fb)->mod);
-
-       if (drm_fb_to_armada_fb(dcrtc->crtc.primary->fb)->fmt > CFG_420)
-               val |= CFG_PALETTE_ENA;
-
-       drm_to_armada_plane(crtc->primary)->state.ctrl0 = val;
-       drm_to_armada_plane(crtc->primary)->state.src_hw =
-       drm_to_armada_plane(crtc->primary)->state.dst_hw =
-               adj->crtc_vdisplay << 16 | adj->crtc_hdisplay;
-       drm_to_armada_plane(crtc->primary)->state.dst_yx = 0;
+       bool interlaced = !!(adj->flags & DRM_MODE_FLAG_INTERLACE);
 
        i = 0;
        rm = adj->crtc_hsync_start - adj->crtc_hdisplay;
@@ -655,18 +569,6 @@ static int armada_drm_crtc_mode_set(struct drm_crtc *crtc,
                adj->crtc_vsync_end,
                adj->crtc_vtotal, tm, bm);
 
-       /* Wait for pending flips to complete */
-       armada_drm_plane_work_wait(drm_to_armada_plane(dcrtc->crtc.primary),
-                                  MAX_SCHEDULE_TIMEOUT);
-
-       drm_crtc_vblank_off(crtc);
-
-       val = dcrtc->dumb_ctrl & ~CFG_DUMB_ENA;
-       if (val != dcrtc->dumb_ctrl) {
-               dcrtc->dumb_ctrl = val;
-               writel_relaxed(val, dcrtc->base + LCD_SPU_DUMB_CTRL);
-       }
-
        /*
         * If we are blanked, we would have disabled the clock.  Re-enable
         * it so that compute_clock() does the right thing.
@@ -689,9 +591,6 @@ static int armada_drm_crtc_mode_set(struct drm_crtc *crtc,
 
        spin_lock_irqsave(&dcrtc->irq_lock, flags);
 
-       /* Ensure graphic fifo is enabled */
-       armada_reg_queue_mod(regs, i, 0, CFG_PDWN64x66, LCD_SPU_SRAM_PARA1);
-
        /* Even interlaced/progressive frame */
        dcrtc->v[1].spu_v_h_total = adj->crtc_vtotal << 16 |
                                    adj->crtc_htotal;
@@ -734,53 +633,48 @@ static int armada_drm_crtc_mode_set(struct drm_crtc *crtc,
        armada_reg_queue_end(regs, i);
 
        armada_drm_crtc_update_regs(dcrtc, regs);
-
-       armada_drm_primary_set(crtc, crtc->primary, x, y);
        spin_unlock_irqrestore(&dcrtc->irq_lock, flags);
-
-       armada_drm_crtc_update(dcrtc);
-
-       drm_crtc_vblank_on(crtc);
-       armada_drm_crtc_finish_fb(dcrtc, old_fb, dpms_blanked(dcrtc->dpms));
-
-       return 0;
 }
 
 /* The mode_config.mutex will be held for this call */
-static int armada_drm_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
-       struct drm_framebuffer *old_fb)
+static void armada_drm_crtc_disable(struct drm_crtc *crtc)
 {
-       struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
-       struct armada_regs regs[4];
-       unsigned i;
-
-       i = armada_drm_crtc_calc_fb(crtc->primary->fb, crtc->x, crtc->y, regs,
-                                   dcrtc->interlaced);
-       armada_reg_queue_end(regs, i);
+       armada_drm_crtc_dpms(crtc, DRM_MODE_DPMS_OFF);
 
-       /* Wait for pending flips to complete */
-       armada_drm_plane_work_wait(drm_to_armada_plane(dcrtc->crtc.primary),
-                                  MAX_SCHEDULE_TIMEOUT);
+       /* Disable our primary plane when we disable the CRTC. */
+       crtc->primary->funcs->disable_plane(crtc->primary, NULL);
+}
 
-       /* Take a reference to the new fb as we're using it */
-       drm_framebuffer_get(crtc->primary->fb);
+static void armada_drm_crtc_atomic_begin(struct drm_crtc *crtc,
+                                        struct drm_crtc_state *old_crtc_state)
+{
+       struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
+       struct armada_plane *dplane;
 
-       /* Update the base in the CRTC */
-       armada_drm_crtc_update_regs(dcrtc, regs);
+       DRM_DEBUG_KMS("[CRTC:%d:%s]\n", crtc->base.id, crtc->name);
 
-       /* Drop our previously held reference */
-       armada_drm_crtc_finish_fb(dcrtc, old_fb, dpms_blanked(dcrtc->dpms));
+       /* Wait 100ms for any plane works to complete */
+       dplane = drm_to_armada_plane(crtc->primary);
+       if (WARN_ON(armada_drm_plane_work_wait(dplane, HZ / 10) == 0))
+               armada_drm_plane_work_cancel(dcrtc, dplane);
 
-       return 0;
+       dcrtc->regs_idx = 0;
+       dcrtc->regs = dcrtc->atomic_regs;
 }
 
-/* The mode_config.mutex will be held for this call */
-static void armada_drm_crtc_disable(struct drm_crtc *crtc)
+static void armada_drm_crtc_atomic_flush(struct drm_crtc *crtc,
+                                        struct drm_crtc_state *old_crtc_state)
 {
-       armada_drm_crtc_dpms(crtc, DRM_MODE_DPMS_OFF);
+       struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
+       unsigned long flags;
 
-       /* Disable our primary plane when we disable the CRTC. */
-       crtc->primary->funcs->disable_plane(crtc->primary, NULL);
+       DRM_DEBUG_KMS("[CRTC:%d:%s]\n", crtc->base.id, crtc->name);
+
+       armada_reg_queue_end(dcrtc->regs, dcrtc->regs_idx);
+
+       spin_lock_irqsave(&dcrtc->irq_lock, flags);
+       armada_drm_crtc_update_regs(dcrtc, dcrtc->regs);
+       spin_unlock_irqrestore(&dcrtc->irq_lock, flags);
 }
 
 static const struct drm_crtc_helper_funcs armada_crtc_helper_funcs = {
@@ -788,9 +682,12 @@ static const struct drm_crtc_helper_funcs armada_crtc_helper_funcs = {
        .prepare        = armada_drm_crtc_prepare,
        .commit         = armada_drm_crtc_commit,
        .mode_fixup     = armada_drm_crtc_mode_fixup,
-       .mode_set       = armada_drm_crtc_mode_set,
-       .mode_set_base  = armada_drm_crtc_mode_set_base,
+       .mode_set       = drm_helper_crtc_mode_set,
+       .mode_set_nofb  = armada_drm_crtc_mode_set_nofb,
+       .mode_set_base  = drm_helper_crtc_mode_set_base,
        .disable        = armada_drm_crtc_disable,
+       .atomic_begin   = armada_drm_crtc_atomic_begin,
+       .atomic_flush   = armada_drm_crtc_atomic_flush,
 };
 
 static void armada_load_cursor_argb(void __iomem *base, uint32_t *pix,
@@ -1030,59 +927,81 @@ static void armada_drm_crtc_destroy(struct drm_crtc *crtc)
  * and a mode_set.
  */
 static int armada_drm_crtc_page_flip(struct drm_crtc *crtc,
-       struct drm_framebuffer *fb, struct drm_pending_vblank_event *event, uint32_t page_flip_flags,
-       struct drm_modeset_acquire_ctx *ctx)
+       struct drm_framebuffer *fb, struct drm_pending_vblank_event *event,
+       uint32_t page_flip_flags, struct drm_modeset_acquire_ctx *ctx)
 {
        struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
+       struct drm_plane *plane = crtc->primary;
+       const struct drm_plane_helper_funcs *plane_funcs;
+       struct drm_plane_state *state;
        struct armada_plane_work *work;
-       unsigned i;
        int ret;
 
-       /* We don't support changing the pixel format */
-       if (fb->format != crtc->primary->fb->format)
-               return -EINVAL;
-
-       work = armada_drm_crtc_alloc_plane_work(dcrtc->crtc.primary);
-       if (!work)
+       /* Construct new state for the primary plane */
+       state = drm_atomic_helper_plane_duplicate_state(plane);
+       if (!state)
                return -ENOMEM;
 
-       work->event = event;
-       work->old_fb = dcrtc->crtc.primary->fb;
+       drm_atomic_set_fb_for_plane(state, fb);
 
-       i = armada_drm_crtc_calc_fb(fb, crtc->x, crtc->y, work->regs,
-                                   dcrtc->interlaced);
-       armada_reg_queue_end(work->regs, i);
+       work = armada_drm_crtc_alloc_plane_work(plane);
+       if (!work) {
+               ret = -ENOMEM;
+               goto put_state;
+       }
+
+       /* Make sure we can get vblank interrupts */
+       ret = drm_crtc_vblank_get(crtc);
+       if (ret)
+               goto put_work;
 
        /*
-        * Ensure that we hold a reference on the new framebuffer.
-        * This has to match the behaviour in mode_set.
+        * If we have another work pending, we can't process this flip.
+        * The modeset locks protect us from another user queuing a work
+        * while we're setting up.
         */
-       drm_framebuffer_get(fb);
-
-       ret = armada_drm_plane_work_queue(dcrtc, work);
-       if (ret) {
-               /* Undo our reference above */
-               drm_framebuffer_put(fb);
-               kfree(work);
-               return ret;
+       if (drm_to_armada_plane(plane)->work) {
+               ret = -EBUSY;
+               goto put_vblank;
        }
 
+       work->event = event;
+       work->old_fb = plane->state->fb;
+
        /*
-        * Don't take a reference on the new framebuffer;
-        * drm_mode_page_flip_ioctl() has already grabbed a reference and
-        * will _not_ drop that reference on successful return from this
-        * function.  Simply mark this new framebuffer as the current one.
+        * Hold a ref on the new fb while it's being displayed by the
+        * hardware. The old fb refcount will be released in the worker.
         */
-       dcrtc->crtc.primary->fb = fb;
+       drm_framebuffer_get(state->fb);
+
+       /* Point of no return */
+       swap(plane->state, state);
+
+       dcrtc->regs_idx = 0;
+       dcrtc->regs = work->regs;
+
+       plane_funcs = plane->helper_private;
+       plane_funcs->atomic_update(plane, state);
+       armada_reg_queue_end(dcrtc->regs, dcrtc->regs_idx);
+
+       /* Queue the work - this should never fail */
+       WARN_ON(armada_drm_plane_work_queue(dcrtc, work));
+       work = NULL;
 
        /*
         * Finally, if the display is blanked, we won't receive an
         * interrupt, so complete it now.
         */
        if (dpms_blanked(dcrtc->dpms))
-               armada_drm_plane_work_run(dcrtc, dcrtc->crtc.primary);
-
-       return 0;
+               armada_drm_plane_work_run(dcrtc, plane);
+
+put_vblank:
+       drm_crtc_vblank_put(crtc);
+put_work:
+       kfree(work);
+put_state:
+       drm_atomic_helper_plane_destroy_state(plane, state);
+       return ret;
 }
 
 static int
@@ -1116,31 +1035,87 @@ armada_drm_crtc_set_property(struct drm_crtc *crtc,
 static int armada_drm_crtc_enable_vblank(struct drm_crtc *crtc)
 {
        struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
+       unsigned long flags;
 
+       spin_lock_irqsave(&dcrtc->irq_lock, flags);
        armada_drm_crtc_enable_irq(dcrtc, VSYNC_IRQ_ENA);
+       spin_unlock_irqrestore(&dcrtc->irq_lock, flags);
        return 0;
 }
 
 static void armada_drm_crtc_disable_vblank(struct drm_crtc *crtc)
 {
        struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
+       unsigned long flags;
 
+       spin_lock_irqsave(&dcrtc->irq_lock, flags);
        armada_drm_crtc_disable_irq(dcrtc, VSYNC_IRQ_ENA);
+       spin_unlock_irqrestore(&dcrtc->irq_lock, flags);
 }
 
 static const struct drm_crtc_funcs armada_crtc_funcs = {
+       .reset          = drm_atomic_helper_crtc_reset,
        .cursor_set     = armada_drm_crtc_cursor_set,
        .cursor_move    = armada_drm_crtc_cursor_move,
        .destroy        = armada_drm_crtc_destroy,
        .set_config     = drm_crtc_helper_set_config,
        .page_flip      = armada_drm_crtc_page_flip,
        .set_property   = armada_drm_crtc_set_property,
+       .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
+       .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
        .enable_vblank  = armada_drm_crtc_enable_vblank,
        .disable_vblank = armada_drm_crtc_disable_vblank,
 };
 
-static void armada_drm_primary_update_state(struct drm_plane_state *state,
-       struct armada_regs *regs)
+int armada_drm_plane_prepare_fb(struct drm_plane *plane,
+       struct drm_plane_state *state)
+{
+       DRM_DEBUG_KMS("[PLANE:%d:%s] [FB:%d]\n",
+               plane->base.id, plane->name,
+               state->fb ? state->fb->base.id : 0);
+
+       /*
+        * Take a reference on the new framebuffer - we want to
+        * hold on to it while the hardware is displaying it.
+        */
+       if (state->fb)
+               drm_framebuffer_get(state->fb);
+       return 0;
+}
+
+void armada_drm_plane_cleanup_fb(struct drm_plane *plane,
+       struct drm_plane_state *old_state)
+{
+       DRM_DEBUG_KMS("[PLANE:%d:%s] [FB:%d]\n",
+               plane->base.id, plane->name,
+               old_state->fb ? old_state->fb->base.id : 0);
+
+       if (old_state->fb)
+               drm_framebuffer_put(old_state->fb);
+}
+
+int armada_drm_plane_atomic_check(struct drm_plane *plane,
+       struct drm_plane_state *state)
+{
+       if (state->fb && !WARN_ON(!state->crtc)) {
+               struct drm_crtc *crtc = state->crtc;
+               struct drm_crtc_state *crtc_state;
+
+               if (state->state)
+                       crtc_state = drm_atomic_get_existing_crtc_state(state->state, crtc);
+               else
+                       crtc_state = crtc->state;
+               return drm_atomic_helper_check_plane_state(state, crtc_state,
+                                                          0, INT_MAX,
+                                                          true, false);
+       } else {
+               state->visible = false;
+       }
+       return 0;
+}
+
+static unsigned int armada_drm_primary_update_state(
+       struct drm_plane_state *state, struct armada_regs *regs)
 {
        struct armada_plane *dplane = drm_to_armada_plane(state->plane);
        struct armada_crtc *dcrtc = drm_to_armada_crtc(state->crtc);
@@ -1156,6 +1131,8 @@ static void armada_drm_primary_update_state(struct drm_plane_state *state,
                val |= CFG_GRA_ENA;
        if (drm_rect_width(&state->src) >> 16 != drm_rect_width(&state->dst))
                val |= CFG_GRA_HSMOOTH;
+       if (dcrtc->interlaced)
+               val |= CFG_GRA_FTOGGLE;
 
        was_disabled = !(dplane->state.ctrl0 & CFG_GRA_ENA);
        if (was_disabled)
@@ -1163,163 +1140,103 @@ static void armada_drm_primary_update_state(struct drm_plane_state *state,
                                     0, CFG_PDWN64x66, LCD_SPU_SRAM_PARA1);
 
        dplane->state.ctrl0 = val;
-       dplane->state.src_hw = (drm_rect_height(&state->src) & 0xffff0000) |
-                               drm_rect_width(&state->src) >> 16;
-       dplane->state.dst_hw = drm_rect_height(&state->dst) << 16 |
-                              drm_rect_width(&state->dst);
-       dplane->state.dst_yx = state->dst.y1 << 16 | state->dst.x1;
-
-       armada_drm_gra_plane_regs(regs + idx, &dfb->fb, &dplane->state,
-                                 state->src.x1 >> 16, state->src.y1 >> 16,
-                                 dcrtc->interlaced);
+       dplane->state.src_hw = armada_rect_hw_fp(&state->src);
+       dplane->state.dst_hw = armada_rect_hw(&state->dst);
+       dplane->state.dst_yx = armada_rect_yx(&state->dst);
+
+       idx += armada_drm_crtc_calc_fb(&dfb->fb, state->src.x1 >> 16,
+                                      state->src.y1 >> 16, regs + idx,
+                                      dcrtc->interlaced);
+       armada_reg_queue_set(regs, idx, dplane->state.dst_yx,
+                            LCD_SPU_GRA_OVSA_HPXL_VLN);
+       armada_reg_queue_set(regs, idx, dplane->state.src_hw,
+                            LCD_SPU_GRA_HPXL_VLN);
+       armada_reg_queue_set(regs, idx, dplane->state.dst_hw,
+                            LCD_SPU_GZM_HPXL_VLN);
+       armada_reg_queue_mod(regs, idx, dplane->state.ctrl0, CFG_GRAFORMAT |
+                            CFG_GRA_MOD(CFG_SWAPRB | CFG_SWAPUV |
+                                        CFG_SWAPYU | CFG_YUV2RGB) |
+                            CFG_PALETTE_ENA | CFG_GRA_FTOGGLE |
+                            CFG_GRA_HSMOOTH | CFG_GRA_ENA,
+                            LCD_SPU_DMA_CTRL0);
 
        dplane->state.vsync_update = !was_disabled;
        dplane->state.changed = true;
+
+       return idx;
 }
 
-static int armada_drm_primary_update(struct drm_plane *plane,
-       struct drm_crtc *crtc, struct drm_framebuffer *fb,
-       int crtc_x, int crtc_y, unsigned int crtc_w, unsigned int crtc_h,
-       uint32_t src_x, uint32_t src_y, uint32_t src_w, uint32_t src_h,
-       struct drm_modeset_acquire_ctx *ctx)
+static void armada_drm_primary_plane_atomic_update(struct drm_plane *plane,
+       struct drm_plane_state *old_state)
 {
-       struct armada_plane *dplane = drm_to_armada_plane(plane);
-       struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
-       struct armada_plane_work *work;
-       struct drm_plane_state state = {
-               .plane = plane,
-               .crtc = crtc,
-               .fb = fb,
-               .src_x = src_x,
-               .src_y = src_y,
-               .src_w = src_w,
-               .src_h = src_h,
-               .crtc_x = crtc_x,
-               .crtc_y = crtc_y,
-               .crtc_w = crtc_w,
-               .crtc_h = crtc_h,
-               .rotation = DRM_MODE_ROTATE_0,
-       };
-       struct drm_crtc_state crtc_state = {
-               .crtc = crtc,
-               .enable = crtc->enabled,
-               .mode = crtc->mode,
-       };
-       int ret;
-
-       ret = drm_atomic_helper_check_plane_state(&state, &crtc_state, 0,
-                                                 INT_MAX, true, false);
-       if (ret)
-               return ret;
-
-       work = &dplane->works[dplane->next_work];
-       work->fn = armada_drm_crtc_complete_frame_work;
-
-       if (plane->fb != fb) {
-               /*
-                * Take a reference on the new framebuffer - we want to
-                * hold on to it while the hardware is displaying it.
-                */
-               drm_framebuffer_reference(fb);
-
-               work->old_fb = plane->fb;
-       } else {
-               work->old_fb = NULL;
-       }
-
-       armada_drm_primary_update_state(&state, work->regs);
+       struct drm_plane_state *state = plane->state;
+       struct armada_crtc *dcrtc;
+       struct armada_regs *regs;
 
-       if (!dplane->state.changed)
-               return 0;
+       DRM_DEBUG_KMS("[PLANE:%d:%s]\n", plane->base.id, plane->name);
 
-       /* Wait for pending work to complete */
-       if (armada_drm_plane_work_wait(dplane, HZ / 10) == 0)
-               armada_drm_plane_work_cancel(dcrtc, dplane);
+       if (!state->fb || WARN_ON(!state->crtc))
+               return;
 
-       if (!dplane->state.vsync_update) {
-               work->fn(dcrtc, work);
-               if (work->old_fb)
-                       drm_framebuffer_unreference(work->old_fb);
-               return 0;
-       }
+       DRM_DEBUG_KMS("[PLANE:%d:%s] is on [CRTC:%d:%s] with [FB:%d] visible %u->%u\n",
+               plane->base.id, plane->name,
+               state->crtc->base.id, state->crtc->name,
+               state->fb->base.id,
+               old_state->visible, state->visible);
 
-       /* Queue it for update on the next interrupt if we are enabled */
-       ret = armada_drm_plane_work_queue(dcrtc, work);
-       if (ret) {
-               work->fn(dcrtc, work);
-               if (work->old_fb)
-                       drm_framebuffer_unreference(work->old_fb);
-       }
-
-       dplane->next_work = !dplane->next_work;
+       dcrtc = drm_to_armada_crtc(state->crtc);
+       regs = dcrtc->regs + dcrtc->regs_idx;
 
-       return 0;
+       dcrtc->regs_idx += armada_drm_primary_update_state(state, regs);
 }
 
-int armada_drm_plane_disable(struct drm_plane *plane,
-                            struct drm_modeset_acquire_ctx *ctx)
+static void armada_drm_primary_plane_atomic_disable(struct drm_plane *plane,
+       struct drm_plane_state *old_state)
 {
        struct armada_plane *dplane = drm_to_armada_plane(plane);
        struct armada_crtc *dcrtc;
-       struct armada_plane_work *work;
+       struct armada_regs *regs;
        unsigned int idx = 0;
-       u32 sram_para1, enable_mask;
-
-       if (!plane->crtc)
-               return 0;
 
-       /*
-        * Arrange to power down most RAMs and FIFOs if this is the primary
-        * plane, otherwise just the YUV FIFOs for the overlay plane.
-        */
-       if (plane->type == DRM_PLANE_TYPE_PRIMARY) {
-               sram_para1 = CFG_PDWN256x32 | CFG_PDWN256x24 | CFG_PDWN256x8 |
-                            CFG_PDWN32x32 | CFG_PDWN64x66;
-               enable_mask = CFG_GRA_ENA;
-       } else {
-               sram_para1 = CFG_PDWN16x66 | CFG_PDWN32x66;
-               enable_mask = CFG_DMA_ENA;
-       }
+       DRM_DEBUG_KMS("[PLANE:%d:%s]\n", plane->base.id, plane->name);
 
-       dplane->state.ctrl0 &= ~enable_mask;
+       if (!old_state->crtc)
+               return;
 
-       dcrtc = drm_to_armada_crtc(plane->crtc);
+       DRM_DEBUG_KMS("[PLANE:%d:%s] was on [CRTC:%d:%s] with [FB:%d]\n",
+               plane->base.id, plane->name,
+               old_state->crtc->base.id, old_state->crtc->name,
+               old_state->fb->base.id);
 
-       /*
-        * Try to disable the plane and drop our ref on the framebuffer
-        * at the next frame update. If we fail for any reason, disable
-        * the plane immediately.
-        */
-       work = &dplane->works[dplane->next_work];
-       work->fn = armada_drm_crtc_complete_disable_work;
-       work->cancel = armada_drm_crtc_complete_disable_work;
-       work->old_fb = plane->fb;
-
-       armada_reg_queue_mod(work->regs, idx,
-                            0, enable_mask, LCD_SPU_DMA_CTRL0);
-       armada_reg_queue_mod(work->regs, idx,
-                            sram_para1, 0, LCD_SPU_SRAM_PARA1);
-       armada_reg_queue_end(work->regs, idx);
-
-       /* Wait for any preceding work to complete, but don't wedge */
-       if (WARN_ON(!armada_drm_plane_work_wait(dplane, HZ)))
-               armada_drm_plane_work_cancel(dcrtc, dplane);
+       dplane->state.ctrl0 &= ~CFG_GRA_ENA;
 
-       if (armada_drm_plane_work_queue(dcrtc, work)) {
-               work->fn(dcrtc, work);
-               if (work->old_fb)
-                       drm_framebuffer_unreference(work->old_fb);
-       }
+       dcrtc = drm_to_armada_crtc(old_state->crtc);
+       regs = dcrtc->regs + dcrtc->regs_idx;
 
-       dplane->next_work = !dplane->next_work;
+       /* Disable plane and power down most RAMs and FIFOs */
+       armada_reg_queue_mod(regs, idx, 0, CFG_GRA_ENA, LCD_SPU_DMA_CTRL0);
+       armada_reg_queue_mod(regs, idx, CFG_PDWN256x32 | CFG_PDWN256x24 |
+                            CFG_PDWN256x8 | CFG_PDWN32x32 | CFG_PDWN64x66,
+                            0, LCD_SPU_SRAM_PARA1);
 
-       return 0;
+       dcrtc->regs_idx += idx;
 }
 
+static const struct drm_plane_helper_funcs armada_primary_plane_helper_funcs = {
+       .prepare_fb     = armada_drm_plane_prepare_fb,
+       .cleanup_fb     = armada_drm_plane_cleanup_fb,
+       .atomic_check   = armada_drm_plane_atomic_check,
+       .atomic_update  = armada_drm_primary_plane_atomic_update,
+       .atomic_disable = armada_drm_primary_plane_atomic_disable,
+};
+
 static const struct drm_plane_funcs armada_primary_plane_funcs = {
-       .update_plane   = armada_drm_primary_update,
-       .disable_plane  = armada_drm_plane_disable,
+       .update_plane   = drm_plane_helper_update,
+       .disable_plane  = drm_plane_helper_disable,
        .destroy        = drm_primary_helper_destroy,
+       .reset          = drm_atomic_helper_plane_reset,
+       .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
+       .atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
 };
 
 int armada_drm_plane_init(struct armada_plane *plane)
@@ -1415,6 +1332,7 @@ static int armada_drm_crtc_create(struct drm_device *drm, struct device *dev,
                       CFG_PDWN64x66, dcrtc->base + LCD_SPU_SRAM_PARA1);
        writel_relaxed(0x2032ff81, dcrtc->base + LCD_SPU_DMA_CTRL1);
        writel_relaxed(dcrtc->irq_ena, dcrtc->base + LCD_SPU_IRQ_ENA);
+       readl_relaxed(dcrtc->base + LCD_SPU_IRQ_ISR);
        writel_relaxed(0, dcrtc->base + LCD_SPU_IRQ_ISR);
 
        ret = devm_request_irq(dev, irq, armada_drm_irq, 0, "armada_drm_crtc",
@@ -1447,6 +1365,9 @@ static int armada_drm_crtc_create(struct drm_device *drm, struct device *dev,
                goto err_crtc;
        }
 
+       drm_plane_helper_add(&primary->base,
+                            &armada_primary_plane_helper_funcs);
+
        ret = drm_universal_plane_init(drm, &primary->base, 0,
                                       &armada_primary_plane_funcs,
                                       armada_primary_formats,