drm/exynos: add rotator ipp driver
authorEunchul Kim <chulspro.kim@samsung.com>
Fri, 14 Dec 2012 08:58:56 +0000 (17:58 +0900)
committerInki Dae <daeinki@gmail.com>
Fri, 14 Dec 2012 17:39:41 +0000 (02:39 +0900)
This patch adds IPP subsystem-based rotator driver.
And Rotator supports the following features.
- Image crop operation support.
- Rotate operation support to 90, 180 or 270 degree.
- Flip operation support to vertical, horizontal or both.
  . as limitaions, the pixel format to source buffer should be
    same as the one to destination buffer and no scaler.

This driver is registered to IPP subsystem framework to be used by user side
and user can control the Rotator hardware through some interfaces of IPP
subsystem framework.

Changelog v6:
- fix build warning.

Changelog v1 ~ v5:
- added comments, code fixups and cleanups.

Signed-off-by: Eunchul Kim <chulspro.kim@samsung.com>
Signed-off-by: Youngjun Cho <yj44.cho@samsung.com>
Signed-off-by: Inki Dae <inki.dae@samsung.com>
Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com>
drivers/gpu/drm/exynos/Kconfig
drivers/gpu/drm/exynos/Makefile
drivers/gpu/drm/exynos/exynos_drm_drv.c
drivers/gpu/drm/exynos/exynos_drm_drv.h
drivers/gpu/drm/exynos/exynos_drm_rotator.c [new file with mode: 0644]
drivers/gpu/drm/exynos/exynos_drm_rotator.h [new file with mode: 0644]
drivers/gpu/drm/exynos/regs-rotator.h [new file with mode: 0644]

index 66c02df360e7176207934ec7b8dc3c2d0b27409d..e0056a3ab756fc10891c7de1f20616a7491858c9 100644 (file)
@@ -57,3 +57,10 @@ config DRM_EXYNOS_FIMC
        depends on DRM_EXYNOS_IPP
        help
          Choose this option if you want to use Exynos FIMC for DRM.
+
+config DRM_EXYNOS_ROTATOR
+       bool "Exynos DRM Rotator"
+       depends on DRM_EXYNOS_IPP
+       help
+         Choose this option if you want to use Exynos Rotator for DRM.
+
index 9710024ed0000c85f2badf162c509aa7042e3a92..3b706682b4ab4f5985242238f9fc8b07cbd8a25a 100644 (file)
@@ -18,5 +18,6 @@ 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_FIMC)    += exynos_drm_fimc.o
+exynosdrm-$(CONFIG_DRM_EXYNOS_ROTATOR) += exynos_drm_rotator.o
 
 obj-$(CONFIG_DRM_EXYNOS)               += exynosdrm.o
index 73f02ac53bad3398e6dea100afbd293b9105d07b..09d884ba9e2e181e973aeea7e20c4e71f39672b6 100644 (file)
@@ -378,6 +378,12 @@ static int __init exynos_drm_init(void)
                goto out_fimc;
 #endif
 
+#ifdef CONFIG_DRM_EXYNOS_ROTATOR
+       ret = platform_driver_register(&rotator_driver);
+       if (ret < 0)
+               goto out_rotator;
+#endif
+
 #ifdef CONFIG_DRM_EXYNOS_IPP
        ret = platform_driver_register(&ipp_driver);
        if (ret < 0)
@@ -406,6 +412,11 @@ out_drm:
 out_ipp:
 #endif
 
+#ifdef CONFIG_DRM_EXYNOS_ROTATOR
+       platform_driver_unregister(&rotator_driver);
+out_rotator:
+#endif
+
 #ifdef CONFIG_DRM_EXYNOS_FIMC
        platform_driver_unregister(&fimc_driver);
 out_fimc:
@@ -451,6 +462,10 @@ static void __exit exynos_drm_exit(void)
        platform_driver_unregister(&ipp_driver);
 #endif
 
+#ifdef CONFIG_DRM_EXYNOS_ROTATOR
+       platform_driver_unregister(&rotator_driver);
+#endif
+
 #ifdef CONFIG_DRM_EXYNOS_FIMC
        platform_driver_unregister(&fimc_driver);
 #endif
index 63803508e4dfdb83dab0c1243aeccb574f14e287..de49956539e70422ca69fcd29e67f7bf91efc286 100644 (file)
@@ -350,5 +350,6 @@ extern struct platform_driver exynos_drm_common_hdmi_driver;
 extern struct platform_driver vidi_driver;
 extern struct platform_driver g2d_driver;
 extern struct platform_driver fimc_driver;
+extern struct platform_driver rotator_driver;
 extern struct platform_driver ipp_driver;
 #endif
diff --git a/drivers/gpu/drm/exynos/exynos_drm_rotator.c b/drivers/gpu/drm/exynos/exynos_drm_rotator.c
new file mode 100644 (file)
index 0000000..1c23660
--- /dev/null
@@ -0,0 +1,855 @@
+/*
+ * Copyright (C) 2012 Samsung Electronics Co.Ltd
+ * Authors:
+ *     YoungJun Cho <yj44.cho@samsung.com>
+ *     Eunchul Kim <chulspro.kim@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 <drm/drmP.h>
+#include <drm/exynos_drm.h>
+#include "regs-rotator.h"
+#include "exynos_drm.h"
+#include "exynos_drm_ipp.h"
+
+/*
+ * Rotator supports image crop/rotator and input/output DMA operations.
+ * input DMA reads image data from the memory.
+ * output DMA writes image data to memory.
+ *
+ * M2M operation : supports crop/scale/rotation/csc so on.
+ * Memory ----> Rotator H/W ----> Memory.
+ */
+
+/*
+ * TODO
+ * 1. check suspend/resume api if needed.
+ * 2. need to check use case platform_device_id.
+ * 3. check src/dst size with, height.
+ * 4. need to add supported list in prop_list.
+ */
+
+#define get_rot_context(dev)   platform_get_drvdata(to_platform_device(dev))
+#define get_ctx_from_ippdrv(ippdrv)    container_of(ippdrv,\
+                                       struct rot_context, ippdrv);
+#define rot_read(offset)               readl(rot->regs + (offset))
+#define rot_write(cfg, offset) writel(cfg, rot->regs + (offset))
+
+enum rot_irq_status {
+       ROT_IRQ_STATUS_COMPLETE = 8,
+       ROT_IRQ_STATUS_ILLEGAL  = 9,
+};
+
+/*
+ * A structure of limitation.
+ *
+ * @min_w: minimum width.
+ * @min_h: minimum height.
+ * @max_w: maximum width.
+ * @max_h: maximum height.
+ * @align: align size.
+ */
+struct rot_limit {
+       u32     min_w;
+       u32     min_h;
+       u32     max_w;
+       u32     max_h;
+       u32     align;
+};
+
+/*
+ * A structure of limitation table.
+ *
+ * @ycbcr420_2p: case of YUV.
+ * @rgb888: case of RGB.
+ */
+struct rot_limit_table {
+       struct rot_limit        ycbcr420_2p;
+       struct rot_limit        rgb888;
+};
+
+/*
+ * A structure of rotator context.
+ * @ippdrv: prepare initialization using ippdrv.
+ * @regs_res: register resources.
+ * @regs: memory mapped io registers.
+ * @clock: rotator gate clock.
+ * @limit_tbl: limitation of rotator.
+ * @irq: irq number.
+ * @cur_buf_id: current operation buffer id.
+ * @suspended: suspended state.
+ */
+struct rot_context {
+       struct exynos_drm_ippdrv        ippdrv;
+       struct resource *regs_res;
+       void __iomem    *regs;
+       struct clk      *clock;
+       struct rot_limit_table  *limit_tbl;
+       int     irq;
+       int     cur_buf_id[EXYNOS_DRM_OPS_MAX];
+       bool    suspended;
+};
+
+static void rotator_reg_set_irq(struct rot_context *rot, bool enable)
+{
+       u32 val = rot_read(ROT_CONFIG);
+
+       if (enable == true)
+               val |= ROT_CONFIG_IRQ;
+       else
+               val &= ~ROT_CONFIG_IRQ;
+
+       rot_write(val, ROT_CONFIG);
+}
+
+static u32 rotator_reg_get_fmt(struct rot_context *rot)
+{
+       u32 val = rot_read(ROT_CONTROL);
+
+       val &= ROT_CONTROL_FMT_MASK;
+
+       return val;
+}
+
+static enum rot_irq_status rotator_reg_get_irq_status(struct rot_context *rot)
+{
+       u32 val = rot_read(ROT_STATUS);
+
+       val = ROT_STATUS_IRQ(val);
+
+       if (val == ROT_STATUS_IRQ_VAL_COMPLETE)
+               return ROT_IRQ_STATUS_COMPLETE;
+
+       return ROT_IRQ_STATUS_ILLEGAL;
+}
+
+static irqreturn_t rotator_irq_handler(int irq, void *arg)
+{
+       struct rot_context *rot = arg;
+       struct exynos_drm_ippdrv *ippdrv = &rot->ippdrv;
+       struct drm_exynos_ipp_cmd_node *c_node = ippdrv->cmd;
+       struct drm_exynos_ipp_event_work *event_work = c_node->event_work;
+       enum rot_irq_status irq_status;
+       u32 val;
+
+       /* Get execution result */
+       irq_status = rotator_reg_get_irq_status(rot);
+
+       /* clear status */
+       val = rot_read(ROT_STATUS);
+       val |= ROT_STATUS_IRQ_PENDING((u32)irq_status);
+       rot_write(val, ROT_STATUS);
+
+       if (irq_status == ROT_IRQ_STATUS_COMPLETE) {
+               event_work->ippdrv = ippdrv;
+               event_work->buf_id[EXYNOS_DRM_OPS_DST] =
+                       rot->cur_buf_id[EXYNOS_DRM_OPS_DST];
+               queue_work(ippdrv->event_workq,
+                       (struct work_struct *)event_work);
+       } else
+               DRM_ERROR("the SFR is set illegally\n");
+
+       return IRQ_HANDLED;
+}
+
+static void rotator_align_size(struct rot_context *rot, u32 fmt, u32 *hsize,
+               u32 *vsize)
+{
+       struct rot_limit_table *limit_tbl = rot->limit_tbl;
+       struct rot_limit *limit;
+       u32 mask, val;
+
+       /* Get size limit */
+       if (fmt == ROT_CONTROL_FMT_RGB888)
+               limit = &limit_tbl->rgb888;
+       else
+               limit = &limit_tbl->ycbcr420_2p;
+
+       /* Get mask for rounding to nearest aligned val */
+       mask = ~((1 << limit->align) - 1);
+
+       /* Set aligned width */
+       val = ROT_ALIGN(*hsize, limit->align, mask);
+       if (val < limit->min_w)
+               *hsize = ROT_MIN(limit->min_w, mask);
+       else if (val > limit->max_w)
+               *hsize = ROT_MAX(limit->max_w, mask);
+       else
+               *hsize = val;
+
+       /* Set aligned height */
+       val = ROT_ALIGN(*vsize, limit->align, mask);
+       if (val < limit->min_h)
+               *vsize = ROT_MIN(limit->min_h, mask);
+       else if (val > limit->max_h)
+               *vsize = ROT_MAX(limit->max_h, mask);
+       else
+               *vsize = val;
+}
+
+static int rotator_src_set_fmt(struct device *dev, u32 fmt)
+{
+       struct rot_context *rot = dev_get_drvdata(dev);
+       u32 val;
+
+       val = rot_read(ROT_CONTROL);
+       val &= ~ROT_CONTROL_FMT_MASK;
+
+       switch (fmt) {
+       case DRM_FORMAT_NV12:
+               val |= ROT_CONTROL_FMT_YCBCR420_2P;
+               break;
+       case DRM_FORMAT_XRGB8888:
+               val |= ROT_CONTROL_FMT_RGB888;
+               break;
+       default:
+               DRM_ERROR("invalid image format\n");
+               return -EINVAL;
+       }
+
+       rot_write(val, ROT_CONTROL);
+
+       return 0;
+}
+
+static inline bool rotator_check_reg_fmt(u32 fmt)
+{
+       if ((fmt == ROT_CONTROL_FMT_YCBCR420_2P) ||
+           (fmt == ROT_CONTROL_FMT_RGB888))
+               return true;
+
+       return false;
+}
+
+static int rotator_src_set_size(struct device *dev, int swap,
+               struct drm_exynos_pos *pos,
+               struct drm_exynos_sz *sz)
+{
+       struct rot_context *rot = dev_get_drvdata(dev);
+       u32 fmt, hsize, vsize;
+       u32 val;
+
+       /* Get format */
+       fmt = rotator_reg_get_fmt(rot);
+       if (!rotator_check_reg_fmt(fmt)) {
+               DRM_ERROR("%s:invalid format.\n", __func__);
+               return -EINVAL;
+       }
+
+       /* Align buffer size */
+       hsize = sz->hsize;
+       vsize = sz->vsize;
+       rotator_align_size(rot, fmt, &hsize, &vsize);
+
+       /* Set buffer size configuration */
+       val = ROT_SET_BUF_SIZE_H(vsize) | ROT_SET_BUF_SIZE_W(hsize);
+       rot_write(val, ROT_SRC_BUF_SIZE);
+
+       /* Set crop image position configuration */
+       val = ROT_CROP_POS_Y(pos->y) | ROT_CROP_POS_X(pos->x);
+       rot_write(val, ROT_SRC_CROP_POS);
+       val = ROT_SRC_CROP_SIZE_H(pos->h) | ROT_SRC_CROP_SIZE_W(pos->w);
+       rot_write(val, ROT_SRC_CROP_SIZE);
+
+       return 0;
+}
+
+static int rotator_src_set_addr(struct device *dev,
+               struct drm_exynos_ipp_buf_info *buf_info,
+               u32 buf_id, enum drm_exynos_ipp_buf_type buf_type)
+{
+       struct rot_context *rot = dev_get_drvdata(dev);
+       dma_addr_t addr[EXYNOS_DRM_PLANAR_MAX];
+       u32 val, fmt, hsize, vsize;
+       int i;
+
+       /* Set current buf_id */
+       rot->cur_buf_id[EXYNOS_DRM_OPS_SRC] = buf_id;
+
+       switch (buf_type) {
+       case IPP_BUF_ENQUEUE:
+               /* Set address configuration */
+               for_each_ipp_planar(i)
+                       addr[i] = buf_info->base[i];
+
+               /* Get format */
+               fmt = rotator_reg_get_fmt(rot);
+               if (!rotator_check_reg_fmt(fmt)) {
+                       DRM_ERROR("%s:invalid format.\n", __func__);
+                       return -EINVAL;
+               }
+
+               /* Re-set cb planar for NV12 format */
+               if ((fmt == ROT_CONTROL_FMT_YCBCR420_2P) &&
+                   !addr[EXYNOS_DRM_PLANAR_CB]) {
+
+                       val = rot_read(ROT_SRC_BUF_SIZE);
+                       hsize = ROT_GET_BUF_SIZE_W(val);
+                       vsize = ROT_GET_BUF_SIZE_H(val);
+
+                       /* Set cb planar */
+                       addr[EXYNOS_DRM_PLANAR_CB] =
+                               addr[EXYNOS_DRM_PLANAR_Y] + hsize * vsize;
+               }
+
+               for_each_ipp_planar(i)
+                       rot_write(addr[i], ROT_SRC_BUF_ADDR(i));
+               break;
+       case IPP_BUF_DEQUEUE:
+               for_each_ipp_planar(i)
+                       rot_write(0x0, ROT_SRC_BUF_ADDR(i));
+               break;
+       default:
+               /* Nothing to do */
+               break;
+       }
+
+       return 0;
+}
+
+static int rotator_dst_set_transf(struct device *dev,
+               enum drm_exynos_degree degree,
+               enum drm_exynos_flip flip, bool *swap)
+{
+       struct rot_context *rot = dev_get_drvdata(dev);
+       u32 val;
+
+       /* Set transform configuration */
+       val = rot_read(ROT_CONTROL);
+       val &= ~ROT_CONTROL_FLIP_MASK;
+
+       switch (flip) {
+       case EXYNOS_DRM_FLIP_VERTICAL:
+               val |= ROT_CONTROL_FLIP_VERTICAL;
+               break;
+       case EXYNOS_DRM_FLIP_HORIZONTAL:
+               val |= ROT_CONTROL_FLIP_HORIZONTAL;
+               break;
+       default:
+               /* Flip None */
+               break;
+       }
+
+       val &= ~ROT_CONTROL_ROT_MASK;
+
+       switch (degree) {
+       case EXYNOS_DRM_DEGREE_90:
+               val |= ROT_CONTROL_ROT_90;
+               break;
+       case EXYNOS_DRM_DEGREE_180:
+               val |= ROT_CONTROL_ROT_180;
+               break;
+       case EXYNOS_DRM_DEGREE_270:
+               val |= ROT_CONTROL_ROT_270;
+               break;
+       default:
+               /* Rotation 0 Degree */
+               break;
+       }
+
+       rot_write(val, ROT_CONTROL);
+
+       /* Check degree for setting buffer size swap */
+       if ((degree == EXYNOS_DRM_DEGREE_90) ||
+           (degree == EXYNOS_DRM_DEGREE_270))
+               *swap = true;
+       else
+               *swap = false;
+
+       return 0;
+}
+
+static int rotator_dst_set_size(struct device *dev, int swap,
+               struct drm_exynos_pos *pos,
+               struct drm_exynos_sz *sz)
+{
+       struct rot_context *rot = dev_get_drvdata(dev);
+       u32 val, fmt, hsize, vsize;
+
+       /* Get format */
+       fmt = rotator_reg_get_fmt(rot);
+       if (!rotator_check_reg_fmt(fmt)) {
+               DRM_ERROR("%s:invalid format.\n", __func__);
+               return -EINVAL;
+       }
+
+       /* Align buffer size */
+       hsize = sz->hsize;
+       vsize = sz->vsize;
+       rotator_align_size(rot, fmt, &hsize, &vsize);
+
+       /* Set buffer size configuration */
+       val = ROT_SET_BUF_SIZE_H(vsize) | ROT_SET_BUF_SIZE_W(hsize);
+       rot_write(val, ROT_DST_BUF_SIZE);
+
+       /* Set crop image position configuration */
+       val = ROT_CROP_POS_Y(pos->y) | ROT_CROP_POS_X(pos->x);
+       rot_write(val, ROT_DST_CROP_POS);
+
+       return 0;
+}
+
+static int rotator_dst_set_addr(struct device *dev,
+               struct drm_exynos_ipp_buf_info *buf_info,
+               u32 buf_id, enum drm_exynos_ipp_buf_type buf_type)
+{
+       struct rot_context *rot = dev_get_drvdata(dev);
+       dma_addr_t addr[EXYNOS_DRM_PLANAR_MAX];
+       u32 val, fmt, hsize, vsize;
+       int i;
+
+       /* Set current buf_id */
+       rot->cur_buf_id[EXYNOS_DRM_OPS_DST] = buf_id;
+
+       switch (buf_type) {
+       case IPP_BUF_ENQUEUE:
+               /* Set address configuration */
+               for_each_ipp_planar(i)
+                       addr[i] = buf_info->base[i];
+
+               /* Get format */
+               fmt = rotator_reg_get_fmt(rot);
+               if (!rotator_check_reg_fmt(fmt)) {
+                       DRM_ERROR("%s:invalid format.\n", __func__);
+                       return -EINVAL;
+               }
+
+               /* Re-set cb planar for NV12 format */
+               if ((fmt == ROT_CONTROL_FMT_YCBCR420_2P) &&
+                   !addr[EXYNOS_DRM_PLANAR_CB]) {
+                       /* Get buf size */
+                       val = rot_read(ROT_DST_BUF_SIZE);
+
+                       hsize = ROT_GET_BUF_SIZE_W(val);
+                       vsize = ROT_GET_BUF_SIZE_H(val);
+
+                       /* Set cb planar */
+                       addr[EXYNOS_DRM_PLANAR_CB] =
+                               addr[EXYNOS_DRM_PLANAR_Y] + hsize * vsize;
+               }
+
+               for_each_ipp_planar(i)
+                       rot_write(addr[i], ROT_DST_BUF_ADDR(i));
+               break;
+       case IPP_BUF_DEQUEUE:
+               for_each_ipp_planar(i)
+                       rot_write(0x0, ROT_DST_BUF_ADDR(i));
+               break;
+       default:
+               /* Nothing to do */
+               break;
+       }
+
+       return 0;
+}
+
+static struct exynos_drm_ipp_ops rot_src_ops = {
+       .set_fmt        =       rotator_src_set_fmt,
+       .set_size       =       rotator_src_set_size,
+       .set_addr       =       rotator_src_set_addr,
+};
+
+static struct exynos_drm_ipp_ops rot_dst_ops = {
+       .set_transf     =       rotator_dst_set_transf,
+       .set_size       =       rotator_dst_set_size,
+       .set_addr       =       rotator_dst_set_addr,
+};
+
+static int rotator_init_prop_list(struct exynos_drm_ippdrv *ippdrv)
+{
+       struct drm_exynos_ipp_prop_list *prop_list;
+
+       DRM_DEBUG_KMS("%s\n", __func__);
+
+       prop_list = devm_kzalloc(ippdrv->dev, sizeof(*prop_list), GFP_KERNEL);
+       if (!prop_list) {
+               DRM_ERROR("failed to alloc property list.\n");
+               return -ENOMEM;
+       }
+
+       prop_list->version = 1;
+       prop_list->flip = (1 << EXYNOS_DRM_FLIP_VERTICAL) |
+                               (1 << EXYNOS_DRM_FLIP_HORIZONTAL);
+       prop_list->degree = (1 << EXYNOS_DRM_DEGREE_0) |
+                               (1 << EXYNOS_DRM_DEGREE_90) |
+                               (1 << EXYNOS_DRM_DEGREE_180) |
+                               (1 << EXYNOS_DRM_DEGREE_270);
+       prop_list->csc = 0;
+       prop_list->crop = 0;
+       prop_list->scale = 0;
+
+       ippdrv->prop_list = prop_list;
+
+       return 0;
+}
+
+static inline bool rotator_check_drm_fmt(u32 fmt)
+{
+       switch (fmt) {
+       case DRM_FORMAT_XRGB8888:
+       case DRM_FORMAT_NV12:
+               return true;
+       default:
+               DRM_DEBUG_KMS("%s:not support format\n", __func__);
+               return false;
+       }
+}
+
+static inline bool rotator_check_drm_flip(enum drm_exynos_flip flip)
+{
+       switch (flip) {
+       case EXYNOS_DRM_FLIP_NONE:
+       case EXYNOS_DRM_FLIP_VERTICAL:
+       case EXYNOS_DRM_FLIP_HORIZONTAL:
+               return true;
+       default:
+               DRM_DEBUG_KMS("%s:invalid flip\n", __func__);
+               return false;
+       }
+}
+
+static int rotator_ippdrv_check_property(struct device *dev,
+               struct drm_exynos_ipp_property *property)
+{
+       struct drm_exynos_ipp_config *src_config =
+                                       &property->config[EXYNOS_DRM_OPS_SRC];
+       struct drm_exynos_ipp_config *dst_config =
+                                       &property->config[EXYNOS_DRM_OPS_DST];
+       struct drm_exynos_pos *src_pos = &src_config->pos;
+       struct drm_exynos_pos *dst_pos = &dst_config->pos;
+       struct drm_exynos_sz *src_sz = &src_config->sz;
+       struct drm_exynos_sz *dst_sz = &dst_config->sz;
+       bool swap = false;
+
+       /* Check format configuration */
+       if (src_config->fmt != dst_config->fmt) {
+               DRM_DEBUG_KMS("%s:not support csc feature\n", __func__);
+               return -EINVAL;
+       }
+
+       if (!rotator_check_drm_fmt(dst_config->fmt)) {
+               DRM_DEBUG_KMS("%s:invalid format\n", __func__);
+               return -EINVAL;
+       }
+
+       /* Check transform configuration */
+       if (src_config->degree != EXYNOS_DRM_DEGREE_0) {
+               DRM_DEBUG_KMS("%s:not support source-side rotation\n",
+                       __func__);
+               return -EINVAL;
+       }
+
+       switch (dst_config->degree) {
+       case EXYNOS_DRM_DEGREE_90:
+       case EXYNOS_DRM_DEGREE_270:
+               swap = true;
+       case EXYNOS_DRM_DEGREE_0:
+       case EXYNOS_DRM_DEGREE_180:
+               /* No problem */
+               break;
+       default:
+               DRM_DEBUG_KMS("%s:invalid degree\n", __func__);
+               return -EINVAL;
+       }
+
+       if (src_config->flip != EXYNOS_DRM_FLIP_NONE) {
+               DRM_DEBUG_KMS("%s:not support source-side flip\n", __func__);
+               return -EINVAL;
+       }
+
+       if (!rotator_check_drm_flip(dst_config->flip)) {
+               DRM_DEBUG_KMS("%s:invalid flip\n", __func__);
+               return -EINVAL;
+       }
+
+       /* Check size configuration */
+       if ((src_pos->x + src_pos->w > src_sz->hsize) ||
+               (src_pos->y + src_pos->h > src_sz->vsize)) {
+               DRM_DEBUG_KMS("%s:out of source buffer bound\n", __func__);
+               return -EINVAL;
+       }
+
+       if (swap) {
+               if ((dst_pos->x + dst_pos->h > dst_sz->vsize) ||
+                       (dst_pos->y + dst_pos->w > dst_sz->hsize)) {
+                       DRM_DEBUG_KMS("%s:out of destination buffer bound\n",
+                               __func__);
+                       return -EINVAL;
+               }
+
+               if ((src_pos->w != dst_pos->h) || (src_pos->h != dst_pos->w)) {
+                       DRM_DEBUG_KMS("%s:not support scale feature\n",
+                               __func__);
+                       return -EINVAL;
+               }
+       } else {
+               if ((dst_pos->x + dst_pos->w > dst_sz->hsize) ||
+                       (dst_pos->y + dst_pos->h > dst_sz->vsize)) {
+                       DRM_DEBUG_KMS("%s:out of destination buffer bound\n",
+                               __func__);
+                       return -EINVAL;
+               }
+
+               if ((src_pos->w != dst_pos->w) || (src_pos->h != dst_pos->h)) {
+                       DRM_DEBUG_KMS("%s:not support scale feature\n",
+                               __func__);
+                       return -EINVAL;
+               }
+       }
+
+       return 0;
+}
+
+static int rotator_ippdrv_start(struct device *dev, enum drm_exynos_ipp_cmd cmd)
+{
+       struct rot_context *rot = dev_get_drvdata(dev);
+       u32 val;
+
+       if (rot->suspended) {
+               DRM_ERROR("suspended state\n");
+               return -EPERM;
+       }
+
+       if (cmd != IPP_CMD_M2M) {
+               DRM_ERROR("not support cmd: %d\n", cmd);
+               return -EINVAL;
+       }
+
+       /* Set interrupt enable */
+       rotator_reg_set_irq(rot, true);
+
+       val = rot_read(ROT_CONTROL);
+       val |= ROT_CONTROL_START;
+
+       rot_write(val, ROT_CONTROL);
+
+       return 0;
+}
+
+static int __devinit rotator_probe(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       struct rot_context *rot;
+       struct exynos_drm_ippdrv *ippdrv;
+       int ret;
+
+       rot = devm_kzalloc(dev, 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;
+
+       rot->regs_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (!rot->regs_res) {
+               dev_err(dev, "failed to find registers\n");
+               ret = -ENOENT;
+               goto err_get_resource;
+       }
+
+       rot->regs = devm_request_and_ioremap(dev, rot->regs_res);
+       if (!rot->regs) {
+               dev_err(dev, "failed to map register\n");
+               ret = -ENXIO;
+               goto err_get_resource;
+       }
+
+       rot->irq = platform_get_irq(pdev, 0);
+       if (rot->irq < 0) {
+               dev_err(dev, "failed to get irq\n");
+               ret = rot->irq;
+               goto err_get_irq;
+       }
+
+       ret = request_threaded_irq(rot->irq, NULL, rotator_irq_handler,
+                       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, "failed to get clock\n");
+               ret = PTR_ERR(rot->clock);
+               goto err_clk_get;
+       }
+
+       pm_runtime_enable(dev);
+
+       ippdrv = &rot->ippdrv;
+       ippdrv->dev = dev;
+       ippdrv->ops[EXYNOS_DRM_OPS_SRC] = &rot_src_ops;
+       ippdrv->ops[EXYNOS_DRM_OPS_DST] = &rot_dst_ops;
+       ippdrv->check_property = rotator_ippdrv_check_property;
+       ippdrv->start = rotator_ippdrv_start;
+       ret = rotator_init_prop_list(ippdrv);
+       if (ret < 0) {
+               dev_err(dev, "failed to init property list.\n");
+               goto err_ippdrv_register;
+       }
+
+       DRM_DEBUG_KMS("%s:ippdrv[0x%x]\n", __func__, (int)ippdrv);
+
+       platform_set_drvdata(pdev, rot);
+
+       ret = exynos_drm_ippdrv_register(ippdrv);
+       if (ret < 0) {
+               dev_err(dev, "failed to register drm rotator device\n");
+               goto err_ippdrv_register;
+       }
+
+       dev_info(dev, "The exynos rotator is probed successfully\n");
+
+       return 0;
+
+err_ippdrv_register:
+       devm_kfree(dev, ippdrv->prop_list);
+       pm_runtime_disable(dev);
+       clk_put(rot->clock);
+err_clk_get:
+       free_irq(rot->irq, rot);
+err_get_irq:
+       devm_iounmap(dev, rot->regs);
+err_get_resource:
+       devm_kfree(dev, rot);
+       return ret;
+}
+
+static int __devexit rotator_remove(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       struct rot_context *rot = dev_get_drvdata(dev);
+       struct exynos_drm_ippdrv *ippdrv = &rot->ippdrv;
+
+       devm_kfree(dev, ippdrv->prop_list);
+       exynos_drm_ippdrv_unregister(ippdrv);
+
+       pm_runtime_disable(dev);
+       clk_put(rot->clock);
+
+       free_irq(rot->irq, rot);
+       devm_iounmap(dev, rot->regs);
+
+       devm_kfree(dev, 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,
+       },
+       {},
+};
+
+static int rotator_clk_crtl(struct rot_context *rot, bool enable)
+{
+       DRM_DEBUG_KMS("%s\n", __func__);
+
+       if (enable) {
+               clk_enable(rot->clock);
+               rot->suspended = false;
+       } else {
+               clk_disable(rot->clock);
+               rot->suspended = true;
+       }
+
+       return 0;
+}
+
+
+#ifdef CONFIG_PM_SLEEP
+static int rotator_suspend(struct device *dev)
+{
+       struct rot_context *rot = dev_get_drvdata(dev);
+
+       DRM_DEBUG_KMS("%s\n", __func__);
+
+       if (pm_runtime_suspended(dev))
+               return 0;
+
+       return rotator_clk_crtl(rot, false);
+}
+
+static int rotator_resume(struct device *dev)
+{
+       struct rot_context *rot = dev_get_drvdata(dev);
+
+       DRM_DEBUG_KMS("%s\n", __func__);
+
+       if (!pm_runtime_suspended(dev))
+               return rotator_clk_crtl(rot, true);
+
+       return 0;
+}
+#endif
+
+#ifdef CONFIG_PM_RUNTIME
+static int rotator_runtime_suspend(struct device *dev)
+{
+       struct rot_context *rot = dev_get_drvdata(dev);
+
+       DRM_DEBUG_KMS("%s\n", __func__);
+
+       return  rotator_clk_crtl(rot, false);
+}
+
+static int rotator_runtime_resume(struct device *dev)
+{
+       struct rot_context *rot = dev_get_drvdata(dev);
+
+       DRM_DEBUG_KMS("%s\n", __func__);
+
+       return  rotator_clk_crtl(rot, true);
+}
+#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 (file)
index 0000000..a2d7a14
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd.
+ *
+ * Authors:
+ *     YoungJun Cho <yj44.cho@samsung.com>
+ *     Eunchul Kim <chulspro.kim@samsung.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef        _EXYNOS_DRM_ROTATOR_H_
+#define        _EXYNOS_DRM_ROTATOR_H_
+
+/* TODO */
+
+#endif
diff --git a/drivers/gpu/drm/exynos/regs-rotator.h b/drivers/gpu/drm/exynos/regs-rotator.h
new file mode 100644 (file)
index 0000000..a09ac6e
--- /dev/null
@@ -0,0 +1,73 @@
+/* drivers/gpu/drm/exynos/regs-rotator.h
+ *
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd.
+ *             http://www.samsung.com/
+ *
+ * Register definition file for Samsung Rotator Interface (Rotator) driver
+ *
+ * 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 Foundation.
+*/
+
+#ifndef EXYNOS_REGS_ROTATOR_H
+#define EXYNOS_REGS_ROTATOR_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
+
+/* Buffer Address */
+#define ROT_SRC_BUF_ADDR(n)            (0x30 + ((n) << 2))
+#define ROT_DST_BUF_ADDR(n)            (0x50 + ((n) << 2))
+
+/* Buffer Size */
+#define ROT_SRC_BUF_SIZE               0x3c
+#define ROT_DST_BUF_SIZE               0x5c
+#define ROT_SET_BUF_SIZE_H(x)          ((x) << 16)
+#define ROT_SET_BUF_SIZE_W(x)          ((x) << 0)
+#define ROT_GET_BUF_SIZE_H(x)          ((x) >> 16)
+#define ROT_GET_BUF_SIZE_W(x)          ((x) & 0xffff)
+
+/* Crop Position */
+#define ROT_SRC_CROP_POS               0x40
+#define ROT_DST_CROP_POS               0x60
+#define ROT_CROP_POS_Y(x)              ((x) << 16)
+#define ROT_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)
+
+/* 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))
+
+#endif /* EXYNOS_REGS_ROTATOR_H */
+