drm/ast: Add CRTC helpers for atomic modesetting
[linux-2.6-microblaze.git] / drivers / gpu / drm / ast / ast_mode.c
index a4cbf2d..65ee821 100644 (file)
@@ -31,6 +31,8 @@
 #include <linux/export.h>
 #include <linux/pci.h>
 
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_atomic_state_helper.h>
 #include <drm/drm_crtc.h>
 #include <drm/drm_crtc_helper.h>
 #include <drm/drm_fourcc.h>
@@ -51,6 +53,7 @@ static int ast_cursor_set(struct drm_crtc *crtc,
 static int ast_cursor_move(struct drm_crtc *crtc,
                           int x, int y);
 
+
 static inline void ast_load_palette_index(struct ast_private *ast,
                                     u8 index, u8 red, u8 green,
                                     u8 blue)
@@ -82,13 +85,12 @@ static void ast_crtc_load_lut(struct drm_crtc *crtc)
                ast_load_palette_index(ast, i, *r++ >> 8, *g++ >> 8, *b++ >> 8);
 }
 
-static bool ast_get_vbios_mode_info(struct drm_crtc *crtc, struct drm_display_mode *mode,
+static bool ast_get_vbios_mode_info(const struct drm_framebuffer *fb,
+                                   const struct drm_display_mode *mode,
                                    struct drm_display_mode *adjusted_mode,
                                    struct ast_vbios_mode_info *vbios_mode)
 {
-       struct ast_private *ast = crtc->dev->dev_private;
-       const struct drm_framebuffer *fb = crtc->primary->fb;
-       u32 refresh_rate_index = 0, mode_id, color_index, refresh_rate;
+       u32 refresh_rate_index = 0, refresh_rate;
        const struct ast_vbios_enhtable *best = NULL;
        u32 hborder, vborder;
        bool check_sync;
@@ -96,22 +98,19 @@ static bool ast_get_vbios_mode_info(struct drm_crtc *crtc, struct drm_display_mo
        switch (fb->format->cpp[0] * 8) {
        case 8:
                vbios_mode->std_table = &vbios_stdtable[VGAModeIndex];
-               color_index = VGAModeIndex - 1;
                break;
        case 16:
                vbios_mode->std_table = &vbios_stdtable[HiCModeIndex];
-               color_index = HiCModeIndex;
                break;
        case 24:
        case 32:
                vbios_mode->std_table = &vbios_stdtable[TrueCModeIndex];
-               color_index = TrueCModeIndex;
                break;
        default:
                return false;
        }
 
-       switch (crtc->mode.crtc_hdisplay) {
+       switch (mode->crtc_hdisplay) {
        case 640:
                vbios_mode->enh_table = &res_640x480[refresh_rate_index];
                break;
@@ -122,7 +121,7 @@ static bool ast_get_vbios_mode_info(struct drm_crtc *crtc, struct drm_display_mo
                vbios_mode->enh_table = &res_1024x768[refresh_rate_index];
                break;
        case 1280:
-               if (crtc->mode.crtc_vdisplay == 800)
+               if (mode->crtc_vdisplay == 800)
                        vbios_mode->enh_table = &res_1280x800[refresh_rate_index];
                else
                        vbios_mode->enh_table = &res_1280x1024[refresh_rate_index];
@@ -134,7 +133,7 @@ static bool ast_get_vbios_mode_info(struct drm_crtc *crtc, struct drm_display_mo
                vbios_mode->enh_table = &res_1440x900[refresh_rate_index];
                break;
        case 1600:
-               if (crtc->mode.crtc_vdisplay == 900)
+               if (mode->crtc_vdisplay == 900)
                        vbios_mode->enh_table = &res_1600x900[refresh_rate_index];
                else
                        vbios_mode->enh_table = &res_1600x1200[refresh_rate_index];
@@ -143,7 +142,7 @@ static bool ast_get_vbios_mode_info(struct drm_crtc *crtc, struct drm_display_mo
                vbios_mode->enh_table = &res_1680x1050[refresh_rate_index];
                break;
        case 1920:
-               if (crtc->mode.crtc_vdisplay == 1080)
+               if (mode->crtc_vdisplay == 1080)
                        vbios_mode->enh_table = &res_1920x1080[refresh_rate_index];
                else
                        vbios_mode->enh_table = &res_1920x1200[refresh_rate_index];
@@ -154,7 +153,8 @@ static bool ast_get_vbios_mode_info(struct drm_crtc *crtc, struct drm_display_mo
 
        refresh_rate = drm_mode_vrefresh(mode);
        check_sync = vbios_mode->enh_table->flags & WideScreenMode;
-       do {
+
+       while (1) {
                const struct ast_vbios_enhtable *loop = vbios_mode->enh_table;
 
                while (loop->refresh_rate != 0xff) {
@@ -178,7 +178,8 @@ static bool ast_get_vbios_mode_info(struct drm_crtc *crtc, struct drm_display_mo
                if (best || !check_sync)
                        break;
                check_sync = 0;
-       } while (1);
+       }
+
        if (best)
                vbios_mode->enh_table = best;
 
@@ -203,34 +204,65 @@ static bool ast_get_vbios_mode_info(struct drm_crtc *crtc, struct drm_display_mo
                                         vbios_mode->enh_table->vfp +
                                         vbios_mode->enh_table->vsync);
 
-       refresh_rate_index = vbios_mode->enh_table->refresh_rate_index;
-       mode_id = vbios_mode->enh_table->mode_id;
+       return true;
+}
 
-       if (ast->chip == AST1180) {
-               /* TODO 1180 */
-       } else {
-               ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x8c, (u8)((color_index & 0xf) << 4));
-               ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x8d, refresh_rate_index & 0xff);
-               ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x8e, mode_id & 0xff);
-
-               ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x91, 0x00);
-               if (vbios_mode->enh_table->flags & NewModeInfo) {
-                       ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x91, 0xa8);
-                       ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x92,
-                                         fb->format->cpp[0] * 8);
-                       ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x93, adjusted_mode->clock / 1000);
-                       ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x94, adjusted_mode->crtc_hdisplay);
-                       ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x95, adjusted_mode->crtc_hdisplay >> 8);
-
-                       ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x96, adjusted_mode->crtc_vdisplay);
-                       ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x97, adjusted_mode->crtc_vdisplay >> 8);
-               }
+static void ast_set_vbios_color_reg(struct drm_crtc *crtc,
+                                   const struct drm_framebuffer *fb,
+                                   const struct ast_vbios_mode_info *vbios_mode)
+{
+       struct ast_private *ast = crtc->dev->dev_private;
+       u32 color_index;
+
+       switch (fb->format->cpp[0]) {
+       case 1:
+               color_index = VGAModeIndex - 1;
+               break;
+       case 2:
+               color_index = HiCModeIndex;
+               break;
+       case 3:
+       case 4:
+               color_index = TrueCModeIndex;
+       default:
+               return;
        }
 
-       return true;
+       ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x8c, (u8)((color_index & 0x0f) << 4));
 
+       ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x91, 0x00);
 
+       if (vbios_mode->enh_table->flags & NewModeInfo) {
+               ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x91, 0xa8);
+               ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x92, fb->format->cpp[0] * 8);
+       }
 }
+
+static void ast_set_vbios_mode_reg(struct drm_crtc *crtc,
+                                  const struct drm_display_mode *adjusted_mode,
+                                  const struct ast_vbios_mode_info *vbios_mode)
+{
+       struct ast_private *ast = crtc->dev->dev_private;
+       u32 refresh_rate_index, mode_id;
+
+       refresh_rate_index = vbios_mode->enh_table->refresh_rate_index;
+       mode_id = vbios_mode->enh_table->mode_id;
+
+       ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x8d, refresh_rate_index & 0xff);
+       ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x8e, mode_id & 0xff);
+
+       ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x91, 0x00);
+
+       if (vbios_mode->enh_table->flags & NewModeInfo) {
+               ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x91, 0xa8);
+               ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x93, adjusted_mode->clock / 1000);
+               ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x94, adjusted_mode->crtc_hdisplay);
+               ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x95, adjusted_mode->crtc_hdisplay >> 8);
+               ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x96, adjusted_mode->crtc_vdisplay);
+               ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x97, adjusted_mode->crtc_vdisplay >> 8);
+       }
+}
+
 static void ast_set_std_reg(struct drm_crtc *crtc, struct drm_display_mode *mode,
                            struct ast_vbios_mode_info *vbios_mode)
 {
@@ -253,9 +285,13 @@ static void ast_set_std_reg(struct drm_crtc *crtc, struct drm_display_mode *mode
                ast_set_index_reg(ast, AST_IO_SEQ_PORT, (i + 1) , jreg);
        }
 
-       /* Set CRTC */
+       /* Set CRTC; except base address and offset */
        ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0x11, 0x7f, 0x00);
-       for (i = 0; i < 25; i++)
+       for (i = 0; i < 12; i++)
+               ast_set_index_reg(ast, AST_IO_CRTC_PORT, i, stdtable->crtc[i]);
+       for (i = 14; i < 19; i++)
+               ast_set_index_reg(ast, AST_IO_CRTC_PORT, i, stdtable->crtc[i]);
+       for (i = 20; i < 25; i++)
                ast_set_index_reg(ast, AST_IO_CRTC_PORT, i, stdtable->crtc[i]);
 
        /* set AR */
@@ -415,11 +451,10 @@ static void ast_set_dclk_reg(struct drm_device *dev, struct drm_display_mode *mo
                               ((clk_info->param3 & 0x3) << 4));
 }
 
-static void ast_set_ext_reg(struct drm_crtc *crtc, struct drm_display_mode *mode,
-                            struct ast_vbios_mode_info *vbios_mode)
+static void ast_set_color_reg(struct drm_crtc *crtc,
+                             const struct drm_framebuffer *fb)
 {
        struct ast_private *ast = crtc->dev->dev_private;
-       const struct drm_framebuffer *fb = crtc->primary->fb;
        u8 jregA0 = 0, jregA3 = 0, jregA8 = 0;
 
        switch (fb->format->cpp[0] * 8) {
@@ -444,6 +479,11 @@ static void ast_set_ext_reg(struct drm_crtc *crtc, struct drm_display_mode *mode
        ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xa0, 0x8f, jregA0);
        ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xa3, 0xf0, jregA3);
        ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xa8, 0xfd, jregA8);
+}
+
+static void ast_set_crtthd_reg(struct drm_crtc *crtc)
+{
+       struct ast_private *ast = crtc->dev->dev_private;
 
        /* Set Threshold */
        if (ast->chip == AST2300 || ast->chip == AST2400 ||
@@ -463,7 +503,7 @@ static void ast_set_ext_reg(struct drm_crtc *crtc, struct drm_display_mode *mode
 }
 
 static void ast_set_sync_reg(struct drm_device *dev, struct drm_display_mode *mode,
-                     struct ast_vbios_mode_info *vbios_mode)
+                            struct ast_vbios_mode_info *vbios_mode)
 {
        struct ast_private *ast = dev->dev_private;
        u8 jreg;
@@ -501,6 +541,62 @@ static void ast_set_start_address_crt1(struct drm_crtc *crtc, unsigned offset)
 
 }
 
+/*
+ * Primary plane
+ */
+
+static const uint32_t ast_primary_plane_formats[] = {
+       DRM_FORMAT_XRGB8888,
+       DRM_FORMAT_RGB565,
+       DRM_FORMAT_C8,
+};
+
+int ast_primary_plane_helper_atomic_check(struct drm_plane *plane,
+                                         struct drm_plane_state *state)
+{
+       return 0;
+}
+
+void ast_primary_plane_helper_atomic_update(struct drm_plane *plane,
+                                           struct drm_plane_state *old_state)
+{
+       struct drm_plane_state *state = plane->state;
+       struct drm_crtc *crtc = state->crtc;
+       struct drm_gem_vram_object *gbo;
+       s64 gpu_addr;
+
+       if (!crtc || !state->fb)
+               return;
+
+       gbo = drm_gem_vram_of_gem(state->fb->obj[0]);
+       gpu_addr = drm_gem_vram_offset(gbo);
+       if (WARN_ON_ONCE(gpu_addr < 0))
+               return; /* Bug: we didn't pin the BO to VRAM in prepare_fb. */
+
+       ast_set_offset_reg(crtc);
+       ast_set_start_address_crt1(crtc, (u32)gpu_addr);
+}
+
+static const struct drm_plane_helper_funcs ast_primary_plane_helper_funcs = {
+       .prepare_fb = drm_gem_vram_plane_helper_prepare_fb,
+       .cleanup_fb = drm_gem_vram_plane_helper_cleanup_fb,
+       .atomic_check = ast_primary_plane_helper_atomic_check,
+       .atomic_update = ast_primary_plane_helper_atomic_update,
+};
+
+static const struct drm_plane_funcs ast_primary_plane_funcs = {
+       .update_plane = drm_atomic_helper_update_plane,
+       .disable_plane = drm_atomic_helper_disable_plane,
+       .destroy = drm_plane_cleanup,
+       .reset = drm_atomic_helper_plane_reset,
+       .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
+       .atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
+};
+
+/*
+ * CRTC
+ */
+
 static void ast_crtc_dpms(struct drm_crtc *crtc, int mode)
 {
        struct ast_private *ast = crtc->dev->dev_private;
@@ -573,25 +669,30 @@ static int ast_crtc_mode_set(struct drm_crtc *crtc,
 {
        struct drm_device *dev = crtc->dev;
        struct ast_private *ast = crtc->dev->dev_private;
+       const struct drm_framebuffer *fb = crtc->primary->fb;
        struct ast_vbios_mode_info vbios_mode;
-       bool ret;
+       bool succ;
+
        if (ast->chip == AST1180) {
                DRM_ERROR("AST 1180 modesetting not supported\n");
                return -EINVAL;
        }
 
-       ret = ast_get_vbios_mode_info(crtc, mode, adjusted_mode, &vbios_mode);
-       if (ret == false)
+       succ = ast_get_vbios_mode_info(fb, mode, adjusted_mode, &vbios_mode);
+       if (!succ)
                return -EINVAL;
+
        ast_open_key(ast);
 
+       ast_set_vbios_color_reg(crtc, fb, &vbios_mode);
+       ast_set_vbios_mode_reg(crtc, adjusted_mode, &vbios_mode);
        ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0xa1, 0x06);
-
        ast_set_std_reg(crtc, adjusted_mode, &vbios_mode);
        ast_set_crtc_reg(crtc, adjusted_mode, &vbios_mode);
        ast_set_offset_reg(crtc);
        ast_set_dclk_reg(dev, adjusted_mode, &vbios_mode);
-       ast_set_ext_reg(crtc, adjusted_mode, &vbios_mode);
+       ast_set_color_reg(crtc, fb);
+       ast_set_crtthd_reg(crtc);
        ast_set_sync_reg(dev, adjusted_mode, &vbios_mode);
        ast_set_dac_reg(crtc, adjusted_mode, &vbios_mode);
 
@@ -626,6 +727,103 @@ static void ast_crtc_commit(struct drm_crtc *crtc)
        ast_crtc_load_lut(crtc);
 }
 
+static int ast_crtc_helper_atomic_check(struct drm_crtc *crtc,
+                                       struct drm_crtc_state *state)
+{
+       struct ast_private *ast = crtc->dev->dev_private;
+       struct drm_plane_state *plane_state;
+       bool succ;
+       struct drm_display_mode adjusted_mode;
+       struct ast_vbios_mode_info vbios_mode;
+
+       if (ast->chip == AST1180) {
+               DRM_ERROR("AST 1180 modesetting not supported\n");
+               return -EINVAL;
+       }
+
+       plane_state = crtc->primary->state;
+
+       if (plane_state && plane_state->fb) {
+               succ = ast_get_vbios_mode_info(plane_state->fb, &state->mode,
+                                              &adjusted_mode, &vbios_mode);
+               if (!succ)
+                       return -EINVAL;
+       }
+
+       return 0;
+}
+
+static void ast_crtc_helper_atomic_begin(struct drm_crtc *crtc,
+                                        struct drm_crtc_state *old_crtc_state)
+{
+       struct ast_private *ast = crtc->dev->dev_private;
+
+       ast_open_key(ast);
+}
+
+static void ast_crtc_helper_atomic_flush(struct drm_crtc *crtc,
+                                        struct drm_crtc_state *old_crtc_state)
+{
+       const struct drm_framebuffer *fb = crtc->primary->state->fb;
+       struct drm_display_mode adjusted_mode;
+       struct ast_vbios_mode_info vbios_mode;
+       bool succ;
+
+       crtc->state->no_vblank = true;
+
+       if (!fb)
+               return;
+
+       ast_set_color_reg(crtc, fb);
+
+       memset(&adjusted_mode, 0, sizeof(adjusted_mode));
+       drm_mode_copy(&adjusted_mode, &crtc->state->adjusted_mode);
+
+       succ = ast_get_vbios_mode_info(fb, &crtc->state->adjusted_mode,
+                                      &adjusted_mode, &vbios_mode);
+       if (WARN_ON_ONCE(!succ))
+               return;
+
+       ast_set_vbios_color_reg(crtc, fb, &vbios_mode);
+}
+
+static void
+ast_crtc_helper_atomic_enable(struct drm_crtc *crtc,
+                             struct drm_crtc_state *old_crtc_state)
+{
+       struct drm_device *dev = crtc->dev;
+       struct ast_private *ast = crtc->dev->dev_private;
+       const struct drm_framebuffer *fb = crtc->primary->state->fb;
+       struct drm_display_mode adjusted_mode;
+       struct ast_vbios_mode_info vbios_mode;
+       bool succ;
+
+       memset(&adjusted_mode, 0, sizeof(adjusted_mode));
+       drm_mode_copy(&adjusted_mode, &crtc->state->adjusted_mode);
+
+       succ = ast_get_vbios_mode_info(fb, &crtc->state->adjusted_mode,
+                                      &adjusted_mode, &vbios_mode);
+       if (WARN_ON_ONCE(!succ))
+               return;
+
+       ast_set_vbios_mode_reg(crtc, &adjusted_mode, &vbios_mode);
+       ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0xa1, 0x06);
+       ast_set_std_reg(crtc, &adjusted_mode, &vbios_mode);
+       ast_set_crtc_reg(crtc, &adjusted_mode, &vbios_mode);
+       ast_set_dclk_reg(dev, &adjusted_mode, &vbios_mode);
+       ast_set_crtthd_reg(crtc);
+       ast_set_sync_reg(dev, &adjusted_mode, &vbios_mode);
+       ast_set_dac_reg(crtc, &adjusted_mode, &vbios_mode);
+
+       ast_crtc_dpms(crtc, DRM_MODE_DPMS_ON);
+}
+
+static void
+ast_crtc_helper_atomic_disable(struct drm_crtc *crtc,
+                              struct drm_crtc_state *old_crtc_state)
+{
+       ast_crtc_dpms(crtc, DRM_MODE_DPMS_OFF);
+}
 
 static const struct drm_crtc_helper_funcs ast_crtc_helper_funcs = {
        .dpms = ast_crtc_dpms,
@@ -634,7 +832,11 @@ static const struct drm_crtc_helper_funcs ast_crtc_helper_funcs = {
        .disable = ast_crtc_disable,
        .prepare = ast_crtc_prepare,
        .commit = ast_crtc_commit,
-
+       .atomic_check = ast_crtc_helper_atomic_check,
+       .atomic_begin = ast_crtc_helper_atomic_begin,
+       .atomic_flush = ast_crtc_helper_atomic_flush,
+       .atomic_enable = ast_crtc_helper_atomic_enable,
+       .atomic_disable = ast_crtc_helper_atomic_disable,
 };
 
 static void ast_crtc_reset(struct drm_crtc *crtc)
@@ -665,20 +867,32 @@ static const struct drm_crtc_funcs ast_crtc_funcs = {
        .set_config = drm_crtc_helper_set_config,
        .gamma_set = ast_crtc_gamma_set,
        .destroy = ast_crtc_destroy,
+       .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
+       .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
 };
 
 static int ast_crtc_init(struct drm_device *dev)
 {
+       struct ast_private *ast = dev->dev_private;
        struct ast_crtc *crtc;
+       int ret;
 
        crtc = kzalloc(sizeof(struct ast_crtc), GFP_KERNEL);
        if (!crtc)
                return -ENOMEM;
 
-       drm_crtc_init(dev, &crtc->base, &ast_crtc_funcs);
+       ret = drm_crtc_init_with_planes(dev, &crtc->base, &ast->primary_plane,
+                                       NULL, &ast_crtc_funcs, NULL);
+       if (ret)
+               goto err_kfree;
+
        drm_mode_crtc_set_gamma_size(&crtc->base, 256);
        drm_crtc_helper_add(&crtc->base, &ast_crtc_helper_funcs);
        return 0;
+
+err_kfree:
+       kfree(crtc);
+       return ret;
 }
 
 static void ast_encoder_destroy(struct drm_encoder *encoder)
@@ -712,7 +926,6 @@ static void ast_encoder_commit(struct drm_encoder *encoder)
 
 }
 
-
 static const struct drm_encoder_helper_funcs ast_enc_helper_funcs = {
        .dpms = ast_encoder_dpms,
        .prepare = ast_encoder_prepare,
@@ -883,58 +1096,78 @@ static int ast_connector_init(struct drm_device *dev)
 static int ast_cursor_init(struct drm_device *dev)
 {
        struct ast_private *ast = dev->dev_private;
-       int size;
-       int ret;
-       struct drm_gem_object *obj;
+       size_t size, i;
        struct drm_gem_vram_object *gbo;
-       s64 gpu_addr;
-       void *base;
+       int ret;
 
-       size = (AST_HWC_SIZE + AST_HWC_SIGNATURE_SIZE) * AST_DEFAULT_HWC_NUM;
+       size = roundup(AST_HWC_SIZE + AST_HWC_SIGNATURE_SIZE, PAGE_SIZE);
 
-       ret = ast_gem_create(dev, size, true, &obj);
-       if (ret)
-               return ret;
-       gbo = drm_gem_vram_of_gem(obj);
-       ret = drm_gem_vram_pin(gbo, DRM_GEM_VRAM_PL_FLAG_VRAM);
-       if (ret)
-               goto fail;
-       gpu_addr = drm_gem_vram_offset(gbo);
-       if (gpu_addr < 0) {
-               drm_gem_vram_unpin(gbo);
-               ret = (int)gpu_addr;
-               goto fail;
-       }
+       for (i = 0; i < ARRAY_SIZE(ast->cursor.gbo); ++i) {
+               gbo = drm_gem_vram_create(dev, &dev->vram_mm->bdev,
+                                         size, 0, false);
+               if (IS_ERR(gbo)) {
+                       ret = PTR_ERR(gbo);
+                       goto err_drm_gem_vram_put;
+               }
+               ret = drm_gem_vram_pin(gbo, DRM_GEM_VRAM_PL_FLAG_VRAM |
+                                           DRM_GEM_VRAM_PL_FLAG_TOPDOWN);
+               if (ret) {
+                       drm_gem_vram_put(gbo);
+                       goto err_drm_gem_vram_put;
+               }
 
-       /* kmap the object */
-       base = drm_gem_vram_kmap(gbo, true, NULL);
-       if (IS_ERR(base)) {
-               ret = PTR_ERR(base);
-               goto fail;
+               ast->cursor.gbo[i] = gbo;
        }
 
-       ast->cursor_cache = obj;
        return 0;
-fail:
+
+err_drm_gem_vram_put:
+       while (i) {
+               --i;
+               gbo = ast->cursor.gbo[i];
+               drm_gem_vram_unpin(gbo);
+               drm_gem_vram_put(gbo);
+               ast->cursor.gbo[i] = NULL;
+       }
        return ret;
 }
 
 static void ast_cursor_fini(struct drm_device *dev)
 {
        struct ast_private *ast = dev->dev_private;
-       struct drm_gem_vram_object *gbo =
-               drm_gem_vram_of_gem(ast->cursor_cache);
-       drm_gem_vram_kunmap(gbo);
-       drm_gem_vram_unpin(gbo);
-       drm_gem_object_put_unlocked(ast->cursor_cache);
+       size_t i;
+       struct drm_gem_vram_object *gbo;
+
+       for (i = 0; i < ARRAY_SIZE(ast->cursor.gbo); ++i) {
+               gbo = ast->cursor.gbo[i];
+               drm_gem_vram_unpin(gbo);
+               drm_gem_vram_put(gbo);
+       }
 }
 
 int ast_mode_init(struct drm_device *dev)
 {
+       struct ast_private *ast = dev->dev_private;
+       int ret;
+
+       memset(&ast->primary_plane, 0, sizeof(ast->primary_plane));
+       ret = drm_universal_plane_init(dev, &ast->primary_plane, 0x01,
+                                      &ast_primary_plane_funcs,
+                                      ast_primary_plane_formats,
+                                      ARRAY_SIZE(ast_primary_plane_formats),
+                                      NULL, DRM_PLANE_TYPE_PRIMARY, NULL);
+       if (ret) {
+               DRM_ERROR("ast: drm_universal_plane_init() failed: %d\n", ret);
+               return ret;
+       }
+       drm_plane_helper_add(&ast->primary_plane,
+                            &ast_primary_plane_helper_funcs);
+
        ast_cursor_init(dev);
        ast_crtc_init(dev);
        ast_encoder_init(dev);
        ast_connector_init(dev);
+
        return 0;
 }
 
@@ -1064,23 +1297,6 @@ static void ast_i2c_destroy(struct ast_i2c_chan *i2c)
        kfree(i2c);
 }
 
-static void ast_show_cursor(struct drm_crtc *crtc)
-{
-       struct ast_private *ast = crtc->dev->dev_private;
-       u8 jreg;
-
-       jreg = 0x2;
-       /* enable ARGB cursor */
-       jreg |= 1;
-       ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xcb, 0xfc, jreg);
-}
-
-static void ast_hide_cursor(struct drm_crtc *crtc)
-{
-       struct ast_private *ast = crtc->dev->dev_private;
-       ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xcb, 0xfc, 0x00);
-}
-
 static u32 copy_cursor_image(u8 *src, u8 *dst, int width, int height)
 {
        union {
@@ -1137,21 +1353,99 @@ static u32 copy_cursor_image(u8 *src, u8 *dst, int width, int height)
        return csum;
 }
 
+static int ast_cursor_update(void *dst, void *src, unsigned int width,
+                            unsigned int height)
+{
+       u32 csum;
+
+       /* do data transfer to cursor cache */
+       csum = copy_cursor_image(src, dst, width, height);
+
+       /* write checksum + signature */
+       dst += AST_HWC_SIZE;
+       writel(csum, dst);
+       writel(width, dst + AST_HWC_SIGNATURE_SizeX);
+       writel(height, dst + AST_HWC_SIGNATURE_SizeY);
+       writel(0, dst + AST_HWC_SIGNATURE_HOTSPOTX);
+       writel(0, dst + AST_HWC_SIGNATURE_HOTSPOTY);
+
+       return 0;
+}
+
+static void ast_cursor_set_base(struct ast_private *ast, u64 address)
+{
+       u8 addr0 = (address >> 3) & 0xff;
+       u8 addr1 = (address >> 11) & 0xff;
+       u8 addr2 = (address >> 19) & 0xff;
+
+       ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0xc8, addr0);
+       ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0xc9, addr1);
+       ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0xca, addr2);
+}
+
+static int ast_show_cursor(struct drm_crtc *crtc, void *src,
+                          unsigned int width, unsigned int height)
+{
+       struct ast_private *ast = crtc->dev->dev_private;
+       struct ast_crtc *ast_crtc = to_ast_crtc(crtc);
+       struct drm_gem_vram_object *gbo;
+       void *dst;
+       s64 off;
+       int ret;
+       u8 jreg;
+
+       gbo = ast->cursor.gbo[ast->cursor.next_index];
+       dst = drm_gem_vram_vmap(gbo);
+       if (IS_ERR(dst))
+               return PTR_ERR(dst);
+       off = drm_gem_vram_offset(gbo);
+       if (off < 0) {
+               ret = (int)off;
+               goto err_drm_gem_vram_vunmap;
+       }
+
+       ret = ast_cursor_update(dst, src, width, height);
+       if (ret)
+               goto err_drm_gem_vram_vunmap;
+       ast_cursor_set_base(ast, off);
+
+       ast_crtc->offset_x = AST_MAX_HWC_WIDTH - width;
+       ast_crtc->offset_y = AST_MAX_HWC_WIDTH - height;
+
+       jreg = 0x2;
+       /* enable ARGB cursor */
+       jreg |= 1;
+       ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xcb, 0xfc, jreg);
+
+       ++ast->cursor.next_index;
+       ast->cursor.next_index %= ARRAY_SIZE(ast->cursor.gbo);
+
+       drm_gem_vram_vunmap(gbo, dst);
+
+       return 0;
+
+err_drm_gem_vram_vunmap:
+       drm_gem_vram_vunmap(gbo, dst);
+       return ret;
+}
+
+static void ast_hide_cursor(struct drm_crtc *crtc)
+{
+       struct ast_private *ast = crtc->dev->dev_private;
+
+       ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xcb, 0xfc, 0x00);
+}
+
 static int ast_cursor_set(struct drm_crtc *crtc,
                          struct drm_file *file_priv,
                          uint32_t handle,
                          uint32_t width,
                          uint32_t height)
 {
-       struct ast_private *ast = crtc->dev->dev_private;
-       struct ast_crtc *ast_crtc = to_ast_crtc(crtc);
        struct drm_gem_object *obj;
        struct drm_gem_vram_object *gbo;
-       s64 dst_gpu;
-       u64 gpu_addr;
-       u32 csum;
+       u8 *src;
        int ret;
-       u8 *src, *dst;
 
        if (!handle) {
                ast_hide_cursor(crtc);
@@ -1173,49 +1467,9 @@ static int ast_cursor_set(struct drm_crtc *crtc,
                goto err_drm_gem_object_put_unlocked;
        }
 
-       dst = drm_gem_vram_kmap(drm_gem_vram_of_gem(ast->cursor_cache),
-                               false, NULL);
-       if (IS_ERR(dst)) {
-               ret = PTR_ERR(dst);
-               goto err_drm_gem_vram_vunmap;
-       }
-       dst_gpu = drm_gem_vram_offset(drm_gem_vram_of_gem(ast->cursor_cache));
-       if (dst_gpu < 0) {
-               ret = (int)dst_gpu;
+       ret = ast_show_cursor(crtc, src, width, height);
+       if (ret)
                goto err_drm_gem_vram_vunmap;
-       }
-
-       dst += (AST_HWC_SIZE + AST_HWC_SIGNATURE_SIZE)*ast->next_cursor;
-
-       /* do data transfer to cursor cache */
-       csum = copy_cursor_image(src, dst, width, height);
-
-       /* write checksum + signature */
-       {
-               struct drm_gem_vram_object *dst_gbo =
-                       drm_gem_vram_of_gem(ast->cursor_cache);
-               u8 *dst = drm_gem_vram_kmap(dst_gbo, false, NULL);
-               dst += (AST_HWC_SIZE + AST_HWC_SIGNATURE_SIZE)*ast->next_cursor + AST_HWC_SIZE;
-               writel(csum, dst);
-               writel(width, dst + AST_HWC_SIGNATURE_SizeX);
-               writel(height, dst + AST_HWC_SIGNATURE_SizeY);
-               writel(0, dst + AST_HWC_SIGNATURE_HOTSPOTX);
-               writel(0, dst + AST_HWC_SIGNATURE_HOTSPOTY);
-
-               /* set pattern offset */
-               gpu_addr = (u64)dst_gpu;
-               gpu_addr += (AST_HWC_SIZE + AST_HWC_SIGNATURE_SIZE)*ast->next_cursor;
-               gpu_addr >>= 3;
-               ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0xc8, gpu_addr & 0xff);
-               ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0xc9, (gpu_addr >> 8) & 0xff);
-               ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0xca, (gpu_addr >> 16) & 0xff);
-       }
-       ast_crtc->offset_x = AST_MAX_HWC_WIDTH - width;
-       ast_crtc->offset_y = AST_MAX_HWC_WIDTH - height;
-
-       ast->next_cursor = (ast->next_cursor + 1) % AST_DEFAULT_HWC_NUM;
-
-       ast_show_cursor(crtc);
 
        drm_gem_vram_vunmap(gbo, src);
        drm_gem_object_put_unlocked(obj);
@@ -1234,13 +1488,17 @@ static int ast_cursor_move(struct drm_crtc *crtc,
 {
        struct ast_crtc *ast_crtc = to_ast_crtc(crtc);
        struct ast_private *ast = crtc->dev->dev_private;
+       struct drm_gem_vram_object *gbo;
        int x_offset, y_offset;
-       u8 *sig;
+       u8 *dst, *sig;
        u8 jreg;
 
-       sig = drm_gem_vram_kmap(drm_gem_vram_of_gem(ast->cursor_cache),
-                               false, NULL);
-       sig += (AST_HWC_SIZE + AST_HWC_SIGNATURE_SIZE)*ast->next_cursor + AST_HWC_SIZE;
+       gbo = ast->cursor.gbo[ast->cursor.next_index];
+       dst = drm_gem_vram_vmap(gbo);
+       if (IS_ERR(dst))
+               return PTR_ERR(dst);
+
+       sig = dst + AST_HWC_SIZE;
        writel(x, sig + AST_HWC_SIGNATURE_X);
        writel(y, sig + AST_HWC_SIGNATURE_Y);
 
@@ -1267,5 +1525,7 @@ static int ast_cursor_move(struct drm_crtc *crtc,
               0x01; /* enable ARGB4444 cursor */
        ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xcb, 0xfc, jreg);
 
+       drm_gem_vram_vunmap(gbo, dst);
+
        return 0;
 }