drm/tegra: Atomic conversion, phase 1
[deliverable/linux.git] / drivers / gpu / drm / tegra / dc.c
index 3367960286a6f162455e0363a54dace2c6882883..f7254ab7a5c709666c67eddf92a515786aadf16e 100644 (file)
 #include "drm.h"
 #include "gem.h"
 
+#include <drm/drm_atomic_helper.h>
 #include <drm/drm_plane_helper.h>
 
 struct tegra_dc_soc_info {
+       bool supports_border_color;
        bool supports_interlacing;
        bool supports_cursor;
        bool supports_block_linear;
@@ -52,7 +54,39 @@ static void tegra_dc_cursor_commit(struct tegra_dc *dc)
        tegra_dc_writel(dc, CURSOR_ACT_REQ, DC_CMD_STATE_CONTROL);
 }
 
-static void tegra_dc_commit(struct tegra_dc *dc)
+/*
+ * Reads the active copy of a register. This takes the dc->lock spinlock to
+ * prevent races with the VBLANK processing which also needs access to the
+ * active copy of some registers.
+ */
+static u32 tegra_dc_readl_active(struct tegra_dc *dc, unsigned long offset)
+{
+       unsigned long flags;
+       u32 value;
+
+       spin_lock_irqsave(&dc->lock, flags);
+
+       tegra_dc_writel(dc, READ_MUX, DC_CMD_STATE_ACCESS);
+       value = tegra_dc_readl(dc, offset);
+       tegra_dc_writel(dc, 0, DC_CMD_STATE_ACCESS);
+
+       spin_unlock_irqrestore(&dc->lock, flags);
+       return value;
+}
+
+/*
+ * Double-buffered registers have two copies: ASSEMBLY and ACTIVE. When the
+ * *_ACT_REQ bits are set the ASSEMBLY copy is latched into the ACTIVE copy.
+ * Latching happens mmediately if the display controller is in STOP mode or
+ * on the next frame boundary otherwise.
+ *
+ * Triple-buffered registers have three copies: ASSEMBLY, ARM and ACTIVE. The
+ * ASSEMBLY copy is latched into the ARM copy immediately after *_UPDATE bits
+ * are written. When the *_ACT_REQ bits are written, the ARM copy is latched
+ * into the ACTIVE copy, either immediately if the display controller is in
+ * STOP mode, or at the next frame boundary otherwise.
+ */
+void tegra_dc_commit(struct tegra_dc *dc)
 {
        tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL);
        tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL);
@@ -121,6 +155,9 @@ static bool tegra_dc_format_is_yuv(unsigned int format, bool *planar)
                return true;
        }
 
+       if (planar)
+               *planar = false;
+
        return false;
 }
 
@@ -164,11 +201,11 @@ static inline u32 compute_initial_dda(unsigned int in)
        return dfixed_frac(inf);
 }
 
-static int tegra_dc_setup_window(struct tegra_dc *dc, unsigned int index,
-                                const struct tegra_dc_window *window)
+static void tegra_dc_setup_window(struct tegra_dc *dc, unsigned int index,
+                                 const struct tegra_dc_window *window)
 {
        unsigned h_offset, v_offset, h_size, v_size, h_dda, v_dda, bpp;
-       unsigned long value;
+       unsigned long value, flags;
        bool yuv, planar;
 
        /*
@@ -181,6 +218,8 @@ static int tegra_dc_setup_window(struct tegra_dc *dc, unsigned int index,
        else
                bpp = planar ? 1 : 2;
 
+       spin_lock_irqsave(&dc->lock, flags);
+
        value = WINDOW_A_SELECT << index;
        tegra_dc_writel(dc, value, DC_CMD_DISPLAY_WINDOW_HEADER);
 
@@ -272,8 +311,11 @@ static int tegra_dc_setup_window(struct tegra_dc *dc, unsigned int index,
                        break;
 
                case TEGRA_BO_TILING_MODE_BLOCK:
-                       DRM_ERROR("hardware doesn't support block linear mode\n");
-                       return -EINVAL;
+                       /*
+                        * No need to handle this here because ->atomic_check
+                        * will already have filtered it out.
+                        */
+                       break;
                }
 
                tegra_dc_writel(dc, value, DC_WIN_BUFFER_ADDR_MODE);
@@ -331,28 +373,7 @@ static int tegra_dc_setup_window(struct tegra_dc *dc, unsigned int index,
 
        tegra_dc_window_commit(dc, index);
 
-       return 0;
-}
-
-static int tegra_window_plane_disable(struct drm_plane *plane)
-{
-       struct tegra_dc *dc = to_tegra_dc(plane->crtc);
-       struct tegra_plane *p = to_tegra_plane(plane);
-       u32 value;
-
-       if (!plane->crtc)
-               return 0;
-
-       value = WINDOW_A_SELECT << p->index;
-       tegra_dc_writel(dc, value, DC_CMD_DISPLAY_WINDOW_HEADER);
-
-       value = tegra_dc_readl(dc, DC_WIN_WIN_OPTIONS);
-       value &= ~WIN_ENABLE;
-       tegra_dc_writel(dc, value, DC_WIN_WIN_OPTIONS);
-
-       tegra_dc_window_commit(dc, p->index);
-
-       return 0;
+       spin_unlock_irqrestore(&dc->lock, flags);
 }
 
 static void tegra_plane_destroy(struct drm_plane *plane)
@@ -369,62 +390,157 @@ static const u32 tegra_primary_plane_formats[] = {
        DRM_FORMAT_RGB565,
 };
 
-static int tegra_primary_plane_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)
+static void tegra_primary_plane_destroy(struct drm_plane *plane)
 {
-       struct tegra_bo *bo = tegra_fb_get_plane(fb, 0);
+       tegra_plane_destroy(plane);
+}
+
+static const struct drm_plane_funcs tegra_primary_plane_funcs = {
+       .update_plane = drm_plane_helper_update,
+       .disable_plane = drm_plane_helper_disable,
+       .destroy = tegra_primary_plane_destroy,
+       .atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
+};
+
+static int tegra_plane_prepare_fb(struct drm_plane *plane,
+                                 struct drm_framebuffer *fb)
+{
+       return 0;
+}
+
+static void tegra_plane_cleanup_fb(struct drm_plane *plane,
+                                  struct drm_framebuffer *fb)
+{
+}
+
+static int tegra_plane_atomic_check(struct drm_plane *plane,
+                                   struct drm_plane_state *state)
+{
+       struct tegra_dc *dc = to_tegra_dc(state->crtc);
+       struct tegra_bo_tiling tiling;
+       int err;
+
+       /* no need for further checks if the plane is being disabled */
+       if (!state->crtc)
+               return 0;
+
+       err = tegra_fb_get_tiling(state->fb, &tiling);
+       if (err < 0)
+               return err;
+
+       if (tiling.mode == TEGRA_BO_TILING_MODE_BLOCK &&
+           !dc->soc->supports_block_linear) {
+               DRM_ERROR("hardware doesn't support block linear mode\n");
+               return -EINVAL;
+       }
+
+       /*
+        * Tegra doesn't support different strides for U and V planes so we
+        * error out if the user tries to display a framebuffer with such a
+        * configuration.
+        */
+       if (drm_format_num_planes(state->fb->pixel_format) > 2) {
+               if (state->fb->pitches[2] != state->fb->pitches[1]) {
+                       DRM_ERROR("unsupported UV-plane configuration\n");
+                       return -EINVAL;
+               }
+       }
+
+       return 0;
+}
+
+static void tegra_plane_atomic_update(struct drm_plane *plane,
+                                     struct drm_plane_state *old_state)
+{
+       struct tegra_dc *dc = to_tegra_dc(plane->state->crtc);
+       struct drm_framebuffer *fb = plane->state->fb;
        struct tegra_plane *p = to_tegra_plane(plane);
-       struct tegra_dc *dc = to_tegra_dc(crtc);
        struct tegra_dc_window window;
+       unsigned int i;
        int err;
 
+       /* rien ne va plus */
+       if (!plane->state->crtc || !plane->state->fb)
+               return;
+
        memset(&window, 0, sizeof(window));
-       window.src.x = src_x >> 16;
-       window.src.y = src_y >> 16;
-       window.src.w = src_w >> 16;
-       window.src.h = src_h >> 16;
-       window.dst.x = crtc_x;
-       window.dst.y = crtc_y;
-       window.dst.w = crtc_w;
-       window.dst.h = crtc_h;
+       window.src.x = plane->state->src_x >> 16;
+       window.src.y = plane->state->src_y >> 16;
+       window.src.w = plane->state->src_w >> 16;
+       window.src.h = plane->state->src_h >> 16;
+       window.dst.x = plane->state->crtc_x;
+       window.dst.y = plane->state->crtc_y;
+       window.dst.w = plane->state->crtc_w;
+       window.dst.h = plane->state->crtc_h;
        window.format = tegra_dc_format(fb->pixel_format, &window.swap);
        window.bits_per_pixel = fb->bits_per_pixel;
        window.bottom_up = tegra_fb_is_bottom_up(fb);
 
        err = tegra_fb_get_tiling(fb, &window.tiling);
-       if (err < 0)
-               return err;
+       WARN_ON(err < 0);
 
-       window.base[0] = bo->paddr + fb->offsets[0];
-       window.stride[0] = fb->pitches[0];
+       for (i = 0; i < drm_format_num_planes(fb->pixel_format); i++) {
+               struct tegra_bo *bo = tegra_fb_get_plane(fb, i);
 
-       err = tegra_dc_setup_window(dc, p->index, &window);
-       if (err < 0)
-               return err;
+               window.base[i] = bo->paddr + fb->offsets[i];
+               window.stride[i] = fb->pitches[i];
+       }
 
-       return 0;
+       tegra_dc_setup_window(dc, p->index, &window);
 }
 
-static void tegra_primary_plane_destroy(struct drm_plane *plane)
+static void tegra_plane_atomic_disable(struct drm_plane *plane,
+                                      struct drm_plane_state *old_state)
 {
-       tegra_window_plane_disable(plane);
-       tegra_plane_destroy(plane);
+       struct tegra_plane *p = to_tegra_plane(plane);
+       struct tegra_dc *dc;
+       unsigned long flags;
+       u32 value;
+
+       /* rien ne va plus */
+       if (!old_state || !old_state->crtc)
+               return;
+
+       dc = to_tegra_dc(old_state->crtc);
+
+       spin_lock_irqsave(&dc->lock, flags);
+
+       value = WINDOW_A_SELECT << p->index;
+       tegra_dc_writel(dc, value, DC_CMD_DISPLAY_WINDOW_HEADER);
+
+       value = tegra_dc_readl(dc, DC_WIN_WIN_OPTIONS);
+       value &= ~WIN_ENABLE;
+       tegra_dc_writel(dc, value, DC_WIN_WIN_OPTIONS);
+
+       tegra_dc_window_commit(dc, p->index);
+
+       spin_unlock_irqrestore(&dc->lock, flags);
 }
 
-static const struct drm_plane_funcs tegra_primary_plane_funcs = {
-       .update_plane = tegra_primary_plane_update,
-       .disable_plane = tegra_window_plane_disable,
-       .destroy = tegra_primary_plane_destroy,
+static const struct drm_plane_helper_funcs tegra_primary_plane_helper_funcs = {
+       .prepare_fb = tegra_plane_prepare_fb,
+       .cleanup_fb = tegra_plane_cleanup_fb,
+       .atomic_check = tegra_plane_atomic_check,
+       .atomic_update = tegra_plane_atomic_update,
+       .atomic_disable = tegra_plane_atomic_disable,
 };
 
 static struct drm_plane *tegra_dc_primary_plane_create(struct drm_device *drm,
                                                       struct tegra_dc *dc)
 {
+       /*
+        * Ideally this would use drm_crtc_mask(), but that would require the
+        * CRTC to already be in the mode_config's list of CRTCs. However, it
+        * will only be added to that list in the drm_crtc_init_with_planes()
+        * (in tegra_dc_init()), which in turn requires registration of these
+        * planes. So we have ourselves a nice little chicken and egg problem
+        * here.
+        *
+        * We work around this by manually creating the mask from the number
+        * of CRTCs that have been registered, and should therefore always be
+        * the same as drm_crtc_index() after registration.
+        */
+       unsigned long possible_crtcs = 1 << drm->mode_config.num_crtc;
        struct tegra_plane *plane;
        unsigned int num_formats;
        const u32 *formats;
@@ -437,7 +553,7 @@ static struct drm_plane *tegra_dc_primary_plane_create(struct drm_device *drm,
        num_formats = ARRAY_SIZE(tegra_primary_plane_formats);
        formats = tegra_primary_plane_formats;
 
-       err = drm_universal_plane_init(drm, &plane->base, 1 << dc->pipe,
+       err = drm_universal_plane_init(drm, &plane->base, possible_crtcs,
                                       &tegra_primary_plane_funcs, formats,
                                       num_formats, DRM_PLANE_TYPE_PRIMARY);
        if (err < 0) {
@@ -445,6 +561,8 @@ static struct drm_plane *tegra_dc_primary_plane_create(struct drm_device *drm,
                return ERR_PTR(err);
        }
 
+       drm_plane_helper_add(&plane->base, &tegra_primary_plane_helper_funcs);
+
        return &plane->base;
 }
 
@@ -452,27 +570,42 @@ static const u32 tegra_cursor_plane_formats[] = {
        DRM_FORMAT_RGBA8888,
 };
 
-static int tegra_cursor_plane_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)
+static int tegra_cursor_atomic_check(struct drm_plane *plane,
+                                    struct drm_plane_state *state)
 {
-       struct tegra_bo *bo = tegra_fb_get_plane(fb, 0);
-       struct tegra_dc *dc = to_tegra_dc(crtc);
-       u32 value = CURSOR_CLIP_DISPLAY;
+       /* no need for further checks if the plane is being disabled */
+       if (!state->crtc)
+               return 0;
 
        /* scaling not supported for cursor */
-       if ((src_w >> 16 != crtc_w) || (src_h >> 16 != crtc_h))
+       if ((state->src_w >> 16 != state->crtc_w) ||
+           (state->src_h >> 16 != state->crtc_h))
                return -EINVAL;
 
        /* only square cursors supported */
-       if (src_w != src_h)
+       if (state->src_w != state->src_h)
+               return -EINVAL;
+
+       if (state->crtc_w != 32 && state->crtc_w != 64 &&
+           state->crtc_w != 128 && state->crtc_w != 256)
                return -EINVAL;
 
-       switch (crtc_w) {
+       return 0;
+}
+
+static void tegra_cursor_atomic_update(struct drm_plane *plane,
+                                      struct drm_plane_state *old_state)
+{
+       struct tegra_bo *bo = tegra_fb_get_plane(plane->state->fb, 0);
+       struct tegra_dc *dc = to_tegra_dc(plane->state->crtc);
+       struct drm_plane_state *state = plane->state;
+       u32 value = CURSOR_CLIP_DISPLAY;
+
+       /* rien ne va plus */
+       if (!plane->state->crtc || !plane->state->fb)
+               return;
+
+       switch (state->crtc_w) {
        case 32:
                value |= CURSOR_SIZE_32x32;
                break;
@@ -490,7 +623,9 @@ static int tegra_cursor_plane_update(struct drm_plane *plane,
                break;
 
        default:
-               return -EINVAL;
+               WARN(1, "cursor size %ux%u not supported\n", state->crtc_w,
+                    state->crtc_h);
+               return;
        }
 
        value |= (bo->paddr >> 10) & 0x3fffff;
@@ -516,23 +651,25 @@ static int tegra_cursor_plane_update(struct drm_plane *plane,
        tegra_dc_writel(dc, value, DC_DISP_BLEND_CURSOR_CONTROL);
 
        /* position the cursor */
-       value = (crtc_y & 0x3fff) << 16 | (crtc_x & 0x3fff);
+       value = (state->crtc_y & 0x3fff) << 16 | (state->crtc_x & 0x3fff);
        tegra_dc_writel(dc, value, DC_DISP_CURSOR_POSITION);
 
        /* apply changes */
        tegra_dc_cursor_commit(dc);
        tegra_dc_commit(dc);
-
-       return 0;
 }
 
-static int tegra_cursor_plane_disable(struct drm_plane *plane)
+static void tegra_cursor_atomic_disable(struct drm_plane *plane,
+                                       struct drm_plane_state *old_state)
 {
-       struct tegra_dc *dc = to_tegra_dc(plane->crtc);
+       struct tegra_dc *dc;
        u32 value;
 
-       if (!plane->crtc)
-               return 0;
+       /* rien ne va plus */
+       if (!old_state || !old_state->crtc)
+               return;
+
+       dc = to_tegra_dc(old_state->crtc);
 
        value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS);
        value &= ~CURSOR_ENABLE;
@@ -540,14 +677,21 @@ static int tegra_cursor_plane_disable(struct drm_plane *plane)
 
        tegra_dc_cursor_commit(dc);
        tegra_dc_commit(dc);
-
-       return 0;
 }
 
 static const struct drm_plane_funcs tegra_cursor_plane_funcs = {
-       .update_plane = tegra_cursor_plane_update,
-       .disable_plane = tegra_cursor_plane_disable,
+       .update_plane = drm_plane_helper_update,
+       .disable_plane = drm_plane_helper_disable,
        .destroy = tegra_plane_destroy,
+       .atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
+};
+
+static const struct drm_plane_helper_funcs tegra_cursor_plane_helper_funcs = {
+       .prepare_fb = tegra_plane_prepare_fb,
+       .cleanup_fb = tegra_plane_cleanup_fb,
+       .atomic_check = tegra_cursor_atomic_check,
+       .atomic_update = tegra_cursor_atomic_update,
+       .atomic_disable = tegra_cursor_atomic_disable,
 };
 
 static struct drm_plane *tegra_dc_cursor_plane_create(struct drm_device *drm,
@@ -573,71 +717,21 @@ static struct drm_plane *tegra_dc_cursor_plane_create(struct drm_device *drm,
                return ERR_PTR(err);
        }
 
-       return &plane->base;
-}
-
-static int tegra_overlay_plane_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 tegra_plane *p = to_tegra_plane(plane);
-       struct tegra_dc *dc = to_tegra_dc(crtc);
-       struct tegra_dc_window window;
-       unsigned int i;
-       int err;
-
-       memset(&window, 0, sizeof(window));
-       window.src.x = src_x >> 16;
-       window.src.y = src_y >> 16;
-       window.src.w = src_w >> 16;
-       window.src.h = src_h >> 16;
-       window.dst.x = crtc_x;
-       window.dst.y = crtc_y;
-       window.dst.w = crtc_w;
-       window.dst.h = crtc_h;
-       window.format = tegra_dc_format(fb->pixel_format, &window.swap);
-       window.bits_per_pixel = fb->bits_per_pixel;
-       window.bottom_up = tegra_fb_is_bottom_up(fb);
-
-       err = tegra_fb_get_tiling(fb, &window.tiling);
-       if (err < 0)
-               return err;
+       drm_plane_helper_add(&plane->base, &tegra_cursor_plane_helper_funcs);
 
-       for (i = 0; i < drm_format_num_planes(fb->pixel_format); i++) {
-               struct tegra_bo *bo = tegra_fb_get_plane(fb, i);
-
-               window.base[i] = bo->paddr + fb->offsets[i];
-
-               /*
-                * Tegra doesn't support different strides for U and V planes
-                * so we display a warning if the user tries to display a
-                * framebuffer with such a configuration.
-                */
-               if (i >= 2) {
-                       if (fb->pitches[i] != window.stride[1])
-                               DRM_ERROR("unsupported UV-plane configuration\n");
-               } else {
-                       window.stride[i] = fb->pitches[i];
-               }
-       }
-
-       return tegra_dc_setup_window(dc, p->index, &window);
+       return &plane->base;
 }
 
 static void tegra_overlay_plane_destroy(struct drm_plane *plane)
 {
-       tegra_window_plane_disable(plane);
        tegra_plane_destroy(plane);
 }
 
 static const struct drm_plane_funcs tegra_overlay_plane_funcs = {
-       .update_plane = tegra_overlay_plane_update,
-       .disable_plane = tegra_window_plane_disable,
+       .update_plane = drm_plane_helper_update,
+       .disable_plane = drm_plane_helper_disable,
        .destroy = tegra_overlay_plane_destroy,
+       .atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
 };
 
 static const uint32_t tegra_overlay_plane_formats[] = {
@@ -650,6 +744,14 @@ static const uint32_t tegra_overlay_plane_formats[] = {
        DRM_FORMAT_YUV422,
 };
 
+static const struct drm_plane_helper_funcs tegra_overlay_plane_helper_funcs = {
+       .prepare_fb = tegra_plane_prepare_fb,
+       .cleanup_fb = tegra_plane_cleanup_fb,
+       .atomic_check = tegra_plane_atomic_check,
+       .atomic_update = tegra_plane_atomic_update,
+       .atomic_disable = tegra_plane_atomic_disable,
+};
+
 static struct drm_plane *tegra_dc_overlay_plane_create(struct drm_device *drm,
                                                       struct tegra_dc *dc,
                                                       unsigned int index)
@@ -676,6 +778,8 @@ static struct drm_plane *tegra_dc_overlay_plane_create(struct drm_device *drm,
                return ERR_PTR(err);
        }
 
+       drm_plane_helper_add(&plane->base, &tegra_overlay_plane_helper_funcs);
+
        return &plane->base;
 }
 
@@ -699,14 +803,16 @@ static int tegra_dc_set_base(struct tegra_dc *dc, int x, int y,
        struct tegra_bo *bo = tegra_fb_get_plane(fb, 0);
        unsigned int h_offset = 0, v_offset = 0;
        struct tegra_bo_tiling tiling;
+       unsigned long value, flags;
        unsigned int format, swap;
-       unsigned long value;
        int err;
 
        err = tegra_fb_get_tiling(fb, &tiling);
        if (err < 0)
                return err;
 
+       spin_lock_irqsave(&dc->lock, flags);
+
        tegra_dc_writel(dc, WINDOW_A_SELECT, DC_CMD_DISPLAY_WINDOW_HEADER);
 
        value = fb->offsets[0] + y * fb->pitches[0] +
@@ -752,6 +858,7 @@ static int tegra_dc_set_base(struct tegra_dc *dc, int x, int y,
 
                case TEGRA_BO_TILING_MODE_BLOCK:
                        DRM_ERROR("hardware doesn't support block linear mode\n");
+                       spin_unlock_irqrestore(&dc->lock, flags);
                        return -EINVAL;
                }
 
@@ -778,6 +885,8 @@ static int tegra_dc_set_base(struct tegra_dc *dc, int x, int y,
        tegra_dc_writel(dc, value << 8, DC_CMD_STATE_CONTROL);
        tegra_dc_writel(dc, value, DC_CMD_STATE_CONTROL);
 
+       spin_unlock_irqrestore(&dc->lock, flags);
+
        return 0;
 }
 
@@ -814,23 +923,32 @@ static void tegra_dc_finish_page_flip(struct tegra_dc *dc)
        unsigned long flags, base;
        struct tegra_bo *bo;
 
-       if (!dc->event)
+       spin_lock_irqsave(&drm->event_lock, flags);
+
+       if (!dc->event) {
+               spin_unlock_irqrestore(&drm->event_lock, flags);
                return;
+       }
 
        bo = tegra_fb_get_plane(crtc->primary->fb, 0);
 
+       spin_lock(&dc->lock);
+
        /* check if new start address has been latched */
+       tegra_dc_writel(dc, WINDOW_A_SELECT, DC_CMD_DISPLAY_WINDOW_HEADER);
        tegra_dc_writel(dc, READ_MUX, DC_CMD_STATE_ACCESS);
        base = tegra_dc_readl(dc, DC_WINBUF_START_ADDR);
        tegra_dc_writel(dc, 0, DC_CMD_STATE_ACCESS);
 
+       spin_unlock(&dc->lock);
+
        if (base == bo->paddr + crtc->primary->fb->offsets[0]) {
-               spin_lock_irqsave(&drm->event_lock, flags);
-               drm_send_vblank_event(drm, dc->pipe, dc->event);
-               drm_vblank_put(drm, dc->pipe);
+               drm_crtc_send_vblank_event(crtc, dc->event);
+               drm_crtc_vblank_put(crtc);
                dc->event = NULL;
-               spin_unlock_irqrestore(&drm->event_lock, flags);
        }
+
+       spin_unlock_irqrestore(&drm->event_lock, flags);
 }
 
 void tegra_dc_cancel_page_flip(struct drm_crtc *crtc, struct drm_file *file)
@@ -843,7 +961,7 @@ void tegra_dc_cancel_page_flip(struct drm_crtc *crtc, struct drm_file *file)
 
        if (dc->event && dc->event->base.file_priv == file) {
                dc->event->base.destroy(&dc->event->base);
-               drm_vblank_put(drm, dc->pipe);
+               drm_crtc_vblank_put(crtc);
                dc->event = NULL;
        }
 
@@ -853,16 +971,16 @@ void tegra_dc_cancel_page_flip(struct drm_crtc *crtc, struct drm_file *file)
 static int tegra_dc_page_flip(struct drm_crtc *crtc, struct drm_framebuffer *fb,
                              struct drm_pending_vblank_event *event, uint32_t page_flip_flags)
 {
+       unsigned int pipe = drm_crtc_index(crtc);
        struct tegra_dc *dc = to_tegra_dc(crtc);
-       struct drm_device *drm = crtc->dev;
 
        if (dc->event)
                return -EBUSY;
 
        if (event) {
-               event->pipe = dc->pipe;
+               event->pipe = pipe;
                dc->event = event;
-               drm_vblank_get(drm, dc->pipe);
+               drm_crtc_vblank_get(crtc);
        }
 
        tegra_dc_set_base(dc, 0, 0, fb);
@@ -871,39 +989,90 @@ static int tegra_dc_page_flip(struct drm_crtc *crtc, struct drm_framebuffer *fb,
        return 0;
 }
 
-static void drm_crtc_clear(struct drm_crtc *crtc)
-{
-       memset(crtc, 0, sizeof(*crtc));
-}
-
 static void tegra_dc_destroy(struct drm_crtc *crtc)
 {
        drm_crtc_cleanup(crtc);
-       drm_crtc_clear(crtc);
 }
 
 static const struct drm_crtc_funcs tegra_crtc_funcs = {
        .page_flip = tegra_dc_page_flip,
        .set_config = drm_crtc_helper_set_config,
        .destroy = tegra_dc_destroy,
+       .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
 };
 
+static void tegra_dc_stop(struct tegra_dc *dc)
+{
+       u32 value;
+
+       /* stop the display controller */
+       value = tegra_dc_readl(dc, DC_CMD_DISPLAY_COMMAND);
+       value &= ~DISP_CTRL_MODE_MASK;
+       tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND);
+
+       tegra_dc_commit(dc);
+}
+
+static bool tegra_dc_idle(struct tegra_dc *dc)
+{
+       u32 value;
+
+       value = tegra_dc_readl_active(dc, DC_CMD_DISPLAY_COMMAND);
+
+       return (value & DISP_CTRL_MODE_MASK) == 0;
+}
+
+static int tegra_dc_wait_idle(struct tegra_dc *dc, unsigned long timeout)
+{
+       timeout = jiffies + msecs_to_jiffies(timeout);
+
+       while (time_before(jiffies, timeout)) {
+               if (tegra_dc_idle(dc))
+                       return 0;
+
+               usleep_range(1000, 2000);
+       }
+
+       dev_dbg(dc->dev, "timeout waiting for DC to become idle\n");
+       return -ETIMEDOUT;
+}
+
 static void tegra_crtc_disable(struct drm_crtc *crtc)
 {
        struct tegra_dc *dc = to_tegra_dc(crtc);
-       struct drm_device *drm = crtc->dev;
-       struct drm_plane *plane;
+       u32 value;
 
-       drm_for_each_legacy_plane(plane, &drm->mode_config.plane_list) {
-               if (plane->crtc == crtc) {
-                       tegra_window_plane_disable(plane);
-                       plane->crtc = NULL;
+       if (!tegra_dc_idle(dc)) {
+               tegra_dc_stop(dc);
 
-                       if (plane->fb) {
-                               drm_framebuffer_unreference(plane->fb);
-                               plane->fb = NULL;
-                       }
-               }
+               /*
+                * Ignore the return value, there isn't anything useful to do
+                * in case this fails.
+                */
+               tegra_dc_wait_idle(dc, 100);
+       }
+
+       /*
+        * This should really be part of the RGB encoder driver, but clearing
+        * these bits has the side-effect of stopping the display controller.
+        * When that happens no VBLANK interrupts will be raised. At the same
+        * time the encoder is disabled before the display controller, so the
+        * above code is always going to timeout waiting for the controller
+        * to go idle.
+        *
+        * Given the close coupling between the RGB encoder and the display
+        * controller doing it here is still kind of okay. None of the other
+        * encoder drivers require these bits to be cleared.
+        *
+        * XXX: Perhaps given that the display controller is switched off at
+        * this point anyway maybe clearing these bits isn't even useful for
+        * the RGB encoder?
+        */
+       if (dc->rgb) {
+               value = tegra_dc_readl(dc, DC_CMD_DISPLAY_POWER_CONTROL);
+               value &= ~(PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE |
+                          PW4_ENABLE | PM0_ENABLE | PM1_ENABLE);
+               tegra_dc_writel(dc, value, DC_CMD_DISPLAY_POWER_CONTROL);
        }
 
        drm_crtc_vblank_off(crtc);
@@ -947,33 +1116,15 @@ static int tegra_dc_set_timings(struct tegra_dc *dc,
        return 0;
 }
 
-static int tegra_crtc_setup_clk(struct drm_crtc *crtc,
-                               struct drm_display_mode *mode)
+int tegra_dc_setup_clock(struct tegra_dc *dc, struct clk *parent,
+                        unsigned long pclk, unsigned int div)
 {
-       unsigned long pclk = mode->clock * 1000;
-       struct tegra_dc *dc = to_tegra_dc(crtc);
-       struct tegra_output *output = NULL;
-       struct drm_encoder *encoder;
-       unsigned int div;
        u32 value;
-       long err;
-
-       list_for_each_entry(encoder, &crtc->dev->mode_config.encoder_list, head)
-               if (encoder->crtc == crtc) {
-                       output = encoder_to_output(encoder);
-                       break;
-               }
-
-       if (!output)
-               return -ENODEV;
+       int err;
 
-       /*
-        * This assumes that the parent clock is pll_d_out0 or pll_d2_out
-        * respectively, each of which divides the base pll_d by 2.
-        */
-       err = tegra_output_setup_clock(output, dc->clk, pclk, &div);
+       err = clk_set_parent(dc->clk, parent);
        if (err < 0) {
-               dev_err(dc->dev, "failed to setup clock: %ld\n", err);
+               dev_err(dc->dev, "failed to set parent clock: %d\n", err);
                return err;
        }
 
@@ -985,62 +1136,24 @@ static int tegra_crtc_setup_clk(struct drm_crtc *crtc,
        return 0;
 }
 
-static int tegra_crtc_mode_set(struct drm_crtc *crtc,
-                              struct drm_display_mode *mode,
-                              struct drm_display_mode *adjusted,
-                              int x, int y, struct drm_framebuffer *old_fb)
+static void tegra_crtc_mode_set_nofb(struct drm_crtc *crtc)
 {
-       struct tegra_bo *bo = tegra_fb_get_plane(crtc->primary->fb, 0);
+       struct drm_display_mode *mode = &crtc->state->adjusted_mode;
        struct tegra_dc *dc = to_tegra_dc(crtc);
-       struct tegra_dc_window window;
        u32 value;
-       int err;
-
-       err = tegra_crtc_setup_clk(crtc, mode);
-       if (err) {
-               dev_err(dc->dev, "failed to setup clock for CRTC: %d\n", err);
-               return err;
-       }
 
        /* program display mode */
        tegra_dc_set_timings(dc, mode);
 
+       if (dc->soc->supports_border_color)
+               tegra_dc_writel(dc, 0, DC_DISP_BORDER_COLOR);
+
        /* interlacing isn't supported yet, so disable it */
        if (dc->soc->supports_interlacing) {
                value = tegra_dc_readl(dc, DC_DISP_INTERLACE_CONTROL);
                value &= ~INTERLACE_ENABLE;
                tegra_dc_writel(dc, value, DC_DISP_INTERLACE_CONTROL);
        }
-
-       /* setup window parameters */
-       memset(&window, 0, sizeof(window));
-       window.src.x = 0;
-       window.src.y = 0;
-       window.src.w = mode->hdisplay;
-       window.src.h = mode->vdisplay;
-       window.dst.x = 0;
-       window.dst.y = 0;
-       window.dst.w = mode->hdisplay;
-       window.dst.h = mode->vdisplay;
-       window.format = tegra_dc_format(crtc->primary->fb->pixel_format,
-                                       &window.swap);
-       window.bits_per_pixel = crtc->primary->fb->bits_per_pixel;
-       window.stride[0] = crtc->primary->fb->pitches[0];
-       window.base[0] = bo->paddr;
-
-       err = tegra_dc_setup_window(dc, 0, &window);
-       if (err < 0)
-               dev_err(dc->dev, "failed to enable root plane\n");
-
-       return 0;
-}
-
-static int tegra_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
-                                   struct drm_framebuffer *old_fb)
-{
-       struct tegra_dc *dc = to_tegra_dc(crtc);
-
-       return tegra_dc_set_base(dc, x, y, crtc->primary->fb);
 }
 
 static void tegra_crtc_prepare(struct drm_crtc *crtc)
@@ -1051,10 +1164,6 @@ static void tegra_crtc_prepare(struct drm_crtc *crtc)
 
        drm_crtc_vblank_off(crtc);
 
-       /* hardware initialization */
-       reset_control_deassert(dc->rst);
-       usleep_range(10000, 20000);
-
        if (dc->pipe)
                syncpt = SYNCPT_VBLANK1;
        else
@@ -1095,18 +1204,31 @@ static void tegra_crtc_commit(struct drm_crtc *crtc)
        tegra_dc_commit(dc);
 }
 
-static void tegra_crtc_load_lut(struct drm_crtc *crtc)
+static int tegra_crtc_atomic_check(struct drm_crtc *crtc,
+                                  struct drm_crtc_state *state)
+{
+       return 0;
+}
+
+static void tegra_crtc_atomic_begin(struct drm_crtc *crtc)
+{
+}
+
+static void tegra_crtc_atomic_flush(struct drm_crtc *crtc)
 {
 }
 
 static const struct drm_crtc_helper_funcs tegra_crtc_helper_funcs = {
        .disable = tegra_crtc_disable,
        .mode_fixup = tegra_crtc_mode_fixup,
-       .mode_set = tegra_crtc_mode_set,
-       .mode_set_base = tegra_crtc_mode_set_base,
+       .mode_set = drm_helper_crtc_mode_set,
+       .mode_set_nofb = tegra_crtc_mode_set_nofb,
+       .mode_set_base = drm_helper_crtc_mode_set_base,
        .prepare = tegra_crtc_prepare,
        .commit = tegra_crtc_commit,
-       .load_lut = tegra_crtc_load_lut,
+       .atomic_check = tegra_crtc_atomic_check,
+       .atomic_begin = tegra_crtc_atomic_begin,
+       .atomic_flush = tegra_crtc_atomic_flush,
 };
 
 static irqreturn_t tegra_dc_irq(int irq, void *data)
@@ -1127,7 +1249,7 @@ static irqreturn_t tegra_dc_irq(int irq, void *data)
                /*
                dev_dbg(dc->dev, "%s(): vertical blank\n", __func__);
                */
-               drm_handle_vblank(dc->base.dev, dc->pipe);
+               drm_crtc_handle_vblank(&dc->base);
                tegra_dc_finish_page_flip(dc);
        }
 
@@ -1552,6 +1674,7 @@ static const struct host1x_client_ops dc_client_ops = {
 };
 
 static const struct tegra_dc_soc_info tegra20_dc_soc_info = {
+       .supports_border_color = true,
        .supports_interlacing = false,
        .supports_cursor = false,
        .supports_block_linear = false,
@@ -1560,6 +1683,7 @@ static const struct tegra_dc_soc_info tegra20_dc_soc_info = {
 };
 
 static const struct tegra_dc_soc_info tegra30_dc_soc_info = {
+       .supports_border_color = true,
        .supports_interlacing = false,
        .supports_cursor = false,
        .supports_block_linear = false,
@@ -1568,6 +1692,7 @@ static const struct tegra_dc_soc_info tegra30_dc_soc_info = {
 };
 
 static const struct tegra_dc_soc_info tegra114_dc_soc_info = {
+       .supports_border_color = true,
        .supports_interlacing = false,
        .supports_cursor = false,
        .supports_block_linear = false,
@@ -1576,6 +1701,7 @@ static const struct tegra_dc_soc_info tegra114_dc_soc_info = {
 };
 
 static const struct tegra_dc_soc_info tegra124_dc_soc_info = {
+       .supports_border_color = false,
        .supports_interlacing = true,
        .supports_cursor = true,
        .supports_block_linear = true,
This page took 0.069824 seconds and 5 git commands to generate.