diff mbox

[v2,22/22] drm/exynos: add support for plane rotation, scalling and colospace convesion

Message ID 1448891617-18830-23-git-send-email-m.szyprowski@samsung.com (mailing list archive)
State New, archived
Headers show

Commit Message

Marek Szyprowski Nov. 30, 2015, 1:53 p.m. UTC
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@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 mbox

Patch

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@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@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