]> git.kernelconcepts.de Git - karo-tx-linux.git/commitdiff
ENGR00273974-1 media: port mxc vout driver from 3.5.7 to 3.10
authorLiu Ying <Ying.Liu@freescale.com>
Mon, 5 Aug 2013 07:47:22 +0000 (15:47 +0800)
committerLothar Waßmann <LW@KARO-electronics.de>
Wed, 20 Aug 2014 08:06:10 +0000 (10:06 +0200)
This is porting mxc vout driver from imx_3.5.7 kernel to
imx_3.10 kernel.

* Put the driver in drivers/media/platform/ directory instead
  of drivers/media/video/ directory, since the later one is
  renamed to the former one in 3.10 kernel.
* Change the ipu-v3.h header file from <mach/ipu-v3.h> to
  <linux/ipu-v3.h>.
* Change the mxc_vidioc_s_crop() function's implementation since
  the definition of the vidioc_s_crop() function is modified to
  make the last argument be constant.
* Set vfl_dir to be VFL_DIR_TX since the ioctrl validity checks
  are improved in 3.10 kernel.
* Remove 'defaut y' setting for VIDEO_MXC_OUTPUT and
  VIDEO_MXC_IPU_OUTPUT Kconfigs. They may be configured by kernel
  default configure or by user.
* Make VIDEO_MXC_OUTPUT Kconfig depend on FB_MXC Kconfig since
  we need framebuffers to be rendered.
* Make VIDEO_MXC_IPU_OUTPUT Kconfig be tristate.
* Split <linux/mxc_v4l2.h> header file up into include/linux/
  and include/uapi/linux/ directories so that the userspace may
  include the mxc_v4l2.h header file.

Signed-off-by: Liu Ying <Ying.Liu@freescale.com>
drivers/media/platform/Kconfig
drivers/media/platform/Makefile
drivers/media/platform/mxc/output/Kconfig [new file with mode: 0644]
drivers/media/platform/mxc/output/Makefile [new file with mode: 0644]
drivers/media/platform/mxc/output/mxc_vout.c [new file with mode: 0644]
include/linux/mxc_v4l2.h [new file with mode: 0644]
include/uapi/linux/mxc_v4l2.h [new file with mode: 0644]

index 8108c698b5483c9250ec532f33af815b453aae1e..19b4a953ef8891e3e81c8ea9a72c5e2c66c98cba 100644 (file)
@@ -117,6 +117,14 @@ config VIDEO_S3C_CAMIF
          To compile this driver as a module, choose M here: the module
          will be called s3c-camif.
 
+config VIDEO_MXC_OUTPUT
+        tristate "MXC Video For Linux Video Output"
+        depends on VIDEO_DEV && ARCH_MXC && FB_MXC
+        select VIDEOBUF_DMA_CONTIG
+        ---help---
+        This is the video4linux2 output driver based on MXC module.
+
+source "drivers/media/platform/mxc/output/Kconfig"
 source "drivers/media/platform/soc_camera/Kconfig"
 source "drivers/media/platform/exynos4-is/Kconfig"
 source "drivers/media/platform/s5p-tv/Kconfig"
index e5269da91906bd0dea8760e361bb1d599d48b626..8e3372ec48da9d4da19f668d56fb2c67f0a2192d 100644 (file)
@@ -51,4 +51,6 @@ obj-y += davinci/
 
 obj-$(CONFIG_ARCH_OMAP)        += omap/
 
+obj-$(CONFIG_VIDEO_MXC_OUTPUT) += mxc/output/
+
 ccflags-y += -I$(srctree)/drivers/media/i2c
diff --git a/drivers/media/platform/mxc/output/Kconfig b/drivers/media/platform/mxc/output/Kconfig
new file mode 100644 (file)
index 0000000..b684060
--- /dev/null
@@ -0,0 +1,5 @@
+config VIDEO_MXC_IPU_OUTPUT
+       tristate "IPU v4l2 output support"
+       depends on VIDEO_MXC_OUTPUT && MXC_IPU
+       ---help---
+       This is the video4linux2 driver for IPU post processing video output.
diff --git a/drivers/media/platform/mxc/output/Makefile b/drivers/media/platform/mxc/output/Makefile
new file mode 100644 (file)
index 0000000..7b524fe
--- /dev/null
@@ -0,0 +1 @@
+obj-$(CONFIG_VIDEO_MXC_IPU_OUTPUT) += mxc_vout.o
diff --git a/drivers/media/platform/mxc/output/mxc_vout.c b/drivers/media/platform/mxc/output/mxc_vout.c
new file mode 100644 (file)
index 0000000..0dfe387
--- /dev/null
@@ -0,0 +1,2189 @@
+/*
+ * Copyright (C) 2011-2013 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/console.h>
+#include <linux/dma-mapping.h>
+#include <linux/init.h>
+#include <linux/ipu-v3.h>
+#include <linux/module.h>
+#include <linux/mxcfb.h>
+#include <linux/mxc_v4l2.h>
+#include <linux/platform_device.h>
+#include <linux/sched.h>
+#include <linux/types.h>
+#include <linux/videodev2.h>
+#include <linux/vmalloc.h>
+
+#include <media/videobuf-dma-contig.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+
+#define UYVY_BLACK     (0x00800080)
+#define RGB_BLACK      (0x0)
+#define UV_BLACK       (0x80)
+#define Y_BLACK                (0x0)
+
+#define MAX_FB_NUM     6
+#define FB_BUFS                3
+#define VDOA_FB_BUFS   (FB_BUFS - 1)
+#define VALID_HEIGHT_1080P     (1080)
+#define FRAME_HEIGHT_1080P     (1088)
+#define FRAME_WIDTH_1080P      (1920)
+#define CHECK_TILED_1080P_DISPLAY(vout)        \
+       ((((vout)->task.input.format == IPU_PIX_FMT_TILED_NV12) ||      \
+              ((vout)->task.input.format == IPU_PIX_FMT_TILED_NV12F)) &&\
+              ((vout)->task.input.width == FRAME_WIDTH_1080P) &&       \
+              ((vout)->task.input.height == FRAME_HEIGHT_1080P) &&     \
+              ((vout)->task.input.crop.w == FRAME_WIDTH_1080P) &&      \
+              (((vout)->task.input.crop.h == FRAME_HEIGHT_1080P) ||    \
+              ((vout)->task.input.crop.h == VALID_HEIGHT_1080P)) &&    \
+              ((vout)->task.output.width == FRAME_WIDTH_1080P) &&      \
+              ((vout)->task.output.height == VALID_HEIGHT_1080P) &&    \
+              ((vout)->task.output.crop.w == FRAME_WIDTH_1080P) &&     \
+              ((vout)->task.output.crop.h == VALID_HEIGHT_1080P))
+#define CHECK_TILED_1080P_STREAM(vout) \
+       ((((vout)->task.input.format == IPU_PIX_FMT_TILED_NV12) ||      \
+              ((vout)->task.input.format == IPU_PIX_FMT_TILED_NV12F)) &&\
+              ((vout)->task.input.width == FRAME_WIDTH_1080P) &&       \
+              ((vout)->task.input.crop.w == FRAME_WIDTH_1080P) &&      \
+              ((vout)->task.input.height == FRAME_HEIGHT_1080P) &&     \
+              ((vout)->task.input.crop.h == FRAME_HEIGHT_1080P))
+#define IS_PLANAR_PIXEL_FORMAT(format) \
+       (format == IPU_PIX_FMT_NV12 ||          \
+           format == IPU_PIX_FMT_YUV420P2 ||   \
+           format == IPU_PIX_FMT_YUV420P ||    \
+           format == IPU_PIX_FMT_YVU420P ||    \
+           format == IPU_PIX_FMT_YUV422P ||    \
+           format == IPU_PIX_FMT_YVU422P ||    \
+           format == IPU_PIX_FMT_YUV444P)
+
+#define NSEC_PER_FRAME_30FPS           (33333333)
+
+struct mxc_vout_fb {
+       char *name;
+       int ipu_id;
+       struct v4l2_rect crop_bounds;
+       unsigned int disp_fmt;
+       bool disp_support_csc;
+       bool disp_support_windows;
+};
+
+struct dma_mem {
+       void *vaddr;
+       dma_addr_t paddr;
+       size_t size;
+};
+
+struct mxc_vout_output {
+       int open_cnt;
+       struct fb_info *fbi;
+       unsigned long fb_smem_start;
+       unsigned long fb_smem_len;
+       struct video_device *vfd;
+       struct mutex mutex;
+       struct mutex task_lock;
+       enum v4l2_buf_type type;
+
+       struct videobuf_queue vbq;
+       spinlock_t vbq_lock;
+
+       struct list_head queue_list;
+       struct list_head active_list;
+
+       struct v4l2_rect crop_bounds;
+       unsigned int disp_fmt;
+       struct mxcfb_pos win_pos;
+       bool disp_support_windows;
+       bool disp_support_csc;
+
+       bool fmt_init;
+       bool release;
+       bool linear_bypass_pp;
+       bool vdoa_1080p;
+       bool tiled_bypass_pp;
+       struct v4l2_rect in_rect;
+       struct ipu_task task;
+       struct ipu_task vdoa_task;
+       struct dma_mem vdoa_work;
+       struct dma_mem vdoa_output[VDOA_FB_BUFS];
+
+       bool timer_stop;
+       struct hrtimer timer;
+       struct workqueue_struct *v4l_wq;
+       struct work_struct disp_work;
+       unsigned long frame_count;
+       unsigned long vdi_frame_cnt;
+       ktime_t start_ktime;
+
+       int ctrl_rotate;
+       int ctrl_vflip;
+       int ctrl_hflip;
+
+       dma_addr_t disp_bufs[FB_BUFS];
+
+       struct videobuf_buffer *pre1_vb;
+       struct videobuf_buffer *pre2_vb;
+};
+
+struct mxc_vout_dev {
+       struct device   *dev;
+       struct v4l2_device v4l2_dev;
+       struct mxc_vout_output *out[MAX_FB_NUM];
+       int out_num;
+};
+
+/* Driver Configuration macros */
+#define VOUT_NAME              "mxc_vout"
+
+/* Variables configurable through module params*/
+static int debug;
+static int vdi_rate_double;
+static int video_nr = 16;
+
+/* Module parameters */
+module_param(video_nr, int, S_IRUGO);
+MODULE_PARM_DESC(video_nr, "video device numbers");
+module_param(debug, int, 0600);
+MODULE_PARM_DESC(debug, "Debug level (0-1)");
+module_param(vdi_rate_double, int, 0600);
+MODULE_PARM_DESC(vdi_rate_double, "vdi frame rate double on/off");
+
+static const struct v4l2_fmtdesc mxc_formats[] = {
+       {
+               .description = "RGB565",
+               .pixelformat = V4L2_PIX_FMT_RGB565,
+       },
+       {
+               .description = "BGR24",
+               .pixelformat = V4L2_PIX_FMT_BGR24,
+       },
+       {
+               .description = "RGB24",
+               .pixelformat = V4L2_PIX_FMT_RGB24,
+       },
+       {
+               .description = "RGB32",
+               .pixelformat = V4L2_PIX_FMT_RGB32,
+       },
+       {
+               .description = "BGR32",
+               .pixelformat = V4L2_PIX_FMT_BGR32,
+       },
+       {
+               .description = "NV12",
+               .pixelformat = V4L2_PIX_FMT_NV12,
+       },
+       {
+               .description = "UYVY",
+               .pixelformat = V4L2_PIX_FMT_UYVY,
+       },
+       {
+               .description = "YUYV",
+               .pixelformat = V4L2_PIX_FMT_YUYV,
+       },
+       {
+               .description = "YUV422 planar",
+               .pixelformat = V4L2_PIX_FMT_YUV422P,
+       },
+       {
+               .description = "YUV444",
+               .pixelformat = V4L2_PIX_FMT_YUV444,
+       },
+       {
+               .description = "YUV420",
+               .pixelformat = V4L2_PIX_FMT_YUV420,
+       },
+       {
+               .description = "YVU420",
+               .pixelformat = V4L2_PIX_FMT_YVU420,
+       },
+       {
+               .description = "TILED NV12P",
+               .pixelformat = IPU_PIX_FMT_TILED_NV12,
+       },
+       {
+               .description = "TILED NV12F",
+               .pixelformat = IPU_PIX_FMT_TILED_NV12F,
+       },
+       {
+               .description = "YUV444 planar",
+               .pixelformat = IPU_PIX_FMT_YUV444P,
+       },
+};
+
+#define NUM_MXC_VOUT_FORMATS (ARRAY_SIZE(mxc_formats))
+
+#define DEF_INPUT_WIDTH                320
+#define DEF_INPUT_HEIGHT       240
+
+static int mxc_vidioc_streamoff(struct file *file, void *fh,
+                                       enum v4l2_buf_type i);
+
+static struct mxc_vout_fb g_fb_setting[MAX_FB_NUM];
+static int config_disp_output(struct mxc_vout_output *vout);
+static void release_disp_output(struct mxc_vout_output *vout);
+
+static unsigned int get_frame_size(struct mxc_vout_output *vout)
+{
+       unsigned int size;
+
+       if (IPU_PIX_FMT_TILED_NV12 == vout->task.input.format)
+               size = TILED_NV12_FRAME_SIZE(vout->task.input.width,
+                                       vout->task.input.height);
+       else if (IPU_PIX_FMT_TILED_NV12F == vout->task.input.format) {
+               size = TILED_NV12_FRAME_SIZE(vout->task.input.width,
+                                       vout->task.input.height/2);
+               size *= 2;
+       } else
+               size = vout->task.input.width * vout->task.input.height *
+                               fmt_to_bpp(vout->task.input.format)/8;
+
+       return size;
+}
+
+static void free_dma_buf(struct mxc_vout_output *vout, struct dma_mem *buf)
+{
+       dma_free_coherent(vout->vbq.dev, buf->size, buf->vaddr, buf->paddr);
+       v4l2_dbg(1, debug, vout->vfd->v4l2_dev,
+                       "free dma size:0x%x, paddr:0x%x\n",
+                       buf->size, buf->paddr);
+       memset(buf, 0, sizeof(*buf));
+}
+
+static int alloc_dma_buf(struct mxc_vout_output *vout, struct dma_mem *buf)
+{
+
+       buf->vaddr = dma_alloc_coherent(vout->vbq.dev, buf->size, &buf->paddr,
+                                               GFP_DMA | GFP_KERNEL);
+       if (!buf->vaddr) {
+               v4l2_err(vout->vfd->v4l2_dev,
+                       "cannot get dma buf size:0x%x\n", buf->size);
+               return -ENOMEM;
+       }
+       v4l2_dbg(1, debug, vout->vfd->v4l2_dev,
+               "alloc dma buf size:0x%x, paddr:0x%x\n", buf->size, buf->paddr);
+       return 0;
+}
+
+static ipu_channel_t get_ipu_channel(struct fb_info *fbi)
+{
+       ipu_channel_t ipu_ch = CHAN_NONE;
+       mm_segment_t old_fs;
+
+       if (fbi->fbops->fb_ioctl) {
+               old_fs = get_fs();
+               set_fs(KERNEL_DS);
+               fbi->fbops->fb_ioctl(fbi, MXCFB_GET_FB_IPU_CHAN,
+                               (unsigned long)&ipu_ch);
+               set_fs(old_fs);
+       }
+
+       return ipu_ch;
+}
+
+static unsigned int get_ipu_fmt(struct fb_info *fbi)
+{
+       mm_segment_t old_fs;
+       unsigned int fb_fmt;
+
+       if (fbi->fbops->fb_ioctl) {
+               old_fs = get_fs();
+               set_fs(KERNEL_DS);
+               fbi->fbops->fb_ioctl(fbi, MXCFB_GET_DIFMT,
+                               (unsigned long)&fb_fmt);
+               set_fs(old_fs);
+       }
+
+       return fb_fmt;
+}
+
+static void update_display_setting(void)
+{
+       int i;
+       struct fb_info *fbi;
+       struct v4l2_rect bg_crop_bounds[2];
+
+       for (i = 0; i < num_registered_fb; i++) {
+               fbi = registered_fb[i];
+
+               memset(&g_fb_setting[i], 0, sizeof(struct mxc_vout_fb));
+
+               if (!strncmp(fbi->fix.id, "DISP3", 5))
+                       g_fb_setting[i].ipu_id = 0;
+               else
+                       g_fb_setting[i].ipu_id = 1;
+
+               g_fb_setting[i].name = fbi->fix.id;
+               g_fb_setting[i].crop_bounds.left = 0;
+               g_fb_setting[i].crop_bounds.top = 0;
+               g_fb_setting[i].crop_bounds.width = fbi->var.xres;
+               g_fb_setting[i].crop_bounds.height = fbi->var.yres;
+               g_fb_setting[i].disp_fmt = get_ipu_fmt(fbi);
+
+               if (get_ipu_channel(fbi) == MEM_BG_SYNC) {
+                       bg_crop_bounds[g_fb_setting[i].ipu_id] =
+                               g_fb_setting[i].crop_bounds;
+                       g_fb_setting[i].disp_support_csc = true;
+               } else if (get_ipu_channel(fbi) == MEM_FG_SYNC) {
+                       g_fb_setting[i].disp_support_csc = true;
+                       g_fb_setting[i].disp_support_windows = true;
+               }
+       }
+
+       for (i = 0; i < num_registered_fb; i++) {
+               fbi = registered_fb[i];
+
+               if (get_ipu_channel(fbi) == MEM_FG_SYNC)
+                       g_fb_setting[i].crop_bounds =
+                               bg_crop_bounds[g_fb_setting[i].ipu_id];
+       }
+}
+
+/* called after g_fb_setting filled by update_display_setting */
+static int update_setting_from_fbi(struct mxc_vout_output *vout,
+                       struct fb_info *fbi)
+{
+       int i;
+       bool found = false;
+
+       for (i = 0; i < MAX_FB_NUM; i++) {
+               if (g_fb_setting[i].name) {
+                       if (!strcmp(fbi->fix.id, g_fb_setting[i].name)) {
+                               vout->crop_bounds = g_fb_setting[i].crop_bounds;
+                               vout->disp_fmt = g_fb_setting[i].disp_fmt;
+                               vout->disp_support_csc =
+                                       g_fb_setting[i].disp_support_csc;
+                               vout->disp_support_windows =
+                                       g_fb_setting[i].disp_support_windows;
+                               found = true;
+                               break;
+                       }
+               }
+       }
+
+       if (!found) {
+               v4l2_err(vout->vfd->v4l2_dev, "can not find output\n");
+               return -EINVAL;
+       }
+       strlcpy(vout->vfd->name, fbi->fix.id, sizeof(vout->vfd->name));
+
+       memset(&vout->task, 0, sizeof(struct ipu_task));
+
+       vout->task.input.width = DEF_INPUT_WIDTH;
+       vout->task.input.height = DEF_INPUT_HEIGHT;
+       vout->task.input.crop.pos.x = 0;
+       vout->task.input.crop.pos.y = 0;
+       vout->task.input.crop.w = DEF_INPUT_WIDTH;
+       vout->task.input.crop.h = DEF_INPUT_HEIGHT;
+
+       vout->task.output.width = vout->crop_bounds.width;
+       vout->task.output.height = vout->crop_bounds.height;
+       vout->task.output.crop.pos.x = 0;
+       vout->task.output.crop.pos.y = 0;
+       vout->task.output.crop.w = vout->crop_bounds.width;
+       vout->task.output.crop.h = vout->crop_bounds.height;
+       if (colorspaceofpixel(vout->disp_fmt) == YUV_CS)
+               vout->task.output.format = IPU_PIX_FMT_UYVY;
+       else
+               vout->task.output.format = IPU_PIX_FMT_RGB565;
+
+       return 0;
+}
+
+static inline unsigned long get_jiffies(struct timeval *t)
+{
+       struct timeval cur;
+
+       if (t->tv_usec >= 1000000) {
+               t->tv_sec += t->tv_usec / 1000000;
+               t->tv_usec = t->tv_usec % 1000000;
+       }
+
+       do_gettimeofday(&cur);
+       if ((t->tv_sec < cur.tv_sec)
+           || ((t->tv_sec == cur.tv_sec) && (t->tv_usec < cur.tv_usec)))
+               return jiffies;
+
+       if (t->tv_usec < cur.tv_usec) {
+               cur.tv_sec = t->tv_sec - cur.tv_sec - 1;
+               cur.tv_usec = t->tv_usec + 1000000 - cur.tv_usec;
+       } else {
+               cur.tv_sec = t->tv_sec - cur.tv_sec;
+               cur.tv_usec = t->tv_usec - cur.tv_usec;
+       }
+
+       return jiffies + timeval_to_jiffies(&cur);
+}
+
+static bool deinterlace_3_field(struct mxc_vout_output *vout)
+{
+       return (vout->task.input.deinterlace.enable &&
+               (vout->task.input.deinterlace.motion != HIGH_MOTION));
+}
+
+static int set_field_fmt(struct mxc_vout_output *vout, enum v4l2_field field)
+{
+       struct ipu_deinterlace *deinterlace = &vout->task.input.deinterlace;
+
+       switch (field) {
+       /* Images are in progressive format, not interlaced */
+       case V4L2_FIELD_NONE:
+       case V4L2_FIELD_ANY:
+               deinterlace->enable = false;
+               deinterlace->field_fmt = 0;
+               v4l2_dbg(1, debug, vout->vfd->v4l2_dev, "Progressive frame.\n");
+               break;
+       case V4L2_FIELD_INTERLACED_TB:
+               v4l2_dbg(1, debug, vout->vfd->v4l2_dev,
+                               "Enable deinterlace TB.\n");
+               deinterlace->enable = true;
+               deinterlace->field_fmt = IPU_DEINTERLACE_FIELD_TOP;
+               break;
+       case V4L2_FIELD_INTERLACED_BT:
+               v4l2_dbg(1, debug, vout->vfd->v4l2_dev,
+                               "Enable deinterlace BT.\n");
+               deinterlace->enable = true;
+               deinterlace->field_fmt = IPU_DEINTERLACE_FIELD_BOTTOM;
+               break;
+       default:
+               v4l2_err(vout->vfd->v4l2_dev,
+                       "field format:%d not supported yet!\n", field);
+               return -EINVAL;
+       }
+
+       if (IPU_PIX_FMT_TILED_NV12F == vout->task.input.format) {
+               v4l2_dbg(1, debug, vout->vfd->v4l2_dev,
+                               "tiled fmt enable deinterlace.\n");
+               deinterlace->enable = true;
+       }
+
+       if (deinterlace->enable && vdi_rate_double)
+               deinterlace->field_fmt |= IPU_DEINTERLACE_RATE_EN;
+
+       return 0;
+}
+
+static bool is_pp_bypass(struct mxc_vout_output *vout)
+{
+       if ((IPU_PIX_FMT_TILED_NV12 == vout->task.input.format) ||
+               (IPU_PIX_FMT_TILED_NV12F == vout->task.input.format))
+               return false;
+       if ((vout->task.input.width == vout->task.output.width) &&
+               (vout->task.input.height == vout->task.output.height) &&
+               (vout->task.input.crop.w == vout->task.output.crop.w) &&
+               (vout->task.input.crop.h == vout->task.output.crop.h) &&
+               (vout->task.output.rotate < IPU_ROTATE_HORIZ_FLIP) &&
+               !vout->task.input.deinterlace.enable) {
+               if (vout->disp_support_csc)
+                       return true;
+               else if (!need_csc(vout->task.input.format, vout->disp_fmt))
+                       return true;
+       /*
+        * input crop show to full output which can show based on
+        * xres_virtual/yres_virtual
+        */
+       } else if ((vout->task.input.crop.w == vout->task.output.crop.w) &&
+                       (vout->task.output.crop.w == vout->task.output.width) &&
+                       (vout->task.input.crop.h == vout->task.output.crop.h) &&
+                       (vout->task.output.crop.h ==
+                               vout->task.output.height) &&
+                       (vout->task.output.rotate < IPU_ROTATE_HORIZ_FLIP) &&
+                       !vout->task.input.deinterlace.enable) {
+               if (vout->disp_support_csc)
+                       return true;
+               else if (!need_csc(vout->task.input.format, vout->disp_fmt))
+                       return true;
+       }
+       return false;
+}
+
+static void setup_buf_timer(struct mxc_vout_output *vout,
+                       struct videobuf_buffer *vb)
+{
+       ktime_t expiry_time, now;
+
+       /* if timestamp is 0, then default to 30fps */
+       if ((vb->ts.tv_sec == 0) && (vb->ts.tv_usec == 0))
+               expiry_time = ktime_add_ns(vout->start_ktime,
+                               NSEC_PER_FRAME_30FPS * vout->frame_count);
+       else
+               expiry_time = timeval_to_ktime(vb->ts);
+
+       now = hrtimer_cb_get_time(&vout->timer);
+       if ((now.tv64 > expiry_time.tv64)) {
+               v4l2_dbg(1, debug, vout->vfd->v4l2_dev,
+                               "warning: timer timeout already expired.\n");
+               expiry_time = now;
+       }
+
+       hrtimer_start(&vout->timer, expiry_time, HRTIMER_MODE_ABS);
+
+       v4l2_dbg(1, debug, vout->vfd->v4l2_dev, "timer handler next "
+               "schedule: %lldnsecs\n", expiry_time.tv64);
+}
+
+static int show_buf(struct mxc_vout_output *vout, int idx,
+       struct ipu_pos *ipos)
+{
+       struct fb_info *fbi = vout->fbi;
+       struct fb_var_screeninfo var;
+       int ret;
+       u32 fb_base = 0;
+
+       memcpy(&var, &fbi->var, sizeof(var));
+
+       if (vout->linear_bypass_pp || vout->tiled_bypass_pp) {
+               /*
+                * crack fb base
+                * NOTE: should not do other fb operation during v4l2
+                */
+               console_lock();
+               fb_base = fbi->fix.smem_start;
+               fbi->fix.smem_start = vout->task.output.paddr;
+               fbi->var.yoffset = ipos->y + 1;
+               var.xoffset = ipos->x;
+               var.yoffset = ipos->y;
+               var.vmode |= FB_VMODE_YWRAP;
+               ret = fb_pan_display(fbi, &var);
+               fbi->fix.smem_start = fb_base;
+               console_unlock();
+       } else {
+               console_lock();
+               var.yoffset = idx * fbi->var.yres;
+               var.vmode &= ~FB_VMODE_YWRAP;
+               ret = fb_pan_display(fbi, &var);
+               console_unlock();
+       }
+
+       return ret;
+}
+
+static void disp_work_func(struct work_struct *work)
+{
+       struct mxc_vout_output *vout =
+               container_of(work, struct mxc_vout_output, disp_work);
+       struct videobuf_queue *q = &vout->vbq;
+       struct videobuf_buffer *vb, *vb_next = NULL;
+       unsigned long flags = 0;
+       struct ipu_pos ipos;
+       int ret = 0;
+       u32 in_fmt = 0;
+       u32 vdi_cnt = 0;
+       u32 vdi_frame;
+       u32 index = 0;
+       u32 ocrop_h = 0;
+       u32 o_height = 0;
+       u32 tiled_interlaced = 0;
+       bool tiled_fmt = false;
+
+       v4l2_dbg(1, debug, vout->vfd->v4l2_dev, "disp work begin one frame\n");
+
+       spin_lock_irqsave(q->irqlock, flags);
+
+       if (list_empty(&vout->active_list)) {
+               v4l2_warn(vout->vfd->v4l2_dev,
+                       "no entry in active_list, should not be here\n");
+               spin_unlock_irqrestore(q->irqlock, flags);
+               return;
+       }
+
+       vb = list_first_entry(&vout->active_list,
+                       struct videobuf_buffer, queue);
+       ret = set_field_fmt(vout, vb->field);
+       if (ret < 0) {
+               spin_unlock_irqrestore(q->irqlock, flags);
+               return;
+       }
+       if (deinterlace_3_field(vout)) {
+               if (list_is_singular(&vout->active_list)) {
+                       if (list_empty(&vout->queue_list)) {
+                               vout->timer_stop = true;
+                               spin_unlock_irqrestore(q->irqlock, flags);
+                               v4l2_warn(vout->vfd->v4l2_dev,
+                                       "no enough entry for 3 fields "
+                                       "deinterlacer\n");
+                               return;
+                       }
+
+                       /*
+                        * We need to use the next vb even if it is
+                        * not on the active list.
+                        */
+                       vb_next = list_first_entry(&vout->queue_list,
+                                       struct videobuf_buffer, queue);
+               } else
+                       vb_next = list_first_entry(vout->active_list.next,
+                                               struct videobuf_buffer, queue);
+               v4l2_dbg(1, debug, vout->vfd->v4l2_dev,
+                       "cur field_fmt:%d, next field_fmt:%d.\n",
+                       vb->field, vb_next->field);
+               /* repeat the last field during field format changing */
+               if ((vb->field != vb_next->field) &&
+                       (vb_next->field != V4L2_FIELD_NONE))
+                       vb_next = vb;
+       }
+
+       spin_unlock_irqrestore(q->irqlock, flags);
+
+vdi_frame_rate_double:
+       mutex_lock(&vout->task_lock);
+
+       v4l2_dbg(1, debug, vout->vfd->v4l2_dev,
+               "v4l2 frame_cnt:%ld, vb_field:%d, fmt:%d\n",
+               vout->frame_count, vb->field,
+               vout->task.input.deinterlace.field_fmt);
+       if (vb->memory == V4L2_MEMORY_USERPTR)
+               vout->task.input.paddr = vb->baddr;
+       else
+               vout->task.input.paddr = videobuf_to_dma_contig(vb);
+
+       if (vout->task.input.deinterlace.field_fmt & IPU_DEINTERLACE_RATE_EN)
+               index = vout->vdi_frame_cnt % FB_BUFS;
+       else
+               index = vout->frame_count % FB_BUFS;
+       if (vout->linear_bypass_pp) {
+               vout->task.output.paddr = vout->task.input.paddr;
+               ipos.x = vout->task.input.crop.pos.x;
+               ipos.y = vout->task.input.crop.pos.y;
+       } else {
+               if (deinterlace_3_field(vout)) {
+                       if (vb->memory == V4L2_MEMORY_USERPTR)
+                               vout->task.input.paddr_n = vb_next->baddr;
+                       else
+                               vout->task.input.paddr_n =
+                                       videobuf_to_dma_contig(vb_next);
+               }
+               vout->task.output.paddr = vout->disp_bufs[index];
+               if (vout->vdoa_1080p) {
+                       o_height =  vout->task.output.height;
+                       ocrop_h = vout->task.output.crop.h;
+                       vout->task.output.height = FRAME_HEIGHT_1080P;
+                       vout->task.output.crop.h = FRAME_HEIGHT_1080P;
+               }
+               tiled_fmt =
+                       (IPU_PIX_FMT_TILED_NV12 == vout->task.input.format) ||
+                       (IPU_PIX_FMT_TILED_NV12F == vout->task.input.format);
+               if (vout->tiled_bypass_pp) {
+                       ipos.x = vout->task.input.crop.pos.x;
+                       ipos.y = vout->task.input.crop.pos.y;
+               } else if (tiled_fmt) {
+                       vout->vdoa_task.input.paddr = vout->task.input.paddr;
+                       if (deinterlace_3_field(vout))
+                               vout->vdoa_task.input.paddr_n =
+                                               vout->task.input.paddr_n;
+                       vout->vdoa_task.output.paddr = vout->vdoa_work.paddr;
+                       ret = ipu_queue_task(&vout->vdoa_task);
+                       if (ret < 0) {
+                               mutex_unlock(&vout->task_lock);
+                               goto err;
+                       }
+                       vout->task.input.paddr = vout->vdoa_task.output.paddr;
+                       in_fmt = vout->task.input.format;
+                       vout->task.input.format = vout->vdoa_task.output.format;
+                       if (vout->task.input.deinterlace.enable) {
+                               tiled_interlaced = 1;
+                               vout->task.input.deinterlace.enable = 0;
+                       }
+                       v4l2_dbg(1, debug, vout->vfd->v4l2_dev,
+                                       "tiled queue task\n");
+               }
+               ret = ipu_queue_task(&vout->task);
+               if ((!vout->tiled_bypass_pp) && tiled_fmt)
+                       vout->task.input.format = in_fmt;
+               if (tiled_interlaced)
+                       vout->task.input.deinterlace.enable = 1;
+               if (ret < 0) {
+                       mutex_unlock(&vout->task_lock);
+                       goto err;
+               }
+               if (vout->vdoa_1080p) {
+                       vout->task.output.crop.h = ocrop_h;
+                       vout->task.output.height = o_height;
+               }
+       }
+
+       mutex_unlock(&vout->task_lock);
+
+       ret = show_buf(vout, index, &ipos);
+       if (ret < 0)
+               v4l2_dbg(1, debug, vout->vfd->v4l2_dev,
+                       "show buf with ret %d\n", ret);
+
+       if (vout->task.input.deinterlace.field_fmt & IPU_DEINTERLACE_RATE_EN) {
+               vdi_frame = vout->task.input.deinterlace.field_fmt
+                               & IPU_DEINTERLACE_RATE_FRAME1;
+               if (vdi_frame)
+                       vout->task.input.deinterlace.field_fmt &=
+                       ~IPU_DEINTERLACE_RATE_FRAME1;
+               else
+                       vout->task.input.deinterlace.field_fmt |=
+                       IPU_DEINTERLACE_RATE_FRAME1;
+               vout->vdi_frame_cnt++;
+               vdi_cnt++;
+               if (vdi_cnt < IPU_DEINTERLACE_MAX_FRAME)
+                       goto vdi_frame_rate_double;
+       }
+       spin_lock_irqsave(q->irqlock, flags);
+
+       list_del(&vb->queue);
+
+       /*
+        * The videobuf before the last one has been shown. Set
+        * VIDEOBUF_DONE state here to avoid tearing issue in ic bypass
+        * case, which makes sure a buffer being shown will not be
+        * dequeued to be overwritten. It also brings side-effect that
+        * the last 2 buffers can not be dequeued correctly, apps need
+        * to take care of it.
+        */
+       if (vout->pre2_vb) {
+               vout->pre2_vb->state = VIDEOBUF_DONE;
+               wake_up_interruptible(&vout->pre2_vb->done);
+               vout->pre2_vb = NULL;
+       }
+
+       if (vout->linear_bypass_pp) {
+               vout->pre2_vb = vout->pre1_vb;
+               vout->pre1_vb = vb;
+       } else {
+               if (vout->pre1_vb) {
+                       vout->pre1_vb->state = VIDEOBUF_DONE;
+                       wake_up_interruptible(&vout->pre1_vb->done);
+                       vout->pre1_vb = NULL;
+               }
+               vb->state = VIDEOBUF_DONE;
+               wake_up_interruptible(&vb->done);
+       }
+
+       vout->frame_count++;
+
+       /* pick next queue buf to setup timer */
+       if (list_empty(&vout->queue_list))
+               vout->timer_stop = true;
+       else {
+               vb = list_first_entry(&vout->queue_list,
+                               struct videobuf_buffer, queue);
+               setup_buf_timer(vout, vb);
+       }
+
+       spin_unlock_irqrestore(q->irqlock, flags);
+
+       v4l2_dbg(1, debug, vout->vfd->v4l2_dev, "disp work finish one frame\n");
+
+       return;
+err:
+       v4l2_err(vout->vfd->v4l2_dev, "display work fail ret = %d\n", ret);
+       vout->timer_stop = true;
+       vb->state = VIDEOBUF_ERROR;
+       return;
+}
+
+static enum hrtimer_restart mxc_vout_timer_handler(struct hrtimer *timer)
+{
+       struct mxc_vout_output *vout = container_of(timer,
+                                                   struct mxc_vout_output,
+                                                   timer);
+       struct videobuf_queue *q = &vout->vbq;
+       struct videobuf_buffer *vb;
+       unsigned long flags = 0;
+
+       spin_lock_irqsave(q->irqlock, flags);
+
+       /*
+        * put first queued entry into active, if previous entry did not
+        * finish, setup current entry's timer again.
+        */
+       if (list_empty(&vout->queue_list)) {
+               spin_unlock_irqrestore(q->irqlock, flags);
+               return HRTIMER_NORESTART;
+       }
+
+       /* move videobuf from queued list to active list */
+       vb = list_first_entry(&vout->queue_list,
+                       struct videobuf_buffer, queue);
+       list_del(&vb->queue);
+       list_add_tail(&vb->queue, &vout->active_list);
+
+       if (queue_work(vout->v4l_wq, &vout->disp_work) == 0) {
+               v4l2_warn(vout->vfd->v4l2_dev,
+               "disp work was in queue already, queue buf again next time\n");
+               list_del(&vb->queue);
+               list_add(&vb->queue, &vout->queue_list);
+               spin_unlock_irqrestore(q->irqlock, flags);
+               return HRTIMER_NORESTART;
+       }
+
+       vb->state = VIDEOBUF_ACTIVE;
+
+       spin_unlock_irqrestore(q->irqlock, flags);
+
+       return HRTIMER_NORESTART;
+}
+
+/* Video buffer call backs */
+
+/*
+ * Buffer setup function is called by videobuf layer when REQBUF ioctl is
+ * called. This is used to setup buffers and return size and count of
+ * buffers allocated. After the call to this buffer, videobuf layer will
+ * setup buffer queue depending on the size and count of buffers
+ */
+static int mxc_vout_buffer_setup(struct videobuf_queue *q, unsigned int *count,
+                         unsigned int *size)
+{
+       struct mxc_vout_output *vout = q->priv_data;
+       unsigned int frame_size;
+
+       if (!vout)
+               return -EINVAL;
+
+       if (V4L2_BUF_TYPE_VIDEO_OUTPUT != q->type)
+               return -EINVAL;
+
+       frame_size = get_frame_size(vout);
+       *size = PAGE_ALIGN(frame_size);
+
+       return 0;
+}
+
+/*
+ * This function will be called when VIDIOC_QBUF ioctl is called.
+ * It prepare buffers before give out for the display. This function
+ * converts user space virtual address into physical address if userptr memory
+ * exchange mechanism is used.
+ */
+static int mxc_vout_buffer_prepare(struct videobuf_queue *q,
+                           struct videobuf_buffer *vb,
+                           enum v4l2_field field)
+{
+       vb->state = VIDEOBUF_PREPARED;
+       return 0;
+}
+
+/*
+ * Buffer queue funtion will be called from the videobuf layer when _QBUF
+ * ioctl is called. It is used to enqueue buffer, which is ready to be
+ * displayed.
+ * This function is protected by q->irqlock.
+ */
+static void mxc_vout_buffer_queue(struct videobuf_queue *q,
+                         struct videobuf_buffer *vb)
+{
+       struct mxc_vout_output *vout = q->priv_data;
+       struct videobuf_buffer *active_vb;
+
+       list_add_tail(&vb->queue, &vout->queue_list);
+       vb->state = VIDEOBUF_QUEUED;
+
+       if (vout->timer_stop) {
+               if (deinterlace_3_field(vout) &&
+                       !list_empty(&vout->active_list)) {
+                       active_vb = list_first_entry(&vout->active_list,
+                                       struct videobuf_buffer, queue);
+                       setup_buf_timer(vout, active_vb);
+               } else {
+                       setup_buf_timer(vout, vb);
+               }
+               vout->timer_stop = false;
+       }
+}
+
+/*
+ * Buffer release function is called from videobuf layer to release buffer
+ * which are already allocated
+ */
+static void mxc_vout_buffer_release(struct videobuf_queue *q,
+                           struct videobuf_buffer *vb)
+{
+       vb->state = VIDEOBUF_NEEDS_INIT;
+}
+
+static int mxc_vout_mmap(struct file *file, struct vm_area_struct *vma)
+{
+       int ret;
+       struct mxc_vout_output *vout = file->private_data;
+
+       if (!vout)
+               return -ENODEV;
+
+       ret = videobuf_mmap_mapper(&vout->vbq, vma);
+       if (ret < 0)
+               v4l2_err(vout->vfd->v4l2_dev,
+                               "offset invalid [offset=0x%lx]\n",
+                               (vma->vm_pgoff << PAGE_SHIFT));
+
+       return ret;
+}
+
+static int mxc_vout_release(struct file *file)
+{
+       unsigned int ret = 0;
+       struct videobuf_queue *q;
+       struct mxc_vout_output *vout = file->private_data;
+
+       if (!vout)
+               return 0;
+
+       if (--vout->open_cnt == 0) {
+               q = &vout->vbq;
+               if (q->streaming)
+                       mxc_vidioc_streamoff(file, vout, vout->type);
+               else {
+                       release_disp_output(vout);
+                       videobuf_queue_cancel(q);
+               }
+               destroy_workqueue(vout->v4l_wq);
+               ret = videobuf_mmap_free(q);
+       }
+
+       return ret;
+}
+
+static int mxc_vout_open(struct file *file)
+{
+       struct mxc_vout_output *vout = NULL;
+       int ret = 0;
+
+       vout = video_drvdata(file);
+
+       if (vout == NULL)
+               return -ENODEV;
+
+       if (vout->open_cnt++ == 0) {
+               vout->ctrl_rotate = 0;
+               vout->ctrl_vflip = 0;
+               vout->ctrl_hflip = 0;
+               update_display_setting();
+               ret = update_setting_from_fbi(vout, vout->fbi);
+               if (ret < 0)
+                       goto err;
+
+               vout->v4l_wq = create_singlethread_workqueue("v4l2q");
+               if (!vout->v4l_wq) {
+                       v4l2_err(vout->vfd->v4l2_dev,
+                                       "Could not create work queue\n");
+                       ret = -ENOMEM;
+                       goto err;
+               }
+
+               INIT_WORK(&vout->disp_work, disp_work_func);
+
+               INIT_LIST_HEAD(&vout->queue_list);
+               INIT_LIST_HEAD(&vout->active_list);
+
+               vout->fmt_init = false;
+               vout->frame_count = 0;
+               vout->vdi_frame_cnt = 0;
+
+               vout->win_pos.x = 0;
+               vout->win_pos.y = 0;
+               vout->release = true;
+       }
+
+       file->private_data = vout;
+
+err:
+       return ret;
+}
+
+/*
+ * V4L2 ioctls
+ */
+static int mxc_vidioc_querycap(struct file *file, void *fh,
+               struct v4l2_capability *cap)
+{
+       struct mxc_vout_output *vout = fh;
+
+       strlcpy(cap->driver, VOUT_NAME, sizeof(cap->driver));
+       strlcpy(cap->card, vout->vfd->name, sizeof(cap->card));
+       cap->bus_info[0] = '\0';
+       cap->capabilities = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_OUTPUT;
+
+       return 0;
+}
+
+static int mxc_vidioc_enum_fmt_vid_out(struct file *file, void *fh,
+                       struct v4l2_fmtdesc *fmt)
+{
+       if (fmt->index >= NUM_MXC_VOUT_FORMATS)
+               return -EINVAL;
+
+       strlcpy(fmt->description, mxc_formats[fmt->index].description,
+                       sizeof(fmt->description));
+       fmt->pixelformat = mxc_formats[fmt->index].pixelformat;
+
+       return 0;
+}
+
+static int mxc_vidioc_g_fmt_vid_out(struct file *file, void *fh,
+                       struct v4l2_format *f)
+{
+       struct mxc_vout_output *vout = fh;
+       struct v4l2_rect rect;
+
+       f->fmt.pix.width = vout->task.input.width;
+       f->fmt.pix.height = vout->task.input.height;
+       f->fmt.pix.pixelformat = vout->task.input.format;
+       f->fmt.pix.sizeimage = get_frame_size(vout);
+
+       if (f->fmt.pix.priv) {
+               rect.left = vout->task.input.crop.pos.x;
+               rect.top = vout->task.input.crop.pos.y;
+               rect.width = vout->task.input.crop.w;
+               rect.height = vout->task.input.crop.h;
+               if (copy_to_user((void __user *)f->fmt.pix.priv,
+                               &rect, sizeof(rect)))
+                       return -EFAULT;
+       }
+       v4l2_dbg(1, debug, vout->vfd->v4l2_dev,
+                       "frame_size:0x%x, pix_fmt:0x%x\n",
+                       f->fmt.pix.sizeimage,
+                       vout->task.input.format);
+
+       return 0;
+}
+
+static inline int ipu_try_task(struct mxc_vout_output *vout)
+{
+       int ret;
+       struct ipu_task *task = &vout->task;
+
+again:
+       ret = ipu_check_task(task);
+       if (ret != IPU_CHECK_OK) {
+               if (ret > IPU_CHECK_ERR_MIN) {
+                       if (ret == IPU_CHECK_ERR_SPLIT_INPUTW_OVER) {
+                               task->input.crop.w -= 8;
+                               goto again;
+                       }
+                       if (ret == IPU_CHECK_ERR_SPLIT_INPUTH_OVER) {
+                               task->input.crop.h -= 8;
+                               goto again;
+                       }
+                       if (ret == IPU_CHECK_ERR_SPLIT_OUTPUTW_OVER) {
+                               if (vout->disp_support_windows) {
+                                       task->output.width -= 8;
+                                       task->output.crop.w =
+                                               task->output.width;
+                               } else
+                                       task->output.crop.w -= 8;
+                               goto again;
+                       }
+                       if (ret == IPU_CHECK_ERR_SPLIT_OUTPUTH_OVER) {
+                               if (vout->disp_support_windows) {
+                                       task->output.height -= 8;
+                                       task->output.crop.h =
+                                               task->output.height;
+                               } else
+                                       task->output.crop.h -= 8;
+                               goto again;
+                       }
+                       ret = -EINVAL;
+               }
+       } else
+               ret = 0;
+
+       return ret;
+}
+
+static inline int vdoaipu_try_task(struct mxc_vout_output *vout)
+{
+       int ret;
+       int is_1080p_stream;
+       size_t size;
+       struct ipu_task *ipu_task = &vout->task;
+       struct ipu_crop *icrop = &ipu_task->input.crop;
+       struct ipu_task *vdoa_task = &vout->vdoa_task;
+       u32 deinterlace = 0;
+       u32 in_fmt;
+
+       if (vout->task.input.deinterlace.enable)
+               deinterlace = 1;
+
+       memset(vdoa_task, 0, sizeof(*vdoa_task));
+       vdoa_task->output.format = IPU_PIX_FMT_NV12;
+       memcpy(&vdoa_task->input, &ipu_task->input,
+                       sizeof(ipu_task->input));
+       if ((icrop->w % IPU_PIX_FMT_TILED_NV12_MBALIGN) ||
+               (icrop->h % IPU_PIX_FMT_TILED_NV12_MBALIGN)) {
+               vdoa_task->input.crop.w =
+                       ALIGN(icrop->w, IPU_PIX_FMT_TILED_NV12_MBALIGN);
+               vdoa_task->input.crop.h =
+                       ALIGN(icrop->h, IPU_PIX_FMT_TILED_NV12_MBALIGN);
+       }
+       vdoa_task->output.width = vdoa_task->input.crop.w;
+       vdoa_task->output.height = vdoa_task->input.crop.h;
+       vdoa_task->output.crop.w = vdoa_task->input.crop.w;
+       vdoa_task->output.crop.h = vdoa_task->input.crop.h;
+
+       size = PAGE_ALIGN(vdoa_task->input.crop.w *
+                                       vdoa_task->input.crop.h *
+                                       fmt_to_bpp(vdoa_task->output.format)/8);
+       if (size > vout->vdoa_work.size) {
+               if (vout->vdoa_work.vaddr)
+                       free_dma_buf(vout, &vout->vdoa_work);
+               vout->vdoa_work.size = size;
+               ret = alloc_dma_buf(vout, &vout->vdoa_work);
+               if (ret < 0)
+                       return ret;
+       }
+       ret = ipu_check_task(vdoa_task);
+       if (ret != IPU_CHECK_OK)
+               return -EINVAL;
+
+       is_1080p_stream = CHECK_TILED_1080P_STREAM(vout);
+       if (is_1080p_stream)
+               ipu_task->input.crop.h = VALID_HEIGHT_1080P;
+       in_fmt = ipu_task->input.format;
+       ipu_task->input.format = vdoa_task->output.format;
+       ipu_task->input.height = vdoa_task->output.height;
+       ipu_task->input.width = vdoa_task->output.width;
+       if (deinterlace)
+               ipu_task->input.deinterlace.enable = 0;
+       ret = ipu_try_task(vout);
+       if (deinterlace)
+               ipu_task->input.deinterlace.enable = 1;
+       ipu_task->input.format = in_fmt;
+
+       return ret;
+}
+
+static int mxc_vout_try_task(struct mxc_vout_output *vout)
+{
+       int ret = 0;
+       struct ipu_output *output = &vout->task.output;
+       struct ipu_input *input = &vout->task.input;
+       struct ipu_crop *crop = &input->crop;
+       u32 o_height = 0;
+       u32 ocrop_h = 0;
+       bool tiled_fmt = false;
+       bool tiled_need_pp = false;
+
+       vout->vdoa_1080p = CHECK_TILED_1080P_DISPLAY(vout);
+       if (vout->vdoa_1080p) {
+               input->crop.h = FRAME_HEIGHT_1080P;
+               o_height = output->height;
+               ocrop_h = output->crop.h;
+               output->height = FRAME_HEIGHT_1080P;
+               output->crop.h = FRAME_HEIGHT_1080P;
+       }
+
+       if ((IPU_PIX_FMT_TILED_NV12 == input->format) ||
+               (IPU_PIX_FMT_TILED_NV12F == input->format)) {
+               if ((input->width % IPU_PIX_FMT_TILED_NV12_MBALIGN) ||
+                       (input->height % IPU_PIX_FMT_TILED_NV12_MBALIGN) ||
+                       (crop->pos.x % IPU_PIX_FMT_TILED_NV12_MBALIGN) ||
+                       (crop->pos.y % IPU_PIX_FMT_TILED_NV12_MBALIGN)) {
+                       v4l2_err(vout->vfd->v4l2_dev,
+                               "ERR: tiled fmt needs 16 pixel align.\n");
+                       return -EINVAL;
+               }
+               if ((crop->w % IPU_PIX_FMT_TILED_NV12_MBALIGN) ||
+                       (crop->h % IPU_PIX_FMT_TILED_NV12_MBALIGN))
+                       tiled_need_pp = true;
+       } else {
+               crop->w -= crop->w % 8;
+               crop->h -= crop->h % 8;
+       }
+       /* assume task.output already set by S_CROP */
+       vout->linear_bypass_pp = is_pp_bypass(vout);
+       if (vout->linear_bypass_pp) {
+               v4l2_info(vout->vfd->v4l2_dev, "Bypass IC.\n");
+               output->format = input->format;
+       } else {
+               /* if need CSC, choose IPU-DP or IPU_IC do it */
+               if (vout->disp_support_csc) {
+                       if (colorspaceofpixel(input->format) == YUV_CS)
+                               output->format = IPU_PIX_FMT_UYVY;
+                       else
+                               output->format = IPU_PIX_FMT_RGB565;
+               } else {
+                       if (colorspaceofpixel(vout->disp_fmt) == YUV_CS)
+                               output->format = IPU_PIX_FMT_UYVY;
+                       else
+                               output->format = IPU_PIX_FMT_RGB565;
+               }
+
+               vout->tiled_bypass_pp = false;
+               if ((IPU_PIX_FMT_TILED_NV12 == input->format) ||
+                       (IPU_PIX_FMT_TILED_NV12F == input->format)) {
+                       /* check resize/rotate/flip, or csc task */
+                       if (!(tiled_need_pp ||
+                               (IPU_ROTATE_NONE != output->rotate) ||
+                               (input->crop.w != output->crop.w) ||
+                               (input->crop.h != output->crop.h) ||
+                               (!vout->disp_support_csc &&
+                               (colorspaceofpixel(vout->disp_fmt) == RGB_CS)))
+                               ) {
+                               /* IC bypass */
+                               output->format = IPU_PIX_FMT_NV12;
+                               v4l2_dbg(1, debug, vout->vfd->v4l2_dev,
+                                               "tiled bypass pp\n");
+                               vout->tiled_bypass_pp = true;
+                       }
+                       tiled_fmt = true;
+               }
+
+               if ((!vout->tiled_bypass_pp) && tiled_fmt)
+                       ret = vdoaipu_try_task(vout);
+               else
+                       ret = ipu_try_task(vout);
+       }
+
+       if (vout->vdoa_1080p) {
+               output->height = o_height;
+               output->crop.h = ocrop_h;
+       }
+
+       v4l2_dbg(1, debug, vout->vfd->v4l2_dev,
+                       "icrop.w:%u, icrop.h:%u, iw:%u, ih:%u,"
+                       "ocrop.w:%u, ocrop.h:%u, ow:%u, oh:%u\n",
+                       input->crop.w, input->crop.h,
+                       input->width, input->height,
+                       output->crop.w, output->crop.h,
+                       output->width, output->height);
+       return ret;
+}
+
+static int mxc_vout_try_format(struct mxc_vout_output *vout,
+                               struct v4l2_format *f)
+{
+       int ret = 0;
+       struct v4l2_rect rect;
+
+       if ((f->fmt.pix.field != V4L2_FIELD_NONE) &&
+               (IPU_PIX_FMT_TILED_NV12 == vout->task.input.format)) {
+               v4l2_err(vout->vfd->v4l2_dev,
+                       "progressive tiled fmt should used V4L2_FIELD_NONE!\n");
+               return -EINVAL;
+       }
+
+       if (f->fmt.pix.priv && copy_from_user(&rect,
+               (void __user *)f->fmt.pix.priv, sizeof(rect)))
+               return -EFAULT;
+
+       vout->task.input.width = f->fmt.pix.width;
+       vout->task.input.height = f->fmt.pix.height;
+       vout->task.input.format = f->fmt.pix.pixelformat;
+
+       ret = set_field_fmt(vout, f->fmt.pix.field);
+       if (ret < 0)
+               return ret;
+
+       if (f->fmt.pix.priv) {
+               vout->task.input.crop.pos.x = rect.left;
+               vout->task.input.crop.pos.y = rect.top;
+               vout->task.input.crop.w = rect.width;
+               vout->task.input.crop.h = rect.height;
+       } else {
+               vout->task.input.crop.pos.x = 0;
+               vout->task.input.crop.pos.y = 0;
+               vout->task.input.crop.w = f->fmt.pix.width;
+               vout->task.input.crop.h = f->fmt.pix.height;
+       }
+       memcpy(&vout->in_rect, &vout->task.input.crop, sizeof(vout->in_rect));
+
+       ret = mxc_vout_try_task(vout);
+       if (!ret) {
+               if (f->fmt.pix.priv) {
+                       rect.width = vout->task.input.crop.w;
+                       rect.height = vout->task.input.crop.h;
+                       if (copy_to_user((void __user *)f->fmt.pix.priv,
+                               &rect, sizeof(rect)))
+                               ret = -EFAULT;
+               } else {
+                       f->fmt.pix.width = vout->task.input.crop.w;
+                       f->fmt.pix.height = vout->task.input.crop.h;
+               }
+       }
+
+       return ret;
+}
+
+static int mxc_vidioc_s_fmt_vid_out(struct file *file, void *fh,
+                       struct v4l2_format *f)
+{
+       struct mxc_vout_output *vout = fh;
+       int ret = 0;
+
+       if (vout->vbq.streaming)
+               return -EBUSY;
+
+       mutex_lock(&vout->task_lock);
+       ret = mxc_vout_try_format(vout, f);
+       if (ret >= 0)
+               vout->fmt_init = true;
+       mutex_unlock(&vout->task_lock);
+
+       return ret;
+}
+
+static int mxc_vidioc_cropcap(struct file *file, void *fh,
+               struct v4l2_cropcap *cropcap)
+{
+       struct mxc_vout_output *vout = fh;
+
+       if (cropcap->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
+               return -EINVAL;
+
+       cropcap->bounds = vout->crop_bounds;
+       cropcap->defrect = vout->crop_bounds;
+
+       return 0;
+}
+
+static int mxc_vidioc_g_crop(struct file *file, void *fh,
+                               struct v4l2_crop *crop)
+{
+       struct mxc_vout_output *vout = fh;
+
+       if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
+               return -EINVAL;
+
+       if (vout->disp_support_windows) {
+               crop->c.left = vout->win_pos.x;
+               crop->c.top = vout->win_pos.y;
+               crop->c.width = vout->task.output.width;
+               crop->c.height = vout->task.output.height;
+       } else {
+               if (vout->task.output.crop.w && vout->task.output.crop.h) {
+                       crop->c.left = vout->task.output.crop.pos.x;
+                       crop->c.top = vout->task.output.crop.pos.y;
+                       crop->c.width = vout->task.output.crop.w;
+                       crop->c.height = vout->task.output.crop.h;
+               } else {
+                       crop->c.left = 0;
+                       crop->c.top = 0;
+                       crop->c.width = vout->task.output.width;
+                       crop->c.height = vout->task.output.height;
+               }
+       }
+
+       return 0;
+}
+
+static int mxc_vidioc_s_crop(struct file *file, void *fh,
+                               const struct v4l2_crop *crop)
+{
+       struct mxc_vout_output *vout = fh;
+       struct v4l2_rect *b = &vout->crop_bounds;
+       struct v4l2_crop fix_up_crop;
+       int ret = 0;
+
+       memcpy(&fix_up_crop, crop, sizeof(*crop));
+
+       if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
+               return -EINVAL;
+
+       if (crop->c.width < 0 || crop->c.height < 0)
+               return -EINVAL;
+
+       if (crop->c.width == 0)
+               fix_up_crop.c.width = b->width - b->left;
+       if (crop->c.height == 0)
+               fix_up_crop.c.height = b->height - b->top;
+
+       if (crop->c.top < b->top)
+               fix_up_crop.c.top = b->top;
+       if (crop->c.top >= b->top + b->height)
+               fix_up_crop.c.top = b->top + b->height - 1;
+       if (crop->c.height > b->top - crop->c.top + b->height)
+               fix_up_crop.c.height =
+                       b->top - fix_up_crop.c.top + b->height;
+
+       if (crop->c.left < b->left)
+               fix_up_crop.c.left = b->left;
+       if (crop->c.left >= b->left + b->width)
+               fix_up_crop.c.left = b->left + b->width - 1;
+       if (crop->c.width > b->left - crop->c.left + b->width)
+               fix_up_crop.c.width =
+                       b->left - fix_up_crop.c.left + b->width;
+
+       /* stride line limitation */
+       fix_up_crop.c.height -= fix_up_crop.c.height % 8;
+       fix_up_crop.c.width -= fix_up_crop.c.width % 8;
+       if ((fix_up_crop.c.width <= 0) || (fix_up_crop.c.height <= 0) ||
+               ((fix_up_crop.c.left + fix_up_crop.c.width) >
+                (b->left + b->width)) ||
+               ((fix_up_crop.c.top + fix_up_crop.c.height) >
+                (b->top + b->height))) {
+               v4l2_err(vout->vfd->v4l2_dev, "s_crop err: %d, %d, %d, %d",
+                       fix_up_crop.c.left, fix_up_crop.c.top,
+                       fix_up_crop.c.width, fix_up_crop.c.height);
+               return -EINVAL;
+       }
+
+       /* the same setting, return */
+       if (vout->disp_support_windows) {
+               if ((vout->win_pos.x == fix_up_crop.c.left) &&
+                       (vout->win_pos.y == fix_up_crop.c.top) &&
+                       (vout->task.output.crop.w == fix_up_crop.c.width) &&
+                       (vout->task.output.crop.h == fix_up_crop.c.height))
+                       return 0;
+       } else {
+               if ((vout->task.output.crop.pos.x == fix_up_crop.c.left) &&
+                       (vout->task.output.crop.pos.y == fix_up_crop.c.top) &&
+                       (vout->task.output.crop.w == fix_up_crop.c.width) &&
+                       (vout->task.output.crop.h == fix_up_crop.c.height))
+                       return 0;
+       }
+
+       /* wait current work finish */
+       if (vout->vbq.streaming)
+               flush_workqueue(vout->v4l_wq);
+
+       mutex_lock(&vout->task_lock);
+
+       if (vout->disp_support_windows) {
+               vout->task.output.crop.pos.x = 0;
+               vout->task.output.crop.pos.y = 0;
+               vout->win_pos.x = fix_up_crop.c.left;
+               vout->win_pos.y = fix_up_crop.c.top;
+               vout->task.output.width = fix_up_crop.c.width;
+               vout->task.output.height = fix_up_crop.c.height;
+       } else {
+               vout->task.output.crop.pos.x = fix_up_crop.c.left;
+               vout->task.output.crop.pos.y = fix_up_crop.c.top;
+       }
+
+       vout->task.output.crop.w = fix_up_crop.c.width;
+       vout->task.output.crop.h = fix_up_crop.c.height;
+
+       /*
+        * must S_CROP before S_FMT, for fist time S_CROP, will not check
+        * ipu task, it will check in S_FMT, after S_FMT, S_CROP should
+        * check ipu task too.
+        */
+       if (vout->fmt_init) {
+               if (vout->vbq.streaming)
+                       release_disp_output(vout);
+
+               memcpy(&vout->task.input.crop, &vout->in_rect,
+                       sizeof(vout->in_rect));
+               ret = mxc_vout_try_task(vout);
+               if (ret < 0) {
+                       v4l2_err(vout->vfd->v4l2_dev,
+                                       "vout check task failed\n");
+                       goto done;
+               }
+               if (vout->vbq.streaming) {
+                       ret = config_disp_output(vout);
+                       if (ret < 0) {
+                               v4l2_err(vout->vfd->v4l2_dev,
+                                       "Config display output failed\n");
+                               goto done;
+                       }
+               }
+       }
+
+done:
+       mutex_unlock(&vout->task_lock);
+
+       return ret;
+}
+
+static int mxc_vidioc_queryctrl(struct file *file, void *fh,
+               struct v4l2_queryctrl *ctrl)
+{
+       int ret = 0;
+
+       switch (ctrl->id) {
+       case V4L2_CID_ROTATE:
+               ret = v4l2_ctrl_query_fill(ctrl, 0, 270, 90, 0);
+               break;
+       case V4L2_CID_VFLIP:
+               ret = v4l2_ctrl_query_fill(ctrl, 0, 1, 1, 0);
+               break;
+       case V4L2_CID_HFLIP:
+               ret = v4l2_ctrl_query_fill(ctrl, 0, 1, 1, 0);
+               break;
+       case V4L2_CID_MXC_MOTION:
+               ret = v4l2_ctrl_query_fill(ctrl, 0, 2, 1, 0);
+               break;
+       default:
+               ctrl->name[0] = '\0';
+               ret = -EINVAL;
+       }
+       return ret;
+}
+
+static int mxc_vidioc_g_ctrl(struct file *file, void *fh,
+                               struct v4l2_control *ctrl)
+{
+       int ret = 0;
+       struct mxc_vout_output *vout = fh;
+
+       switch (ctrl->id) {
+       case V4L2_CID_ROTATE:
+               ctrl->value = vout->ctrl_rotate;
+               break;
+       case V4L2_CID_VFLIP:
+               ctrl->value = vout->ctrl_vflip;
+               break;
+       case V4L2_CID_HFLIP:
+               ctrl->value = vout->ctrl_hflip;
+               break;
+       case V4L2_CID_MXC_MOTION:
+               if (vout->task.input.deinterlace.enable)
+                       ctrl->value = vout->task.input.deinterlace.motion;
+               else
+                       ctrl->value = 0;
+               break;
+       default:
+               ret = -EINVAL;
+       }
+       return ret;
+}
+
+static void setup_task_rotation(struct mxc_vout_output *vout)
+{
+       if (vout->ctrl_rotate == 0) {
+               if (vout->ctrl_vflip && vout->ctrl_hflip)
+                       vout->task.output.rotate = IPU_ROTATE_180;
+               else if (vout->ctrl_vflip)
+                       vout->task.output.rotate = IPU_ROTATE_VERT_FLIP;
+               else if (vout->ctrl_hflip)
+                       vout->task.output.rotate = IPU_ROTATE_HORIZ_FLIP;
+               else
+                       vout->task.output.rotate = IPU_ROTATE_NONE;
+       } else if (vout->ctrl_rotate == 90) {
+               if (vout->ctrl_vflip && vout->ctrl_hflip)
+                       vout->task.output.rotate = IPU_ROTATE_90_LEFT;
+               else if (vout->ctrl_vflip)
+                       vout->task.output.rotate = IPU_ROTATE_90_RIGHT_VFLIP;
+               else if (vout->ctrl_hflip)
+                       vout->task.output.rotate = IPU_ROTATE_90_RIGHT_HFLIP;
+               else
+                       vout->task.output.rotate = IPU_ROTATE_90_RIGHT;
+       } else if (vout->ctrl_rotate == 180) {
+               if (vout->ctrl_vflip && vout->ctrl_hflip)
+                       vout->task.output.rotate = IPU_ROTATE_NONE;
+               else if (vout->ctrl_vflip)
+                       vout->task.output.rotate = IPU_ROTATE_HORIZ_FLIP;
+               else if (vout->ctrl_hflip)
+                       vout->task.output.rotate = IPU_ROTATE_VERT_FLIP;
+               else
+                       vout->task.output.rotate = IPU_ROTATE_180;
+       } else if (vout->ctrl_rotate == 270) {
+               if (vout->ctrl_vflip && vout->ctrl_hflip)
+                       vout->task.output.rotate = IPU_ROTATE_90_RIGHT;
+               else if (vout->ctrl_vflip)
+                       vout->task.output.rotate = IPU_ROTATE_90_RIGHT_HFLIP;
+               else if (vout->ctrl_hflip)
+                       vout->task.output.rotate = IPU_ROTATE_90_RIGHT_VFLIP;
+               else
+                       vout->task.output.rotate = IPU_ROTATE_90_LEFT;
+       }
+}
+
+static int mxc_vidioc_s_ctrl(struct file *file, void *fh,
+                               struct v4l2_control *ctrl)
+{
+       int ret = 0;
+       struct mxc_vout_output *vout = fh;
+
+       /* wait current work finish */
+       if (vout->vbq.streaming)
+               flush_workqueue(vout->v4l_wq);
+
+       mutex_lock(&vout->task_lock);
+       switch (ctrl->id) {
+       case V4L2_CID_ROTATE:
+       {
+               vout->ctrl_rotate = (ctrl->value/90) * 90;
+               if (vout->ctrl_rotate > 270)
+                       vout->ctrl_rotate = 270;
+               setup_task_rotation(vout);
+               break;
+       }
+       case V4L2_CID_VFLIP:
+       {
+               vout->ctrl_vflip = ctrl->value;
+               setup_task_rotation(vout);
+               break;
+       }
+       case V4L2_CID_HFLIP:
+       {
+               vout->ctrl_hflip = ctrl->value;
+               setup_task_rotation(vout);
+               break;
+       }
+       case V4L2_CID_MXC_MOTION:
+       {
+               vout->task.input.deinterlace.motion = ctrl->value;
+               break;
+       }
+       default:
+               ret = -EINVAL;
+               goto done;
+       }
+
+       if (vout->fmt_init) {
+               if (vout->vbq.streaming)
+                       release_disp_output(vout);
+
+               memcpy(&vout->task.input.crop, &vout->in_rect,
+                               sizeof(vout->in_rect));
+               ret = mxc_vout_try_task(vout);
+               if (ret < 0) {
+                       v4l2_err(vout->vfd->v4l2_dev,
+                                       "vout check task failed\n");
+                       goto done;
+               }
+               if (vout->vbq.streaming) {
+                       ret = config_disp_output(vout);
+                       if (ret < 0) {
+                               v4l2_err(vout->vfd->v4l2_dev,
+                                       "Config display output failed\n");
+                               goto done;
+                       }
+               }
+       }
+
+done:
+       mutex_unlock(&vout->task_lock);
+
+       return ret;
+}
+
+static int mxc_vidioc_reqbufs(struct file *file, void *fh,
+                       struct v4l2_requestbuffers *req)
+{
+       int ret = 0;
+       struct mxc_vout_output *vout = fh;
+       struct videobuf_queue *q = &vout->vbq;
+
+       if (req->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
+               return -EINVAL;
+
+       /* should not be here after streaming, videobuf_reqbufs will control */
+       mutex_lock(&vout->task_lock);
+
+       ret = videobuf_reqbufs(q, req);
+
+       mutex_unlock(&vout->task_lock);
+       return ret;
+}
+
+static int mxc_vidioc_querybuf(struct file *file, void *fh,
+                       struct v4l2_buffer *b)
+{
+       int ret;
+       struct mxc_vout_output *vout = fh;
+
+       ret = videobuf_querybuf(&vout->vbq, b);
+       if (!ret) {
+               /* return physical address */
+               struct videobuf_buffer *vb = vout->vbq.bufs[b->index];
+               if (b->flags & V4L2_BUF_FLAG_MAPPED)
+                       b->m.offset = videobuf_to_dma_contig(vb);
+       }
+
+       return ret;
+}
+
+static int mxc_vidioc_qbuf(struct file *file, void *fh,
+                       struct v4l2_buffer *buffer)
+{
+       struct mxc_vout_output *vout = fh;
+
+       return videobuf_qbuf(&vout->vbq, buffer);
+}
+
+static int mxc_vidioc_dqbuf(struct file *file, void *fh, struct v4l2_buffer *b)
+{
+       struct mxc_vout_output *vout = fh;
+
+       if (!vout->vbq.streaming)
+               return -EINVAL;
+
+       if (file->f_flags & O_NONBLOCK)
+               return videobuf_dqbuf(&vout->vbq, (struct v4l2_buffer *)b, 1);
+       else
+               return videobuf_dqbuf(&vout->vbq, (struct v4l2_buffer *)b, 0);
+}
+
+static int set_window_position(struct mxc_vout_output *vout,
+                               struct mxcfb_pos *pos)
+{
+       struct fb_info *fbi = vout->fbi;
+       mm_segment_t old_fs;
+       int ret = 0;
+
+       if (vout->disp_support_windows) {
+               old_fs = get_fs();
+               set_fs(KERNEL_DS);
+               ret = fbi->fbops->fb_ioctl(fbi, MXCFB_SET_OVERLAY_POS,
+                               (unsigned long)pos);
+               set_fs(old_fs);
+       }
+
+       return ret;
+}
+
+static int config_disp_output(struct mxc_vout_output *vout)
+{
+       struct dma_mem *buf = NULL;
+       struct fb_info *fbi = vout->fbi;
+       struct fb_var_screeninfo var;
+       int i, fb_num, ret;
+       u32 fb_base;
+       u32 size;
+       u32 display_buf_size;
+       u32 *pixel = NULL;
+       u32 color;
+       int j;
+
+       memcpy(&var, &fbi->var, sizeof(var));
+       fb_base = fbi->fix.smem_start;
+
+       var.xres = vout->task.output.width;
+       var.yres = vout->task.output.height;
+       if (vout->linear_bypass_pp || vout->tiled_bypass_pp) {
+               fb_num = 1;
+               /* input crop */
+               if (vout->task.input.width > vout->task.output.width)
+                       var.xres_virtual = vout->task.input.width;
+               else
+                       var.xres_virtual = var.xres;
+               if (vout->task.input.height > vout->task.output.height)
+                       var.yres_virtual = vout->task.input.height;
+               else
+                       var.yres_virtual = var.yres;
+               var.rotate = vout->task.output.rotate;
+               var.vmode |= FB_VMODE_YWRAP;
+       } else {
+               fb_num = FB_BUFS;
+               var.xres_virtual = var.xres;
+               var.yres_virtual = fb_num * var.yres;
+               var.vmode &= ~FB_VMODE_YWRAP;
+       }
+       var.bits_per_pixel = fmt_to_bpp(vout->task.output.format);
+       var.nonstd = vout->task.output.format;
+
+       v4l2_dbg(1, debug, vout->vfd->v4l2_dev,
+                       "set display fb to %d %d\n",
+                       var.xres, var.yres);
+
+       ret = set_window_position(vout, &vout->win_pos);
+       if (ret < 0) {
+               v4l2_err(vout->vfd->v4l2_dev, "ERR: set_win_pos ret:%d\n", ret);
+               return ret;
+       }
+
+       /* Init display channel through fb API */
+       var.yoffset = 0;
+       var.activate |= FB_ACTIVATE_FORCE;
+       console_lock();
+       fbi->flags |= FBINFO_MISC_USEREVENT;
+       ret = fb_set_var(fbi, &var);
+       fbi->flags &= ~FBINFO_MISC_USEREVENT;
+       console_unlock();
+       if (ret < 0) {
+               v4l2_err(vout->vfd->v4l2_dev,
+                               "ERR:%s fb_set_var ret:%d\n", __func__, ret);
+               return ret;
+       }
+       if (vout->linear_bypass_pp || vout->tiled_bypass_pp)
+               display_buf_size = fbi->fix.line_length * fbi->var.yres_virtual;
+       else
+               display_buf_size = fbi->fix.line_length * fbi->var.yres;
+       for (i = 0; i < fb_num; i++)
+               vout->disp_bufs[i] = fbi->fix.smem_start + i * display_buf_size;
+       if (vout->tiled_bypass_pp) {
+               size = PAGE_ALIGN(vout->task.input.crop.w *
+                                       vout->task.input.crop.h *
+                                       fmt_to_bpp(vout->task.output.format)/8);
+               if (size > vout->vdoa_output[0].size) {
+                       for (i = 0; i < VDOA_FB_BUFS; i++) {
+                               buf = &vout->vdoa_output[i];
+                               if (buf->vaddr)
+                                       free_dma_buf(vout, buf);
+                               buf->size = size;
+                               ret = alloc_dma_buf(vout, buf);
+                               if (ret < 0)
+                                       goto err;
+                       }
+               }
+               for (i = fb_num; i < (fb_num + VDOA_FB_BUFS); i++)
+                       vout->disp_bufs[i] =
+                               vout->vdoa_output[i - fb_num].paddr;
+       }
+       vout->fb_smem_len = fbi->fix.smem_len;
+       vout->fb_smem_start = fbi->fix.smem_start;
+       if (fb_base != fbi->fix.smem_start) {
+               v4l2_dbg(1, debug, vout->vfd->v4l2_dev,
+                       "realloc fb mem size:0x%x@0x%lx,old paddr @0x%x\n",
+                       fbi->fix.smem_len, fbi->fix.smem_start, fb_base);
+       }
+
+       /* fill black when video config changed */
+       color = colorspaceofpixel(vout->task.output.format) == YUV_CS ?
+                       UYVY_BLACK : RGB_BLACK;
+       if (IS_PLANAR_PIXEL_FORMAT(vout->task.output.format)) {
+               size = display_buf_size * 8 /
+                       fmt_to_bpp(vout->task.output.format);
+               memset(fbi->screen_base, Y_BLACK, size);
+               memset(fbi->screen_base + size, UV_BLACK,
+                               display_buf_size - size);
+       } else {
+               pixel = (u32 *)fbi->screen_base;
+               for (i = 0; i < (display_buf_size >> 2); i++)
+                       *pixel++ = color;
+       }
+       console_lock();
+       fbi->flags |= FBINFO_MISC_USEREVENT;
+       ret = fb_blank(fbi, FB_BLANK_UNBLANK);
+       fbi->flags &= ~FBINFO_MISC_USEREVENT;
+       console_unlock();
+       vout->release = false;
+
+       return ret;
+err:
+       for (j = i - 1; j >= 0; j--) {
+               buf = &vout->vdoa_output[j];
+               if (buf->vaddr)
+                       free_dma_buf(vout, buf);
+       }
+       return ret;
+}
+
+static inline void wait_for_vsync(struct mxc_vout_output *vout)
+{
+       struct fb_info *fbi = vout->fbi;
+       mm_segment_t old_fs;
+
+       if (fbi->fbops->fb_ioctl) {
+               old_fs = get_fs();
+               set_fs(KERNEL_DS);
+               fbi->fbops->fb_ioctl(fbi, MXCFB_WAIT_FOR_VSYNC,
+                               (unsigned long)NULL);
+               set_fs(old_fs);
+       }
+
+       return;
+}
+
+static void release_disp_output(struct mxc_vout_output *vout)
+{
+       struct fb_info *fbi = vout->fbi;
+       struct mxcfb_pos pos;
+
+       if (vout->release)
+               return;
+       console_lock();
+       fbi->flags |= FBINFO_MISC_USEREVENT;
+       fb_blank(fbi, FB_BLANK_POWERDOWN);
+       fbi->flags &= ~FBINFO_MISC_USEREVENT;
+       console_unlock();
+
+       /* restore pos to 0,0 avoid fb pan display hang? */
+       pos.x = 0;
+       pos.y = 0;
+       set_window_position(vout, &pos);
+
+       if (get_ipu_channel(fbi) == MEM_BG_SYNC) {
+               console_lock();
+               fbi->fix.smem_start = vout->disp_bufs[0];
+               fbi->flags |= FBINFO_MISC_USEREVENT;
+               fb_blank(fbi, FB_BLANK_UNBLANK);
+               fbi->flags &= ~FBINFO_MISC_USEREVENT;
+               console_unlock();
+
+       }
+
+       vout->release = true;
+}
+
+static int mxc_vidioc_streamon(struct file *file, void *fh,
+                               enum v4l2_buf_type i)
+{
+       struct mxc_vout_output *vout = fh;
+       struct videobuf_queue *q = &vout->vbq;
+       int ret;
+
+       if (q->streaming) {
+               v4l2_err(vout->vfd->v4l2_dev,
+                               "video output already run\n");
+               ret = -EBUSY;
+               goto done;
+       }
+
+       if (deinterlace_3_field(vout) && list_is_singular(&q->stream)) {
+               v4l2_err(vout->vfd->v4l2_dev,
+                       "deinterlacing: need queue 2 frame before streamon\n");
+               ret = -EINVAL;
+               goto done;
+       }
+
+       ret = config_disp_output(vout);
+       if (ret < 0) {
+               v4l2_err(vout->vfd->v4l2_dev,
+                               "Config display output failed\n");
+               goto done;
+       }
+
+       hrtimer_init(&vout->timer, CLOCK_REALTIME, HRTIMER_MODE_ABS);
+       vout->timer.function = mxc_vout_timer_handler;
+       vout->timer_stop = true;
+
+       vout->start_ktime = hrtimer_cb_get_time(&vout->timer);
+
+       vout->pre1_vb = NULL;
+       vout->pre2_vb = NULL;
+
+       ret = videobuf_streamon(q);
+done:
+       return ret;
+}
+
+static int mxc_vidioc_streamoff(struct file *file, void *fh,
+                               enum v4l2_buf_type i)
+{
+       struct mxc_vout_output *vout = fh;
+       struct videobuf_queue *q = &vout->vbq;
+       int ret = 0;
+
+       if (q->streaming) {
+               flush_workqueue(vout->v4l_wq);
+
+               hrtimer_cancel(&vout->timer);
+
+               /*
+                * Wait for 2 vsyncs to make sure
+                * frames are drained on triple
+                * buffer.
+                */
+               wait_for_vsync(vout);
+               wait_for_vsync(vout);
+
+               release_disp_output(vout);
+
+               ret = videobuf_streamoff(&vout->vbq);
+       }
+       INIT_LIST_HEAD(&vout->queue_list);
+       INIT_LIST_HEAD(&vout->active_list);
+
+       return ret;
+}
+
+static const struct v4l2_ioctl_ops mxc_vout_ioctl_ops = {
+       .vidioc_querycap                        = mxc_vidioc_querycap,
+       .vidioc_enum_fmt_vid_out                = mxc_vidioc_enum_fmt_vid_out,
+       .vidioc_g_fmt_vid_out                   = mxc_vidioc_g_fmt_vid_out,
+       .vidioc_s_fmt_vid_out                   = mxc_vidioc_s_fmt_vid_out,
+       .vidioc_cropcap                         = mxc_vidioc_cropcap,
+       .vidioc_g_crop                          = mxc_vidioc_g_crop,
+       .vidioc_s_crop                          = mxc_vidioc_s_crop,
+       .vidioc_queryctrl                       = mxc_vidioc_queryctrl,
+       .vidioc_g_ctrl                          = mxc_vidioc_g_ctrl,
+       .vidioc_s_ctrl                          = mxc_vidioc_s_ctrl,
+       .vidioc_reqbufs                         = mxc_vidioc_reqbufs,
+       .vidioc_querybuf                        = mxc_vidioc_querybuf,
+       .vidioc_qbuf                            = mxc_vidioc_qbuf,
+       .vidioc_dqbuf                           = mxc_vidioc_dqbuf,
+       .vidioc_streamon                        = mxc_vidioc_streamon,
+       .vidioc_streamoff                       = mxc_vidioc_streamoff,
+};
+
+static const struct v4l2_file_operations mxc_vout_fops = {
+       .owner          = THIS_MODULE,
+       .unlocked_ioctl = video_ioctl2,
+       .mmap           = mxc_vout_mmap,
+       .open           = mxc_vout_open,
+       .release        = mxc_vout_release,
+};
+
+static struct video_device mxc_vout_template = {
+       .name           = "MXC Video Output",
+       .fops           = &mxc_vout_fops,
+       .ioctl_ops      = &mxc_vout_ioctl_ops,
+       .release        = video_device_release,
+};
+
+static struct videobuf_queue_ops mxc_vout_vbq_ops = {
+       .buf_setup = mxc_vout_buffer_setup,
+       .buf_prepare = mxc_vout_buffer_prepare,
+       .buf_release = mxc_vout_buffer_release,
+       .buf_queue = mxc_vout_buffer_queue,
+};
+
+static void mxc_vout_free_output(struct mxc_vout_dev *dev)
+{
+       int i;
+       int j;
+       struct mxc_vout_output *vout;
+       struct video_device *vfd;
+
+       for (i = 0; i < dev->out_num; i++) {
+               vout = dev->out[i];
+               vfd = vout->vfd;
+               if (vout->vdoa_work.vaddr)
+                       free_dma_buf(vout, &vout->vdoa_work);
+               for (j = 0; j < VDOA_FB_BUFS; j++) {
+                       if (vout->vdoa_output[j].vaddr)
+                               free_dma_buf(vout, &vout->vdoa_output[j]);
+               }
+               if (vfd) {
+                       if (!video_is_registered(vfd))
+                               video_device_release(vfd);
+                       else
+                               video_unregister_device(vfd);
+               }
+               kfree(vout);
+       }
+}
+
+static int mxc_vout_setup_output(struct mxc_vout_dev *dev)
+{
+       struct videobuf_queue *q;
+       struct fb_info *fbi;
+       struct mxc_vout_output *vout;
+       int i, ret = 0;
+
+       update_display_setting();
+
+       /* all output/overlay based on fb */
+       for (i = 0; i < num_registered_fb; i++) {
+               fbi = registered_fb[i];
+
+               vout = kzalloc(sizeof(struct mxc_vout_output), GFP_KERNEL);
+               if (!vout) {
+                       ret = -ENOMEM;
+                       break;
+               }
+
+               dev->out[dev->out_num] = vout;
+               dev->out_num++;
+
+               vout->fbi = fbi;
+               vout->type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
+               vout->vfd = video_device_alloc();
+               if (!vout->vfd) {
+                       ret = -ENOMEM;
+                       break;
+               }
+
+               *vout->vfd = mxc_vout_template;
+               vout->vfd->debug = debug;
+               vout->vfd->v4l2_dev = &dev->v4l2_dev;
+               vout->vfd->lock = &vout->mutex;
+               vout->vfd->vfl_dir = VFL_DIR_TX;
+
+               mutex_init(&vout->mutex);
+               mutex_init(&vout->task_lock);
+
+               strlcpy(vout->vfd->name, fbi->fix.id, sizeof(vout->vfd->name));
+
+               video_set_drvdata(vout->vfd, vout);
+
+               if (video_register_device(vout->vfd,
+                       VFL_TYPE_GRABBER, video_nr + i) < 0) {
+                       ret = -ENODEV;
+                       break;
+               }
+
+               q = &vout->vbq;
+               q->dev = dev->dev;
+               spin_lock_init(&vout->vbq_lock);
+               videobuf_queue_dma_contig_init(q, &mxc_vout_vbq_ops, q->dev,
+                               &vout->vbq_lock, vout->type, V4L2_FIELD_NONE,
+                               sizeof(struct videobuf_buffer), vout, NULL);
+
+               v4l2_info(vout->vfd->v4l2_dev, "V4L2 device registered as %s\n",
+                               video_device_node_name(vout->vfd));
+
+       }
+
+       return ret;
+}
+
+static int mxc_vout_probe(struct platform_device *pdev)
+{
+       int ret;
+       struct mxc_vout_dev *dev;
+
+       dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+       if (!dev)
+               return -ENOMEM;
+
+       dev->dev = &pdev->dev;
+       dev->dev->dma_mask = kmalloc(sizeof(*dev->dev->dma_mask), GFP_KERNEL);
+       *dev->dev->dma_mask = DMA_BIT_MASK(32);
+       dev->dev->coherent_dma_mask = DMA_BIT_MASK(32);
+
+       ret = v4l2_device_register(dev->dev, &dev->v4l2_dev);
+       if (ret) {
+               dev_err(dev->dev, "v4l2_device_register failed\n");
+               goto free_dev;
+       }
+
+       ret = mxc_vout_setup_output(dev);
+       if (ret < 0)
+               goto rel_vdev;
+
+       return 0;
+
+rel_vdev:
+       mxc_vout_free_output(dev);
+       v4l2_device_unregister(&dev->v4l2_dev);
+free_dev:
+       kfree(dev);
+       return ret;
+}
+
+static int mxc_vout_remove(struct platform_device *pdev)
+{
+       struct v4l2_device *v4l2_dev = platform_get_drvdata(pdev);
+       struct mxc_vout_dev *dev = container_of(v4l2_dev, struct
+                       mxc_vout_dev, v4l2_dev);
+
+       mxc_vout_free_output(dev);
+       v4l2_device_unregister(v4l2_dev);
+       kfree(dev);
+       return 0;
+}
+
+static const struct of_device_id mxc_v4l2_dt_ids[] = {
+       { .compatible = "fsl,mxc_v4l2_output", },
+       { /* sentinel */ }
+};
+
+static struct platform_driver mxc_vout_driver = {
+       .driver = {
+               .name = "mxc_v4l2_output",
+               .of_match_table = mxc_v4l2_dt_ids,
+       },
+       .probe = mxc_vout_probe,
+       .remove = mxc_vout_remove,
+};
+
+static int __init mxc_vout_init(void)
+{
+       if (platform_driver_register(&mxc_vout_driver) != 0) {
+               printk(KERN_ERR VOUT_NAME ":Could not register Video driver\n");
+               return -EINVAL;
+       }
+       return 0;
+}
+
+static void mxc_vout_cleanup(void)
+{
+       platform_driver_unregister(&mxc_vout_driver);
+}
+
+module_init(mxc_vout_init);
+module_exit(mxc_vout_cleanup);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("V4L2-driver for MXC video output");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mxc_v4l2.h b/include/linux/mxc_v4l2.h
new file mode 100644 (file)
index 0000000..e05bd04
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2004-2013 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU Lesser General
+ * Public License.  You may obtain a copy of the GNU Lesser General
+ * Public License Version 2.1 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/lgpl-license.html
+ * http://www.gnu.org/copyleft/lgpl.html
+ */
+
+/*!
+ * @file linux/mxc_v4l2.h
+ *
+ * @brief MXC V4L2 private header file
+ *
+ * @ingroup MXC V4L2
+ */
+
+#ifndef __LINUX_MXC_V4L2_H__
+#define __LINUX_MXC_V4L2_H__
+
+#include <uapi/linux/mxc_v4l2.h>
+
+#endif
diff --git a/include/uapi/linux/mxc_v4l2.h b/include/uapi/linux/mxc_v4l2.h
new file mode 100644 (file)
index 0000000..49345fe
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2013 Freescale Semiconductor, Inc. All Rights Reserved
+ */
+
+/*
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*!
+ * @file uapi/linux/mxc_v4l2.h
+ *
+ * @brief MXC V4L2 private header file
+ *
+ * @ingroup MXC V4L2
+ */
+
+#ifndef __ASM_ARCH_MXC_V4L2_H__
+#define __ASM_ARCH_MXC_V4L2_H__
+
+/*
+ * For IPUv1 and IPUv3, V4L2_CID_MXC_ROT means encoder ioctl ID.
+ * And V4L2_CID_MXC_VF_ROT is viewfinder ioctl ID only for IPUv1 and IPUv3.
+ */
+#define V4L2_CID_MXC_ROT               (V4L2_CID_PRIVATE_BASE + 0)
+#define V4L2_CID_MXC_FLASH             (V4L2_CID_PRIVATE_BASE + 1)
+#define V4L2_CID_MXC_VF_ROT            (V4L2_CID_PRIVATE_BASE + 2)
+#define V4L2_CID_MXC_MOTION            (V4L2_CID_PRIVATE_BASE + 3)
+#define V4L2_CID_MXC_SWITCH_CAM                (V4L2_CID_PRIVATE_BASE + 6)
+
+#define V4L2_MXC_ROTATE_NONE                   0
+#define V4L2_MXC_ROTATE_VERT_FLIP              1
+#define V4L2_MXC_ROTATE_HORIZ_FLIP             2
+#define V4L2_MXC_ROTATE_180                    3
+#define V4L2_MXC_ROTATE_90_RIGHT               4
+#define V4L2_MXC_ROTATE_90_RIGHT_VFLIP         5
+#define V4L2_MXC_ROTATE_90_RIGHT_HFLIP         6
+#define V4L2_MXC_ROTATE_90_LEFT                        7
+
+struct v4l2_mxc_offset {
+       uint32_t u_offset;
+       uint32_t v_offset;
+};
+
+#endif