This patch adds generic plane rotation property for all supported
drivers. This has been implemented with additional help from Exynos IPP
(Exynos Image Post-Processing subsystem) with temporary framebuffers.
Besides rotation, scaling and color space conversion are also supported.

Signed-off-by: Marek Szyprowski <m.szyprowski at samsung.com>
---
 drivers/gpu/drm/exynos/Kconfig                |   8 +
 drivers/gpu/drm/exynos/Makefile               |   1 +
 drivers/gpu/drm/exynos/exynos_drm_drv.h       |   9 +
 drivers/gpu/drm/exynos/exynos_drm_ipp.c       | 154 ++++++++++-
 drivers/gpu/drm/exynos/exynos_drm_ipp.h       |   4 +
 drivers/gpu/drm/exynos/exynos_drm_plane.c     |  28 +-
 drivers/gpu/drm/exynos/exynos_drm_plane_ipp.c | 369 ++++++++++++++++++++++++++
 drivers/gpu/drm/exynos/exynos_drm_plane_ipp.h |  73 +++++
 8 files changed, 641 insertions(+), 5 deletions(-)
 create mode 100644 drivers/gpu/drm/exynos/exynos_drm_plane_ipp.c
 create mode 100644 drivers/gpu/drm/exynos/exynos_drm_plane_ipp.h

diff --git a/drivers/gpu/drm/exynos/Kconfig b/drivers/gpu/drm/exynos/Kconfig
index 83efca941388..e7d414aefbdc 100644
--- a/drivers/gpu/drm/exynos/Kconfig
+++ b/drivers/gpu/drm/exynos/Kconfig
@@ -104,6 +104,14 @@ config DRM_EXYNOS_IPP
        help
          Choose this option if you want to use IPP feature for DRM.

+config DRM_EXYNOS_PLANE_IPP
+       depends on DRM_EXYNOS_IPP
+       bool "Use IPP framework for implementing unsupported plane properties"
+       help
+         Choose this option if you want to let IPP framework to provide plane
+         properties (like rotation, overlay scaling and more pixel formats),
+         which are not supported by hardware CRTC drivers.
+
 config DRM_EXYNOS_FIMC
        bool "FIMC"
        depends on DRM_EXYNOS_IPP && MFD_SYSCON
diff --git a/drivers/gpu/drm/exynos/Makefile b/drivers/gpu/drm/exynos/Makefile
index 6496532aaa91..92c3f7cac7a9 100644
--- a/drivers/gpu/drm/exynos/Makefile
+++ b/drivers/gpu/drm/exynos/Makefile
@@ -19,6 +19,7 @@ exynosdrm-$(CONFIG_DRM_EXYNOS_HDMI)   += exynos_hdmi.o
 exynosdrm-$(CONFIG_DRM_EXYNOS_VIDI)    += exynos_drm_vidi.o
 exynosdrm-$(CONFIG_DRM_EXYNOS_G2D)     += exynos_drm_g2d.o
 exynosdrm-$(CONFIG_DRM_EXYNOS_IPP)     += exynos_drm_ipp.o
+exynosdrm-$(CONFIG_DRM_EXYNOS_PLANE_IPP)       += exynos_drm_plane_ipp.o
 exynosdrm-$(CONFIG_DRM_EXYNOS_FIMC)    += exynos_drm_fimc.o
 exynosdrm-$(CONFIG_DRM_EXYNOS_ROTATOR) += exynos_drm_rotator.o
 exynosdrm-$(CONFIG_DRM_EXYNOS_GSC)     += exynos_drm_gsc.o
diff --git a/drivers/gpu/drm/exynos/exynos_drm_drv.h 
b/drivers/gpu/drm/exynos/exynos_drm_drv.h
index 670f6d06a2a9..b17e419935db 100644
--- a/drivers/gpu/drm/exynos/exynos_drm_drv.h
+++ b/drivers/gpu/drm/exynos/exynos_drm_drv.h
@@ -70,6 +70,13 @@ struct exynos_drm_plane_state {
        unsigned int h_ratio;
        unsigned int v_ratio;
        struct drm_framebuffer *fb;
+       unsigned int rotation;
+
+       unsigned int ipp_needed;
+       uint32_t ipp_pixel_format;
+       struct exynos_drm_rect ipp_src;
+       struct exynos_drm_rect ipp_dst;
+       struct drm_framebuffer *ipp_fb;
 };

 static inline struct exynos_drm_plane_state *
@@ -93,6 +100,8 @@ struct exynos_drm_plane {
        const struct exynos_drm_plane_config *config;
        unsigned int zpos;
        struct drm_framebuffer *pending_fb;
+       struct drm_framebuffer *ipp_cur_fb;
+       struct drm_framebuffer *ipp_next_fb;
 };

 #define EXYNOS_DRM_PLANE_CAP_DOUBLE    (1 << 0)
diff --git a/drivers/gpu/drm/exynos/exynos_drm_ipp.c 
b/drivers/gpu/drm/exynos/exynos_drm_ipp.c
index 44a6689e0f4c..231cfbfe036c 100644
--- a/drivers/gpu/drm/exynos/exynos_drm_ipp.c
+++ b/drivers/gpu/drm/exynos/exynos_drm_ipp.c
@@ -20,6 +20,7 @@
 #include <drm/drmP.h>
 #include <drm/exynos_drm.h>
 #include "exynos_drm_drv.h"
+#include "exynos_drm_fb.h"
 #include "exynos_drm_gem.h"
 #include "exynos_drm_ipp.h"
 #include "exynos_drm_iommu.h"
@@ -1513,7 +1514,7 @@ static int ipp_send_event(struct exynos_drm_ippdrv 
*ippdrv,

        spin_lock_irqsave(&drm_dev->event_lock, flags);
        list_move_tail(&e->base.link, &e->base.file_priv->event_list);
-       wake_up_interruptible(&e->base.file_priv->event_wait);
+       wake_up(&e->base.file_priv->event_wait);
        spin_unlock_irqrestore(&drm_dev->event_lock, flags);
        mutex_unlock(&c_node->event_lock);

@@ -1579,6 +1580,157 @@ err_completion:
                complete(&c_node->start_complete);
 }

+static struct drm_exynos_ipp_mem_node
+               *ipp_get_internal_mem_node(struct drm_device *drm_dev,
+               struct drm_exynos_ipp_cmd_node *c_node,
+               __u32 prop_id, enum drm_exynos_ops_id ops_id,
+               struct drm_framebuffer *fb)
+{
+       struct drm_exynos_ipp_mem_node *m_node;
+       struct drm_exynos_ipp_buf_info *buf_info;
+       int i;
+
+       m_node = kzalloc(sizeof(*m_node), GFP_KERNEL);
+       if (!m_node)
+               return ERR_PTR(-ENOMEM);
+
+       buf_info = &m_node->buf_info;
+
+       m_node->ops_id = ops_id;
+       m_node->prop_id = prop_id;
+       INIT_LIST_HEAD(&m_node->list);
+
+       DRM_DEBUG_KMS("m_node[0x%x]ops_id[%d]prop_id[%d]\n", (int)m_node, 
ops_id, prop_id);
+
+       for_each_ipp_planar(i) {
+               buf_info->obj[i] = NULL;
+               buf_info->base[i] = exynos_drm_fb_dma_addr(fb, i);
+               buf_info->size[i] = fb->pitches[i] * fb->height;
+       }
+
+       mutex_lock(&c_node->mem_lock);
+       list_add_tail(&m_node->list, &c_node->mem_list[ops_id]);
+       mutex_unlock(&c_node->mem_lock);
+
+       return m_node;
+}
+
+
+static int exynos_drm_ipp_internal_enqueue_buf(struct drm_device *drm_dev,
+               __u32 prop_id, enum drm_exynos_ops_id ops_id,
+               struct drm_framebuffer *fb)
+{
+       struct drm_exynos_ipp_cmd_node *c_node;
+       struct drm_exynos_ipp_mem_node *m_node;
+       int ret;
+       struct drm_exynos_ipp_queue_buf qbuf = {
+               .ops_id = ops_id,
+               .buf_type = IPP_BUF_ENQUEUE,
+               .prop_id = prop_id,
+       };
+
+       DRM_DEBUG_KMS("prop_id[%d]ops_id[%s]\n",
+               prop_id, ops_id ? "dst" : "src");
+
+       c_node = ipp_find_obj(&ctx->prop_idr, &ctx->prop_lock, prop_id);
+       if (!c_node) {
+               DRM_ERROR("failed to get command node.\n");
+               return -ENODEV;
+       }
+
+       m_node = ipp_get_internal_mem_node(drm_dev, c_node, prop_id,
+                                          ops_id, fb);
+       if (IS_ERR(m_node)) {
+               DRM_ERROR("failed to get m_node.\n");
+               return PTR_ERR(m_node);
+       }
+
+       if (ops_id == EXYNOS_DRM_OPS_DST) {
+               ret = ipp_get_event(drm_dev, c_node, &qbuf);
+               if (ret) {
+                       DRM_ERROR("failed to get event.\n");
+                       goto err_clean_node;
+               }
+
+               ret = ipp_queue_buf_with_run(c_node, m_node, &qbuf);
+               if (ret) {
+                       DRM_ERROR("failed to run command.\n");
+                       goto err_clean_node;
+               }
+       }
+
+       return 0;
+
+err_clean_node:
+       DRM_ERROR("clean memory nodes.\n");
+
+       ipp_clean_queue_buf(drm_dev, c_node, &qbuf);
+       return ret;
+}
+
+int exynos_ipp_process_internal(struct drm_device *drm_dev,
+       struct drm_exynos_ipp_config *src_conf, struct drm_framebuffer *src_fb,
+       struct drm_exynos_ipp_config *dst_conf, struct drm_framebuffer *dst_fb)
+{
+       int ret;
+       struct drm_exynos_ipp_property property = {
+               .config = {
+                       *src_conf,
+                       *dst_conf,
+               },
+               .cmd = IPP_CMD_M2M,
+       };
+       struct drm_exynos_ipp_cmd_ctrl ctrl = {
+               /* .prop_id */
+               .ctrl = IPP_CTRL_PLAY,
+       };
+       struct drm_exynos_ipp_cmd_ctrl ctrl_stop = {
+               /* .prop_id */
+               .ctrl = IPP_CTRL_STOP,
+       };
+       struct drm_file virt_file = { };
+
+       INIT_LIST_HEAD(&virt_file.lhead);
+       INIT_LIST_HEAD(&virt_file.fbs);
+       mutex_init(&virt_file.fbs_lock);
+       INIT_LIST_HEAD(&virt_file.blobs);
+       INIT_LIST_HEAD(&virt_file.event_list);
+       init_waitqueue_head(&virt_file.event_wait);
+       virt_file.event_space = 4096;
+       virt_file.uid = current_euid();
+       virt_file.pid = get_pid(task_pid(current));
+       virt_file.authenticated = capable(CAP_SYS_ADMIN);
+       virt_file.lock_count = 0;
+
+       ret = exynos_drm_ipp_set_property(drm_dev, &property, &virt_file);
+       if (ret)
+               return ret;
+
+       ctrl.prop_id = property.prop_id;
+       ctrl_stop.prop_id = property.prop_id;
+
+       ret = exynos_drm_ipp_internal_enqueue_buf(drm_dev, property.prop_id,
+                                                 EXYNOS_DRM_OPS_SRC, src_fb);
+       if (ret)
+               goto cleanup;
+
+       ret = exynos_drm_ipp_internal_enqueue_buf(drm_dev, property.prop_id,
+                                                 EXYNOS_DRM_OPS_DST, dst_fb);
+       if (ret)
+               goto cleanup;
+
+       ret = exynos_drm_ipp_cmd_ctrl(drm_dev, &ctrl, &virt_file);
+       if (ret)
+               goto cleanup;
+
+       wait_event(virt_file.event_wait, !list_empty(&virt_file.event_list));
+
+cleanup:
+       exynos_drm_ipp_cmd_ctrl(drm_dev, &ctrl_stop, &virt_file);
+
+       return ret;
+}
+
 static int ipp_subdrv_probe(struct drm_device *drm_dev, struct device *dev)
 {
        struct exynos_drm_ippdrv *ippdrv;
diff --git a/drivers/gpu/drm/exynos/exynos_drm_ipp.h 
b/drivers/gpu/drm/exynos/exynos_drm_ipp.h
index 1dc13bf57b16..7e95437edecb 100644
--- a/drivers/gpu/drm/exynos/exynos_drm_ipp.h
+++ b/drivers/gpu/drm/exynos/exynos_drm_ipp.h
@@ -24,6 +24,10 @@
 #define IPP_GET_LCD_HEIGHT     _IOR('F', 303, int)
 #define IPP_SET_WRITEBACK      _IOW('F', 304, u32)

+int exynos_ipp_process_internal(struct drm_device *drm_dev,
+       struct drm_exynos_ipp_config *src_conf, struct drm_framebuffer *src_fb,
+       struct drm_exynos_ipp_config *dst_conf, struct drm_framebuffer *dst_fb);
+
 /* definition of state */
 enum drm_exynos_ipp_state {
        IPP_STATE_IDLE,
diff --git a/drivers/gpu/drm/exynos/exynos_drm_plane.c 
b/drivers/gpu/drm/exynos/exynos_drm_plane.c
index 8a1242b5a938..34ed1d6b8184 100644
--- a/drivers/gpu/drm/exynos/exynos_drm_plane.c
+++ b/drivers/gpu/drm/exynos/exynos_drm_plane.c
@@ -19,6 +19,7 @@
 #include "exynos_drm_fb.h"
 #include "exynos_drm_gem.h"
 #include "exynos_drm_plane.h"
+#include "exynos_drm_plane_ipp.h"

 /*
  * This function is to get X or Y size shown via screen. This needs length and
@@ -56,7 +57,8 @@ static int exynos_plane_get_size(int start, unsigned length, 
unsigned last)
        return size;
 }

-static void exynos_plane_mode_set(struct exynos_drm_plane_state *exynos_state)
+static void exynos_plane_mode_set(struct exynos_drm_plane_state *exynos_state,
+                                 const struct exynos_drm_plane_config *config)

 {
        struct drm_plane_state *state = &exynos_state->base;
@@ -85,6 +87,9 @@ static void exynos_plane_mode_set(struct 
exynos_drm_plane_state *exynos_state)
        src_w = state->src_w >> 16;
        src_h = state->src_h >> 16;

+       exynos_plane_ipp_setup(exynos_state, config, &src_x, &src_y, &src_w,
+                              &src_h, &crtc_w, &crtc_h);
+
        /* set ratio */
        exynos_state->h_ratio = (src_w << 16) / crtc_w;
        exynos_state->v_ratio = (src_h << 16) / crtc_h;
@@ -163,6 +168,13 @@ static void exynos_drm_plane_destroy_state(struct 
drm_plane *plane,
 {
        struct exynos_drm_plane_state *old_exynos_state =
                                        to_exynos_plane_state(old_state);
+       struct exynos_drm_plane *exynos_plane = to_exynos_plane(plane);
+       /*
+        * This is the only place in the code, where all temporary objects
+        * used for processing are no longer used and can be freed.
+        */
+       exynos_plane_ipp_cleanup(exynos_plane, old_exynos_state);
+
        __drm_atomic_helper_plane_destroy_state(plane, old_state);
        kfree(old_exynos_state);
 }
@@ -174,6 +186,7 @@ static struct drm_plane_funcs exynos_plane_funcs = {
        .reset          = exynos_drm_plane_reset,
        .atomic_duplicate_state = exynos_drm_plane_duplicate_state,
        .atomic_destroy_state = exynos_drm_plane_destroy_state,
+       .set_property = drm_atomic_helper_plane_set_property,
 };

 static int
@@ -218,16 +231,21 @@ static int exynos_plane_atomic_check(struct drm_plane 
*plane,
                return 0;

        /* translate state into exynos_state */
-       exynos_plane_mode_set(exynos_state);
+       exynos_plane_mode_set(exynos_state, exynos_plane->config);

        ret = exynos_drm_plane_check_size(exynos_plane->config, exynos_state);
-       return ret;
+       if (ret)
+               return ret;
+
+       return exynos_plane_ipp_check(exynos_plane, exynos_state);
 }

 static void exynos_plane_atomic_update(struct drm_plane *plane,
                                       struct drm_plane_state *old_state)
 {
        struct drm_plane_state *state = plane->state;
+       struct exynos_drm_plane_state *exynos_state =
+                                               to_exynos_plane_state(state);
        struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(state->crtc);
        struct exynos_drm_plane *exynos_plane = to_exynos_plane(plane);

@@ -237,6 +255,8 @@ static void exynos_plane_atomic_update(struct drm_plane 
*plane,
        plane->crtc = state->crtc;
        exynos_plane->pending_fb = state->fb;

+       exynos_plane_ipp_update(exynos_plane, exynos_state);
+
        if (exynos_crtc->ops->update_plane)
                exynos_crtc->ops->update_plane(exynos_crtc, exynos_plane);
 }
@@ -308,5 +328,5 @@ int exynos_plane_init(struct drm_device *dev,
                exynos_plane_attach_zpos_property(&exynos_plane->base,
                                                  config->zpos);

-       return 0;
+       return exynos_plane_ipp_init(dev, exynos_plane);
 }
diff --git a/drivers/gpu/drm/exynos/exynos_drm_plane_ipp.c 
b/drivers/gpu/drm/exynos/exynos_drm_plane_ipp.c
new file mode 100644
index 000000000000..3086fd29f482
--- /dev/null
+++ b/drivers/gpu/drm/exynos/exynos_drm_plane_ipp.c
@@ -0,0 +1,369 @@
+/*
+ * Copyright (C) 2015 Samsung Electronics Co.Ltd
+ * Authors: Marek Szyprowski <m.szyprowski at samsung.com>
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <drm/drmP.h>
+
+#include <drm/exynos_drm.h>
+#include "exynos_drm_drv.h"
+#include "exynos_drm_fb.h"
+#include "exynos_drm_gem.h"
+#include "exynos_drm_ipp.h"
+#include "exynos_drm_plane.h"
+#include "exynos_drm_plane_ipp.h"
+#include "exynos_drm_iommu.h"
+
+static uint32_t preferred_formats[] = {
+       DRM_FORMAT_XRGB8888,
+       DRM_FORMAT_RGB565,
+       DRM_FORMAT_NV12,
+};
+
+static uint32_t supported_formats[] = {
+       DRM_FORMAT_RGB565,
+       DRM_FORMAT_XRGB8888,
+       DRM_FORMAT_YUYV,
+       DRM_FORMAT_YVYU,
+       DRM_FORMAT_UYVY,
+       DRM_FORMAT_VYUY,
+       DRM_FORMAT_NV12,
+       DRM_FORMAT_NV16,
+       DRM_FORMAT_NV21,
+       DRM_FORMAT_NV61,
+       DRM_FORMAT_YUV422,
+       DRM_FORMAT_YUV420,
+       DRM_FORMAT_YVU420,
+};
+
+static bool
+exynos_plane_check_format(const struct exynos_drm_plane_config *config,
+                              uint32_t format)
+{
+       int i;
+
+       for (i = 0; i < config->num_pixel_formats; i++)
+               if (config->pixel_formats[i] == format)
+                       return true;
+       return false;
+}
+
+static struct drm_framebuffer *exynos_plane_ipp_alloc_fb(struct drm_device 
*dev,
+                                       struct exynos_drm_plane_state *state)
+{
+       struct exynos_drm_gem *exynos_gem[MAX_FB_BUFFER] = { NULL };
+       struct drm_mode_fb_cmd2 mode_cmd = { 0 };
+       struct drm_framebuffer *fb;
+       unsigned int size;
+       unsigned int flags;
+       int i, cpp;
+       int num_planes = drm_format_num_planes(state->ipp_pixel_format);
+       int ret = 0;
+
+
+       if (is_drm_iommu_supported(dev))
+               flags = EXYNOS_BO_NONCONTIG | EXYNOS_BO_WC;
+       else
+               flags = EXYNOS_BO_CONTIG | EXYNOS_BO_WC;
+
+       mode_cmd.width = state->ipp_dst.x + state->ipp_dst.w;
+       mode_cmd.height = state->ipp_dst.y + state->ipp_dst.h;
+       mode_cmd.pixel_format = state->ipp_pixel_format;
+
+       for (i = 0; i < num_planes; i++) {
+               cpp = drm_format_plane_cpp(state->ipp_pixel_format, i);
+               mode_cmd.pitches[i] = roundup(mode_cmd.width,
+                                       EXYNOS_DRM_PITCH_ALIGN) * cpp;
+               mode_cmd.offsets[i] = 0;
+               size = mode_cmd.height * mode_cmd.pitches[i];
+               exynos_gem[i] = exynos_drm_gem_create(dev, flags, size);
+               if (IS_ERR(exynos_gem[i])) {
+                       ret = PTR_ERR(exynos_gem[i]);
+                       goto err_free;
+               }
+       }
+
+       fb = exynos_drm_framebuffer_init(dev, &mode_cmd, exynos_gem, 
num_planes);
+       if (IS_ERR(fb)) {
+               ret = PTR_ERR(fb);
+               goto err_free;
+       }
+
+       return fb;
+
+err_free:
+       for (;i >= 0; i--)
+               exynos_drm_gem_destroy(exynos_gem[i]);
+       return ERR_PTR(ret);
+}
+
+static bool exynos_plane_ipp_check_fb(struct drm_framebuffer *ipp_fb,
+                               struct exynos_drm_plane_state *state)
+{
+       if (state->ipp_dst.x + state->ipp_dst.w <= ipp_fb->width &&
+           state->ipp_dst.y + state->ipp_dst.h <= ipp_fb->height &&
+           state->base.fb->pixel_format == ipp_fb->pixel_format)
+               return true;
+       return false;
+}
+
+static int exynos_plane_ipp_transform(struct exynos_drm_plane_state *state)
+{
+       struct drm_framebuffer *src_fb = state->base.fb;
+       struct drm_framebuffer *dst_fb = state->ipp_fb;
+       struct drm_exynos_ipp_config src_config = {
+               .ops_id = EXYNOS_DRM_OPS_SRC,
+               .sz = {
+                       .hsize = src_fb->pitches[0] /
+                               drm_format_plane_cpp(src_fb->pixel_format, 0),
+                       .vsize = src_fb->height,
+               },
+               .fmt = src_fb->pixel_format,
+               .pos = {
+                       .x = state->ipp_src.x,
+                       .y = state->ipp_src.y,
+                       .w = state->ipp_src.w,
+                       .h = state->ipp_src.h,
+               },
+       };
+       struct drm_exynos_ipp_config dst_config = {
+               .sz = {
+                       .hsize = dst_fb->pitches[0] /
+                               drm_format_plane_cpp(dst_fb->pixel_format, 0),
+                       .vsize = dst_fb->height,
+               },
+               .fmt = dst_fb->pixel_format,
+               .pos = {
+                       .x = state->ipp_dst.x,
+                       .y = state->ipp_dst.y,
+                       .w = state->ipp_dst.w,
+                       .h = state->ipp_dst.h,
+               },
+       };
+       int degree = 0, flip = 0;
+
+       if (state->rotation & BIT(DRM_ROTATE_180))
+               degree = EXYNOS_DRM_DEGREE_180;
+       else if (state->rotation & BIT(DRM_ROTATE_90))
+               degree = EXYNOS_DRM_DEGREE_90;
+       else if (state->rotation & BIT(DRM_ROTATE_270))
+               degree = EXYNOS_DRM_DEGREE_270;
+
+       if (state->rotation & BIT(DRM_REFLECT_X))
+               flip |= EXYNOS_DRM_FLIP_HORIZONTAL;
+       if (state->rotation & BIT(DRM_REFLECT_Y))
+               flip |= EXYNOS_DRM_FLIP_VERTICAL;
+
+       dst_config.flip = flip;
+       dst_config.degree = degree;
+
+       return exynos_ipp_process_internal(src_fb->dev, &src_config, src_fb,
+                                          &dst_config, dst_fb);
+}
+
+void exynos_plane_ipp_setup(struct exynos_drm_plane_state *state,
+                           const struct exynos_drm_plane_config *config,
+                           unsigned int *src_x, unsigned int *src_y,
+                           unsigned int *src_w, unsigned int *src_h,
+                           unsigned int *crtc_w, unsigned int *crtc_h)
+{
+       int rotation = state->base.rotation;
+       int pre_x, pre_y, post_x, post_y;
+       int i;
+       bool supported_pixel_format =
+               exynos_plane_check_format(config, state->base.fb->pixel_format);
+
+       state->rotation = rotation;
+
+       /* check if ipp is really needed */
+       if (rotation == BIT(DRM_ROTATE_0) && supported_pixel_format &&
+           (*src_w == *crtc_w ||
+            ((config->capabilities & EXYNOS_DRM_PLANE_CAP_DOUBLE) &&
+              *src_w * 2 == *crtc_w) ||
+             config->capabilities & EXYNOS_DRM_PLANE_CAP_SCALE) &&
+           (*src_h == *crtc_h ||
+            ((config->capabilities & EXYNOS_DRM_PLANE_CAP_DOUBLE) &&
+              *src_h * 2 == *crtc_h) ||
+             config->capabilities & EXYNOS_DRM_PLANE_CAP_SCALE))
+               return;
+
+       state->ipp_needed = true;
+
+       state->ipp_pixel_format = state->base.fb->pixel_format;
+       if (!supported_pixel_format) {
+               for (i = 0; i < ARRAY_SIZE(preferred_formats); i++)
+                       if (exynos_plane_check_format(config,
+                                                     preferred_formats[i])) {
+                               state->ipp_pixel_format = preferred_formats[i];
+                               break;
+                       }
+       }
+
+       state->ipp_src.x = rounddown(*src_x, EXYNOS_DRM_PITCH_ALIGN);
+       state->ipp_src.y = rounddown(*src_y, EXYNOS_DRM_PITCH_ALIGN);
+       state->ipp_dst.x = 0;
+       state->ipp_dst.y = 0;
+
+       pre_x = *src_x & (EXYNOS_DRM_PITCH_ALIGN - 1);
+       pre_y = *src_y & (EXYNOS_DRM_PITCH_ALIGN - 1);
+
+       state->ipp_src.w = roundup(*src_w + pre_x, EXYNOS_DRM_PITCH_ALIGN);
+       state->ipp_src.h = roundup(*src_h + pre_y, EXYNOS_DRM_PITCH_ALIGN);
+       if (state->ipp_src.w > state->base.fb->pitches[0])
+               state->ipp_src.w = state->base.fb->pitches[0];
+       if (state->ipp_src.h > state->base.fb->height)
+               state->ipp_src.h = state->base.fb->height;
+
+       state->ipp_dst.w = state->ipp_src.w;
+       state->ipp_dst.h = state->ipp_src.h;
+
+       post_x = state->ipp_src.w - pre_x - *src_w;
+       post_y = state->ipp_src.h - pre_y - *src_h;
+
+       if (rotation & BIT(DRM_REFLECT_Y))
+               swap(pre_y, post_y);
+
+       if (rotation & BIT(DRM_REFLECT_X))
+               swap(pre_x, post_x);
+
+       switch (rotation & 0xf) {
+       case BIT(DRM_ROTATE_0):
+               *src_x = pre_x;
+               *src_y = pre_y;
+               break;
+       case BIT(DRM_ROTATE_90):
+               *src_x = post_y;
+               *src_y = pre_x;
+               swap(*src_w, *src_h);
+               swap(state->ipp_dst.w, state->ipp_dst.h);
+               break;
+       case BIT(DRM_ROTATE_180):
+               *src_x = post_x;
+               *src_y = post_y;
+               break;
+       case BIT(DRM_ROTATE_270):
+               *src_x = pre_y;
+               *src_y = post_x;
+               swap(*src_w, *src_h);
+               swap(state->ipp_dst.w, state->ipp_dst.h);
+               break;
+       }
+
+       /* apply scalling */
+       state->ipp_dst.w = state->ipp_dst.w * *crtc_w / *src_w;
+       state->ipp_dst.h = state->ipp_dst.h * *crtc_h / *src_h;
+
+       *src_x = *src_x * *crtc_w / *src_w;
+       *src_y = *src_y * *crtc_h / *src_h;
+       *src_w = *crtc_w;
+       *src_h = *crtc_h;
+}
+
+int exynos_plane_ipp_check(struct exynos_drm_plane *plane,
+                          struct exynos_drm_plane_state *state)
+{
+       if (!state->ipp_needed)
+               return 0;
+
+       /* check if currently allocated ipp fb can be reused */
+       if (plane->ipp_next_fb &&
+               !exynos_plane_ipp_check_fb(plane->ipp_next_fb, state)) {
+               drm_framebuffer_unreference(plane->ipp_next_fb);
+               plane->ipp_next_fb = NULL;
+       }
+
+       /* allocate new ipp fb */
+       if (!plane->ipp_next_fb) {
+               struct drm_framebuffer *ipp_fb;
+
+               ipp_fb = exynos_plane_ipp_alloc_fb(plane->base.dev, state);
+               if (IS_ERR(ipp_fb))
+                       return PTR_ERR(ipp_fb);
+               plane->ipp_next_fb = ipp_fb;
+       }
+
+       state->fb = state->ipp_fb = plane->ipp_next_fb;
+
+       /* perform transformation */
+       return exynos_plane_ipp_transform(state);
+}
+
+void exynos_plane_ipp_update(struct exynos_drm_plane *plane,
+                            struct exynos_drm_plane_state *state)
+{
+       if (!state->ipp_needed)
+               return;
+
+       if (plane->ipp_next_fb)
+               swap(plane->ipp_next_fb, plane->ipp_cur_fb);
+}
+
+void exynos_plane_ipp_cleanup(struct exynos_drm_plane *plane,
+                             struct exynos_drm_plane_state *old_state)
+{
+       struct exynos_drm_plane_state *state;
+
+       /* get current plane state */
+       state = to_exynos_plane_state(plane->base.state);
+
+       if (!state->ipp_needed) {
+               if (plane->ipp_cur_fb) {
+                       drm_framebuffer_unreference(plane->ipp_cur_fb);
+                       plane->ipp_cur_fb = NULL;
+               }
+               if (plane->ipp_next_fb) {
+                       drm_framebuffer_unreference(plane->ipp_next_fb);
+                       plane->ipp_next_fb = NULL;
+               }
+       }
+}
+
+int exynos_plane_ipp_init(struct drm_device *dev,
+                         struct exynos_drm_plane *plane)
+{
+       int i, j, num_formats, ipp_formats = 0;
+       uint32_t *formats;
+
+       for (i = 0; i < ARRAY_SIZE(supported_formats); i++)
+               if (!exynos_plane_check_format(plane->config,
+                                              supported_formats[i]))
+                       ipp_formats++;
+
+       num_formats = plane->config->num_pixel_formats + ipp_formats;
+       formats = kmalloc_array(num_formats, sizeof(uint32_t), GFP_KERNEL);
+       if (!formats)
+               return -ENOMEM;
+
+       memcpy(formats, plane->config->pixel_formats,
+              plane->config->num_pixel_formats * sizeof(uint32_t));
+       j = plane->config->num_pixel_formats;
+
+       for (i = 0; i < ARRAY_SIZE(supported_formats); i++)
+               if (!exynos_plane_check_format(plane->config,
+                                              supported_formats[i]))
+                       formats[j++] = supported_formats[i];
+
+       kfree(plane->base.format_types);
+       plane->base.format_types = formats;
+       plane->base.format_count = num_formats;
+
+       if (!dev->mode_config.rotation_property)
+               dev->mode_config.rotation_property =
+                       drm_mode_create_rotation_property(dev,
+                               BIT(DRM_ROTATE_0) | BIT(DRM_ROTATE_90) |
+                               BIT(DRM_ROTATE_180) | BIT(DRM_ROTATE_270) |
+                               BIT(DRM_REFLECT_X) | BIT(DRM_REFLECT_Y));
+
+       if (dev->mode_config.rotation_property)
+               drm_object_attach_property(&plane->base.base,
+                                          dev->mode_config.rotation_property,
+                                          BIT(DRM_ROTATE_0));
+
+       return 0;
+}
diff --git a/drivers/gpu/drm/exynos/exynos_drm_plane_ipp.h 
b/drivers/gpu/drm/exynos/exynos_drm_plane_ipp.h
new file mode 100644
index 000000000000..44b7f2c53829
--- /dev/null
+++ b/drivers/gpu/drm/exynos/exynos_drm_plane_ipp.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2015 Samsung Electronics Co.Ltd
+ * Authors: Marek Szyprowski <m.szyprowski at samsung.com>
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ *
+ */
+
+#ifndef _EXYNOS_DRM_PLANE_IPP_H_
+#define _EXYNOS_DRM_PLANE_IPP_H_
+
+#ifdef CONFIG_DRM_EXYNOS_PLANE_IPP
+
+void exynos_plane_ipp_setup(struct exynos_drm_plane_state *state,
+                           const struct exynos_drm_plane_config *config,
+                           unsigned int *src_x, unsigned int *src_y,
+                           unsigned int *src_w, unsigned int *src_h,
+                           unsigned int *crtc_w, unsigned int *crtc_h);
+
+int exynos_plane_ipp_check(struct exynos_drm_plane *plane,
+                          struct exynos_drm_plane_state *state);
+
+void exynos_plane_ipp_update(struct exynos_drm_plane *plane,
+                            struct exynos_drm_plane_state *state);
+
+void exynos_plane_ipp_cleanup(struct exynos_drm_plane *plane,
+                             struct exynos_drm_plane_state *old_state);
+
+int exynos_plane_ipp_init(struct drm_device *dev,
+                         struct exynos_drm_plane *plane);
+
+#else
+
+static inline
+void exynos_plane_ipp_setup(struct exynos_drm_plane_state *state,
+                           const struct exynos_drm_plane_config *config,
+                           unsigned int *src_x, unsigned int *src_y,
+                           unsigned int *src_w, unsigned int *src_h,
+                           unsigned int *crtc_w, unsigned int *crtc_h)
+{
+}
+
+static inline
+int exynos_plane_ipp_check(struct exynos_drm_plane *plane,
+                          struct exynos_drm_plane_state *state)
+{
+       return 0;
+}
+
+static inline
+void exynos_plane_ipp_update(struct exynos_drm_plane *plane,
+                            struct exynos_drm_plane_state *state)
+{
+}
+
+static inline
+void exynos_plane_ipp_cleanup(struct exynos_drm_plane *plane,
+                             struct exynos_drm_plane_state *old_state)
+{
+}
+
+static inline
+int exynos_plane_ipp_init(struct drm_device *dev,
+                         struct exynos_drm_plane *plane)
+{
+       return 0;
+}
+
+#endif
+#endif
-- 
1.9.2

Reply via email to