[RFC] drm/exynos: add rotator driver

Inki Dae inki.dae at samsung.com
Fri Apr 27 02:43:05 PDT 2012


From: YoungJun Cho <yj44.cho at samsung.com>

The rotator supports rotating and flipping image data.
This rotator driver is exynos drm specific and supports only Exynos series from
Exynos4X12 and this patch is just for RFC.

The rotator is performed by 1 task simply.
1. Configures control, buffer, cropped image information then start the relevant
   operation. And it is synchronous operation, so there is no special event to
   notify its completion.

The rotator driver supports following features.
1. Rotation : 90, 180, 270 degree
2. Flipping : vertical, horizontal flip
3. Image format : RGB888, NV12, NV12M format

The rotator driver has following limitations.
1. Source and destination image format have to be same
2. No scale feature

We add one ioctl for Exynos rotator.

- ioctls
DRM_EXYNOS_ROTATOR_EXEC : set configuration and execute operation to driver

Signed-off-by: YoungJun Cho <yj44.cho at samsung.com>
Signed-off-by: Inki Dae <inki.dae at samsung.com>
Signed-off-by: Kyungmin Park <kyungmin.park at samsung.com>
---
 drivers/gpu/drm/exynos/Kconfig              |    6 +
 drivers/gpu/drm/exynos/Makefile             |    1 +
 drivers/gpu/drm/exynos/exynos_drm_drv.c     |   18 +
 drivers/gpu/drm/exynos/exynos_drm_drv.h     |   13 +
 drivers/gpu/drm/exynos/exynos_drm_rotator.c |  853 +++++++++++++++++++++++++++
 drivers/gpu/drm/exynos/exynos_drm_rotator.h |   25 +
 include/drm/exynos_drm.h                    |   99 ++++
 7 files changed, 1015 insertions(+)
 create mode 100644 drivers/gpu/drm/exynos/exynos_drm_rotator.c
 create mode 100644 drivers/gpu/drm/exynos/exynos_drm_rotator.h

diff --git a/drivers/gpu/drm/exynos/Kconfig b/drivers/gpu/drm/exynos/Kconfig
index 3343ac4..35166a8 100644
--- a/drivers/gpu/drm/exynos/Kconfig
+++ b/drivers/gpu/drm/exynos/Kconfig
@@ -27,3 +27,9 @@ config DRM_EXYNOS_VIDI
 	depends on DRM_EXYNOS
 	help
 	  Choose this option if you want to use Exynos VIDI for DRM.
+
+config DRM_EXYNOS_ROTATOR
+	bool "Samsung DRM Rotator"
+	depends on DRM_EXYNOS
+	help
+	  Choose this option if you want to use Samsung Rotator for DRM.
diff --git a/drivers/gpu/drm/exynos/Makefile b/drivers/gpu/drm/exynos/Makefile
index 9e0bff8..71968bf 100644
--- a/drivers/gpu/drm/exynos/Makefile
+++ b/drivers/gpu/drm/exynos/Makefile
@@ -13,5 +13,6 @@ exynosdrm-$(CONFIG_DRM_EXYNOS_HDMI)	+= exynos_hdmi.o exynos_mixer.o \
 					   exynos_ddc.o exynos_hdmiphy.o \
 					   exynos_drm_hdmi.o
 exynosdrm-$(CONFIG_DRM_EXYNOS_VIDI)	+= exynos_drm_vidi.o
+exynosdrm-$(CONFIG_DRM_EXYNOS_ROTATOR) += exynos_drm_rotator.o
 
 obj-$(CONFIG_DRM_EXYNOS)		+= exynosdrm.o
diff --git a/drivers/gpu/drm/exynos/exynos_drm_drv.c b/drivers/gpu/drm/exynos/exynos_drm_drv.c
index a6819b5..9e4d5e1 100644
--- a/drivers/gpu/drm/exynos/exynos_drm_drv.c
+++ b/drivers/gpu/drm/exynos/exynos_drm_drv.c
@@ -37,6 +37,7 @@
 #include "exynos_drm_fbdev.h"
 #include "exynos_drm_fb.h"
 #include "exynos_drm_gem.h"
+#include "exynos_drm_rotator.h"
 #include "exynos_drm_plane.h"
 #include "exynos_drm_vidi.h"
 
@@ -211,6 +212,8 @@ static struct drm_ioctl_desc exynos_ioctls[] = {
 			DRM_UNLOCKED | DRM_AUTH),
 	DRM_IOCTL_DEF_DRV(EXYNOS_VIDI_CONNECTION,
 			vidi_connection_ioctl, DRM_UNLOCKED | DRM_AUTH),
+	DRM_IOCTL_DEF_DRV(EXYNOS_ROTATOR_EXEC,
+			exynos_drm_rotator_exec_ioctl, DRM_UNLOCKED | DRM_AUTH),
 };
 
 static const struct file_operations exynos_drm_driver_fops = {
@@ -307,6 +310,12 @@ static int __init exynos_drm_init(void)
 		goto out_vidi;
 #endif
 
+#ifdef CONFIG_DRM_EXYNOS_ROTATOR
+	ret = platform_driver_register(&rotator_driver);
+	if (ret < 0)
+		goto out_rotator;
+#endif
+
 	ret = platform_driver_register(&exynos_drm_platform_driver);
 	if (ret < 0)
 		goto out;
@@ -314,6 +323,11 @@ static int __init exynos_drm_init(void)
 	return 0;
 
 out:
+#ifdef CONFIG_DRM_EXYNOS_ROTATOR
+	platform_driver_unregister(&rotator_driver);
+out_rotator:
+#endif
+
 #ifdef CONFIG_DRM_EXYNOS_VIDI
 out_vidi:
 	platform_driver_unregister(&vidi_driver);
@@ -341,6 +355,10 @@ static void __exit exynos_drm_exit(void)
 
 	platform_driver_unregister(&exynos_drm_platform_driver);
 
+#ifdef CONFIG_DRM_EXYNOS_ROTATOR
+	platform_driver_unregister(&rotator_driver);
+#endif
+
 #ifdef CONFIG_DRM_EXYNOS_HDMI
 	platform_driver_unregister(&exynos_drm_common_hdmi_driver);
 	platform_driver_unregister(&mixer_driver);
diff --git a/drivers/gpu/drm/exynos/exynos_drm_drv.h b/drivers/gpu/drm/exynos/exynos_drm_drv.h
index 1d81417..9c60b30 100644
--- a/drivers/gpu/drm/exynos/exynos_drm_drv.h
+++ b/drivers/gpu/drm/exynos/exynos_drm_drv.h
@@ -206,6 +206,19 @@ struct exynos_drm_manager {
 };
 
 /*
+ * Exynos drm rotator private structure
+ *
+ * @dev: device object to device driver for using driver data.
+ */
+struct exynos_drm_rot_private {
+	struct device	*dev;
+};
+
+struct drm_exynos_file_private {
+	struct exynos_drm_rot_private	*rot_priv;
+};
+
+/*
  * Exynos drm private structure.
  */
 struct exynos_drm_private {
diff --git a/drivers/gpu/drm/exynos/exynos_drm_rotator.c b/drivers/gpu/drm/exynos/exynos_drm_rotator.c
new file mode 100644
index 0000000..186dd07
--- /dev/null
+++ b/drivers/gpu/drm/exynos/exynos_drm_rotator.c
@@ -0,0 +1,853 @@
+/*
+ * Copyright (C) 2012 Samsung Electronics Co.Ltd
+ * Authors: YoungJun Cho <yj44.cho 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 version 2 as
+ * published by the Free Software Foundationr
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/pm_runtime.h>
+#include <linux/pm_qos_params.h>
+
+#include "drmP.h"
+#include "exynos_drm.h"
+#include "exynos_drm_drv.h"
+#include "exynos_drm_gem.h"
+
+/* Configuration */
+#define ROT_CONFIG			0x00
+#define ROT_CONFIG_IRQ			(3 << 8)
+
+/* Image Control */
+#define ROT_CONTROL			0x10
+#define ROT_CONTROL_PATTERN_WRITE	(1 << 16)
+#define ROT_CONTROL_FMT_YCBCR420_2P	(1 << 8)
+#define ROT_CONTROL_FMT_RGB888		(6 << 8)
+#define ROT_CONTROL_FMT_MASK		(7 << 8)
+#define ROT_CONTROL_FLIP_VERTICAL	(2 << 6)
+#define ROT_CONTROL_FLIP_HORIZONTAL	(3 << 6)
+#define ROT_CONTROL_FLIP_MASK		(3 << 6)
+#define ROT_CONTROL_ROT_90		(1 << 4)
+#define ROT_CONTROL_ROT_180		(2 << 4)
+#define ROT_CONTROL_ROT_270		(3 << 4)
+#define ROT_CONTROL_ROT_MASK		(3 << 4)
+#define ROT_CONTROL_START		(1 << 0)
+
+/* Status */
+#define ROT_STATUS			0x20
+#define ROT_STATUS_IRQ_PENDING(x)	(1 << (x))
+#define ROT_STATUS_IRQ(x)		(((x) >> 8) & 0x3)
+#define ROT_STATUS_IRQ_VAL_COMPLETE	1
+#define ROT_STATUS_IRQ_VAL_ILLEGAL	2
+
+/* Sourc Buffer Address */
+#define ROT_SRC_BUF_ADDR(n)		(0x30 + ((n) << 2))
+
+/* Source Buffer Size */
+#define ROT_SRC_BUF_SIZE		0x3c
+#define ROT_SRC_BUF_SIZE_H(x)		((x) << 16)
+#define ROT_SRC_BUF_SIZE_W(x)		((x) << 0)
+
+/* Source Crop Position */
+#define ROT_SRC_CROP_POS		0x40
+#define ROT_SRC_CROP_POS_Y(x)		((x) << 16)
+#define ROT_SRC_CROP_POS_X(x)		((x) << 0)
+
+/* Source Crop Size */
+#define ROT_SRC_CROP_SIZE		0x44
+#define ROT_SRC_CROP_SIZE_H(x)		((x) << 16)
+#define ROT_SRC_CROP_SIZE_W(x)		((x) << 0)
+
+/* Destination Buffer Address */
+#define ROT_DST_BUF_ADDR(n)		(0x50 + ((n) << 2))
+
+/* Destination Buffer Size */
+#define ROT_DST_BUF_SIZE		0x5c
+#define ROT_DST_BUF_SIZE_H(x)		((x) << 16)
+#define ROT_DST_BUF_SIZE_W(x)		((x) << 0)
+
+/* Destination Crop Position */
+#define ROT_DST_CROP_POS		0x60
+#define ROT_DST_CROP_POS_Y(x)		((x) << 16)
+#define ROT_DST_CROP_POS_X(x)		((x) << 0)
+
+/* Round to nearest aligned value */
+#define ROT_ALIGN(x, align, mask)	((*(x) + (1 << ((align) - 1))) & (mask))
+/* Minimum limit value */
+#define ROT_MIN(min, mask)		(((min) + ~(mask)) & (mask))
+/* Maximum limit value */
+#define ROT_MAX(max, mask)		((max) & (mask))
+
+enum rot_irq_status {
+	ROT_IRQ_STATUS_COMPLETE	= 8,
+	ROT_IRQ_STATUS_ILLEGAL	= 9,
+};
+
+struct rot_limit {
+	u32	min_w;
+	u32	min_h;
+	u32	max_w;
+	u32	max_h;
+	u32	align;
+};
+
+struct rot_limit_table {
+	struct rot_limit	ycbcr420_2p;
+	struct rot_limit	rgb888;
+};
+
+struct rot_context {
+	struct rot_limit_table		*limit_tbl;
+	struct clk			*clock;
+	struct resource			*regs_res;
+	void __iomem			*regs;
+	int				irq;
+	int				exec_ret;
+	struct exynos_drm_subdrv	subdrv;
+	struct completion		complete;
+	struct mutex			exec_mutex;
+	spinlock_t			irq_lock;
+	struct pm_qos_request_list	pm_qos;
+	bool				suspended;
+};
+
+struct rot_buffer {
+	dma_addr_t	src_addr[DRM_EXYNOS_ROT_MAX_BUF];
+	dma_addr_t	dst_addr[DRM_EXYNOS_ROT_MAX_BUF];
+	u32		src_cnt;
+	u32		dst_cnt;
+	u32		src_w;
+	u32		src_h;
+	u32		dst_w;
+	u32		dst_h;
+};
+
+static void rotator_reg_set_irq(struct rot_context *rot, bool enable)
+{
+	u32 value = readl(rot->regs + ROT_CONFIG);
+
+	if (enable == true)
+		value |= ROT_CONFIG_IRQ;
+	else
+		value &= ~ROT_CONFIG_IRQ;
+
+	writel(value, rot->regs + ROT_CONFIG);
+}
+
+static void rotator_reg_set_format(struct rot_context *rot, u32 img_fmt)
+{
+	u32 value = readl(rot->regs + ROT_CONTROL);
+	value &= ~ROT_CONTROL_FMT_MASK;
+
+	switch (img_fmt) {
+	case DRM_FORMAT_NV12:
+	case DRM_FORMAT_NV12M:
+		value |= ROT_CONTROL_FMT_YCBCR420_2P;
+		break;
+	case DRM_FORMAT_RGB888:
+		value |= ROT_CONTROL_FMT_RGB888;
+		break;
+	default:
+		DRM_ERROR("invalid image format\n");
+		return;
+	}
+
+	writel(value, rot->regs + ROT_CONTROL);
+}
+
+static void rotator_reg_set_flip(struct rot_context *rot,
+						enum drm_exynos_rot_flip flip)
+{
+	u32 value = readl(rot->regs + ROT_CONTROL);
+	value &= ~ROT_CONTROL_FLIP_MASK;
+
+	switch (flip) {
+	case ROT_FLIP_VERTICAL:
+		value |= ROT_CONTROL_FLIP_VERTICAL;
+		break;
+	case ROT_FLIP_HORIZONTAL:
+		value |= ROT_CONTROL_FLIP_HORIZONTAL;
+		break;
+	default:
+		/* Flip None */
+		break;
+	}
+
+	writel(value, rot->regs + ROT_CONTROL);
+}
+
+static void rotator_reg_set_rotation(struct rot_context *rot,
+					enum drm_exynos_rot_degree degree)
+{
+	u32 value = readl(rot->regs + ROT_CONTROL);
+	value &= ~ROT_CONTROL_ROT_MASK;
+
+	switch (degree) {
+	case ROT_DEGREE_90:
+		value |= ROT_CONTROL_ROT_90;
+		break;
+	case ROT_DEGREE_180:
+		value |= ROT_CONTROL_ROT_180;
+		break;
+	case ROT_DEGREE_270:
+		value |= ROT_CONTROL_ROT_270;
+		break;
+	default:
+		/* Rotation 0 Degree */
+		break;
+	}
+
+	writel(value, rot->regs + ROT_CONTROL);
+}
+
+static void rotator_reg_set_start(struct rot_context *rot)
+{
+	u32 value = readl(rot->regs + ROT_CONTROL);
+
+	value |= ROT_CONTROL_START;
+
+	writel(value, rot->regs + ROT_CONTROL);
+}
+
+static enum rot_irq_status rotator_reg_get_irq_status(struct rot_context *rot)
+{
+	u32 value = readl(rot->regs + ROT_STATUS);
+	value = ROT_STATUS_IRQ(value);
+
+	if (value == ROT_STATUS_IRQ_VAL_COMPLETE)
+		return ROT_IRQ_STATUS_COMPLETE;
+	else
+		return ROT_IRQ_STATUS_ILLEGAL;
+}
+
+static void rotator_reg_set_irq_status_clear(struct rot_context *rot,
+						enum rot_irq_status status)
+{
+	u32 value = readl(rot->regs + ROT_STATUS);
+
+	value |= ROT_STATUS_IRQ_PENDING((u32)status);
+
+	writel(value, rot->regs + ROT_STATUS);
+}
+
+static void rotator_reg_set_src_buf_addr(struct rot_context *rot,
+							dma_addr_t addr, int i)
+{
+	writel(addr, rot->regs + ROT_SRC_BUF_ADDR(i));
+}
+
+static void rotator_reg_set_src_buf_size(struct rot_context *rot, u32 w, u32 h)
+{
+	u32 value = ROT_SRC_BUF_SIZE_H(h) | ROT_SRC_BUF_SIZE_W(w);
+
+	writel(value, rot->regs + ROT_SRC_BUF_SIZE);
+}
+
+static void rotator_reg_set_src_crop_pos(struct rot_context *rot, u32 x, u32 y)
+{
+	u32 value = ROT_SRC_CROP_POS_Y(y) | ROT_SRC_CROP_POS_X(x);
+
+	writel(value, rot->regs + ROT_SRC_CROP_POS);
+}
+
+static void rotator_reg_set_src_crop_size(struct rot_context *rot, u32 w, u32 h)
+{
+	u32 value = ROT_SRC_CROP_SIZE_H(h) | ROT_SRC_CROP_SIZE_W(w);
+
+	writel(value, rot->regs + ROT_SRC_CROP_SIZE);
+}
+
+static void rotator_reg_set_dst_buf_addr(struct rot_context *rot,
+							dma_addr_t addr, int i)
+{
+	writel(addr, rot->regs + ROT_DST_BUF_ADDR(i));
+}
+
+static void rotator_reg_set_dst_buf_size(struct rot_context *rot, u32 w, u32 h)
+{
+	u32 value = ROT_DST_BUF_SIZE_H(h) | ROT_DST_BUF_SIZE_W(w);
+
+	writel(value, rot->regs + ROT_DST_BUF_SIZE);
+}
+
+static void rotator_reg_set_dst_crop_pos(struct rot_context *rot, u32 x, u32 y)
+{
+	u32 value = ROT_DST_CROP_POS_Y(y) | ROT_DST_CROP_POS_X(x);
+
+	writel(value, rot->regs + ROT_DST_CROP_POS);
+}
+
+static void rotator_reg_get_dump(struct rot_context *rot)
+{
+	u32 value, i;
+
+	for (i = 0; i <= ROT_DST_CROP_POS; i += 0x4) {
+		value = readl(rot->regs + i);
+		DRM_INFO("+0x%x: 0x%x", i, value);
+	}
+}
+
+static bool rotator_check_format_n_handle_valid(u32 img_fmt,
+							u32 src_buf_handle_cnt,
+							u32 dst_buf_handle_cnt)
+{
+	bool ret = false;
+
+	if ((src_buf_handle_cnt != dst_buf_handle_cnt)
+						|| (src_buf_handle_cnt == 0))
+		return ret;
+
+	switch (img_fmt) {
+	case DRM_FORMAT_NV12M:
+		if (src_buf_handle_cnt == 2)
+			ret = true;
+		break;
+	case DRM_FORMAT_NV12:
+	case DRM_FORMAT_RGB888:
+		if (src_buf_handle_cnt == 1)
+			ret = true;
+		break;
+	default:
+		DRM_ERROR("invalid image format\n");
+		break;
+	}
+
+	return ret;
+}
+
+static void rotator_align_size(struct rot_limit *limit, u32 mask, u32 *w,
+									u32 *h)
+{
+	u32 value;
+
+	value = ROT_ALIGN(w, limit->align, mask);
+	if (value < limit->min_w)
+		*w = ROT_MIN(limit->min_w, mask);
+	else if (value > limit->max_w)
+		*w = ROT_MAX(limit->max_w, mask);
+	else
+		*w = value;
+
+	value = ROT_ALIGN(h, limit->align, mask);
+	if (value < limit->min_h)
+		*h = ROT_MIN(limit->min_h, mask);
+	else if (value > limit->max_h)
+		*h = ROT_MAX(limit->max_h, mask);
+	else
+		*h = value;
+}
+
+static void rotator_align_buffer(struct rot_context *rot,
+					struct rot_buffer *buf,
+					struct drm_exynos_rot_buffer *req_buf,
+					struct drm_exynos_rot_control *control)
+{
+	struct rot_limit_table *limit_tbl = rot->limit_tbl;
+	struct rot_limit *limit;
+	u32 mask;
+
+	/* Get size limit */
+	if (control->img_fmt == DRM_FORMAT_RGB888)
+		limit = &limit_tbl->rgb888;
+	else
+		limit = &limit_tbl->ycbcr420_2p;
+
+	/* Get mask for rounding to nearest aligned value */
+	mask = ~((1 << limit->align) - 1);
+
+	/* For source buffer */
+	buf->src_w = req_buf->src_w;
+	buf->src_h = req_buf->src_h;
+	rotator_align_size(limit, mask, &buf->src_w, &buf->src_h);
+
+	/* For destination buffer */
+	buf->dst_w = req_buf->dst_w;
+	buf->dst_h = req_buf->dst_h;
+	rotator_align_size(limit, mask, &buf->dst_w, &buf->dst_h);
+}
+
+static bool rotator_check_crop_boundary(struct rot_buffer *buf,
+					struct drm_exynos_rot_control *control,
+					struct drm_exynos_rot_crop *crop)
+{
+	bool ret = true;
+
+	/* Check source crop position */
+	if ((crop->src_x + crop->src_w > buf->src_w)
+				|| (crop->src_y + crop->src_h > buf->src_h))
+		return false;
+
+	/* Check destination crop position */
+	switch (control->degree) {
+	case ROT_DEGREE_90:
+	case ROT_DEGREE_270:
+		if ((crop->dst_x + crop->src_h > buf->dst_w)
+				|| (crop->dst_y + crop->src_w > buf->dst_h))
+			ret = false;
+		break;
+	default:
+		if ((crop->dst_x + crop->src_w > buf->dst_w)
+				|| (crop->dst_y + crop->src_h > buf->dst_h))
+			ret = false;
+		break;
+	}
+
+	return ret;
+}
+
+static int rotator_get_dma_addr(struct rot_buffer *buf,
+					struct drm_exynos_rot_buffer *req_buf,
+					struct drm_device *drm_dev,
+					struct drm_file *file)
+{
+	void *addr;
+
+	/* For source buffer */
+	buf->src_cnt = 0;
+	while (buf->src_cnt < req_buf->src_cnt) {
+		addr = exynos_drm_gem_get_dma_addr(drm_dev,
+					req_buf->src_handle[buf->src_cnt],
+					file);
+		if (IS_ERR_OR_NULL(addr)) {
+			DRM_ERROR("failed to get src dma_addr [%u]\n",
+								buf->src_cnt);
+			return -EINVAL;
+		}
+		buf->src_addr[(buf->src_cnt)++] = *(dma_addr_t *)addr;
+	}
+
+	/* For destination buffer */
+	buf->dst_cnt = 0;
+	while (buf->dst_cnt < req_buf->dst_cnt) {
+		addr = exynos_drm_gem_get_dma_addr(drm_dev,
+					req_buf->dst_handle[buf->dst_cnt],
+					file);
+		if (IS_ERR_OR_NULL(addr)) {
+			DRM_ERROR("failed to get dst dma_addr [%u]\n",
+								buf->dst_cnt);
+			return -EINVAL;
+		}
+		buf->dst_addr[(buf->dst_cnt)++] = *(dma_addr_t *)addr;
+	}
+
+	return 0;
+}
+
+static void rotator_put_dma_addr(struct rot_buffer *buf,
+					struct drm_exynos_rot_buffer *req_buf,
+					struct drm_device *drm_dev,
+					struct drm_file *file)
+{
+	/* For destination buffer */
+	while (buf->dst_cnt > 0)
+		exynos_drm_gem_put_dma_addr(drm_dev,
+					req_buf->dst_handle[--(buf->dst_cnt)],
+					file);
+
+	/* For source buffer */
+	while (buf->src_cnt > 0)
+		exynos_drm_gem_put_dma_addr(drm_dev,
+					req_buf->src_handle[--(buf->src_cnt)],
+					file);
+}
+
+static void rotator_execute(struct rot_context *rot,
+					struct rot_buffer *buf,
+					struct drm_exynos_rot_control *control,
+					struct drm_exynos_rot_crop *crop)
+{
+	int i;
+
+	pm_runtime_get_sync(rot->subdrv.dev);
+
+	/* Set interrupt enable */
+	rotator_reg_set_irq(rot, true);
+
+	/* Set control registers */
+	rotator_reg_set_format(rot, control->img_fmt);
+	rotator_reg_set_flip(rot, control->flip);
+	rotator_reg_set_rotation(rot, control->degree);
+
+	/* Set source buffer address */
+	for (i = 0; i < DRM_EXYNOS_ROT_MAX_BUF; i++)
+		rotator_reg_set_src_buf_addr(rot, buf->src_addr[i], i);
+
+	/* Set source buffer size */
+	rotator_reg_set_src_buf_size(rot, buf->src_w, buf->src_h);
+
+	/* Set destination buffer address */
+	for (i = 0; i < DRM_EXYNOS_ROT_MAX_BUF; i++)
+		rotator_reg_set_dst_buf_addr(rot, buf->dst_addr[i], i);
+
+	/* Set destination buffer size */
+	rotator_reg_set_dst_buf_size(rot, buf->dst_w, buf->dst_h);
+
+	/* Set source crop image position */
+	rotator_reg_set_src_crop_pos(rot, crop->src_x, crop->src_y);
+
+	/* Set source crop image size */
+	rotator_reg_set_src_crop_size(rot, crop->src_w, crop->src_h);
+
+	/* Set destination crop image position */
+	rotator_reg_set_dst_crop_pos(rot, crop->dst_x, crop->dst_y);
+
+	/* Start rotator operation */
+	rotator_reg_set_start(rot);
+}
+
+int exynos_drm_rotator_exec_ioctl(struct drm_device *drm_dev, void *data,
+							struct drm_file *file)
+{
+	struct drm_exynos_file_private *file_priv = file->driver_priv;
+	struct exynos_drm_rot_private *priv = file_priv->rot_priv;
+	struct device *dev = priv->dev;
+	struct rot_context *rot;
+	struct drm_exynos_rot_exec_data *req = data;
+	struct drm_exynos_rot_buffer *req_buf = &req->buf;
+	struct drm_exynos_rot_control *control = &req->control;
+	struct drm_exynos_rot_crop *crop = &req->crop;
+	struct rot_buffer buf;
+
+	if (!dev) {
+		DRM_ERROR("failed to get dev\n");
+		return -ENODEV;
+	}
+
+	rot = dev_get_drvdata(dev);
+	if (!rot) {
+		DRM_ERROR("failed to get drvdata\n");
+		return -EFAULT;
+	}
+
+	if (rot->suspended) {
+		DRM_ERROR("suspended state\n");
+		return -EPERM;
+	}
+
+	if (!rotator_check_format_n_handle_valid(control->img_fmt,
+							req_buf->src_cnt,
+							req_buf->dst_cnt)) {
+		DRM_ERROR("format or handles are invalid\n");
+		return -EINVAL;
+	}
+
+	init_completion(&rot->complete);
+
+	/* Align buffer */
+	rotator_align_buffer(rot, &buf, req_buf, control);
+
+	/* Check crop boundary */
+	if (!rotator_check_crop_boundary(&buf, control, crop)) {
+		DRM_ERROR("boundary errror\n");
+		return -EINVAL;
+	}
+
+	/* Get DMA address */
+	rot->exec_ret = rotator_get_dma_addr(&buf, req_buf, drm_dev, file);
+	if (rot->exec_ret < 0)
+		goto err_get_dma_addr;
+
+	/* Assign another src/dst_addr for NV12 image format */
+	if (control->img_fmt == DRM_FORMAT_NV12) {
+		u32 size = crop->src_w * crop->src_h;
+
+		buf.src_addr[buf.src_cnt + 1] =
+					buf.src_addr[buf.src_cnt] + size;
+		buf.dst_addr[buf.dst_cnt + 1] =
+					buf.dst_addr[buf.dst_cnt] + size;
+	}
+
+	/* Execute */
+	mutex_lock(&rot->exec_mutex);
+	rotator_execute(rot, &buf, control, crop);
+	if (!wait_for_completion_timeout(&rot->complete, 2 * HZ)) {
+		DRM_ERROR("timeout error\n");
+		rot->exec_ret = -ETIMEDOUT;
+		mutex_unlock(&rot->exec_mutex);
+		goto err_get_dma_addr;
+	}
+	mutex_unlock(&rot->exec_mutex);
+
+	/* Put DMA address */
+	rotator_put_dma_addr(&buf, req_buf, drm_dev, file);
+
+	return rot->exec_ret;
+
+err_get_dma_addr:
+	rotator_put_dma_addr(&buf, req_buf, drm_dev, file);
+	return rot->exec_ret;
+}
+EXPORT_SYMBOL_GPL(exynos_drm_rotator_exec_ioctl);
+
+static irqreturn_t rotator_irq_thread(int irq, void *arg)
+{
+	struct rot_context *rot = (struct rot_context *)arg;
+	enum rot_irq_status irq_status;
+	unsigned long flags;
+
+	pm_qos_update_request(&rot->pm_qos, 0);
+
+	/* Get execution result */
+	spin_lock_irqsave(&rot->irq_lock, flags);
+	irq_status = rotator_reg_get_irq_status(rot);
+	rotator_reg_set_irq_status_clear(rot, irq_status);
+	spin_unlock_irqrestore(&rot->irq_lock, flags);
+
+	rot->exec_ret = 0;
+	if (irq_status != ROT_IRQ_STATUS_COMPLETE) {
+		DRM_ERROR("the SFR is set illegally\n");
+		rot->exec_ret = -EINVAL;
+		rotator_reg_get_dump(rot);
+	}
+
+	pm_runtime_put(rot->subdrv.dev);
+
+	complete(&rot->complete);
+
+	return IRQ_HANDLED;
+}
+
+static int rotator_subdrv_open(struct drm_device *drm_dev, struct device *dev,
+							struct drm_file *file)
+{
+	struct drm_exynos_file_private *file_priv = file->driver_priv;
+	struct exynos_drm_rot_private *priv;
+
+	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+	if (!priv) {
+		dev_err(dev, "failed to allocate priv\n");
+		return -ENOMEM;
+	}
+
+	priv->dev = dev;
+
+	file_priv->rot_priv = priv;
+
+	return 0;
+}
+
+static void rotator_subdrv_close(struct drm_device *drm_dev, struct device *dev,
+							struct drm_file *file)
+{
+	struct drm_exynos_file_private *file_priv = file->driver_priv;
+
+	kfree(file_priv->rot_priv);
+
+	return;
+}
+
+static int __devinit rotator_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct rot_context *rot;
+	struct resource *res;
+	struct exynos_drm_subdrv *subdrv;
+	int ret;
+
+	rot = kzalloc(sizeof(*rot), GFP_KERNEL);
+	if (!rot) {
+		dev_err(dev, "failed to allocate rot\n");
+		return -ENOMEM;
+	}
+
+	rot->limit_tbl = (struct rot_limit_table *)
+				platform_get_device_id(pdev)->driver_data;
+
+	mutex_init(&rot->exec_mutex);
+	spin_lock_init(&rot->irq_lock);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_err(dev, "failed to find registers\n");
+		ret = -ENOENT;
+		goto err_get_resource;
+	}
+
+	rot->regs_res = request_mem_region(res->start, resource_size(res),
+								dev_name(dev));
+	if (!rot->regs_res) {
+		dev_err(dev, "failed to claim register region\n");
+		ret = -ENOENT;
+		goto err_get_resource;
+	}
+
+	rot->regs = ioremap(res->start, resource_size(res));
+	if (!rot->regs) {
+		dev_err(dev, "failed to map register\n");
+		ret = -ENXIO;
+		goto err_ioremap;
+	}
+
+	rot->irq = platform_get_irq(pdev, 0);
+	if (rot->irq < 0) {
+		dev_err(dev, "faild to get irq\n");
+		ret = rot->irq;
+		goto err_get_irq;
+	}
+
+	ret = request_threaded_irq(rot->irq, NULL, rotator_irq_thread,
+					IRQF_ONESHOT, "drm_rotator", rot);
+	if (ret < 0) {
+		dev_err(dev, "failed to request irq\n");
+		goto err_get_irq;
+	}
+
+	rot->clock = clk_get(dev, "rotator");
+	if (IS_ERR_OR_NULL(rot->clock)) {
+		dev_err(dev, "faild to get clock\n");
+		ret = PTR_ERR(rot->clock);
+		goto err_clk_get;
+	}
+
+	pm_runtime_enable(dev);
+	pm_qos_add_request(&rot->pm_qos, PM_QOS_BUS_DMA_THROUGHPUT, 0);
+
+	subdrv = &rot->subdrv;
+	subdrv->dev = dev;
+	subdrv->open = rotator_subdrv_open;
+	subdrv->close = rotator_subdrv_close;
+
+	platform_set_drvdata(pdev, rot);
+
+	ret = exynos_drm_subdrv_register(subdrv);
+	if (ret < 0) {
+		dev_err(dev, "failed to register drm rotator device\n");
+		goto err_subdrv_register;
+	}
+
+	dev_info(dev, "The exynos rotator is probed successfully\n");
+
+	return 0;
+
+err_subdrv_register:
+	pm_runtime_disable(dev);
+	clk_put(rot->clock);
+err_clk_get:
+	free_irq(rot->irq, rot);
+err_get_irq:
+	iounmap(rot->regs);
+err_ioremap:
+	release_resource(rot->regs_res);
+	kfree(rot->regs_res);
+err_get_resource:
+	kfree(rot);
+	return ret;
+}
+
+static int __devexit rotator_remove(struct platform_device *pdev)
+{
+	struct rot_context *rot = platform_get_drvdata(pdev);
+
+	pm_qos_remove_request(&rot->pm_qos);
+
+	exynos_drm_subdrv_unregister(&rot->subdrv);
+
+	pm_runtime_disable(&pdev->dev);
+	clk_put(rot->clock);
+
+	free_irq(rot->irq, rot);
+
+	iounmap(rot->regs);
+
+	release_resource(rot->regs_res);
+	kfree(rot->regs_res);
+
+	kfree(rot);
+
+	return 0;
+}
+
+struct rot_limit_table rot_limit_tbl = {
+	.ycbcr420_2p = {
+		.min_w = 32,
+		.min_h = 32,
+		.max_w = SZ_32K,
+		.max_h = SZ_32K,
+		.align = 3,
+	},
+	.rgb888 = {
+		.min_w = 8,
+		.min_h = 8,
+		.max_w = SZ_8K,
+		.max_h = SZ_8K,
+		.align = 2,
+	},
+};
+
+struct platform_device_id rotator_driver_ids[] = {
+	{
+		.name		= "exynos-rot",
+		.driver_data	= (unsigned long)&rot_limit_tbl,
+	},
+	{},
+};
+
+#ifdef CONFIG_PM_SLEEP
+static int rotator_suspend(struct device *dev)
+{
+	struct rot_context *rot = dev_get_drvdata(dev);
+
+	/* Check & wait for running state */
+	mutex_lock(&rot->exec_mutex);
+	mutex_unlock(&rot->exec_mutex);
+
+	rot->suspended = true;
+
+	return 0;
+}
+
+static int rotator_resume(struct device *dev)
+{
+	struct rot_context *rot = dev_get_drvdata(dev);
+
+	rot->suspended = false;
+
+	return 0;
+}
+#endif
+
+#ifdef CONFIG_PM_RUNTIME
+static int rotator_runtime_suspend(struct device *dev)
+{
+	struct rot_context *rot = dev_get_drvdata(dev);
+
+	clk_disable(rot->clock);
+
+	return 0;
+}
+
+static int rotator_runtime_resume(struct device *dev)
+{
+	struct rot_context *rot = dev_get_drvdata(dev);
+
+	clk_enable(rot->clock);
+	pm_qos_update_request(&rot->pm_qos, 400000);
+
+	return 0;
+}
+#endif
+
+static const struct dev_pm_ops rotator_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(rotator_suspend, rotator_resume)
+	SET_RUNTIME_PM_OPS(rotator_runtime_suspend, rotator_runtime_resume,
+									NULL)
+};
+
+struct platform_driver rotator_driver = {
+	.probe		= rotator_probe,
+	.remove		= __devexit_p(rotator_remove),
+	.id_table	= rotator_driver_ids,
+	.driver		= {
+		.name	= "exynos-rot",
+		.owner	= THIS_MODULE,
+		.pm	= &rotator_pm_ops,
+	},
+};
diff --git a/drivers/gpu/drm/exynos/exynos_drm_rotator.h b/drivers/gpu/drm/exynos/exynos_drm_rotator.h
new file mode 100644
index 0000000..5f383d5
--- /dev/null
+++ b/drivers/gpu/drm/exynos/exynos_drm_rotator.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2012 Samsung Electronics Co.Ltd
+ * Authors: YoungJun Cho <yj44.cho 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 version 2 as
+ * published by the Free Software Foundationr
+ */
+
+#ifndef	_EXYNOS_DRM_ROTATOR_H_
+#define	_EXYNOS_DRM_ROTATOR_H_
+
+#ifdef CONFIG_DRM_EXYNOS_ROTATOR
+extern int exynos_drm_rotator_exec_ioctl(struct drm_device *dev, void *data,
+					 struct drm_file *file_priv);
+#else
+static inline int exynos_drm_rotator_exec_ioctl(struct drm_device *dev,
+						void *data,
+						struct drm_file *file_priv)
+{
+	return -ENOTTY;
+}
+#endif
+
+#endif
diff --git a/include/drm/exynos_drm.h b/include/drm/exynos_drm.h
index e478de4..772cac1 100644
--- a/include/drm/exynos_drm.h
+++ b/include/drm/exynos_drm.h
@@ -100,6 +100,99 @@ enum e_drm_exynos_gem_mem_type {
 	EXYNOS_BO_MASK		= EXYNOS_BO_NONCONTIG
 };
 
+enum drm_exynos_rot_flip {
+	ROT_FLIP_NONE,
+	ROT_FLIP_VERTICAL,
+	ROT_FLIP_HORIZONTAL,
+};
+
+enum drm_exynos_rot_degree {
+	ROT_DEGREE_0,
+	ROT_DEGREE_90,
+	ROT_DEGREE_180,
+	ROT_DEGREE_270,
+};
+
+#define DRM_EXYNOS_ROT_MAX_BUF	3
+
+/**
+ * A structure for rotator buffer
+ *
+ * @src_handle: Source GEM handles.
+ * @dst_handle: Destination GEM handles.
+ *          - *_handle[0] : For RGB or Y buffer.
+ *          - *_handle[1] : For CbCr or Cb buffer.
+ *          - *_handle[2] : For Cr buffer.
+ * @src_cnt: Number of source GEM handles.
+ * @dst_cnt: Number of destination GEM handles.
+ * @src_w: Source Buffer width.
+ * @src_h: Source Buffer height.
+ * @dst_w: Destination Buffer width.
+ * @dst_h: Destination Buffer height.
+ */
+struct drm_exynos_rot_buffer {
+	__u32	src_handle[DRM_EXYNOS_ROT_MAX_BUF];
+	__u32	dst_handle[DRM_EXYNOS_ROT_MAX_BUF];
+	__u32	src_cnt;
+	__u32	dst_cnt;
+	__u32	src_w;
+	__u32	src_h;
+	__u32	dst_w;
+	__u32	dst_h;
+};
+
+/**
+ * A structure for rotator control.
+ *
+ * @img_fmt: Source / destination buffer (image)format.
+ *           - fourcc code from drm_fourcc.h
+ *           - DRM_FORMAT_RGB888
+ *           - DRM_FORMAT_NV12
+ *           - DRM_FORMAT_NV12M
+ * @flip: Flip operation value.
+ * @degree: Rotation operation degree value.
+ */
+struct drm_exynos_rot_control {
+	__u32				img_fmt;
+	enum drm_exynos_rot_flip	flip;
+	enum drm_exynos_rot_degree	degree;
+	__u32				reserved;
+};
+
+/**
+ * A structure for rotator crop.
+ *
+ * @src_x: Cropped image position x in source buffer[FROM].
+ * @src_y: Cropped image position y in source buffer[FROM].
+ * @src_w: Cropped image width in source buffer.
+ * @src_h: Cropped image height in source buffer.
+ * @dst_x: Cropped image position x in destination buffer[TO].
+ * @dst_y: Cropped image position y in destination buffer[TO].
+ */
+struct drm_exynos_rot_crop {
+	__u32	src_x;
+	__u32	src_y;
+	__u32	src_w;
+	__u32	src_h;
+	__u32	dst_x;
+	__u32	dst_y;
+};
+
+/**
+ * A structure for rotator operation.
+ *
+ * @buf: (Image)Buffer data.
+ * @control: Control data.
+ * @crop: Cropped image data.
+ * @user_data: Not used yet.
+ */
+struct drm_exynos_rot_exec_data {
+	struct drm_exynos_rot_buffer	buf;
+	struct drm_exynos_rot_control	control;
+	struct drm_exynos_rot_crop	crop;
+	__u64				user_data;
+};
+
 #define DRM_EXYNOS_GEM_CREATE		0x00
 #define DRM_EXYNOS_GEM_MAP_OFFSET	0x01
 #define DRM_EXYNOS_GEM_MMAP		0x02
@@ -107,6 +200,9 @@ enum e_drm_exynos_gem_mem_type {
 #define DRM_EXYNOS_PLANE_SET_ZPOS	0x06
 #define DRM_EXYNOS_VIDI_CONNECTION	0x07
 
+/* Rotator */
+#define DRM_EXYNOS_ROTATOR_EXEC		0x30
+
 #define DRM_IOCTL_EXYNOS_GEM_CREATE		DRM_IOWR(DRM_COMMAND_BASE + \
 		DRM_EXYNOS_GEM_CREATE, struct drm_exynos_gem_create)
 
@@ -122,6 +218,9 @@ enum e_drm_exynos_gem_mem_type {
 #define DRM_IOCTL_EXYNOS_VIDI_CONNECTION	DRM_IOWR(DRM_COMMAND_BASE + \
 		DRM_EXYNOS_VIDI_CONNECTION, struct drm_exynos_vidi_connection)
 
+#define DRM_IOCTL_EXYNOS_ROTATOR_EXEC		DRM_IOWR(DRM_COMMAND_BASE + \
+		DRM_EXYNOS_ROTATOR_EXEC, struct drm_exynos_rot_exec_data)
+
 #ifdef __KERNEL__
 
 /**
-- 
1.7.9.5



More information about the dri-devel mailing list