summaryrefslogtreecommitdiff
path: root/drivers/media/video/mxc/output
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/media/video/mxc/output')
-rw-r--r--drivers/media/video/mxc/output/Kconfig28
-rw-r--r--drivers/media/video/mxc/output/Makefile14
-rw-r--r--drivers/media/video/mxc/output/mx31_v4l2_wvga_output.c1928
-rw-r--r--drivers/media/video/mxc/output/mxc_pxp_v4l2.c1237
-rw-r--r--drivers/media/video/mxc/output/mxc_pxp_v4l2.h82
-rw-r--r--drivers/media/video/mxc/output/mxc_v4l2_output.c2793
-rw-r--r--drivers/media/video/mxc/output/mxc_v4l2_output.h157
7 files changed, 6239 insertions, 0 deletions
diff --git a/drivers/media/video/mxc/output/Kconfig b/drivers/media/video/mxc/output/Kconfig
new file mode 100644
index 000000000000..2153ad248907
--- /dev/null
+++ b/drivers/media/video/mxc/output/Kconfig
@@ -0,0 +1,28 @@
+config VIDEO_MXC_IPU_OUTPUT
+ bool "IPU v4l2 support"
+ depends on VIDEO_MXC_OUTPUT && MXC_IPU
+ default y
+ ---help---
+ This is the video4linux2 driver for IPU post processing video output.
+
+config VIDEO_MXC_IPUV1_WVGA_OUTPUT
+ bool "IPUv1 WVGA v4l2 display support"
+ depends on VIDEO_MXC_OUTPUT && MXC_IPU
+ default n
+ ---help---
+ This is the video4linux2 driver for IPUv1 WVGA post processing video output.
+
+config VIDEO_MXC_EMMA_OUTPUT
+ bool
+ depends on VIDEO_MXC_OUTPUT && MXC_EMMA && FB_MXC_SYNC_PANEL
+ default y
+ ---help---
+ This is the video4linux2 driver for EMMA post processing video output.
+
+config VIDEO_MXC_OUTPUT_FBSYNC
+ bool "Synchronize the output with LCDC refresh"
+ depends on VIDEO_MXC_EMMA_OUTPUT
+ default y
+ ---help---
+ Synchronize the post-processing with LCDC EOF (End of Frame) to
+ prevent tearing issue. If unsure, say Y.
diff --git a/drivers/media/video/mxc/output/Makefile b/drivers/media/video/mxc/output/Makefile
new file mode 100644
index 000000000000..500442544902
--- /dev/null
+++ b/drivers/media/video/mxc/output/Makefile
@@ -0,0 +1,14 @@
+ifeq ($(CONFIG_VIDEO_MXC_EMMA_OUTPUT),y)
+ mx27_output-objs := mx27_v4l2_output.o mx27_pp.o
+ obj-$(CONFIG_VIDEO_MXC_OUTPUT) += mx27_output.o
+endif
+
+ifeq ($(CONFIG_VIDEO_MXC_IPU_OUTPUT),y)
+ obj-$(CONFIG_VIDEO_MXC_OUTPUT) += mxc_v4l2_output.o
+endif
+ifeq ($(CONFIG_VIDEO_MXC_PXP_V4L2),y)
+ obj-$(CONFIG_VIDEO_MXC_PXP_V4L2) += mxc_pxp_v4l2.o
+endif
+ifeq ($(CONFIG_VIDEO_MXC_IPUV1_WVGA_OUTPUT),y)
+ obj-$(CONFIG_VIDEO_MXC_OUTPUT) += mx31_v4l2_wvga_output.o
+endif
diff --git a/drivers/media/video/mxc/output/mx31_v4l2_wvga_output.c b/drivers/media/video/mxc/output/mx31_v4l2_wvga_output.c
new file mode 100644
index 000000000000..fe4346ee56dd
--- /dev/null
+++ b/drivers/media/video/mxc/output/mx31_v4l2_wvga_output.c
@@ -0,0 +1,1928 @@
+/*
+ * Copyright 2005-2010 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
+ */
+
+/*!
+ * @file drivers/media/video/mxc/output/mxc_v4l2_output.c
+ *
+ * @brief MXC V4L2 Video Output Driver
+ *
+ * Video4Linux2 Output Device using MXC IPU Post-processing functionality.
+ *
+ * @ingroup MXC_V4L2_OUTPUT
+ */
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/console.h>
+#include <linux/fs.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <asm/cacheflush.h>
+#include <asm/io.h>
+#include <asm/semaphore.h>
+#include <linux/dma-mapping.h>
+
+#include <mach/mxcfb.h>
+#include <mach/ipu.h>
+
+#include "mxc_v4l2_output.h"
+
+vout_data *g_vout;
+#define SDC_FG_FB_FORMAT IPU_PIX_FMT_RGB565
+
+struct v4l2_output mxc_outputs[2] = {
+ {
+ .index = MXC_V4L2_OUT_2_SDC,
+ .name = "DISP3 Video Out",
+ .type = V4L2_OUTPUT_TYPE_ANALOG, /* not really correct,
+ but no other choice */
+ .audioset = 0,
+ .modulator = 0,
+ .std = V4L2_STD_UNKNOWN},
+ {
+ .index = MXC_V4L2_OUT_2_ADC,
+ .name = "DISPx Video Out",
+ .type = V4L2_OUTPUT_TYPE_ANALOG, /* not really correct,
+ but no other choice */
+ .audioset = 0,
+ .modulator = 0,
+ .std = V4L2_STD_UNKNOWN}
+};
+
+static int video_nr = 16;
+static DEFINE_SPINLOCK(g_lock);
+static unsigned int g_pp_out_number;
+static unsigned int g_pp_in_number;
+
+/* debug counters */
+uint32_t g_irq_cnt;
+uint32_t g_buf_output_cnt;
+uint32_t g_buf_q_cnt;
+uint32_t g_buf_dq_cnt;
+
+static inline uint32_t channel_2_dma(ipu_channel_t ch, ipu_buffer_t type)
+{
+ return ((type == IPU_INPUT_BUFFER) ? ((uint32_t) ch & 0xFF) :
+ ((type == IPU_OUTPUT_BUFFER) ? (((uint32_t) ch >> 8) & 0xFF)
+ : (((uint32_t) ch >> 16) & 0xFF)));
+};
+
+static inline uint32_t DMAParamAddr(uint32_t dma_ch)
+{
+ return 0x10000 | (dma_ch << 4);
+};
+
+#define QUEUE_SIZE (MAX_FRAME_NUM + 1)
+static inline int queue_size(v4l_queue *q)
+{
+ if (q->tail >= q->head)
+ return q->tail - q->head;
+ else
+ return (q->tail + QUEUE_SIZE) - q->head;
+}
+
+static inline int queue_buf(v4l_queue *q, int idx)
+{
+ if (((q->tail + 1) % QUEUE_SIZE) == q->head)
+ return -1; /* queue full */
+ q->list[q->tail] = idx;
+ q->tail = (q->tail + 1) % QUEUE_SIZE;
+ return 0;
+}
+
+static inline int dequeue_buf(v4l_queue *q)
+{
+ int ret;
+ if (q->tail == q->head)
+ return -1; /* queue empty */
+ ret = q->list[q->head];
+ q->head = (q->head + 1) % QUEUE_SIZE;
+ return ret;
+}
+
+static inline int peek_next_buf(v4l_queue *q)
+{
+ if (q->tail == q->head)
+ return -1; /* queue empty */
+ return q->list[q->head];
+}
+
+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);
+}
+
+/*!
+ * Private function to free buffers
+ *
+ * @param bufs_paddr Array of physical address of buffers to be freed
+ *
+ * @param bufs_vaddr Array of virtual address of buffers to be freed
+ *
+ * @param num_buf Number of buffers to be freed
+ *
+ * @param size Size for each buffer to be free
+ *
+ * @return status 0 success.
+ */
+static int mxc_free_buffers(dma_addr_t bufs_paddr[], void *bufs_vaddr[],
+ int num_buf, int size)
+{
+ int i;
+
+ for (i = 0; i < num_buf; i++) {
+ if (bufs_vaddr[i] != 0) {
+ dma_free_coherent(0, size, bufs_vaddr[i],
+ bufs_paddr[i]);
+ pr_debug("freed @ paddr=0x%08X\n", (u32) bufs_paddr[i]);
+ bufs_paddr[i] = 0;
+ bufs_vaddr[i] = NULL;
+ }
+ }
+ return 0;
+}
+
+/*!
+ * Private function to allocate buffers
+ *
+ * @param bufs_paddr Output array of physical address of buffers allocated
+ *
+ * @param bufs_vaddr Output array of virtual address of buffers allocated
+ *
+ * @param num_buf Input number of buffers to allocate
+ *
+ * @param size Input size for each buffer to allocate
+ *
+ * @return status -0 Successfully allocated a buffer, -ENOBUFS failed.
+ */
+static int mxc_allocate_buffers(dma_addr_t bufs_paddr[], void *bufs_vaddr[],
+ int num_buf, int size)
+{
+ int i;
+
+ for (i = 0; i < num_buf; i++) {
+ bufs_vaddr[i] = dma_alloc_coherent(0, size,
+ &bufs_paddr[i],
+ GFP_DMA | GFP_KERNEL);
+
+ if (bufs_vaddr[i] == 0) {
+ mxc_free_buffers(bufs_paddr, bufs_vaddr, i, size);
+ printk(KERN_ERR "dma_alloc_coherent failed.\n");
+ return -ENOBUFS;
+ }
+ pr_debug("allocated @ paddr=0x%08X, size=%d.\n",
+ (u32) bufs_paddr[i], size);
+ }
+
+ return 0;
+}
+
+/*
+ * Returns bits per pixel for given pixel format
+ *
+ * @param pixelformat V4L2_PIX_FMT_RGB565,
+ * V4L2_PIX_FMT_BGR24 or V4L2_PIX_FMT_BGR32
+ *
+ * @return bits per pixel of pixelformat
+ */
+static u32 fmt_to_bpp(u32 pixelformat)
+{
+ u32 bpp;
+
+ switch (pixelformat) {
+ case V4L2_PIX_FMT_RGB565:
+ bpp = 16;
+ break;
+ case V4L2_PIX_FMT_BGR24:
+ case V4L2_PIX_FMT_RGB24:
+ bpp = 24;
+ break;
+ case V4L2_PIX_FMT_BGR32:
+ case V4L2_PIX_FMT_RGB32:
+ bpp = 32;
+ break;
+ default:
+ bpp = 8;
+ break;
+ }
+ return bpp;
+}
+
+static u32 bpp_to_fmt(struct fb_info *fbi)
+{
+ if (fbi->var.nonstd)
+ return fbi->var.nonstd;
+
+ if (fbi->var.bits_per_pixel == 24)
+ return V4L2_PIX_FMT_BGR24;
+ else if (fbi->var.bits_per_pixel == 32)
+ return V4L2_PIX_FMT_BGR32;
+ else if (fbi->var.bits_per_pixel == 16)
+ return V4L2_PIX_FMT_RGB565;
+
+ return 0;
+}
+
+static void mxc_v4l2out_timer_handler(unsigned long arg)
+{
+ int index;
+ unsigned long timeout;
+ unsigned long lock_flags = 0;
+ vout_data *vout = (vout_data *) arg;
+
+ dev_dbg(vout->video_dev->dev, "timer handler: %lu\n", jiffies);
+
+ spin_lock_irqsave(&g_lock, lock_flags);
+
+ if ((vout->state == STATE_STREAM_STOPPING)
+ || (vout->state == STATE_STREAM_OFF))
+ goto exit0;
+ /*
+ * If timer occurs before IPU h/w is ready, then set the state to
+ * paused and the timer will be set again when next buffer is queued
+ * or PP comletes
+ */
+ if (vout->ipu_buf[0] != -1) {
+ dev_dbg(vout->video_dev->dev, "IPU buffer busy\n");
+ vout->state = STATE_STREAM_PAUSED;
+ goto exit0;
+ }
+
+ /* One frame buffer should be ready here */
+ if (vout->frame_count % 2 == 1) {
+ /* set BUF0 rdy */
+ if (ipu_select_buffer(vout->display_ch, IPU_INPUT_BUFFER, 0) <
+ 0)
+ pr_debug("error selecting display buf 0");
+ } else {
+ if (ipu_select_buffer(vout->display_ch, IPU_INPUT_BUFFER, 1) <
+ 0)
+ pr_debug("error selecting display buf 1");
+ }
+
+ /* Dequeue buffer and pass to IPU */
+ index = dequeue_buf(&vout->ready_q);
+ if (index == -1) { /* no buffers ready, should never occur */
+ dev_err(vout->video_dev->dev,
+ "mxc_v4l2out: timer - no queued buffers ready\n");
+ goto exit0;
+ }
+
+ g_buf_dq_cnt++;
+ vout->frame_count++;
+ vout->ipu_buf[1] = vout->ipu_buf[0] = index;
+
+ if (ipu_update_channel_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER,
+ 0,
+ vout->v4l2_bufs[vout->ipu_buf[0]].m.
+ offset) < 0)
+ goto exit0;
+
+ if (ipu_update_channel_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER,
+ 1,
+ vout->v4l2_bufs[vout->ipu_buf[0]].m.
+ offset + vout->v2f.fmt.pix.width / 2) < 0)
+ goto exit0;
+
+ /* All buffer should now ready in IPU out, tranfer to display buf */
+ if (ipu_update_channel_buffer(vout->post_proc_ch, IPU_OUTPUT_BUFFER,
+ 0,
+ vout->
+ display_bufs[(vout->frame_count -
+ 1) % 2]) < 0) {
+ dev_err(vout->video_dev->dev,
+ "unable to update buffer %d address\n",
+ vout->next_rdy_ipu_buf);
+ goto exit0;
+ }
+ if (ipu_update_channel_buffer(vout->post_proc_ch, IPU_OUTPUT_BUFFER,
+ 1,
+ vout->
+ display_bufs[(vout->frame_count -
+ 1) % 2] +
+ vout->crop_current.width / 2 *
+ bytes_per_pixel(SDC_FG_FB_FORMAT)) < 0) {
+ dev_err(vout->video_dev->dev,
+ "unable to update buffer %d address\n",
+ vout->next_rdy_ipu_buf);
+ goto exit0;
+ }
+
+ if (ipu_select_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER, 0) < 0) {
+ dev_err(vout->video_dev->dev,
+ "unable to set IPU buffer ready\n");
+ goto exit0;
+ }
+
+ if (ipu_select_buffer(vout->post_proc_ch, IPU_OUTPUT_BUFFER, 0) < 0) {
+ dev_err(vout->video_dev->dev,
+ "unable to set IPU buffer ready\n");
+ goto exit0;
+ }
+
+ /* Setup timer for next buffer */
+ index = peek_next_buf(&vout->ready_q);
+ if (index != -1) {
+ /* if timestamp is 0, then default to 30fps */
+ if ((vout->v4l2_bufs[index].timestamp.tv_sec == 0)
+ && (vout->v4l2_bufs[index].timestamp.tv_usec == 0))
+ timeout =
+ vout->start_jiffies + vout->frame_count * HZ / 30;
+ else
+ timeout =
+ get_jiffies(&vout->v4l2_bufs[index].timestamp);
+
+ if (jiffies >= timeout) {
+ dev_dbg(vout->video_dev->dev,
+ "warning: timer timeout already expired.\n");
+ }
+ if (mod_timer(&vout->output_timer, timeout))
+ dev_dbg(vout->video_dev->dev,
+ "warning: timer was already set\n");
+
+ dev_dbg(vout->video_dev->dev,
+ "timer handler next schedule: %lu\n", timeout);
+ } else {
+ vout->state = STATE_STREAM_PAUSED;
+ }
+
+exit0:
+ spin_unlock_irqrestore(&g_lock, lock_flags);
+}
+
+extern void _ipu_write_param_mem(uint32_t addr, uint32_t *data,
+ uint32_t numWords);
+
+static irqreturn_t mxc_v4l2out_pp_in_irq_handler(int irq, void *dev_id)
+{
+ unsigned long lock_flags = 0;
+ vout_data *vout = dev_id;
+ uint32_t u_offset;
+ uint32_t v_offset;
+ uint32_t local_params[4];
+ uint32_t width, height;
+ uint32_t dma_chan;
+
+ spin_lock_irqsave(&g_lock, lock_flags);
+ g_irq_cnt++;
+
+ dma_chan = channel_2_dma(vout->post_proc_ch, IPU_INPUT_BUFFER);
+ memset(&local_params, 0, sizeof(local_params));
+
+ if (g_pp_in_number % 2 == 1) {
+ u_offset = vout->offset.u_offset - vout->v2f.fmt.pix.width / 4;
+ v_offset = vout->offset.v_offset - vout->v2f.fmt.pix.width / 4;
+ width = vout->v2f.fmt.pix.width / 2;
+ height = vout->v2f.fmt.pix.height;
+ local_params[3] =
+ (uint32_t) ((width - 1) << 12) | ((uint32_t) (height -
+ 1) << 24);
+ local_params[1] = (1UL << (46 - 32)) | (u_offset << (53 - 32));
+ local_params[2] = u_offset >> (64 - 53);
+ local_params[2] |= v_offset << (79 - 64);
+ local_params[3] |= v_offset >> (96 - 79);
+ _ipu_write_param_mem(DMAParamAddr(dma_chan), local_params, 4);
+
+ if (ipu_select_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER, 1) <
+ 0) {
+ dev_err(vout->video_dev->dev,
+ "unable to set IPU buffer ready\n");
+ }
+ } else {
+ u_offset = vout->offset.u_offset;
+ v_offset = vout->offset.v_offset;
+ width = vout->v2f.fmt.pix.width / 2;
+ height = vout->v2f.fmt.pix.height;
+ local_params[3] =
+ (uint32_t) ((width - 1) << 12) | ((uint32_t) (height -
+ 1) << 24);
+ local_params[1] = (1UL << (46 - 32)) | (u_offset << (53 - 32));
+ local_params[2] = u_offset >> (64 - 53);
+ local_params[2] |= v_offset << (79 - 64);
+ local_params[3] |= v_offset >> (96 - 79);
+ _ipu_write_param_mem(DMAParamAddr(dma_chan), local_params, 4);
+ }
+ g_pp_in_number++;
+
+ spin_unlock_irqrestore(&g_lock, lock_flags);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t mxc_v4l2out_pp_out_irq_handler(int irq, void *dev_id)
+{
+ vout_data *vout = dev_id;
+ int index;
+ unsigned long timeout;
+ u32 lock_flags = 0;
+
+ spin_lock_irqsave(&g_lock, lock_flags);
+
+ if (g_pp_out_number % 2 == 1) {
+ if (ipu_select_buffer(vout->post_proc_ch, IPU_OUTPUT_BUFFER, 1)
+ < 0) {
+ dev_err(vout->video_dev->dev,
+ "unable to set IPU buffer ready\n");
+ }
+ } else {
+ if (vout->ipu_buf[0] != -1) {
+ vout->v4l2_bufs[vout->ipu_buf[0]].flags =
+ V4L2_BUF_FLAG_DONE;
+ queue_buf(&vout->done_q, vout->ipu_buf[0]);
+ wake_up_interruptible(&vout->v4l_bufq);
+ vout->ipu_buf[0] = -1;
+ }
+ index = peek_next_buf(&vout->ready_q);
+ if (vout->state == STATE_STREAM_STOPPING) {
+ if ((vout->ipu_buf[0] == -1)
+ && (vout->ipu_buf[1] == -1))
+ vout->state = STATE_STREAM_OFF;
+ } else if ((vout->state == STATE_STREAM_PAUSED)
+ && (index != -1)) {
+ /*!
+ * Setup timer for next buffer,
+ * when stream has been paused
+ */
+ pr_debug("next index %d\n", index);
+
+ /* if timestamp is 0, then default to 30fps */
+ if ((vout->v4l2_bufs[index].timestamp.tv_sec == 0)
+ && (vout->v4l2_bufs[index].timestamp.tv_usec == 0))
+ timeout =
+ vout->start_jiffies +
+ vout->frame_count * HZ / 30;
+ else
+ timeout =
+ get_jiffies(&vout->v4l2_bufs[index].
+ timestamp);
+
+ if (jiffies >= timeout) {
+ pr_debug
+ ("warning: timer timeout"
+ "already expired.\n");
+ }
+
+ vout->state = STATE_STREAM_ON;
+
+ if (mod_timer(&vout->output_timer, timeout))
+ pr_debug("warning: timer was already set\n");
+
+ pr_debug("timer handler next schedule: %lu\n", timeout);
+ }
+ }
+ g_pp_out_number++;
+
+ spin_unlock_irqrestore(&g_lock, lock_flags);
+ return IRQ_HANDLED;
+}
+
+/*!
+ * Start the output stream
+ *
+ * @param vout structure vout_data *
+ *
+ * @return status 0 Success
+ */
+static int mxc_v4l2out_streamon(vout_data *vout)
+{
+ struct device *dev = vout->video_dev->dev;
+ ipu_channel_params_t params;
+ struct mxcfb_pos fb_pos;
+ struct fb_var_screeninfo fbvar;
+ struct fb_info *fbi =
+ registered_fb[vout->output_fb_num[vout->cur_disp_output]];
+ int pp_in_buf[2];
+ u16 out_width;
+ u16 out_height;
+ ipu_channel_t display_input_ch = MEM_PP_MEM;
+ bool use_direct_adc = false;
+ mm_segment_t old_fs;
+
+ if (!vout)
+ return -EINVAL;
+
+ if (vout->state != STATE_STREAM_OFF)
+ return -EBUSY;
+
+ if (queue_size(&vout->ready_q) < 2) {
+ dev_err(dev, "2 buffers not been queued yet!\n");
+ return -EINVAL;
+ }
+
+ out_width = vout->crop_current.width;
+ out_height = vout->crop_current.height;
+
+ vout->next_done_ipu_buf = vout->next_rdy_ipu_buf = 0;
+ vout->ipu_buf[0] = pp_in_buf[0] = dequeue_buf(&vout->ready_q);
+ vout->ipu_buf[1] = pp_in_buf[1] = vout->ipu_buf[0];
+ vout->frame_count = 1;
+ g_pp_out_number = 1;
+ g_pp_in_number = 1;
+
+ ipu_enable_irq(IPU_IRQ_PP_IN_EOF);
+ ipu_enable_irq(IPU_IRQ_PP_OUT_EOF);
+
+ /* Init Display Channel */
+#ifdef CONFIG_FB_MXC_ASYNC_PANEL
+ if (vout->cur_disp_output < DISP3) {
+ mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_OFF, 0);
+ fbi = NULL;
+ if (ipu_can_rotate_in_place(vout->rotate)) {
+ dev_dbg(dev, "Using PP direct to ADC channel\n");
+ use_direct_adc = true;
+ vout->display_ch = MEM_PP_ADC;
+ vout->post_proc_ch = MEM_PP_ADC;
+
+ memset(&params, 0, sizeof(params));
+ params.mem_pp_adc.in_width = vout->v2f.fmt.pix.width;
+ params.mem_pp_adc.in_height = vout->v2f.fmt.pix.height;
+ params.mem_pp_adc.in_pixel_fmt =
+ vout->v2f.fmt.pix.pixelformat;
+ params.mem_pp_adc.out_width = out_width;
+ params.mem_pp_adc.out_height = out_height;
+ params.mem_pp_adc.out_pixel_fmt = SDC_FG_FB_FORMAT;
+#ifdef CONFIG_FB_MXC_EPSON_PANEL
+ params.mem_pp_adc.out_left =
+ 2 + vout->crop_current.left;
+#else
+ params.mem_pp_adc.out_left =
+ 12 + vout->crop_current.left;
+#endif
+ params.mem_pp_adc.out_top = vout->crop_current.top;
+ if (ipu_init_channel(
+ vout->post_proc_ch, &params) != 0) {
+ dev_err(dev, "Error initializing PP chan\n");
+ return -EINVAL;
+ }
+
+ if (ipu_init_channel_buffer(vout->post_proc_ch,
+ IPU_INPUT_BUFFER,
+ params.mem_pp_adc.
+ in_pixel_fmt,
+ params.mem_pp_adc.in_width,
+ params.mem_pp_adc.in_height,
+ vout->v2f.fmt.pix.
+ bytesperline /
+ bytes_per_pixel(params.
+ mem_pp_adc.
+ in_pixel_fmt),
+ vout->rotate,
+ vout->
+ v4l2_bufs[pp_in_buf[0]].m.
+ offset,
+ vout->
+ v4l2_bufs[pp_in_buf[1]].m.
+ offset,
+ vout->offset.u_offset,
+ vout->offset.v_offset) !=
+ 0) {
+ dev_err(dev, "Error initializing PP in buf\n");
+ return -EINVAL;
+ }
+
+ if (ipu_init_channel_buffer(vout->post_proc_ch,
+ IPU_OUTPUT_BUFFER,
+ params.mem_pp_adc.
+ out_pixel_fmt, out_width,
+ out_height, out_width,
+ vout->rotate, 0, 0, 0,
+ 0) != 0) {
+ dev_err(dev,
+ "Error initializing PP"
+ "output buffer\n");
+ return -EINVAL;
+ }
+
+ } else {
+ dev_dbg(dev, "Using ADC SYS2 channel\n");
+ vout->display_ch = ADC_SYS2;
+ vout->post_proc_ch = MEM_PP_MEM;
+
+ if (vout->display_bufs[0]) {
+ mxc_free_buffers(vout->display_bufs,
+ vout->display_bufs_vaddr,
+ 2, vout->display_buf_size);
+ }
+
+ vout->display_buf_size = vout->crop_current.width *
+ vout->crop_current.height *
+ fmt_to_bpp(SDC_FG_FB_FORMAT) / 8;
+ mxc_allocate_buffers(vout->display_bufs,
+ vout->display_bufs_vaddr,
+ 2, vout->display_buf_size);
+
+ memset(&params, 0, sizeof(params));
+ params.adc_sys2.disp = vout->cur_disp_output;
+ params.adc_sys2.ch_mode = WriteTemplateNonSeq;
+#ifdef CONFIG_FB_MXC_EPSON_PANEL
+ params.adc_sys2.out_left = 2 + vout->crop_current.left;
+#else
+ params.adc_sys2.out_left = 12 + vout->crop_current.left;
+#endif
+ params.adc_sys2.out_top = vout->crop_current.top;
+ if (ipu_init_channel(ADC_SYS2, &params) < 0)
+ return -EINVAL;
+
+ if (ipu_init_channel_buffer(vout->display_ch,
+ IPU_INPUT_BUFFER,
+ SDC_FG_FB_FORMAT,
+ out_width, out_height,
+ out_width, IPU_ROTATE_NONE,
+ vout->display_bufs[0],
+ vout->display_bufs[1], 0,
+ 0) != 0) {
+ dev_err(dev,
+ "Error initializing SDC FG buffer\n");
+ return -EINVAL;
+ }
+ }
+ } else
+#endif
+ { /* Use SDC */
+ dev_dbg(dev, "Using SDC channel\n");
+
+ fbvar = fbi->var;
+ if (vout->cur_disp_output == 3) {
+ vout->display_ch = MEM_FG_SYNC;
+ fbvar.bits_per_pixel = 16;
+ fbvar.nonstd = IPU_PIX_FMT_UYVY;
+
+ fbvar.xres = fbvar.xres_virtual = out_width;
+ fbvar.yres = out_height;
+ fbvar.yres_virtual = out_height * 2;
+ } else if (vout->cur_disp_output == 5) {
+ vout->display_ch = MEM_DC_SYNC;
+ fbvar.bits_per_pixel = 16;
+ fbvar.nonstd = IPU_PIX_FMT_UYVY;
+
+ fbvar.xres = fbvar.xres_virtual = out_width;
+ fbvar.yres = out_height;
+ fbvar.yres_virtual = out_height * 2;
+ } else {
+ vout->display_ch = MEM_BG_SYNC;
+ }
+
+ fbvar.activate |= FB_ACTIVATE_FORCE;
+ fb_set_var(fbi, &fbvar);
+
+ fb_pos.x = vout->crop_current.left;
+ fb_pos.y = vout->crop_current.top;
+ if (fbi->fbops->fb_ioctl) {
+ old_fs = get_fs();
+ set_fs(KERNEL_DS);
+ fbi->fbops->fb_ioctl(fbi, MXCFB_SET_OVERLAY_POS,
+ (unsigned long)&fb_pos);
+ set_fs(old_fs);
+ }
+
+ vout->display_bufs[1] = fbi->fix.smem_start;
+ vout->display_bufs[0] = fbi->fix.smem_start +
+ (fbi->fix.line_length * fbi->var.yres);
+ vout->display_buf_size = vout->crop_current.width *
+ vout->crop_current.height * fbi->var.bits_per_pixel / 8;
+
+ vout->post_proc_ch = MEM_PP_MEM;
+ }
+
+ /* Init PP */
+ if (use_direct_adc == false) {
+ if (vout->rotate >= IPU_ROTATE_90_RIGHT) {
+ out_width = vout->crop_current.height;
+ out_height = vout->crop_current.width;
+ }
+ memset(&params, 0, sizeof(params));
+ params.mem_pp_mem.in_width = vout->v2f.fmt.pix.width / 2;
+ params.mem_pp_mem.in_height = vout->v2f.fmt.pix.height;
+ params.mem_pp_mem.in_pixel_fmt = vout->v2f.fmt.pix.pixelformat;
+ params.mem_pp_mem.out_width = out_width / 2;
+ params.mem_pp_mem.out_height = out_height;
+ if (vout->display_ch == ADC_SYS2)
+ params.mem_pp_mem.out_pixel_fmt = SDC_FG_FB_FORMAT;
+ else
+ params.mem_pp_mem.out_pixel_fmt = bpp_to_fmt(fbi);
+ if (ipu_init_channel(vout->post_proc_ch, &params) != 0) {
+ dev_err(dev, "Error initializing PP channel\n");
+ return -EINVAL;
+ }
+
+ if (ipu_init_channel_buffer(vout->post_proc_ch,
+ IPU_INPUT_BUFFER,
+ params.mem_pp_mem.in_pixel_fmt,
+ params.mem_pp_mem.in_width,
+ params.mem_pp_mem.in_height,
+ vout->v2f.fmt.pix.bytesperline /
+ bytes_per_pixel(params.mem_pp_mem.
+ in_pixel_fmt),
+ IPU_ROTATE_NONE,
+ vout->v4l2_bufs[pp_in_buf[0]].m.
+ offset,
+ vout->v4l2_bufs[pp_in_buf[0]].m.
+ offset + params.mem_pp_mem.in_width,
+ vout->offset.u_offset,
+ vout->offset.v_offset) != 0) {
+ dev_err(dev, "Error initializing PP input buffer\n");
+ return -EINVAL;
+ }
+
+ if (!ipu_can_rotate_in_place(vout->rotate)) {
+ if (vout->rot_pp_bufs[0]) {
+ mxc_free_buffers(vout->rot_pp_bufs,
+ vout->rot_pp_bufs_vaddr, 2,
+ vout->display_buf_size);
+ }
+ if (mxc_allocate_buffers
+ (vout->rot_pp_bufs, vout->rot_pp_bufs_vaddr, 2,
+ vout->display_buf_size) < 0)
+ return -ENOBUFS;
+
+ if (ipu_init_channel_buffer(vout->post_proc_ch,
+ IPU_OUTPUT_BUFFER,
+ params.mem_pp_mem.
+ out_pixel_fmt, out_width,
+ out_height, out_width,
+ IPU_ROTATE_NONE,
+ vout->rot_pp_bufs[0],
+ vout->rot_pp_bufs[1], 0,
+ 0) != 0) {
+ dev_err(dev,
+ "Error initializing"
+ "PP output buffer\n");
+ return -EINVAL;
+ }
+
+ if (ipu_init_channel(MEM_ROT_PP_MEM, NULL) != 0) {
+ dev_err(dev,
+ "Error initializing PP ROT channel\n");
+ return -EINVAL;
+ }
+
+ if (ipu_init_channel_buffer(MEM_ROT_PP_MEM,
+ IPU_INPUT_BUFFER,
+ params.mem_pp_mem.
+ out_pixel_fmt, out_width,
+ out_height, out_width,
+ vout->rotate,
+ vout->rot_pp_bufs[0],
+ vout->rot_pp_bufs[1], 0,
+ 0) != 0) {
+ dev_err(dev,
+ "Error initializing PP ROT"
+ "input buffer\n");
+ return -EINVAL;
+ }
+
+ /* swap width and height */
+ if (vout->rotate >= IPU_ROTATE_90_RIGHT) {
+ out_width = vout->crop_current.width;
+ out_height = vout->crop_current.height;
+ }
+
+ if (ipu_init_channel_buffer(MEM_ROT_PP_MEM,
+ IPU_OUTPUT_BUFFER,
+ params.mem_pp_mem.
+ out_pixel_fmt, out_width,
+ out_height, out_width,
+ IPU_ROTATE_NONE,
+ vout->display_bufs[0],
+ vout->display_bufs[1], 0,
+ 0) != 0) {
+ dev_err(dev,
+ "Error initializing PP"
+ "output buffer\n");
+ return -EINVAL;
+ }
+
+ if (ipu_link_channels(vout->post_proc_ch,
+ MEM_ROT_PP_MEM) < 0)
+ return -EINVAL;
+
+ ipu_select_buffer(MEM_ROT_PP_MEM, IPU_OUTPUT_BUFFER, 0);
+ ipu_select_buffer(MEM_ROT_PP_MEM, IPU_OUTPUT_BUFFER, 1);
+
+ ipu_enable_channel(MEM_ROT_PP_MEM);
+
+ display_input_ch = MEM_ROT_PP_MEM;
+ } else {
+ if (ipu_init_channel_buffer(vout->post_proc_ch,
+ IPU_OUTPUT_BUFFER,
+ params.mem_pp_mem.
+ out_pixel_fmt,
+ out_width / 2,
+ out_height,
+ out_width,
+ vout->rotate,
+ vout->display_bufs[0],
+ vout->display_bufs[0]
+ +
+ out_width / 2 *
+ bytes_per_pixel
+ (SDC_FG_FB_FORMAT), 0,
+ 0) != 0) {
+ dev_err(dev,
+ "Error initializing PP"
+ "output buffer\n");
+ return -EINVAL;
+ }
+ }
+ if (ipu_unlink_channels(
+ display_input_ch, vout->display_ch) < 0) {
+ dev_err(dev, "Error linking ipu channels\n");
+ return -EINVAL;
+ }
+ }
+
+ vout->state = STATE_STREAM_PAUSED;
+
+ ipu_select_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER, 0);
+
+ if (use_direct_adc == false) {
+ ipu_select_buffer(vout->post_proc_ch, IPU_OUTPUT_BUFFER, 0);
+ ipu_enable_channel(vout->post_proc_ch);
+
+ if (fbi) {
+ acquire_console_sem();
+ fb_blank(fbi, FB_BLANK_UNBLANK);
+ release_console_sem();
+ } else {
+ ipu_enable_channel(vout->display_ch);
+ }
+ } else {
+ ipu_enable_channel(vout->post_proc_ch);
+ }
+
+ vout->start_jiffies = jiffies;
+ dev_dbg(dev,
+ "streamon: start time = %lu jiffies\n", vout->start_jiffies);
+
+ return 0;
+}
+
+/*!
+ * Shut down the voutera
+ *
+ * @param vout structure vout_data *
+ *
+ * @return status 0 Success
+ */
+static int mxc_v4l2out_streamoff(vout_data *vout)
+{
+ struct fb_info *fbi =
+ registered_fb[vout->output_fb_num[vout->cur_disp_output]];
+ int i, retval = 0;
+ unsigned long lockflag = 0;
+
+ if (!vout)
+ return -EINVAL;
+
+ if (vout->state == STATE_STREAM_OFF)
+ return 0;
+
+ spin_lock_irqsave(&g_lock, lockflag);
+
+ del_timer(&vout->output_timer);
+
+ if (vout->state == STATE_STREAM_ON)
+ vout->state = STATE_STREAM_STOPPING;
+
+ ipu_disable_irq(IPU_IRQ_PP_IN_EOF);
+ ipu_disable_irq(IPU_IRQ_PP_OUT_EOF);
+
+ spin_unlock_irqrestore(&g_lock, lockflag);
+
+ if (vout->post_proc_ch == MEM_PP_MEM) { /* SDC or ADC with Rotation */
+ if (!ipu_can_rotate_in_place(vout->rotate)) {
+ ipu_unlink_channels(MEM_PP_MEM, MEM_ROT_PP_MEM);
+ ipu_unlink_channels(MEM_ROT_PP_MEM, vout->display_ch);
+ ipu_disable_channel(MEM_ROT_PP_MEM, true);
+
+ if (vout->rot_pp_bufs[0]) {
+ mxc_free_buffers(vout->rot_pp_bufs,
+ vout->rot_pp_bufs_vaddr, 2,
+ vout->display_buf_size);
+ }
+ } else {
+ ipu_unlink_channels(MEM_PP_MEM, vout->display_ch);
+ }
+ ipu_disable_channel(MEM_PP_MEM, true);
+
+ if (vout->display_ch == ADC_SYS2) {
+ ipu_disable_channel(vout->display_ch, true);
+ ipu_uninit_channel(vout->display_ch);
+ } else {
+ fbi->var.activate |= FB_ACTIVATE_FORCE;
+ fb_set_var(fbi, &fbi->var);
+
+ if (vout->display_ch == MEM_FG_SYNC) {
+ acquire_console_sem();
+ fb_blank(fbi, FB_BLANK_POWERDOWN);
+ release_console_sem();
+ }
+
+ vout->display_bufs[0] = 0;
+ vout->display_bufs[1] = 0;
+ }
+
+ ipu_uninit_channel(MEM_PP_MEM);
+ if (!ipu_can_rotate_in_place(vout->rotate))
+ ipu_uninit_channel(MEM_ROT_PP_MEM);
+ } else { /* ADC Direct */
+ ipu_disable_channel(MEM_PP_ADC, true);
+ ipu_uninit_channel(MEM_PP_ADC);
+ }
+ vout->ready_q.head = vout->ready_q.tail = 0;
+ vout->done_q.head = vout->done_q.tail = 0;
+ for (i = 0; i < vout->buffer_cnt; i++) {
+ vout->v4l2_bufs[i].flags = 0;
+ vout->v4l2_bufs[i].timestamp.tv_sec = 0;
+ vout->v4l2_bufs[i].timestamp.tv_usec = 0;
+ }
+
+ vout->state = STATE_STREAM_OFF;
+
+#ifdef CONFIG_FB_MXC_ASYNC_PANEL
+ if (vout->cur_disp_output < DISP3) {
+ if (vout->display_bufs[0] != 0) {
+ mxc_free_buffers(vout->display_bufs,
+ vout->display_bufs_vaddr, 2,
+ vout->display_buf_size);
+ }
+
+ mxcfb_set_refresh_mode(registered_fb
+ [vout->
+ output_fb_num[vout->cur_disp_output]],
+ MXCFB_REFRESH_PARTIAL, 0);
+ }
+#endif
+
+ return retval;
+}
+
+/*
+ * Valid whether the palette is supported
+ *
+ * @param palette V4L2_PIX_FMT_RGB565, V4L2_PIX_FMT_BGR24 or V4L2_PIX_FMT_BGR32
+ *
+ * @return 1 if supported, 0 if failed
+ */
+static inline int valid_mode(u32 palette)
+{
+ return ((palette == V4L2_PIX_FMT_RGB565) ||
+ (palette == V4L2_PIX_FMT_BGR24) ||
+ (palette == V4L2_PIX_FMT_RGB24) ||
+ (palette == V4L2_PIX_FMT_BGR32) ||
+ (palette == V4L2_PIX_FMT_RGB32) ||
+ (palette == V4L2_PIX_FMT_NV12) ||
+ (palette == V4L2_PIX_FMT_YUV422P) ||
+ (palette == V4L2_PIX_FMT_YUV420));
+}
+
+/*
+ * V4L2 - Handles VIDIOC_G_FMT Ioctl
+ *
+ * @param vout structure vout_data *
+ *
+ * @param v4l2_format structure v4l2_format *
+ *
+ * @return status 0 success, EINVAL failed
+ */
+static int mxc_v4l2out_g_fmt(vout_data *vout, struct v4l2_format *f)
+{
+ if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
+ return -EINVAL;
+ *f = vout->v2f;
+ return 0;
+}
+
+/*
+ * V4L2 - Handles VIDIOC_S_FMT Ioctl
+ *
+ * @param vout structure vout_data *
+ *
+ * @param v4l2_format structure v4l2_format *
+ *
+ * @return status 0 success, EINVAL failed
+ */
+static int mxc_v4l2out_s_fmt(vout_data *vout, struct v4l2_format *f)
+{
+ int retval = 0;
+ u32 size = 0;
+ u32 bytesperline;
+
+ if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) {
+ retval = -EINVAL;
+ goto err0;
+ }
+ if (!valid_mode(f->fmt.pix.pixelformat)) {
+ dev_err(vout->video_dev->dev, "pixel format not supported\n");
+ retval = -EINVAL;
+ goto err0;
+ }
+
+ bytesperline = (f->fmt.pix.width * fmt_to_bpp(f->fmt.pix.pixelformat)) /
+ 8;
+ if (f->fmt.pix.bytesperline < bytesperline) {
+ f->fmt.pix.bytesperline = bytesperline;
+ } else {
+ bytesperline = f->fmt.pix.bytesperline;
+ }
+
+ switch (f->fmt.pix.pixelformat) {
+ case V4L2_PIX_FMT_YUV422P:
+ /* byteperline for YUV planar formats is for
+ Y plane only */
+ size = bytesperline * f->fmt.pix.height * 2;
+ break;
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_NV12:
+ size = (bytesperline * f->fmt.pix.height * 3) / 2;
+ break;
+ default:
+ size = bytesperline * f->fmt.pix.height;
+ break;
+ }
+
+ /* Return the actual size of the image to the app */
+ if (f->fmt.pix.sizeimage < size)
+ f->fmt.pix.sizeimage = size;
+ else
+ size = f->fmt.pix.sizeimage;
+
+ vout->v2f.fmt.pix = f->fmt.pix;
+ if (vout->v2f.fmt.pix.priv != 0) {
+ if (copy_from_user(&vout->offset,
+ (void *)vout->v2f.fmt.pix.priv,
+ sizeof(vout->offset))) {
+ retval = -EFAULT;
+ goto err0;
+ }
+ } else {
+ vout->offset.u_offset = 0;
+ vout->offset.v_offset = 0;
+ }
+
+ retval = 0;
+err0:
+ return retval;
+}
+
+/*
+ * V4L2 - Handles VIDIOC_G_CTRL Ioctl
+ *
+ * @param vout structure vout_data *
+ *
+ * @param c structure v4l2_control *
+ *
+ * @return status 0 success, EINVAL failed
+ */
+static int mxc_get_v42lout_control(vout_data *vout, struct v4l2_control *c)
+{
+ switch (c->id) {
+ case V4L2_CID_HFLIP:
+ return (vout->rotate & IPU_ROTATE_HORIZ_FLIP) ? 1 : 0;
+ case V4L2_CID_VFLIP:
+ return (vout->rotate & IPU_ROTATE_VERT_FLIP) ? 1 : 0;
+ case (V4L2_CID_PRIVATE_BASE + 1):
+ return vout->rotate;
+ default:
+ return -EINVAL;
+ }
+}
+
+/*
+ * V4L2 - Handles VIDIOC_S_CTRL Ioctl
+ *
+ * @param vout structure vout_data *
+ *
+ * @param c structure v4l2_control *
+ *
+ * @return status 0 success, EINVAL failed
+ */
+static int mxc_set_v42lout_control(vout_data *vout, struct v4l2_control *c)
+{
+ switch (c->id) {
+ case V4L2_CID_HFLIP:
+ vout->rotate |= c->value ? IPU_ROTATE_HORIZ_FLIP :
+ IPU_ROTATE_NONE;
+ break;
+ case V4L2_CID_VFLIP:
+ vout->rotate |= c->value ? IPU_ROTATE_VERT_FLIP :
+ IPU_ROTATE_NONE;
+ break;
+ case V4L2_CID_MXC_ROT:
+ vout->rotate = c->value;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/*!
+ * V4L2 interface - open function
+ *
+ * @param inode structure inode *
+ *
+ * @param file structure file *
+ *
+ * @return status 0 success, ENODEV invalid device instance,
+ * ENODEV timeout, ERESTARTSYS interrupted by user
+ */
+static int mxc_v4l2out_open(struct inode *inode, struct file *file)
+{
+ struct video_device *dev = video_devdata(file);
+ vout_data *vout = video_get_drvdata(dev);
+ int err;
+
+ if (!vout)
+ return -ENODEV;
+
+ down(&vout->busy_lock);
+
+ err = -EINTR;
+ if (signal_pending(current))
+ goto oops;
+
+ if (vout->open_count++ == 0) {
+ ipu_request_irq(IPU_IRQ_PP_IN_EOF,
+ mxc_v4l2out_pp_in_irq_handler,
+ 0, dev->name, vout);
+ ipu_request_irq(IPU_IRQ_PP_OUT_EOF,
+ mxc_v4l2out_pp_out_irq_handler,
+ 0, dev->name, vout);
+
+ init_waitqueue_head(&vout->v4l_bufq);
+
+ init_timer(&vout->output_timer);
+ vout->output_timer.function = mxc_v4l2out_timer_handler;
+ vout->output_timer.data = (unsigned long)vout;
+
+ vout->state = STATE_STREAM_OFF;
+ vout->rotate = IPU_ROTATE_NONE;
+ g_irq_cnt = g_buf_output_cnt = g_buf_q_cnt = g_buf_dq_cnt = 0;
+
+ }
+
+ file->private_data = dev;
+
+ up(&vout->busy_lock);
+
+ return 0;
+
+oops:
+ up(&vout->busy_lock);
+ return err;
+}
+
+/*!
+ * V4L2 interface - close function
+ *
+ * @param inode struct inode *
+ *
+ * @param file struct file *
+ *
+ * @return 0 success
+ */
+static int mxc_v4l2out_close(struct inode *inode, struct file *file)
+{
+ struct video_device *dev = video_devdata(file);
+ vout_data *vout = video_get_drvdata(dev);
+
+ if (--vout->open_count == 0) {
+ if (vout->state != STATE_STREAM_OFF)
+ mxc_v4l2out_streamoff(vout);
+
+ ipu_free_irq(IPU_IRQ_PP_IN_EOF, vout);
+ ipu_free_irq(IPU_IRQ_PP_OUT_EOF, vout);
+
+ file->private_data = NULL;
+
+ mxc_free_buffers(vout->queue_buf_paddr, vout->queue_buf_vaddr,
+ vout->buffer_cnt, vout->queue_buf_size);
+ vout->buffer_cnt = 0;
+ mxc_free_buffers(vout->rot_pp_bufs, vout->rot_pp_bufs_vaddr, 2,
+ vout->display_buf_size);
+
+ /* capture off */
+ wake_up_interruptible(&vout->v4l_bufq);
+ }
+
+ return 0;
+}
+
+/*!
+ * V4L2 interface - ioctl function
+ *
+ * @param inode struct inode *
+ *
+ * @param file struct file *
+ *
+ * @param ioctlnr unsigned int
+ *
+ * @param arg void *
+ *
+ * @return 0 success, ENODEV for invalid device instance,
+ * -1 for other errors.
+ */
+static int
+mxc_v4l2out_do_ioctl(struct inode *inode, struct file *file,
+ unsigned int ioctlnr, void *arg)
+{
+ struct video_device *vdev = file->private_data;
+ vout_data *vout = video_get_drvdata(vdev);
+ int retval = 0;
+ int i = 0;
+
+ if (!vout)
+ return -EBADF;
+
+ /* make this _really_ smp-safe */
+ if (down_interruptible(&vout->busy_lock))
+ return -EBUSY;
+
+ switch (ioctlnr) {
+ case VIDIOC_QUERYCAP:
+ {
+ struct v4l2_capability *cap = arg;
+ strcpy(cap->driver, "mxc_v4l2_output");
+ cap->version = 0;
+ cap->capabilities =
+ V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING;
+ cap->card[0] = '\0';
+ cap->bus_info[0] = '\0';
+ retval = 0;
+ break;
+ }
+ case VIDIOC_G_FMT:
+ {
+ struct v4l2_format *gf = arg;
+ retval = mxc_v4l2out_g_fmt(vout, gf);
+ break;
+ }
+ case VIDIOC_S_FMT:
+ {
+ struct v4l2_format *sf = arg;
+ if (vout->state != STATE_STREAM_OFF) {
+ retval = -EBUSY;
+ break;
+ }
+ retval = mxc_v4l2out_s_fmt(vout, sf);
+ break;
+ }
+ case VIDIOC_REQBUFS:
+ {
+ struct v4l2_requestbuffers *req = arg;
+ if ((req->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) ||
+ (req->memory != V4L2_MEMORY_MMAP)) {
+ dev_dbg(vdev->dev,
+ "VIDIOC_REQBUFS: incorrect"
+ "buffer type\n");
+ retval = -EINVAL;
+ break;
+ }
+
+ if (req->count == 0)
+ mxc_v4l2out_streamoff(vout);
+
+ if (vout->state == STATE_STREAM_OFF) {
+ if (vout->queue_buf_paddr[0] != 0) {
+ mxc_free_buffers(vout->queue_buf_paddr,
+ vout->queue_buf_vaddr,
+ vout->buffer_cnt,
+ vout->queue_buf_size);
+ dev_dbg(vdev->dev,
+ "VIDIOC_REQBUFS:"
+ "freed buffers\n");
+ }
+ vout->buffer_cnt = 0;
+ } else {
+ dev_dbg(vdev->dev,
+ "VIDIOC_REQBUFS: Buffer is in use\n");
+ retval = -EBUSY;
+ break;
+ }
+
+ if (req->count == 0)
+ break;
+
+ if (req->count < MIN_FRAME_NUM)
+ req->count = MIN_FRAME_NUM;
+ else if (req->count > MAX_FRAME_NUM)
+ req->count = MAX_FRAME_NUM;
+ vout->buffer_cnt = req->count;
+ vout->queue_buf_size =
+ PAGE_ALIGN(vout->v2f.fmt.pix.sizeimage);
+
+ retval = mxc_allocate_buffers(vout->queue_buf_paddr,
+ vout->queue_buf_vaddr,
+ vout->buffer_cnt,
+ vout->queue_buf_size);
+ if (retval < 0)
+ break;
+
+ /* Init buffer queues */
+ vout->done_q.head = 0;
+ vout->done_q.tail = 0;
+ vout->ready_q.head = 0;
+ vout->ready_q.tail = 0;
+
+ for (i = 0; i < vout->buffer_cnt; i++) {
+ memset(&(vout->v4l2_bufs[i]), 0,
+ sizeof(vout->v4l2_bufs[i]));
+ vout->v4l2_bufs[i].flags = 0;
+ vout->v4l2_bufs[i].memory = V4L2_MEMORY_MMAP;
+ vout->v4l2_bufs[i].index = i;
+ vout->v4l2_bufs[i].type =
+ V4L2_BUF_TYPE_VIDEO_OUTPUT;
+ vout->v4l2_bufs[i].length =
+ PAGE_ALIGN(vout->v2f.fmt.pix.sizeimage);
+ vout->v4l2_bufs[i].m.offset =
+ (unsigned long)vout->queue_buf_paddr[i];
+ vout->v4l2_bufs[i].timestamp.tv_sec = 0;
+ vout->v4l2_bufs[i].timestamp.tv_usec = 0;
+ }
+ break;
+ }
+ case VIDIOC_QUERYBUF:
+ {
+ struct v4l2_buffer *buf = arg;
+ u32 type = buf->type;
+ int index = buf->index;
+
+ if ((type != V4L2_BUF_TYPE_VIDEO_OUTPUT) ||
+ (index >= vout->buffer_cnt)) {
+ dev_dbg(vdev->dev,
+ "VIDIOC_QUERYBUFS: incorrect"
+ "buffer type\n");
+ retval = -EINVAL;
+ break;
+ }
+ down(&vout->param_lock);
+ memcpy(buf, &(vout->v4l2_bufs[index]), sizeof(*buf));
+ up(&vout->param_lock);
+ break;
+ }
+ case VIDIOC_QBUF:
+ {
+ struct v4l2_buffer *buf = arg;
+ int index = buf->index;
+ unsigned long lock_flags;
+
+ if ((buf->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) ||
+ (index >= vout->buffer_cnt)) {
+ retval = -EINVAL;
+ break;
+ }
+
+ dev_dbg(vdev->dev, "VIDIOC_QBUF: %d\n", buf->index);
+
+ /* mmapped buffers are L1 WB cached,
+ * so we need to clean them */
+ if (buf->flags & V4L2_BUF_FLAG_MAPPED)
+ flush_cache_all();
+
+ spin_lock_irqsave(&g_lock, lock_flags);
+
+ memcpy(&(vout->v4l2_bufs[index]), buf, sizeof(*buf));
+ vout->v4l2_bufs[index].flags |= V4L2_BUF_FLAG_QUEUED;
+
+ g_buf_q_cnt++;
+ queue_buf(&vout->ready_q, index);
+ if (vout->state == STATE_STREAM_PAUSED) {
+ unsigned long timeout;
+
+ index = peek_next_buf(&vout->ready_q);
+
+ /* if timestamp is 0, then default to 30fps */
+ if ((vout->v4l2_bufs[index].timestamp.tv_sec ==
+ 0)
+ && (vout->v4l2_bufs[index].timestamp.
+ tv_usec == 0))
+ timeout =
+ vout->start_jiffies +
+ vout->frame_count * HZ / 30;
+ else
+ timeout =
+ get_jiffies(&vout->v4l2_bufs[index].
+ timestamp);
+
+ if (jiffies >= timeout) {
+ dev_dbg(vout->video_dev->dev,
+ "warning: timer timeout"
+ "already expired.\n");
+ }
+ vout->output_timer.expires = timeout;
+ dev_dbg(vdev->dev,
+ "QBUF: frame #%u timeout @"
+ " %lu jiffies, current = %lu\n",
+ vout->frame_count, timeout, jiffies);
+ add_timer(&vout->output_timer);
+ vout->state = STATE_STREAM_ON;
+ }
+
+ spin_unlock_irqrestore(&g_lock, lock_flags);
+ break;
+ }
+ case VIDIOC_DQBUF:
+ {
+ struct v4l2_buffer *buf = arg;
+ int idx;
+
+ if ((queue_size(&vout->done_q) == 0) &&
+ (file->f_flags & O_NONBLOCK)) {
+ retval = -EAGAIN;
+ break;
+ }
+
+ if (!wait_event_interruptible_timeout(vout->v4l_bufq,
+ queue_size(&vout->
+ done_q)
+ != 0, 10 * HZ)) {
+ dev_dbg(vdev->dev, "VIDIOC_DQBUF: timeout\n");
+ retval = -ETIME;
+ break;
+ } else if (signal_pending(current)) {
+ dev_dbg(vdev->dev,
+ "VIDIOC_DQBUF: interrupt received\n");
+ retval = -ERESTARTSYS;
+ break;
+ }
+ idx = dequeue_buf(&vout->done_q);
+ if (idx == -1) { /* No frame free */
+ dev_dbg(vdev->dev,
+ "VIDIOC_DQBUF: no free buffers\n");
+ retval = -EAGAIN;
+ break;
+ }
+ if ((vout->v4l2_bufs[idx].flags & V4L2_BUF_FLAG_DONE) ==
+ 0)
+ dev_dbg(vdev->dev,
+ "VIDIOC_DQBUF: buffer in done q, "
+ "but not flagged as done\n");
+
+ vout->v4l2_bufs[idx].flags = 0;
+ memcpy(buf, &(vout->v4l2_bufs[idx]), sizeof(*buf));
+ dev_dbg(vdev->dev, "VIDIOC_DQBUF: %d\n", buf->index);
+ break;
+ }
+ case VIDIOC_STREAMON:
+ {
+ retval = mxc_v4l2out_streamon(vout);
+ break;
+ }
+ case VIDIOC_STREAMOFF:
+ {
+ retval = mxc_v4l2out_streamoff(vout);
+ break;
+ }
+ case VIDIOC_G_CTRL:
+ {
+ retval = mxc_get_v42lout_control(vout, arg);
+ break;
+ }
+ case VIDIOC_S_CTRL:
+ {
+ retval = mxc_set_v42lout_control(vout, arg);
+ break;
+ }
+ case VIDIOC_CROPCAP:
+ {
+ struct v4l2_cropcap *cap = arg;
+
+ if (cap->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) {
+ retval = -EINVAL;
+ break;
+ }
+
+ cap->bounds = vout->crop_bounds[vout->cur_disp_output];
+ cap->defrect = vout->crop_bounds[vout->cur_disp_output];
+ retval = 0;
+ break;
+ }
+ case VIDIOC_G_CROP:
+ {
+ struct v4l2_crop *crop = arg;
+
+ if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) {
+ retval = -EINVAL;
+ break;
+ }
+ crop->c = vout->crop_current;
+ break;
+ }
+ case VIDIOC_S_CROP:
+ {
+ struct v4l2_crop *crop = arg;
+ struct v4l2_rect *b =
+ &(vout->crop_bounds[vout->cur_disp_output]);
+
+ if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) {
+ retval = -EINVAL;
+ break;
+ }
+ if (crop->c.height < 0) {
+ retval = -EINVAL;
+ break;
+ }
+ if (crop->c.width < 0) {
+ retval = -EINVAL;
+ break;
+ }
+
+ /* only full screen supported for SDC BG */
+ if (vout->cur_disp_output == 4) {
+ crop->c = vout->crop_current;
+ break;
+ }
+
+ if (crop->c.top < b->top)
+ crop->c.top = b->top;
+ if (crop->c.top >= b->top + b->height)
+ crop->c.top = b->top + b->height - 1;
+ if (crop->c.height > b->top - crop->c.top + b->height)
+ crop->c.height =
+ b->top - crop->c.top + b->height;
+
+ if (crop->c.left < b->left)
+ crop->c.left = b->left;
+ if (crop->c.left >= b->left + b->width)
+ crop->c.left = b->left + b->width - 1;
+ if (crop->c.width > b->left - crop->c.left + b->width)
+ crop->c.width =
+ b->left - crop->c.left + b->width;
+
+ /* stride line limitation */
+ crop->c.height -= crop->c.height % 8;
+ crop->c.width -= crop->c.width % 8;
+
+ vout->crop_current = crop->c;
+ break;
+ }
+ case VIDIOC_ENUMOUTPUT:
+ {
+ struct v4l2_output *output = arg;
+
+ if ((output->index >= 5) ||
+ (vout->output_enabled[output->index] == false)) {
+ retval = -EINVAL;
+ break;
+ }
+
+ if (output->index < 3) {
+ *output = mxc_outputs[MXC_V4L2_OUT_2_ADC];
+ output->name[4] = '0' + output->index;
+ } else {
+ *output = mxc_outputs[MXC_V4L2_OUT_2_SDC];
+ }
+ break;
+ }
+ case VIDIOC_G_OUTPUT:
+ {
+ int *p_output_num = arg;
+
+ *p_output_num = vout->cur_disp_output;
+ break;
+ }
+ case VIDIOC_S_OUTPUT:
+ {
+ int *p_output_num = arg;
+ int fbnum;
+ struct v4l2_rect *b;
+
+ if ((*p_output_num >= MXC_V4L2_OUT_NUM_OUTPUTS) ||
+ (vout->output_enabled[*p_output_num] == false)) {
+ retval = -EINVAL;
+ break;
+ }
+
+ if (vout->state != STATE_STREAM_OFF) {
+ retval = -EBUSY;
+ break;
+ }
+
+ vout->cur_disp_output = *p_output_num;
+
+ /* Update bounds in case they have changed */
+ b = &vout->crop_bounds[vout->cur_disp_output];
+
+ fbnum = vout->output_fb_num[vout->cur_disp_output];
+ if (vout->cur_disp_output == 3)
+ fbnum = vout->output_fb_num[4];
+
+ b->width = registered_fb[fbnum]->var.xres;
+ b->height = registered_fb[fbnum]->var.yres;
+
+ vout->crop_current = *b;
+ break;
+ }
+ case VIDIOC_ENUM_FMT:
+ case VIDIOC_TRY_FMT:
+ case VIDIOC_QUERYCTRL:
+ case VIDIOC_G_PARM:
+ case VIDIOC_ENUMSTD:
+ case VIDIOC_G_STD:
+ case VIDIOC_S_STD:
+ case VIDIOC_G_TUNER:
+ case VIDIOC_S_TUNER:
+ case VIDIOC_G_FREQUENCY:
+ case VIDIOC_S_FREQUENCY:
+ default:
+ retval = -EINVAL;
+ break;
+ }
+
+ up(&vout->busy_lock);
+ return retval;
+}
+
+/*
+ * V4L2 interface - ioctl function
+ *
+ * @return None
+ */
+static int
+mxc_v4l2out_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ return video_usercopy(inode, file, cmd, arg, mxc_v4l2out_do_ioctl);
+}
+
+/*!
+ * V4L2 interface - mmap function
+ *
+ * @param file structure file *
+ *
+ * @param vma structure vm_area_struct *
+ *
+ * @return status 0 Success, EINTR busy lock error,
+ * ENOBUFS remap_page error
+ */
+static int mxc_v4l2out_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ struct video_device *vdev = video_devdata(file);
+ unsigned long size = vma->vm_end - vma->vm_start;
+ int res = 0;
+ int i;
+ vout_data *vout = video_get_drvdata(vdev);
+
+ dev_dbg(vdev->dev, "pgoff=0x%lx, start=0x%lx, end=0x%lx\n",
+ vma->vm_pgoff, vma->vm_start, vma->vm_end);
+
+ /* make this _really_ smp-safe */
+ if (down_interruptible(&vout->busy_lock))
+ return -EINTR;
+
+ for (i = 0; i < vout->buffer_cnt; i++) {
+ if ((vout->v4l2_bufs[i].m.offset ==
+ (vma->vm_pgoff << PAGE_SHIFT)) &&
+ (vout->v4l2_bufs[i].length >= size)) {
+ vout->v4l2_bufs[i].flags |= V4L2_BUF_FLAG_MAPPED;
+ break;
+ }
+ }
+ if (i == vout->buffer_cnt) {
+ res = -ENOBUFS;
+ goto mxc_mmap_exit;
+ }
+
+ /* make buffers inner write-back, outer write-thru cacheable */
+ vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
+
+ if (remap_pfn_range(vma, vma->vm_start,
+ vma->vm_pgoff, size, vma->vm_page_prot)) {
+ dev_dbg(vdev->dev, "mmap remap_pfn_range failed\n");
+ res = -ENOBUFS;
+ goto mxc_mmap_exit;
+ }
+
+ vma->vm_flags &= ~VM_IO; /* using shared anonymous pages */
+
+mxc_mmap_exit:
+ up(&vout->busy_lock);
+ return res;
+}
+
+/*!
+ * V4L2 interface - poll function
+ *
+ * @param file structure file *
+ *
+ * @param wait structure poll_table *
+ *
+ * @return status POLLIN | POLLRDNORM
+ */
+static unsigned int mxc_v4l2out_poll(struct file *file, poll_table * wait)
+{
+ struct video_device *dev = video_devdata(file);
+ vout_data *vout = video_get_drvdata(dev);
+
+ wait_queue_head_t *queue = NULL;
+ int res = POLLIN | POLLRDNORM;
+
+ if (down_interruptible(&vout->busy_lock))
+ return -EINTR;
+
+ queue = &vout->v4l_bufq;
+ poll_wait(file, queue, wait);
+
+ up(&vout->busy_lock);
+ return res;
+}
+
+static struct
+file_operations mxc_v4l2out_fops = {
+ .owner = THIS_MODULE,
+ .open = mxc_v4l2out_open,
+ .release = mxc_v4l2out_close,
+ .ioctl = mxc_v4l2out_ioctl,
+ .mmap = mxc_v4l2out_mmap,
+ .poll = mxc_v4l2out_poll,
+};
+
+static struct video_device mxc_v4l2out_template = {
+ .owner = THIS_MODULE,
+ .name = "MXC Video Output",
+ .type = 0,
+ .type2 = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING,
+ .fops = &mxc_v4l2out_fops,
+ .release = video_device_release,
+};
+
+/*!
+ * Probe routine for the framebuffer driver. It is called during the
+ * driver binding process. The following functions are performed in
+ * this routine: Framebuffer initialization, Memory allocation and
+ * mapping, Framebuffer registration, IPU initialization.
+ *
+ * @return Appropriate error code to the kernel common code
+ */
+static int mxc_v4l2out_probe(struct platform_device *pdev)
+{
+ int i;
+ vout_data *vout;
+
+ /*
+ * Allocate sufficient memory for the fb structure
+ */
+ g_vout = vout = kmalloc(sizeof(vout_data), GFP_KERNEL);
+
+ if (!vout)
+ return 0;
+
+ memset(vout, 0, sizeof(vout_data));
+
+ vout->video_dev = video_device_alloc();
+ if (vout->video_dev == NULL)
+ return -1;
+ vout->video_dev->dev = &pdev->dev;
+ vout->video_dev->minor = -1;
+
+ *(vout->video_dev) = mxc_v4l2out_template;
+
+ /* register v4l device */
+ if (video_register_device(vout->video_dev,
+ VFL_TYPE_GRABBER, video_nr) == -1) {
+ dev_dbg(&pdev->dev, "video_register_device failed\n");
+ return 0;
+ }
+ dev_info(&pdev->dev, "Registered device video%d\n",
+ vout->video_dev->minor & 0x1f);
+ vout->video_dev->dev = &pdev->dev;
+
+ video_set_drvdata(vout->video_dev, vout);
+
+ init_MUTEX(&vout->param_lock);
+ init_MUTEX(&vout->busy_lock);
+
+ /* setup outputs and cropping */
+ vout->cur_disp_output = -1;
+ for (i = 0; i < num_registered_fb; i++) {
+ char *idstr = registered_fb[i]->fix.id;
+ if (strncmp(idstr, "DISP", 4) == 0) {
+ int disp_num = idstr[4] - '0';
+ if (disp_num == 3) {
+ if (strcmp(idstr, "DISP3 BG - DI1") == 0)
+ disp_num = 5;
+ else if (strncmp(idstr, "DISP3 BG", 8) == 0)
+ disp_num = 4;
+ }
+ vout->crop_bounds[disp_num].left = 0;
+ vout->crop_bounds[disp_num].top = 0;
+ vout->crop_bounds[disp_num].width =
+ registered_fb[i]->var.xres;
+ vout->crop_bounds[disp_num].height =
+ registered_fb[i]->var.yres;
+ vout->output_enabled[disp_num] = true;
+ vout->output_fb_num[disp_num] = i;
+ if (vout->cur_disp_output == -1)
+ vout->cur_disp_output = disp_num;
+ }
+
+ }
+ vout->crop_current = vout->crop_bounds[vout->cur_disp_output];
+
+ platform_set_drvdata(pdev, vout);
+
+ return 0;
+}
+
+static int mxc_v4l2out_remove(struct platform_device *pdev)
+{
+ vout_data *vout = platform_get_drvdata(pdev);
+
+ if (vout->video_dev) {
+ if (-1 != vout->video_dev->minor)
+ video_unregister_device(vout->video_dev);
+ else
+ video_device_release(vout->video_dev);
+ vout->video_dev = NULL;
+ }
+
+ platform_set_drvdata(pdev, NULL);
+
+ kfree(vout);
+
+ return 0;
+}
+
+/*!
+ * This structure contains pointers to the power management callback functions.
+ */
+static struct platform_driver mxc_v4l2out_driver = {
+ .driver = {
+ .name = "MXC Video Output",
+ },
+ .probe = mxc_v4l2out_probe,
+ .remove = mxc_v4l2out_remove,
+};
+
+static struct platform_device mxc_v4l2out_device = {
+ .name = "MXC Video Output",
+ .id = 0,
+};
+
+/*!
+ * mxc v4l2 init function
+ *
+ */
+static int mxc_v4l2out_init(void)
+{
+ u8 err = 0;
+
+ err = platform_driver_register(&mxc_v4l2out_driver);
+ if (err == 0)
+ platform_device_register(&mxc_v4l2out_device);
+ return err;
+}
+
+/*!
+ * mxc v4l2 cleanup function
+ *
+ */
+static void mxc_v4l2out_clean(void)
+{
+ video_unregister_device(g_vout->video_dev);
+
+ platform_driver_unregister(&mxc_v4l2out_driver);
+ platform_device_unregister(&mxc_v4l2out_device);
+ kfree(g_vout);
+ g_vout = NULL;
+}
+
+module_init(mxc_v4l2out_init);
+module_exit(mxc_v4l2out_clean);
+
+module_param(video_nr, int, 0444);
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("V4L2-driver for MXC video output");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("video");
diff --git a/drivers/media/video/mxc/output/mxc_pxp_v4l2.c b/drivers/media/video/mxc/output/mxc_pxp_v4l2.c
new file mode 100644
index 000000000000..2855bb675d1d
--- /dev/null
+++ b/drivers/media/video/mxc/output/mxc_pxp_v4l2.c
@@ -0,0 +1,1237 @@
+/*
+ * Copyright (C) 2010 Freescale Semiconductor, Inc.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+/*
+ * Based on STMP378X PxP driver
+ * Copyright 2008-2009 Embedded Alley Solutions, Inc All Rights Reserved.
+ */
+
+#include <linux/dma-mapping.h>
+#include <linux/fb.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/vmalloc.h>
+#include <linux/videodev2.h>
+#include <linux/dmaengine.h>
+#include <linux/pxp_dma.h>
+#include <linux/delay.h>
+#include <linux/console.h>
+#include <linux/mxcfb.h>
+
+#include <media/videobuf-dma-contig.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-dev.h>
+#include <media/v4l2-ioctl.h>
+
+#include "mxc_pxp_v4l2.h"
+
+#define PXP_DRIVER_NAME "pxp-v4l2"
+#define PXP_DRIVER_MAJOR 2
+#define PXP_DRIVER_MINOR 0
+
+#define PXP_DEF_BUFS 2
+#define PXP_MIN_PIX 8
+
+#define V4L2_OUTPUT_TYPE_INTERNAL 4
+
+static struct pxp_data_format pxp_s0_formats[] = {
+ {
+ .name = "24-bit RGB",
+ .bpp = 4,
+ .fourcc = V4L2_PIX_FMT_RGB24,
+ .colorspace = V4L2_COLORSPACE_SRGB,
+ }, {
+ .name = "16-bit RGB 5:6:5",
+ .bpp = 2,
+ .fourcc = V4L2_PIX_FMT_RGB565,
+ .colorspace = V4L2_COLORSPACE_SRGB,
+ }, {
+ .name = "16-bit RGB 5:5:5",
+ .bpp = 2,
+ .fourcc = V4L2_PIX_FMT_RGB555,
+ .colorspace = V4L2_COLORSPACE_SRGB,
+ }, {
+ .name = "YUV 4:2:0 Planar",
+ .bpp = 2,
+ .fourcc = V4L2_PIX_FMT_YUV420,
+ .colorspace = V4L2_COLORSPACE_JPEG,
+ }, {
+ .name = "YUV 4:2:2 Planar",
+ .bpp = 2,
+ .fourcc = V4L2_PIX_FMT_YUV422P,
+ .colorspace = V4L2_COLORSPACE_JPEG,
+ },
+};
+
+static unsigned int v4l2_fmt_to_pxp_fmt(u32 v4l2_pix_fmt)
+{
+ u32 pxp_fmt = 0;
+
+ if (v4l2_pix_fmt == V4L2_PIX_FMT_RGB24)
+ pxp_fmt = PXP_PIX_FMT_RGB24;
+ else if (v4l2_pix_fmt == V4L2_PIX_FMT_RGB565)
+ pxp_fmt = PXP_PIX_FMT_RGB565;
+ else if (v4l2_pix_fmt == V4L2_PIX_FMT_RGB555)
+ pxp_fmt = PXP_PIX_FMT_RGB555;
+ else if (v4l2_pix_fmt == V4L2_PIX_FMT_RGB555)
+ pxp_fmt = PXP_PIX_FMT_RGB555;
+ else if (v4l2_pix_fmt == V4L2_PIX_FMT_YUV420)
+ pxp_fmt = PXP_PIX_FMT_YUV420P;
+ else if (v4l2_pix_fmt == V4L2_PIX_FMT_YUV422P)
+ pxp_fmt = PXP_PIX_FMT_YUV422P;
+
+ return pxp_fmt;
+}
+struct v4l2_queryctrl pxp_controls[] = {
+ {
+ .id = V4L2_CID_HFLIP,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "Horizontal Flip",
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 0,
+ .flags = 0,
+ }, {
+ .id = V4L2_CID_VFLIP,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "Vertical Flip",
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 0,
+ .flags = 0,
+ }, {
+ .id = V4L2_CID_PRIVATE_BASE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Rotation",
+ .minimum = 0,
+ .maximum = 270,
+ .step = 90,
+ .default_value = 0,
+ .flags = 0,
+ }, {
+ .id = V4L2_CID_PRIVATE_BASE + 1,
+ .name = "Background Color",
+ .minimum = 0,
+ .maximum = 0xFFFFFF,
+ .step = 1,
+ .default_value = 0,
+ .flags = 0,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ }, {
+ .id = V4L2_CID_PRIVATE_BASE + 2,
+ .name = "Set S0 Chromakey",
+ .minimum = -1,
+ .maximum = 0xFFFFFF,
+ .step = 1,
+ .default_value = -1,
+ .flags = 0,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ }, {
+ .id = V4L2_CID_PRIVATE_BASE + 3,
+ .name = "YUV Colorspace",
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 0,
+ .flags = 0,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ },
+};
+
+/* callback function */
+static void video_dma_done(void *arg)
+{
+ struct pxp_tx_desc *tx_desc = to_tx_desc(arg);
+ struct dma_chan *chan = tx_desc->txd.chan;
+ struct pxp_channel *pxp_chan = to_pxp_channel(chan);
+ struct pxps *pxp = pxp_chan->client;
+ struct videobuf_buffer *vb;
+
+ dev_dbg(chan->device->dev, "callback cookie %d, active DMA 0x%08x\n",
+ tx_desc->txd.cookie,
+ pxp->active ? sg_dma_address(&pxp->active->sg[0]) : 0);
+
+ spin_lock(&pxp->lock);
+ if (pxp->active) {
+ vb = &pxp->active->vb;
+
+ list_del_init(&vb->queue);
+ vb->state = VIDEOBUF_DONE;
+ do_gettimeofday(&vb->ts);
+ vb->field_count++;
+ wake_up(&vb->done);
+ }
+
+ if (list_empty(&pxp->outq)) {
+ pxp->active = NULL;
+ spin_unlock(&pxp->lock);
+
+ return;
+ }
+
+ pxp->active = list_entry(pxp->outq.next,
+ struct pxp_buffer, vb.queue);
+ pxp->active->vb.state = VIDEOBUF_ACTIVE;
+ spin_unlock(&pxp->lock);
+}
+
+static int acquire_dma_channel(struct pxps *pxp)
+{
+ dma_cap_mask_t mask;
+ struct dma_chan *chan;
+ struct pxp_channel **pchan = &pxp->pxp_channel[0];
+
+ if (*pchan) {
+ struct videobuf_buffer *vb, *_vb;
+ dma_release_channel(&(*pchan)->dma_chan);
+ *pchan = NULL;
+ pxp->active = NULL;
+ list_for_each_entry_safe(vb, _vb, &pxp->outq, queue) {
+ list_del_init(&vb->queue);
+ vb->state = VIDEOBUF_ERROR;
+ wake_up(&vb->done);
+ }
+ }
+
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_SLAVE, mask);
+ dma_cap_set(DMA_PRIVATE, mask);
+ chan = dma_request_channel(mask, NULL, NULL);
+ if (!chan)
+ return -EBUSY;
+
+ *pchan = to_pxp_channel(chan);
+ (*pchan)->client = pxp;
+
+ return 0;
+}
+
+static int _get_fbinfo(struct fb_info **fbi)
+{
+ int i;
+ for (i = 0; i < num_registered_fb; i++) {
+ char *idstr = registered_fb[i]->fix.id;
+ if (strcmp(idstr, "mxc_elcdif_fb") == 0) {
+ *fbi = registered_fb[i];
+ return 0;
+ }
+ }
+
+ return -ENODEV;
+}
+
+static int pxp_set_fbinfo(struct pxps *pxp)
+{
+ struct fb_info *fbi;
+ struct v4l2_framebuffer *fb = &pxp->fb;
+ int err;
+
+ err = _get_fbinfo(&fbi);
+ if (err)
+ return err;
+
+ fb->fmt.width = fbi->var.xres;
+ fb->fmt.height = fbi->var.yres;
+ if (fbi->var.bits_per_pixel == 16)
+ fb->fmt.pixelformat = V4L2_PIX_FMT_RGB565;
+ else
+ fb->fmt.pixelformat = V4L2_PIX_FMT_RGB24;
+ fb->base = (void *)fbi->fix.smem_start;
+
+ return 0;
+}
+
+static int _get_cur_fb_blank(struct pxps *pxp)
+{
+ struct fb_info *fbi;
+ mm_segment_t old_fs;
+ int err = 0;
+
+ err = _get_fbinfo(&fbi);
+ if (err)
+ return err;
+
+ if (fbi->fbops->fb_ioctl) {
+ old_fs = get_fs();
+ set_fs(KERNEL_DS);
+ err = fbi->fbops->fb_ioctl(fbi, MXCFB_GET_FB_BLANK,
+ (unsigned int)(&pxp->fb_blank));
+ set_fs(old_fs);
+ }
+
+ return err;
+}
+
+static int set_fb_blank(int blank)
+{
+ struct fb_info *fbi;
+ int err = 0;
+
+ err = _get_fbinfo(&fbi);
+ if (err)
+ return err;
+
+ acquire_console_sem();
+ fb_blank(fbi, blank);
+ release_console_sem();
+
+ return err;
+}
+
+static int pxp_set_cstate(struct pxps *pxp, struct v4l2_control *vc)
+{
+
+ if (vc->id == V4L2_CID_HFLIP) {
+ pxp->pxp_conf.proc_data.hflip = vc->value;
+ } else if (vc->id == V4L2_CID_VFLIP) {
+ pxp->pxp_conf.proc_data.vflip = vc->value;
+ } else if (vc->id == V4L2_CID_PRIVATE_BASE) {
+ if (vc->value % 90)
+ return -ERANGE;
+ pxp->pxp_conf.proc_data.rotate = vc->value;
+ } else if (vc->id == V4L2_CID_PRIVATE_BASE + 1) {
+ pxp->pxp_conf.proc_data.bgcolor = vc->value;
+ } else if (vc->id == V4L2_CID_PRIVATE_BASE + 2) {
+ pxp->pxp_conf.s0_param.color_key = vc->value;
+ } else if (vc->id == V4L2_CID_PRIVATE_BASE + 3) {
+ pxp->pxp_conf.proc_data.yuv = vc->value;
+ }
+
+ return 0;
+}
+
+static int pxp_get_cstate(struct pxps *pxp, struct v4l2_control *vc)
+{
+ if (vc->id == V4L2_CID_HFLIP)
+ vc->value = pxp->pxp_conf.proc_data.hflip;
+ else if (vc->id == V4L2_CID_VFLIP)
+ vc->value = pxp->pxp_conf.proc_data.vflip;
+ else if (vc->id == V4L2_CID_PRIVATE_BASE)
+ vc->value = pxp->pxp_conf.proc_data.rotate;
+ else if (vc->id == V4L2_CID_PRIVATE_BASE + 1)
+ vc->value = pxp->pxp_conf.proc_data.bgcolor;
+ else if (vc->id == V4L2_CID_PRIVATE_BASE + 2)
+ vc->value = pxp->pxp_conf.s0_param.color_key;
+ else if (vc->id == V4L2_CID_PRIVATE_BASE + 3)
+ vc->value = pxp->pxp_conf.proc_data.yuv;
+
+ return 0;
+}
+
+static int pxp_enumoutput(struct file *file, void *fh,
+ struct v4l2_output *o)
+{
+ struct pxps *pxp = video_get_drvdata(video_devdata(file));
+
+ if ((o->index < 0) || (o->index > 1))
+ return -EINVAL;
+
+ memset(o, 0, sizeof(struct v4l2_output));
+ if (o->index == 0) {
+ strcpy(o->name, "PxP Display Output");
+ pxp->output = 0;
+ } else {
+ strcpy(o->name, "PxP Virtual Output");
+ pxp->output = 1;
+ }
+ o->type = V4L2_OUTPUT_TYPE_INTERNAL;
+ o->std = 0;
+ o->reserved[0] = pxp->outb_phys;
+
+ return 0;
+}
+
+static int pxp_g_output(struct file *file, void *fh,
+ unsigned int *i)
+{
+ struct pxps *pxp = video_get_drvdata(video_devdata(file));
+
+ *i = pxp->output;
+
+ return 0;
+}
+
+static int pxp_s_output(struct file *file, void *fh,
+ unsigned int i)
+{
+ struct pxps *pxp = video_get_drvdata(video_devdata(file));
+ struct v4l2_pix_format *fmt = &pxp->fb.fmt;
+ int bpp;
+
+ if ((i < 0) || (i > 1))
+ return -EINVAL;
+
+ if (pxp->outb)
+ return 0;
+
+ /* Output buffer is same format as fbdev */
+ if (fmt->pixelformat == V4L2_PIX_FMT_RGB24)
+ bpp = 4;
+ else
+ bpp = 2;
+
+ pxp->outb_size = fmt->width * fmt->height * bpp;
+ pxp->outb = kmalloc(fmt->width * fmt->height * bpp, GFP_KERNEL);
+ pxp->outb_phys = virt_to_phys(pxp->outb);
+ dma_map_single(NULL, pxp->outb,
+ fmt->width * fmt->height * bpp, DMA_TO_DEVICE);
+
+ pxp->pxp_conf.out_param.width = fmt->width;
+ pxp->pxp_conf.out_param.height = fmt->height;
+ if (fmt->pixelformat == V4L2_PIX_FMT_RGB24)
+ pxp->pxp_conf.out_param.pixel_fmt = PXP_PIX_FMT_RGB24;
+ else
+ pxp->pxp_conf.out_param.pixel_fmt = PXP_PIX_FMT_RGB565;
+
+ return 0;
+}
+
+static int pxp_enum_fmt_video_output(struct file *file, void *fh,
+ struct v4l2_fmtdesc *fmt)
+{
+ enum v4l2_buf_type type = fmt->type;
+ int index = fmt->index;
+
+ if ((fmt->index < 0) || (fmt->index >= ARRAY_SIZE(pxp_s0_formats)))
+ return -EINVAL;
+
+ memset(fmt, 0, sizeof(struct v4l2_fmtdesc));
+ fmt->index = index;
+ fmt->type = type;
+ fmt->pixelformat = pxp_s0_formats[index].fourcc;
+ strcpy(fmt->description, pxp_s0_formats[index].name);
+
+ return 0;
+}
+
+static int pxp_g_fmt_video_output(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ struct v4l2_pix_format *pf = &f->fmt.pix;
+ struct pxps *pxp = video_get_drvdata(video_devdata(file));
+ struct pxp_data_format *fmt = pxp->s0_fmt;
+
+ pf->width = pxp->pxp_conf.s0_param.width;
+ pf->height = pxp->pxp_conf.s0_param.height;
+ pf->pixelformat = fmt->fourcc;
+ pf->field = V4L2_FIELD_NONE;
+ pf->bytesperline = fmt->bpp * pf->width;
+ pf->sizeimage = pf->bytesperline * pf->height;
+ pf->colorspace = fmt->colorspace;
+ pf->priv = 0;
+
+ return 0;
+}
+
+static struct pxp_data_format *pxp_get_format(struct v4l2_format *f)
+{
+ struct pxp_data_format *fmt;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(pxp_s0_formats); i++) {
+ fmt = &pxp_s0_formats[i];
+ if (fmt->fourcc == f->fmt.pix.pixelformat)
+ break;
+ }
+
+ if (i == ARRAY_SIZE(pxp_s0_formats))
+ return NULL;
+
+ return &pxp_s0_formats[i];
+}
+
+static int pxp_try_fmt_video_output(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ int w = f->fmt.pix.width;
+ int h = f->fmt.pix.height;
+ struct pxp_data_format *fmt = pxp_get_format(f);
+
+ if (!fmt)
+ return -EINVAL;
+
+ w = min(w, 2040);
+ w = max(w, 8);
+ h = min(h, 2040);
+ h = max(h, 8);
+ f->fmt.pix.field = V4L2_FIELD_NONE;
+ f->fmt.pix.width = w;
+ f->fmt.pix.height = h;
+ f->fmt.pix.pixelformat = fmt->fourcc;
+
+ return 0;
+}
+
+static int pxp_s_fmt_video_output(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ struct pxps *pxp = video_get_drvdata(video_devdata(file));
+ struct v4l2_pix_format *pf = &f->fmt.pix;
+ int ret;
+
+ ret = acquire_dma_channel(pxp);
+ if (ret < 0)
+ return ret;
+
+ ret = pxp_try_fmt_video_output(file, fh, f);
+ if (ret == 0) {
+ pxp->s0_fmt = pxp_get_format(f);
+ pxp->pxp_conf.s0_param.pixel_fmt =
+ v4l2_fmt_to_pxp_fmt(pxp->s0_fmt->fourcc);
+ pxp->pxp_conf.s0_param.width = pf->width;
+ pxp->pxp_conf.s0_param.height = pf->height;
+ }
+
+
+ return ret;
+}
+
+static int pxp_g_fmt_output_overlay(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ struct pxps *pxp = video_get_drvdata(video_devdata(file));
+ struct v4l2_window *wf = &f->fmt.win;
+
+ memset(wf, 0, sizeof(struct v4l2_window));
+ wf->chromakey = pxp->s1_chromakey;
+ wf->global_alpha = pxp->global_alpha;
+ wf->field = V4L2_FIELD_NONE;
+ wf->clips = NULL;
+ wf->clipcount = 0;
+ wf->bitmap = NULL;
+ wf->w.left = pxp->pxp_conf.proc_data.srect.left;
+ wf->w.top = pxp->pxp_conf.proc_data.srect.top;
+ wf->w.width = pxp->pxp_conf.proc_data.srect.width;
+ wf->w.height = pxp->pxp_conf.proc_data.srect.height;
+
+ return 0;
+}
+
+static int pxp_try_fmt_output_overlay(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ struct pxps *pxp = video_get_drvdata(video_devdata(file));
+ struct v4l2_window *wf = &f->fmt.win;
+ struct v4l2_rect srect;
+ u32 s1_chromakey = wf->chromakey;
+ u8 global_alpha = wf->global_alpha;
+
+ memcpy(&srect, &(wf->w), sizeof(struct v4l2_rect));
+
+ pxp_g_fmt_output_overlay(file, fh, f);
+
+ wf->chromakey = s1_chromakey;
+ wf->global_alpha = global_alpha;
+
+ /* Constrain parameters to the input buffer */
+ wf->w.left = srect.left;
+ wf->w.top = srect.top;
+ wf->w.width = min(srect.width,
+ ((__s32)pxp->pxp_conf.s0_param.width - wf->w.left));
+ wf->w.height = min(srect.height,
+ ((__s32)pxp->pxp_conf.s0_param.height - wf->w.top));
+
+ return 0;
+}
+
+static int pxp_s_fmt_output_overlay(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ struct pxps *pxp = video_get_drvdata(video_devdata(file));
+ struct v4l2_window *wf = &f->fmt.win;
+ int ret = pxp_try_fmt_output_overlay(file, fh, f);
+
+ if (ret == 0) {
+ pxp->global_alpha = wf->global_alpha;
+ pxp->s1_chromakey = wf->chromakey;
+ pxp->pxp_conf.proc_data.srect.left = wf->w.left;
+ pxp->pxp_conf.proc_data.srect.top = wf->w.top;
+ pxp->pxp_conf.proc_data.srect.width = wf->w.width;
+ pxp->pxp_conf.proc_data.srect.height = wf->w.height;
+ pxp->pxp_conf.ol_param[0].global_alpha = pxp->global_alpha;
+ pxp->pxp_conf.ol_param[0].color_key = pxp->s1_chromakey;
+ pxp->pxp_conf.ol_param[0].color_key_enable =
+ pxp->s1_chromakey_state;
+ }
+
+ return ret;
+}
+
+static int pxp_reqbufs(struct file *file, void *priv,
+ struct v4l2_requestbuffers *r)
+{
+ struct pxps *pxp = video_get_drvdata(video_devdata(file));
+
+ return videobuf_reqbufs(&pxp->s0_vbq, r);
+}
+
+static int pxp_querybuf(struct file *file, void *priv,
+ struct v4l2_buffer *b)
+{
+ struct pxps *pxp = video_get_drvdata(video_devdata(file));
+
+ return videobuf_querybuf(&pxp->s0_vbq, b);
+}
+
+static int pxp_qbuf(struct file *file, void *priv,
+ struct v4l2_buffer *b)
+{
+ struct pxps *pxp = video_get_drvdata(video_devdata(file));
+
+ return videobuf_qbuf(&pxp->s0_vbq, b);
+}
+
+static int pxp_dqbuf(struct file *file, void *priv,
+ struct v4l2_buffer *b)
+{
+ struct pxps *pxp = video_get_drvdata(video_devdata(file));
+
+ return videobuf_dqbuf(&pxp->s0_vbq, b, file->f_flags & O_NONBLOCK);
+}
+
+static int pxp_streamon(struct file *file, void *priv,
+ enum v4l2_buf_type t)
+{
+ struct pxps *pxp = video_get_drvdata(video_devdata(file));
+ int ret = 0;
+
+ if (t != V4L2_BUF_TYPE_VIDEO_OUTPUT)
+ return -EINVAL;
+
+ _get_cur_fb_blank(pxp);
+ set_fb_blank(FB_BLANK_UNBLANK);
+
+ ret = videobuf_streamon(&pxp->s0_vbq);
+
+ if (!ret && (pxp->output == 0))
+ mxc_elcdif_frame_addr_setup(pxp->outb_phys);
+
+ return ret;
+}
+
+static int pxp_streamoff(struct file *file, void *priv,
+ enum v4l2_buf_type t)
+{
+ struct pxps *pxp = video_get_drvdata(video_devdata(file));
+ int ret = 0;
+
+ if ((t != V4L2_BUF_TYPE_VIDEO_OUTPUT))
+ return -EINVAL;
+
+ ret = videobuf_streamoff(&pxp->s0_vbq);
+
+ if (!ret)
+ mxc_elcdif_frame_addr_setup((dma_addr_t)pxp->fb.base);
+
+ if (pxp->fb_blank)
+ set_fb_blank(FB_BLANK_POWERDOWN);
+
+ return ret;
+}
+
+static int pxp_buf_setup(struct videobuf_queue *q,
+ unsigned int *count, unsigned *size)
+{
+ struct pxps *pxp = q->priv_data;
+
+ *size = pxp->pxp_conf.s0_param.width *
+ pxp->pxp_conf.s0_param.height * pxp->s0_fmt->bpp;
+
+ if (0 == *count)
+ *count = PXP_DEF_BUFS;
+
+ return 0;
+}
+
+static void pxp_buf_free(struct videobuf_queue *q, struct pxp_buffer *buf)
+{
+ struct videobuf_buffer *vb = &buf->vb;
+ struct dma_async_tx_descriptor *txd = buf->txd;
+
+ BUG_ON(in_interrupt());
+
+ pr_debug("%s (vb=0x%p) 0x%08lx %d\n", __func__,
+ vb, vb->baddr, vb->bsize);
+
+ /*
+ * This waits until this buffer is out of danger, i.e., until it is no
+ * longer in STATE_QUEUED or STATE_ACTIVE
+ */
+ videobuf_waiton(vb, 0, 0);
+ if (txd)
+ async_tx_ack(txd);
+
+ videobuf_dma_contig_free(q, vb);
+ buf->txd = NULL;
+
+ vb->state = VIDEOBUF_NEEDS_INIT;
+}
+
+static int pxp_buf_prepare(struct videobuf_queue *q,
+ struct videobuf_buffer *vb,
+ enum v4l2_field field)
+{
+ struct pxps *pxp = q->priv_data;
+ struct pxp_config_data *pxp_conf = &pxp->pxp_conf;
+ struct pxp_proc_data *proc_data = &pxp_conf->proc_data;
+ struct pxp_buffer *buf = container_of(vb, struct pxp_buffer, vb);
+ struct pxp_tx_desc *desc;
+ int ret = 0;
+ int i, length;
+
+ vb->width = pxp->pxp_conf.s0_param.width;
+ vb->height = pxp->pxp_conf.s0_param.height;
+ vb->size = vb->width * vb->height * pxp->s0_fmt->bpp;
+ vb->field = V4L2_FIELD_NONE;
+ if (vb->state != VIDEOBUF_NEEDS_INIT)
+ pxp_buf_free(q, buf);
+
+ if (vb->state == VIDEOBUF_NEEDS_INIT) {
+ struct pxp_channel *pchan = pxp->pxp_channel[0];
+ struct scatterlist *sg = &buf->sg;
+
+ /* This actually (allocates and) maps buffers */
+ ret = videobuf_iolock(q, vb, NULL);
+ if (ret) {
+ pr_err("fail to call videobuf_iolock, ret = %d\n", ret);
+ goto fail;
+ }
+
+ /*
+ * sg[0] for input(S0)
+ * Sg[1] for output
+ */
+ sg_init_table(sg, 3);
+
+ buf->txd = pchan->dma_chan.device->device_prep_slave_sg(
+ &pchan->dma_chan, sg, 3, DMA_FROM_DEVICE,
+ DMA_PREP_INTERRUPT);
+ if (!buf->txd) {
+ ret = -EIO;
+ goto fail;
+ }
+
+ buf->txd->callback_param = buf->txd;
+ buf->txd->callback = video_dma_done;
+
+ desc = to_tx_desc(buf->txd);
+ length = desc->len;
+ for (i = 0; i < length; i++) {
+ if (i == 0) {/* S0 */
+ memcpy(&desc->proc_data, proc_data,
+ sizeof(struct pxp_proc_data));
+ pxp_conf->s0_param.paddr =
+ videobuf_to_dma_contig(vb);
+ memcpy(&desc->layer_param.s0_param,
+ &pxp_conf->s0_param,
+ sizeof(struct pxp_layer_param));
+ } else if (i == 1) { /* Output */
+ if (proc_data->rotate % 180) {
+ pxp_conf->out_param.width =
+ pxp->fb.fmt.height;
+ pxp_conf->out_param.height =
+ pxp->fb.fmt.width;
+ } else {
+ pxp_conf->out_param.width =
+ pxp->fb.fmt.width;
+ pxp_conf->out_param.height =
+ pxp->fb.fmt.height;
+ }
+
+ pxp_conf->out_param.paddr = pxp->outb_phys;
+ memcpy(&desc->layer_param.out_param,
+ &pxp_conf->out_param,
+ sizeof(struct pxp_layer_param));
+ } else if (pxp_conf->ol_param[0].combine_enable) {
+ /* Overlay */
+ pxp_conf->ol_param[0].paddr =
+ (dma_addr_t)pxp->fb.base;
+ pxp_conf->ol_param[0].width = pxp->fb.fmt.width;
+ pxp_conf->ol_param[0].height =
+ pxp->fb.fmt.height;
+ pxp_conf->ol_param[0].pixel_fmt =
+ pxp_conf->out_param.pixel_fmt;
+ memcpy(&desc->layer_param.ol_param,
+ &pxp_conf->ol_param[0],
+ sizeof(struct pxp_layer_param));
+ }
+
+ desc = desc->next;
+ }
+
+ vb->state = VIDEOBUF_PREPARED;
+ }
+
+ return 0;
+
+fail:
+ pxp_buf_free(q, buf);
+ return ret;
+}
+
+
+static void pxp_buf_queue(struct videobuf_queue *q,
+ struct videobuf_buffer *vb)
+{
+ struct pxps *pxp = q->priv_data;
+ struct pxp_buffer *buf = container_of(vb, struct pxp_buffer, vb);
+ struct dma_async_tx_descriptor *txd = buf->txd;
+ struct pxp_channel *pchan = pxp->pxp_channel[0];
+ dma_cookie_t cookie;
+
+ BUG_ON(!irqs_disabled());
+
+ list_add_tail(&vb->queue, &pxp->outq);
+
+ if (!pxp->active) {
+ pxp->active = buf;
+ vb->state = VIDEOBUF_ACTIVE;
+ } else {
+ vb->state = VIDEOBUF_QUEUED;
+ }
+
+ spin_unlock_irq(&pxp->lock);
+
+ cookie = txd->tx_submit(txd);
+ dev_dbg(&pxp->pdev->dev, "Submitted cookie %d DMA 0x%08x\n",
+ cookie, sg_dma_address(&buf->sg[0]));
+ mdelay(5);
+ /* trigger ePxP */
+ dma_async_issue_pending(&pchan->dma_chan);
+
+ spin_lock_irq(&pxp->lock);
+
+ if (cookie >= 0)
+ return;
+
+ /* Submit error */
+ pr_err("%s: Submit error\n", __func__);
+ vb->state = VIDEOBUF_PREPARED;
+
+ list_del_init(&vb->queue);
+
+ if (pxp->active == buf)
+ pxp->active = NULL;
+}
+
+static void pxp_buf_release(struct videobuf_queue *q,
+ struct videobuf_buffer *vb)
+{
+ struct pxps *pxp = q->priv_data;
+ struct pxp_buffer *buf = container_of(vb, struct pxp_buffer, vb);
+ unsigned long flags;
+
+ spin_lock_irqsave(&pxp->lock, flags);
+ if ((vb->state == VIDEOBUF_ACTIVE || vb->state == VIDEOBUF_QUEUED) &&
+ !list_empty(&vb->queue)) {
+ vb->state = VIDEOBUF_ERROR;
+
+ list_del_init(&vb->queue);
+ if (pxp->active == buf)
+ pxp->active = NULL;
+ }
+ spin_unlock_irqrestore(&pxp->lock, flags);
+
+ pxp_buf_free(q, buf);
+}
+
+static struct videobuf_queue_ops pxp_vbq_ops = {
+ .buf_setup = pxp_buf_setup,
+ .buf_prepare = pxp_buf_prepare,
+ .buf_queue = pxp_buf_queue,
+ .buf_release = pxp_buf_release,
+};
+
+static int pxp_querycap(struct file *file, void *fh,
+ struct v4l2_capability *cap)
+{
+ struct pxps *pxp = video_get_drvdata(video_devdata(file));
+
+ memset(cap, 0, sizeof(*cap));
+ strcpy(cap->driver, "pxp");
+ strcpy(cap->card, "pxp");
+ strlcpy(cap->bus_info, dev_name(&pxp->pdev->dev),
+ sizeof(cap->bus_info));
+
+ cap->version = (PXP_DRIVER_MAJOR << 8) + PXP_DRIVER_MINOR;
+
+ cap->capabilities = V4L2_CAP_VIDEO_OUTPUT |
+ V4L2_CAP_VIDEO_OUTPUT_OVERLAY |
+ V4L2_CAP_STREAMING;
+
+ return 0;
+}
+
+static int pxp_g_fbuf(struct file *file, void *priv,
+ struct v4l2_framebuffer *fb)
+{
+ struct pxps *pxp = video_get_drvdata(video_devdata(file));
+
+ memset(fb, 0, sizeof(*fb));
+
+ fb->capability = V4L2_FBUF_CAP_EXTERNOVERLAY |
+ V4L2_FBUF_CAP_CHROMAKEY |
+ V4L2_FBUF_CAP_LOCAL_ALPHA |
+ V4L2_FBUF_CAP_GLOBAL_ALPHA;
+
+ if (pxp->global_alpha_state)
+ fb->flags |= V4L2_FBUF_FLAG_GLOBAL_ALPHA;
+ if (pxp->s1_chromakey_state)
+ fb->flags |= V4L2_FBUF_FLAG_CHROMAKEY;
+
+ return 0;
+}
+
+static int pxp_s_fbuf(struct file *file, void *priv,
+ struct v4l2_framebuffer *fb)
+{
+ struct pxps *pxp = video_get_drvdata(video_devdata(file));
+
+ pxp->overlay_state =
+ (fb->flags & V4L2_FBUF_FLAG_OVERLAY) != 0;
+ pxp->global_alpha_state =
+ (fb->flags & V4L2_FBUF_FLAG_GLOBAL_ALPHA) != 0;
+ pxp->s1_chromakey_state =
+ (fb->flags & V4L2_FBUF_FLAG_CHROMAKEY) != 0;
+
+ pxp->pxp_conf.ol_param[0].combine_enable = pxp->overlay_state;
+ pxp->pxp_conf.ol_param[0].global_alpha_enable = pxp->global_alpha_state;
+
+ return 0;
+}
+
+static int pxp_g_crop(struct file *file, void *fh,
+ struct v4l2_crop *c)
+{
+ struct pxps *pxp = video_get_drvdata(video_devdata(file));
+
+ if (c->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY)
+ return -EINVAL;
+
+ c->c.left = pxp->pxp_conf.proc_data.drect.left;
+ c->c.top = pxp->pxp_conf.proc_data.drect.top;
+ c->c.width = pxp->pxp_conf.proc_data.drect.width;
+ c->c.height = pxp->pxp_conf.proc_data.drect.height;
+
+ return 0;
+}
+
+static int pxp_s_crop(struct file *file, void *fh,
+ struct v4l2_crop *c)
+{
+ struct pxps *pxp = video_get_drvdata(video_devdata(file));
+ int l = c->c.left;
+ int t = c->c.top;
+ int w = c->c.width;
+ int h = c->c.height;
+ int fbw = pxp->fb.fmt.width;
+ int fbh = pxp->fb.fmt.height;
+
+ if (c->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY)
+ return -EINVAL;
+
+ /* Constrain parameters to FB limits */
+ w = min(w, fbw);
+ w = max(w, PXP_MIN_PIX);
+ h = min(h, fbh);
+ h = max(h, PXP_MIN_PIX);
+ if ((l + w) > fbw)
+ l = 0;
+ if ((t + h) > fbh)
+ t = 0;
+
+ /* Round up values to PxP pixel block */
+ l = roundup(l, PXP_MIN_PIX);
+ t = roundup(t, PXP_MIN_PIX);
+ w = roundup(w, PXP_MIN_PIX);
+ h = roundup(h, PXP_MIN_PIX);
+
+ pxp->pxp_conf.proc_data.drect.left = l;
+ pxp->pxp_conf.proc_data.drect.top = t;
+ pxp->pxp_conf.proc_data.drect.width = w;
+ pxp->pxp_conf.proc_data.drect.height = h;
+
+ return 0;
+}
+
+static int pxp_queryctrl(struct file *file, void *priv,
+ struct v4l2_queryctrl *qc)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(pxp_controls); i++)
+ if (qc->id && qc->id == pxp_controls[i].id) {
+ memcpy(qc, &(pxp_controls[i]), sizeof(*qc));
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int pxp_g_ctrl(struct file *file, void *priv,
+ struct v4l2_control *vc)
+{
+ int i;
+
+ struct pxps *pxp = video_get_drvdata(video_devdata(file));
+
+ for (i = 0; i < ARRAY_SIZE(pxp_controls); i++)
+ if (vc->id == pxp_controls[i].id)
+ return pxp_get_cstate(pxp, vc);
+
+ return -EINVAL;
+}
+
+static int pxp_s_ctrl(struct file *file, void *priv,
+ struct v4l2_control *vc)
+{
+ int i;
+ struct pxps *pxp = video_get_drvdata(video_devdata(file));
+
+ for (i = 0; i < ARRAY_SIZE(pxp_controls); i++)
+ if (vc->id == pxp_controls[i].id) {
+ if (vc->value < pxp_controls[i].minimum ||
+ vc->value > pxp_controls[i].maximum)
+ return -ERANGE;
+ return pxp_set_cstate(pxp, vc);
+ }
+
+ return -EINVAL;
+}
+
+void pxp_release(struct video_device *vfd)
+{
+ struct pxps *pxp = video_get_drvdata(vfd);
+
+ spin_lock(&pxp->lock);
+ video_device_release(vfd);
+ spin_unlock(&pxp->lock);
+}
+
+static int pxp_open(struct file *file)
+{
+ struct pxps *pxp = video_get_drvdata(video_devdata(file));
+ int ret = 0;
+
+ mutex_lock(&pxp->mutex);
+ pxp->users++;
+
+ if (pxp->users > 1) {
+ pxp->users--;
+ ret = -EBUSY;
+ goto out;
+ }
+out:
+ mutex_unlock(&pxp->mutex);
+ if (ret)
+ return ret;
+
+ videobuf_queue_dma_contig_init(&pxp->s0_vbq,
+ &pxp_vbq_ops,
+ &pxp->pdev->dev,
+ &pxp->lock,
+ V4L2_BUF_TYPE_VIDEO_OUTPUT,
+ V4L2_FIELD_NONE,
+ sizeof(struct pxp_buffer),
+ pxp);
+ dev_dbg(&pxp->pdev->dev, "call pxp_open\n");
+
+ return 0;
+}
+
+static int pxp_close(struct file *file)
+{
+ struct pxps *pxp = video_get_drvdata(video_devdata(file));
+
+ videobuf_stop(&pxp->s0_vbq);
+ videobuf_mmap_free(&pxp->s0_vbq);
+ pxp->active = NULL;
+ kfree(pxp->outb);
+ pxp->outb = NULL;
+
+ mutex_lock(&pxp->mutex);
+ pxp->users--;
+ mutex_unlock(&pxp->mutex);
+
+ return 0;
+}
+
+static int pxp_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ struct pxps *pxp = video_get_drvdata(video_devdata(file));
+ int ret;
+
+ ret = videobuf_mmap_mapper(&pxp->s0_vbq, vma);
+
+ return ret;
+}
+
+static const struct v4l2_file_operations pxp_fops = {
+ .owner = THIS_MODULE,
+ .open = pxp_open,
+ .release = pxp_close,
+ .ioctl = video_ioctl2,
+ .mmap = pxp_mmap,
+};
+
+static const struct v4l2_ioctl_ops pxp_ioctl_ops = {
+ .vidioc_querycap = pxp_querycap,
+
+ .vidioc_reqbufs = pxp_reqbufs,
+ .vidioc_querybuf = pxp_querybuf,
+ .vidioc_qbuf = pxp_qbuf,
+ .vidioc_dqbuf = pxp_dqbuf,
+
+ .vidioc_streamon = pxp_streamon,
+ .vidioc_streamoff = pxp_streamoff,
+
+ .vidioc_enum_output = pxp_enumoutput,
+ .vidioc_g_output = pxp_g_output,
+ .vidioc_s_output = pxp_s_output,
+
+ .vidioc_enum_fmt_vid_out = pxp_enum_fmt_video_output,
+ .vidioc_try_fmt_vid_out = pxp_try_fmt_video_output,
+ .vidioc_g_fmt_vid_out = pxp_g_fmt_video_output,
+ .vidioc_s_fmt_vid_out = pxp_s_fmt_video_output,
+
+ .vidioc_try_fmt_vid_out_overlay = pxp_try_fmt_output_overlay,
+ .vidioc_g_fmt_vid_out_overlay = pxp_g_fmt_output_overlay,
+ .vidioc_s_fmt_vid_out_overlay = pxp_s_fmt_output_overlay,
+
+ .vidioc_g_fbuf = pxp_g_fbuf,
+ .vidioc_s_fbuf = pxp_s_fbuf,
+
+ .vidioc_g_crop = pxp_g_crop,
+ .vidioc_s_crop = pxp_s_crop,
+
+ .vidioc_queryctrl = pxp_queryctrl,
+ .vidioc_g_ctrl = pxp_g_ctrl,
+ .vidioc_s_ctrl = pxp_s_ctrl,
+};
+
+static const struct video_device pxp_template = {
+ .name = "PxP",
+ .vfl_type = V4L2_CAP_VIDEO_OUTPUT |
+ V4L2_CAP_VIDEO_OVERLAY |
+ V4L2_CAP_STREAMING,
+ .fops = &pxp_fops,
+ .release = pxp_release,
+ .minor = -1,
+ .ioctl_ops = &pxp_ioctl_ops,
+};
+
+static int pxp_probe(struct platform_device *pdev)
+{
+ struct pxps *pxp;
+ int err = 0;
+
+ pxp = kzalloc(sizeof(*pxp), GFP_KERNEL);
+ if (!pxp) {
+ dev_err(&pdev->dev, "failed to allocate control object\n");
+ err = -ENOMEM;
+ goto exit;
+ }
+
+ dev_set_drvdata(&pdev->dev, pxp);
+
+ INIT_LIST_HEAD(&pxp->outq);
+ spin_lock_init(&pxp->lock);
+ mutex_init(&pxp->mutex);
+
+ pxp->pdev = pdev;
+
+ pxp->vdev = video_device_alloc();
+ if (!pxp->vdev) {
+ dev_err(&pdev->dev, "video_device_alloc() failed\n");
+ err = -ENOMEM;
+ goto freeirq;
+ }
+
+ memcpy(pxp->vdev, &pxp_template, sizeof(pxp_template));
+ video_set_drvdata(pxp->vdev, pxp);
+
+ err = video_register_device(pxp->vdev, VFL_TYPE_GRABBER, 0);
+ if (err) {
+ dev_err(&pdev->dev, "failed to register video device\n");
+ goto freevdev;
+ }
+
+ err = pxp_set_fbinfo(pxp);
+ if (err) {
+ dev_err(&pdev->dev, "failed to call pxp_set_fbinfo\n");
+ goto freevdev;
+ }
+
+ dev_info(&pdev->dev, "initialized\n");
+
+exit:
+ return err;
+
+freevdev:
+ video_device_release(pxp->vdev);
+
+freeirq:
+ kfree(pxp);
+
+ return err;
+}
+
+static int __devexit pxp_remove(struct platform_device *pdev)
+{
+ struct pxps *pxp = platform_get_drvdata(pdev);
+
+ video_unregister_device(pxp->vdev);
+ video_device_release(pxp->vdev);
+
+ kfree(pxp);
+
+ return 0;
+}
+
+static struct platform_driver pxp_driver = {
+ .driver = {
+ .name = PXP_DRIVER_NAME,
+ },
+ .probe = pxp_probe,
+ .remove = __exit_p(pxp_remove),
+};
+
+
+static int __devinit pxp_init(void)
+{
+ return platform_driver_register(&pxp_driver);
+}
+
+static void __exit pxp_exit(void)
+{
+ platform_driver_unregister(&pxp_driver);
+}
+
+module_init(pxp_init);
+module_exit(pxp_exit);
+
+MODULE_DESCRIPTION("MXC PxP V4L2 driver");
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/video/mxc/output/mxc_pxp_v4l2.h b/drivers/media/video/mxc/output/mxc_pxp_v4l2.h
new file mode 100644
index 000000000000..d17eea571374
--- /dev/null
+++ b/drivers/media/video/mxc/output/mxc_pxp_v4l2.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2010 Freescale Semiconductor, Inc.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+/*
+ * Based on STMP378X PxP driver
+ * Copyright 2008-2009 Embedded Alley Solutions, Inc All Rights Reserved.
+ */
+
+#ifndef _MXC_PXP_V4L2_H
+#define _MXC_PXP_V4L2_H
+
+#include <linux/dmaengine.h>
+#include <linux/pxp_dma.h>
+
+struct pxp_buffer {
+ /* Must be first! */
+ struct videobuf_buffer vb;
+
+ /* One descriptor per scatterlist (per frame) */
+ struct dma_async_tx_descriptor *txd;
+
+ struct scatterlist sg[3];
+};
+
+struct pxps {
+ struct platform_device *pdev;
+
+ spinlock_t lock;
+ struct mutex mutex;
+ int users;
+
+ struct video_device *vdev;
+
+ struct videobuf_queue s0_vbq;
+ struct pxp_buffer *active;
+ struct list_head outq;
+ struct pxp_channel *pxp_channel[1]; /* We need 1 channel */
+ struct pxp_config_data pxp_conf;
+
+ int output;
+ u32 *outb;
+ dma_addr_t outb_phys;
+ u32 outb_size;
+
+ /* Current S0 configuration */
+ struct pxp_data_format *s0_fmt;
+
+ struct v4l2_framebuffer fb;
+
+ /* Output overlay support */
+ int overlay_state;
+ int global_alpha_state;
+ u8 global_alpha;
+ int s1_chromakey_state;
+ u32 s1_chromakey;
+
+ int fb_blank;
+};
+
+struct pxp_data_format {
+ char *name;
+ unsigned int bpp;
+ u32 fourcc;
+ enum v4l2_colorspace colorspace;
+};
+
+#endif
diff --git a/drivers/media/video/mxc/output/mxc_v4l2_output.c b/drivers/media/video/mxc/output/mxc_v4l2_output.c
new file mode 100644
index 000000000000..86cd795d6bd6
--- /dev/null
+++ b/drivers/media/video/mxc/output/mxc_v4l2_output.c
@@ -0,0 +1,2793 @@
+/*
+ * Copyright 2005-2011 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
+ */
+
+/*!
+ * @file drivers/media/video/mxc/output/mxc_v4l2_output.c
+ *
+ * @brief MXC V4L2 Video Output Driver
+ *
+ * Video4Linux2 Output Device using MXC IPU Post-processing functionality.
+ *
+ * @ingroup MXC_V4L2_OUTPUT
+ */
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/semaphore.h>
+#include <linux/slab.h>
+#include <linux/console.h>
+#include <linux/fs.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/mxcfb.h>
+#include <media/v4l2-ioctl.h>
+#include <asm/cacheflush.h>
+#include <mach/hardware.h>
+
+#include "mxc_v4l2_output.h"
+
+#define INTERLACED_CONTENT(vout) (((cpu_is_mx51_rev(CHIP_REV_2_0) >= 1) || \
+ cpu_is_mx53()) && \
+ (((vout)->field_fmt == V4L2_FIELD_INTERLACED_TB) || \
+ ((vout)->field_fmt == V4L2_FIELD_INTERLACED_BT)))
+#define LOAD_3FIELDS(vout) ((INTERLACED_CONTENT(vout)) && \
+ ((vout)->motion_sel != HIGH_MOTION))
+
+struct v4l2_output mxc_outputs[1] = {
+ {
+ .index = MXC_V4L2_OUT_2_SDC,
+ .name = "DISP3 Video Out",
+ .type = V4L2_OUTPUT_TYPE_ANALOG, /* not really correct,
+ but no other choice */
+ .audioset = 0,
+ .modulator = 0,
+ .std = V4L2_STD_UNKNOWN}
+};
+
+static int video_nr = 16;
+static DEFINE_SPINLOCK(g_lock);
+static int last_index_n;
+static unsigned int ipu_ic_out_max_width_size;
+static unsigned int ipu_ic_out_max_height_size;
+/* debug counters */
+uint32_t g_irq_cnt;
+uint32_t g_buf_output_cnt;
+uint32_t g_buf_q_cnt;
+uint32_t g_buf_dq_cnt;
+
+#define QUEUE_SIZE (MAX_FRAME_NUM + 1)
+static __inline int queue_size(v4l_queue *q)
+{
+ if (q->tail >= q->head)
+ return q->tail - q->head;
+ else
+ return (q->tail + QUEUE_SIZE) - q->head;
+}
+
+static __inline int queue_buf(v4l_queue *q, int idx)
+{
+ if (((q->tail + 1) % QUEUE_SIZE) == q->head)
+ return -1; /* queue full */
+ q->list[q->tail] = idx;
+ q->tail = (q->tail + 1) % QUEUE_SIZE;
+ return 0;
+}
+
+static __inline int dequeue_buf(v4l_queue *q)
+{
+ int ret;
+ if (q->tail == q->head)
+ return -1; /* queue empty */
+ ret = q->list[q->head];
+ q->head = (q->head + 1) % QUEUE_SIZE;
+ return ret;
+}
+
+static __inline int peek_next_buf(v4l_queue *q)
+{
+ if (q->tail == q->head)
+ return -1; /* queue empty */
+ return q->list[q->head];
+}
+
+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);
+}
+
+/*!
+ * Private function to free buffers
+ *
+ * @param bufs_paddr Array of physical address of buffers to be freed
+ *
+ * @param bufs_vaddr Array of virtual address of buffers to be freed
+ *
+ * @param num_buf Number of buffers to be freed
+ *
+ * @param size Size for each buffer to be free
+ *
+ * @return status 0 success.
+ */
+static int mxc_free_buffers(dma_addr_t bufs_paddr[], void *bufs_vaddr[],
+ int num_buf, int size)
+{
+ int i;
+
+ for (i = 0; i < num_buf; i++) {
+ if (bufs_vaddr[i] != 0) {
+ dma_free_coherent(0, size, bufs_vaddr[i],
+ bufs_paddr[i]);
+ pr_debug("freed @ paddr=0x%08X\n", (u32) bufs_paddr[i]);
+ bufs_paddr[i] = 0;
+ bufs_vaddr[i] = NULL;
+ }
+ }
+ return 0;
+}
+
+/*!
+ * Private function to allocate buffers
+ *
+ * @param bufs_paddr Output array of physical address of buffers allocated
+ *
+ * @param bufs_vaddr Output array of virtual address of buffers allocated
+ *
+ * @param num_buf Input number of buffers to allocate
+ *
+ * @param size Input size for each buffer to allocate
+ *
+ * @return status -0 Successfully allocated a buffer, -ENOBUFS failed.
+ */
+static int mxc_allocate_buffers(dma_addr_t bufs_paddr[], void *bufs_vaddr[],
+ int num_buf, int size)
+{
+ int i;
+
+ for (i = 0; i < num_buf; i++) {
+ bufs_vaddr[i] = dma_alloc_coherent(0, size,
+ &bufs_paddr[i],
+ GFP_DMA | GFP_KERNEL);
+
+ if (bufs_vaddr[i] == 0) {
+ mxc_free_buffers(bufs_paddr, bufs_vaddr, i, size);
+ printk(KERN_ERR "dma_alloc_coherent failed.\n");
+ return -ENOBUFS;
+ }
+ pr_debug("allocated @ paddr=0x%08X, size=%d.\n",
+ (u32) bufs_paddr[i], size);
+ }
+
+ return 0;
+}
+
+/*
+ * Returns bits per pixel for given pixel format
+ *
+ * @param pixelformat V4L2_PIX_FMT_RGB565, V4L2_PIX_FMT_BGR24 or V4L2_PIX_FMT_BGR32
+ *
+ * @return bits per pixel of pixelformat
+ */
+static u32 fmt_to_bpp(u32 pixelformat)
+{
+ u32 bpp;
+
+ bpp = 8 * bytes_per_pixel(pixelformat);
+ return bpp;
+}
+
+static bool format_is_yuv(u32 pixelformat)
+{
+ switch (pixelformat) {
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_UYVY:
+ case V4L2_PIX_FMT_YUYV:
+ case V4L2_PIX_FMT_YUV422P:
+ case V4L2_PIX_FMT_YVU420:
+ case V4L2_PIX_FMT_YUV444:
+ case V4L2_PIX_FMT_NV12:
+ return true;
+ break;
+ }
+ return false;
+}
+
+static u32 bpp_to_fmt(struct fb_info *fbi)
+{
+ if (fbi->var.nonstd)
+ return fbi->var.nonstd;
+
+ if (fbi->var.bits_per_pixel == 24)
+ return V4L2_PIX_FMT_BGR24;
+ else if (fbi->var.bits_per_pixel == 32)
+ return V4L2_PIX_FMT_BGR32;
+ else if (fbi->var.bits_per_pixel == 16)
+ return V4L2_PIX_FMT_RGB565;
+
+ return 0;
+}
+
+/*
+ * we are using double buffer for video playback, ipu need make
+ * sure current buffer should not be the same buffer of next display
+ * one.
+ */
+static int select_display_buffer(vout_data *vout, int next_buf)
+{
+ int ret = 0;
+
+ vout->disp_buf_num = next_buf;
+ if (ipu_get_cur_buffer_idx(vout->display_ch, IPU_INPUT_BUFFER)
+ != next_buf)
+ ret = ipu_select_buffer(vout->display_ch, IPU_INPUT_BUFFER,
+ next_buf);
+ else
+ dev_dbg(&vout->video_dev->dev,
+ "display buffer not ready for select\n");
+ return ret;
+}
+
+static void setup_next_buf_timer(vout_data *vout, int index)
+{
+ unsigned long timeout;
+
+ /* Setup timer for next buffer */
+ /* if timestamp is 0, then default to 30fps */
+ if ((vout->v4l2_bufs[index].timestamp.tv_sec == 0)
+ && (vout->v4l2_bufs[index].timestamp.tv_usec == 0)
+ && vout->start_jiffies)
+ timeout =
+ vout->start_jiffies + vout->frame_count * HZ / 30;
+ else
+ timeout =
+ get_jiffies(&vout->v4l2_bufs[index].timestamp);
+
+ if (jiffies >= timeout) {
+ dev_dbg(&vout->video_dev->dev,
+ "warning: timer timeout already expired.\n");
+ }
+ if (mod_timer(&vout->output_timer, timeout))
+ dev_dbg(&vout->video_dev->dev,
+ "warning: timer was already set\n");
+
+ dev_dbg(&vout->video_dev->dev,
+ "timer handler next schedule: %lu\n", timeout);
+}
+
+static int finish_previous_frame(vout_data *vout)
+{
+ struct fb_info *fbi =
+ registered_fb[vout->output_fb_num[vout->cur_disp_output]];
+ mm_segment_t old_fs;
+ int ret = 0, try = 0;
+
+ /* make sure buf[vout->disp_buf_num] in showing */
+ while (ipu_check_buffer_ready(vout->display_ch,
+ IPU_INPUT_BUFFER, vout->disp_buf_num)) {
+ if (fbi->fbops->fb_ioctl) {
+ old_fs = get_fs();
+ set_fs(KERNEL_DS);
+ ret = fbi->fbops->fb_ioctl(fbi, MXCFB_WAIT_FOR_VSYNC,
+ (unsigned int)NULL);
+ set_fs(old_fs);
+
+ if ((ret < 0) || (try == 1)) {
+ /*
+ * ic_bypass need clear display buffer ready for next update.
+ * when fb doing blank and unblank, it has chance to go into
+ * dead loop: fb unblank just after buffer 1 ready selected.
+ */
+ ipu_clear_buffer_ready(vout->display_ch, IPU_INPUT_BUFFER,
+ vout->disp_buf_num);
+ }
+ }
+ try++;
+ }
+
+ return ret;
+}
+
+static int show_current_frame(vout_data *vout)
+{
+ struct fb_info *fbi =
+ registered_fb[vout->output_fb_num[vout->cur_disp_output]];
+ mm_segment_t old_fs;
+ int ret = 0;
+
+ /* make sure buf[vout->disp_buf_num] begin to show */
+ if (ipu_get_cur_buffer_idx(vout->display_ch, IPU_INPUT_BUFFER)
+ != vout->disp_buf_num) {
+ /* wait for display frame finish */
+ if (fbi->fbops->fb_ioctl) {
+ old_fs = get_fs();
+ set_fs(KERNEL_DS);
+ ret = fbi->fbops->fb_ioctl(fbi, MXCFB_WAIT_FOR_VSYNC,
+ (unsigned int)NULL);
+ set_fs(old_fs);
+ }
+ }
+
+ return ret;
+}
+
+static void icbypass_work_func(struct work_struct *work)
+{
+ vout_data *vout =
+ container_of(work, vout_data, icbypass_work);
+ int index, ret;
+ int last_buf;
+ unsigned long lock_flags = 0;
+
+ finish_previous_frame(vout);
+
+ spin_lock_irqsave(&g_lock, lock_flags);
+
+ index = dequeue_buf(&vout->ready_q);
+ if (index == -1) { /* no buffers ready, should never occur */
+ dev_err(&vout->video_dev->dev,
+ "mxc_v4l2out: timer - no queued buffers ready\n");
+ goto exit;
+ }
+ g_buf_dq_cnt++;
+ vout->frame_count++;
+
+ vout->ipu_buf[vout->next_rdy_ipu_buf] = index;
+ ret = ipu_update_channel_buffer(vout->display_ch, IPU_INPUT_BUFFER,
+ vout->next_rdy_ipu_buf,
+ vout->v4l2_bufs[index].m.offset);
+ ret += select_display_buffer(vout, vout->next_rdy_ipu_buf);
+ if (ret < 0) {
+ dev_err(&vout->video_dev->dev,
+ "unable to update buffer %d address rc=%d\n",
+ vout->next_rdy_ipu_buf, ret);
+ goto exit;
+ }
+ spin_unlock_irqrestore(&g_lock, lock_flags);
+ show_current_frame(vout);
+ spin_lock_irqsave(&g_lock, lock_flags);
+ vout->next_rdy_ipu_buf = !vout->next_rdy_ipu_buf;
+
+ last_buf = vout->ipu_buf[vout->next_done_ipu_buf];
+ if (last_buf != -1) {
+ g_buf_output_cnt++;
+ vout->v4l2_bufs[last_buf].flags = V4L2_BUF_FLAG_DONE;
+ queue_buf(&vout->done_q, last_buf);
+ wake_up_interruptible(&vout->v4l_bufq);
+ vout->ipu_buf[vout->next_done_ipu_buf] = -1;
+ vout->next_done_ipu_buf = !vout->next_done_ipu_buf;
+ }
+
+ if (g_buf_output_cnt > 0) {
+ /* Setup timer for next buffer */
+ index = peek_next_buf(&vout->ready_q);
+ if (index != -1)
+ setup_next_buf_timer(vout, index);
+ else
+ vout->state = STATE_STREAM_PAUSED;
+
+ if (vout->state == STATE_STREAM_STOPPING) {
+ if ((vout->ipu_buf[0] == -1) && (vout->ipu_buf[1] == -1)) {
+ vout->state = STATE_STREAM_OFF;
+ }
+ }
+ }
+exit:
+ spin_unlock_irqrestore(&g_lock, lock_flags);
+}
+
+static int get_cur_fb_blank(vout_data *vout)
+{
+ struct fb_info *fbi =
+ registered_fb[vout->output_fb_num[vout->cur_disp_output]];
+ mm_segment_t old_fs;
+ int ret = 0;
+
+ /* Check BG blank first, if BG is blank, FG should be blank too */
+ if (vout->display_ch == MEM_FG_SYNC) {
+ int i, bg_found = 0;
+ for (i = 0; i < num_registered_fb; i++) {
+ struct fb_info *bg_fbi;
+ char *idstr = registered_fb[i]->fix.id;
+ if (strncmp(idstr, "DISP3 BG", 8) == 0) {
+ bg_found = 1;
+ bg_fbi = registered_fb[i];
+ if (bg_fbi->fbops->fb_ioctl) {
+ old_fs = get_fs();
+ set_fs(KERNEL_DS);
+ ret = bg_fbi->fbops->fb_ioctl(bg_fbi,
+ MXCFB_GET_FB_BLANK,
+ (unsigned int)(&vout->fb_blank));
+ set_fs(old_fs);
+ }
+ }
+ if (bg_found) {
+ if (vout->fb_blank == FB_BLANK_UNBLANK)
+ break;
+ else
+ return ret;
+ }
+ }
+ }
+
+ if (fbi->fbops->fb_ioctl) {
+ old_fs = get_fs();
+ set_fs(KERNEL_DS);
+ ret = fbi->fbops->fb_ioctl(fbi, MXCFB_GET_FB_BLANK,
+ (unsigned int)(&vout->fb_blank));
+ set_fs(old_fs);
+ }
+
+ return ret;
+}
+
+static void mxc_v4l2out_timer_handler(unsigned long arg)
+{
+ int index, ret;
+ unsigned long lock_flags = 0;
+ vout_data *vout = (vout_data *) arg;
+ static int old_fb_blank = FB_BLANK_UNBLANK;
+
+ spin_lock_irqsave(&g_lock, lock_flags);
+
+ if ((vout->state == STATE_STREAM_STOPPING)
+ || (vout->state == STATE_STREAM_OFF))
+ goto exit0;
+
+ /*
+ * If timer occurs before IPU h/w is ready, then set the state to
+ * paused and the timer will be set again when next buffer is queued
+ * or PP comletes
+ */
+ if (vout->ipu_buf[vout->next_rdy_ipu_buf] != -1) {
+ dev_dbg(&vout->video_dev->dev, "IPU buffer busy\n");
+ vout->state = STATE_STREAM_PAUSED;
+ goto exit0;
+ }
+
+ /* VDI need both buffer done before update buffer? */
+ if (INTERLACED_CONTENT(vout) &&
+ (vout->ipu_buf[!vout->next_rdy_ipu_buf] != -1)) {
+ dev_dbg(&vout->video_dev->dev, "IPU buffer busy\n");
+ vout->state = STATE_STREAM_PAUSED;
+ goto exit0;
+ }
+
+ /* Handle ic bypass mode in work queue */
+ if (vout->ic_bypass) {
+ if (queue_work(vout->v4l_wq, &vout->icbypass_work) == 0) {
+ dev_err(&vout->video_dev->dev,
+ "ic bypass work was in queue already!\n ");
+ vout->state = STATE_STREAM_PAUSED;
+ }
+ goto exit0;
+ }
+
+ get_cur_fb_blank(vout);
+ if (vout->fb_blank == FB_BLANK_UNBLANK) {
+ /* if first come back from fb blank, recover correct stack */
+ if (old_fb_blank != FB_BLANK_UNBLANK) {
+ if (vout->next_disp_ipu_buf == 1)
+ ipu_select_buffer(vout->display_ch, IPU_INPUT_BUFFER, 0);
+ else
+ ipu_select_buffer(vout->display_ch, IPU_INPUT_BUFFER, 1);
+ }
+ if (ipu_get_cur_buffer_idx(vout->display_ch, IPU_INPUT_BUFFER)
+ == vout->next_disp_ipu_buf) {
+ dev_dbg(&vout->video_dev->dev, "IPU disp busy\n");
+ index = peek_next_buf(&vout->ready_q);
+ setup_next_buf_timer(vout, index);
+ old_fb_blank = vout->fb_blank;
+ goto exit0;
+ }
+ }
+ old_fb_blank = vout->fb_blank;
+
+ /* Dequeue buffer and pass to IPU */
+ index = dequeue_buf(&vout->ready_q);
+ if (index == -1) { /* no buffers ready, should never occur */
+ dev_err(&vout->video_dev->dev,
+ "mxc_v4l2out: timer - no queued buffers ready\n");
+ goto exit0;
+ }
+ g_buf_dq_cnt++;
+ vout->frame_count++;
+
+ /* update next buffer */
+ if (LOAD_3FIELDS(vout)) {
+ int index_n = index;
+ int index_p = last_index_n;
+ vout->ipu_buf_p[vout->next_rdy_ipu_buf] = last_index_n;
+ vout->ipu_buf[vout->next_rdy_ipu_buf] = index;
+ vout->ipu_buf_n[vout->next_rdy_ipu_buf] = index;
+ ret = ipu_update_channel_buffer(vout->post_proc_ch,
+ IPU_INPUT_BUFFER,
+ vout->next_rdy_ipu_buf,
+ vout->v4l2_bufs[index].m.offset);
+ ret += ipu_update_channel_buffer(MEM_VDI_PRP_VF_MEM_P,
+ IPU_INPUT_BUFFER,
+ vout->next_rdy_ipu_buf,
+ vout->v4l2_bufs[index_p].m.offset + vout->bytesperline);
+ ret += ipu_update_channel_buffer(MEM_VDI_PRP_VF_MEM_N,
+ IPU_INPUT_BUFFER,
+ vout->next_rdy_ipu_buf,
+ vout->v4l2_bufs[index_n].m.offset + vout->bytesperline);
+ last_index_n = index;
+ } else {
+ vout->ipu_buf[vout->next_rdy_ipu_buf] = index;
+ if (vout->pp_split) {
+ vout->ipu_buf[!vout->next_rdy_ipu_buf] = index;
+ /* always left stripe */
+ ret = ipu_update_channel_buffer(vout->post_proc_ch,
+ IPU_INPUT_BUFFER,
+ 0,/* vout->next_rdy_ipu_buf,*/
+ (vout->v4l2_bufs[index].m.offset) +
+ vout->pp_left_stripe.input_column +
+ vout->pp_up_stripe.input_column * vout->bytesperline);
+
+ /* the U/V offset has to be updated inside of IDMAC */
+ /* according to stripe offset */
+ ret += ipu_update_channel_offset(vout->post_proc_ch,
+ IPU_INPUT_BUFFER,
+ vout->v2f.fmt.pix.pixelformat,
+ vout->v2f.fmt.pix.width,
+ vout->v2f.fmt.pix.height,
+ vout->bytesperline,
+ vout->offset.u_offset,
+ vout->offset.v_offset,
+ vout->pp_up_stripe.input_column,
+ vout->pp_left_stripe.input_column);
+ } else
+ ret = ipu_update_channel_buffer(vout->post_proc_ch,
+ IPU_INPUT_BUFFER,
+ vout->next_rdy_ipu_buf,
+ vout->v4l2_bufs[index].m.offset);
+ }
+
+ if (ret < 0) {
+ dev_err(&vout->video_dev->dev,
+ "unable to update buffer %d address rc=%d\n",
+ vout->next_rdy_ipu_buf, ret);
+ goto exit0;
+ }
+
+ /* set next buffer ready */
+ if (LOAD_3FIELDS(vout))
+ ret = ipu_select_multi_vdi_buffer(vout->next_rdy_ipu_buf);
+ else
+ ret = ipu_select_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER,
+ vout->next_rdy_ipu_buf);
+ if (ret < 0) {
+ dev_err(&vout->video_dev->dev,
+ "unable to set IPU buffer ready\n");
+ goto exit0;
+ }
+
+ /* Split mode use buf 0 only, no need swith buf */
+ if (!vout->pp_split)
+ vout->next_rdy_ipu_buf = !vout->next_rdy_ipu_buf;
+
+ /* Always assume display in double buffers */
+ vout->next_disp_ipu_buf = !vout->next_disp_ipu_buf;
+
+ /* Setup timer for next buffer */
+ index = peek_next_buf(&vout->ready_q);
+ if (index != -1)
+ setup_next_buf_timer(vout, index);
+ else
+ vout->state = STATE_STREAM_PAUSED;
+
+ if (vout->state == STATE_STREAM_STOPPING) {
+ if ((vout->ipu_buf[0] == -1) && (vout->ipu_buf[1] == -1)) {
+ vout->state = STATE_STREAM_OFF;
+ }
+ }
+
+ spin_unlock_irqrestore(&g_lock, lock_flags);
+
+ return;
+
+exit0:
+ spin_unlock_irqrestore(&g_lock, lock_flags);
+}
+
+static irqreturn_t mxc_v4l2out_work_irq_handler(int irq, void *dev_id)
+{
+ int last_buf;
+ int index;
+ unsigned long lock_flags = 0;
+ vout_data *vout = dev_id;
+ int pp_out_buf_left_right = 0;
+ int disp_buf_num = 0;
+ int disp_buf_num_next = 1;
+ int local_buffer = 0;
+ int pp_out_buf_offset = 0;
+ int pp_out_buf_up_down = 0;
+ int release_buffer = 0;
+ u32 eba_offset = 0;
+ u32 vertical_offset = 0;
+ u16 x_pos;
+ u16 y_pos;
+ int ret = -1;
+
+ spin_lock_irqsave(&g_lock, lock_flags);
+
+ g_irq_cnt++;
+
+ /* Process previous buffer */
+ if (LOAD_3FIELDS(vout))
+ last_buf = vout->ipu_buf_p[vout->next_done_ipu_buf];
+ else
+ last_buf = vout->ipu_buf[vout->next_done_ipu_buf];
+
+ if (last_buf != -1) {
+ /* If IC split mode on, update output buffer number */
+ if (vout->pp_split) {
+ pp_out_buf_up_down = vout->pp_split_buf_num & 1;/* left/right stripe */
+ pp_out_buf_left_right = (vout->pp_split_buf_num >> 1) & 1; /* up/down */
+ local_buffer = (vout->pp_split == 1) ? pp_out_buf_up_down :
+ pp_out_buf_left_right;
+ disp_buf_num = vout->pp_split_buf_num >> 2;
+ disp_buf_num_next =
+ ((vout->pp_split_buf_num + (vout->pp_split << 0x1)) & 7) >> 2;
+ if ((!pp_out_buf_left_right) ||
+ ((!pp_out_buf_up_down) && (vout->pp_split == 1))) {
+ if (vout->pp_split == 1) {
+ eba_offset = ((pp_out_buf_left_right + pp_out_buf_up_down) & 1) ?
+ vout->pp_right_stripe.input_column :
+ vout->pp_left_stripe.input_column;
+ vertical_offset = pp_out_buf_up_down ?
+ vout->pp_up_stripe.input_column :
+ vout->pp_down_stripe.input_column;
+
+ } else {
+ eba_offset = pp_out_buf_left_right ?
+ vout->pp_left_stripe.input_column :
+ vout->pp_right_stripe.input_column;
+ vertical_offset = pp_out_buf_left_right ?
+ vout->pp_up_stripe.input_column :
+ vout->pp_down_stripe.input_column;
+ }
+
+ ret = ipu_update_channel_buffer(vout->post_proc_ch,
+ IPU_INPUT_BUFFER,
+ (1 - local_buffer),
+ (vout->v4l2_bufs[vout->ipu_buf[disp_buf_num]].m.offset)
+ + eba_offset + vertical_offset * vout->bytesperline);
+ ret += ipu_update_channel_offset(vout->post_proc_ch,
+ IPU_INPUT_BUFFER,
+ vout->v2f.fmt.pix.pixelformat,
+ vout->v2f.fmt.pix.width,
+ vout->v2f.fmt.pix.height,
+ vout->bytesperline,
+ vout->offset.u_offset,
+ vout->offset.v_offset,
+ vertical_offset,
+ eba_offset);
+
+ /* select right stripe */
+ ret += ipu_select_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER,
+ (1 - local_buffer));
+ if (ret < 0)
+ dev_err(&vout->video_dev->dev,
+ "unable to set IPU buffer ready\n");
+ }
+
+ /* offset for next buffer's EBA */
+ eba_offset = 0;
+ if (vout->pp_split == 1) {
+ pp_out_buf_offset = ((vout->pp_split_buf_num >> 1) & 1) ?
+ vout->pp_left_stripe.output_column :
+ vout->pp_right_stripe.output_column;
+
+ eba_offset = ((vout->pp_split_buf_num & 1) ?
+ vout->pp_down_stripe.output_column :
+ vout->pp_up_stripe.output_column);
+
+ } else {
+ pp_out_buf_offset = ((vout->pp_split_buf_num >> 1) & 1) ?
+ vout->pp_right_stripe.output_column :
+ vout->pp_left_stripe.output_column;
+ eba_offset = ((vout->pp_split_buf_num >> 1) & 1) ?
+ vout->pp_down_stripe.output_column :
+ vout->pp_up_stripe.output_column;
+ }
+
+ if (vout->cur_disp_output == 5) {
+ x_pos = (vout->crop_current.left / 8) * 8;
+ y_pos = vout->crop_current.top;
+ eba_offset += (vout->xres * y_pos + x_pos) * vout->bpp / 8;
+ }
+
+
+ /* next buffer update */
+ eba_offset = vout->display_bufs[disp_buf_num_next] +
+ pp_out_buf_offset + eba_offset;
+
+ ipu_update_channel_buffer(vout->post_proc_ch, IPU_OUTPUT_BUFFER,
+ local_buffer, eba_offset);
+
+ /* next buffer ready */
+ ret = ipu_select_buffer(vout->post_proc_ch, IPU_OUTPUT_BUFFER, local_buffer);
+
+ /* next stripe_buffer index 0..7 */
+ vout->pp_split_buf_num = (vout->pp_split_buf_num + vout->pp_split) & 0x7;
+ } else {
+ disp_buf_num = vout->next_done_ipu_buf;
+ ret += ipu_select_buffer(vout->display_input_ch, IPU_OUTPUT_BUFFER,
+ vout->next_done_ipu_buf);
+ }
+
+ /* release buffer. For split mode: if second stripe is done */
+ release_buffer = vout->pp_split ? (!(vout->pp_split_buf_num & 0x3)) : 1;
+ if (release_buffer) {
+ if (vout->fb_blank == FB_BLANK_UNBLANK)
+ select_display_buffer(vout, disp_buf_num);
+ g_buf_output_cnt++;
+ vout->v4l2_bufs[last_buf].flags = V4L2_BUF_FLAG_DONE;
+ queue_buf(&vout->done_q, last_buf);
+ wake_up_interruptible(&vout->v4l_bufq);
+ vout->ipu_buf[vout->next_done_ipu_buf] = -1;
+ if (LOAD_3FIELDS(vout)) {
+ vout->ipu_buf_p[vout->next_done_ipu_buf] = -1;
+ vout->ipu_buf_n[vout->next_done_ipu_buf] = -1;
+ }
+ /* split mode use buf 0 only, no need switch buf */
+ if (!vout->pp_split)
+ vout->next_done_ipu_buf = !vout->next_done_ipu_buf;
+ }
+ } /* end of last_buf != -1 */
+
+ index = peek_next_buf(&vout->ready_q);
+ if (vout->state == STATE_STREAM_STOPPING) {
+ if ((vout->ipu_buf[0] == -1) && (vout->ipu_buf[1] == -1)) {
+ vout->state = STATE_STREAM_OFF;
+ }
+ } else if ((vout->state == STATE_STREAM_PAUSED)
+ && (index != -1)) {
+ /* Setup timer for next buffer, when stream has been paused */
+ pr_debug("next index %d\n", index);
+ setup_next_buf_timer(vout, index);
+ vout->state = STATE_STREAM_ON;
+ }
+
+ spin_unlock_irqrestore(&g_lock, lock_flags);
+
+ return IRQ_HANDLED;
+}
+
+/*!
+ * Initialize VDI channels
+ *
+ * @param vout structure vout_data *
+ *
+ * @return status 0 Success
+ */
+static int init_VDI_channel(vout_data *vout, ipu_channel_params_t params)
+{
+ struct device *dev = &vout->video_dev->dev;
+
+ if (ipu_init_channel(MEM_VDI_PRP_VF_MEM, &params) != 0) {
+ dev_dbg(dev, "Error initializing VDI current channel\n");
+ return -EINVAL;
+ }
+ if (LOAD_3FIELDS(vout)) {
+ if (ipu_init_channel(MEM_VDI_PRP_VF_MEM_P, &params) != 0) {
+ dev_err(dev, "Error initializing VDI previous channel\n");
+ return -EINVAL;
+ }
+ if (ipu_init_channel(MEM_VDI_PRP_VF_MEM_N, &params) != 0) {
+ dev_err(dev, "Error initializing VDI next channel\n");
+ return -EINVAL;
+ }
+ }
+ return 0;
+}
+
+/*!
+ * Initialize VDI channel buffers
+ *
+ * @param vout structure vout_data *
+ *
+ * @return status 0 Success
+ */
+static int init_VDI_in_channel_buffer(vout_data *vout, uint32_t in_pixel_fmt,
+ uint16_t in_width, uint16_t in_height,
+ uint32_t stride,
+ uint32_t u_offset, uint32_t v_offset)
+{
+ struct device *dev = &vout->video_dev->dev;
+
+ if (ipu_init_channel_buffer(MEM_VDI_PRP_VF_MEM, IPU_INPUT_BUFFER,
+ in_pixel_fmt, in_width, in_height, stride,
+ IPU_ROTATE_NONE,
+ vout->v4l2_bufs[vout->ipu_buf[0]].m.offset,
+ vout->v4l2_bufs[vout->ipu_buf[0]].m.offset,
+ u_offset, v_offset) != 0) {
+ dev_err(dev, "Error initializing VDI current input buffer\n");
+ return -EINVAL;
+ }
+ if (LOAD_3FIELDS(vout)) {
+ if (ipu_init_channel_buffer(MEM_VDI_PRP_VF_MEM_P,
+ IPU_INPUT_BUFFER,
+ in_pixel_fmt, in_width, in_height,
+ stride, IPU_ROTATE_NONE,
+ vout->v4l2_bufs[vout->ipu_buf_p[0]].m.offset+vout->bytesperline,
+ vout->v4l2_bufs[vout->ipu_buf_p[0]].m.offset+vout->bytesperline,
+ u_offset, v_offset) != 0) {
+ dev_err(dev, "Error initializing VDI previous input buffer\n");
+ return -EINVAL;
+ }
+ if (ipu_init_channel_buffer(MEM_VDI_PRP_VF_MEM_N,
+ IPU_INPUT_BUFFER,
+ in_pixel_fmt, in_width, in_height,
+ stride, IPU_ROTATE_NONE,
+ vout->v4l2_bufs[vout->ipu_buf_n[0]].m.offset+vout->bytesperline,
+ vout->v4l2_bufs[vout->ipu_buf_n[0]].m.offset+vout->bytesperline,
+ u_offset, v_offset) != 0) {
+ dev_err(dev, "Error initializing VDI next input buffer\n");
+ return -EINVAL;
+ }
+ }
+ return 0;
+}
+
+/*!
+ * Initialize VDI path
+ *
+ * @param vout structure vout_data *
+ *
+ * @return status 0 Success
+ */
+static int init_VDI(ipu_channel_params_t params, vout_data *vout,
+ struct device *dev, struct fb_info *fbi,
+ u16 out_width, u16 out_height)
+{
+ params.mem_prp_vf_mem.in_width = vout->v2f.fmt.pix.width;
+ params.mem_prp_vf_mem.in_height = vout->v2f.fmt.pix.height;
+ params.mem_prp_vf_mem.motion_sel = vout->motion_sel;
+ params.mem_prp_vf_mem.field_fmt = vout->field_fmt;
+ params.mem_prp_vf_mem.in_pixel_fmt = vout->v2f.fmt.pix.pixelformat;
+ params.mem_prp_vf_mem.out_width = out_width;
+ params.mem_prp_vf_mem.out_height = out_height;
+ params.mem_prp_vf_mem.out_pixel_fmt = bpp_to_fmt(fbi);
+
+ if (init_VDI_channel(vout, params) != 0) {
+ dev_err(dev, "Error init_VDI_channel channel\n");
+ return -EINVAL;
+ }
+
+ if (init_VDI_in_channel_buffer(vout,
+ params.mem_prp_vf_mem.in_pixel_fmt,
+ params.mem_prp_vf_mem.in_width,
+ params.mem_prp_vf_mem.in_height,
+ bytes_per_pixel(params.mem_prp_vf_mem.
+ in_pixel_fmt),
+ vout->offset.u_offset,
+ vout->offset.v_offset) != 0) {
+ return -EINVAL;
+ }
+
+ if (!ipu_can_rotate_in_place(vout->rotate)) {
+ if (vout->rot_pp_bufs[0]) {
+ mxc_free_buffers(vout->rot_pp_bufs,
+ vout->rot_pp_bufs_vaddr, 2,
+ vout->display_buf_size);
+ }
+ if (mxc_allocate_buffers
+ (vout->rot_pp_bufs, vout->rot_pp_bufs_vaddr, 2,
+ vout->display_buf_size) < 0) {
+ return -ENOBUFS;
+ }
+
+ if (ipu_init_channel_buffer(vout->post_proc_ch,
+ IPU_OUTPUT_BUFFER,
+ params.mem_prp_vf_mem.
+ out_pixel_fmt, out_width,
+ out_height, out_width,
+ IPU_ROTATE_NONE,
+ vout->rot_pp_bufs[0],
+ vout->rot_pp_bufs[1], 0, 0) != 0) {
+ dev_err(dev, "Error initializing PRP output buffer\n");
+ return -EINVAL;
+ }
+
+ if (ipu_init_channel(MEM_ROT_VF_MEM, NULL) != 0) {
+ dev_err(dev, "Error initializing PP ROT channel\n");
+ return -EINVAL;
+ }
+ if (ipu_init_channel_buffer(MEM_ROT_VF_MEM,
+ IPU_INPUT_BUFFER,
+ params.mem_prp_vf_mem.
+ out_pixel_fmt, out_width,
+ out_height, out_width,
+ vout->rotate,
+ vout->rot_pp_bufs[0],
+ vout->rot_pp_bufs[1], 0, 0) != 0) {
+ dev_err(dev,
+ "Error initializing PP ROT input buffer\n");
+ return -EINVAL;
+ }
+
+ /* swap width and height */
+ if (vout->rotate >= IPU_ROTATE_90_RIGHT) {
+ out_width = vout->crop_current.width;
+ out_height = vout->crop_current.height;
+ }
+
+ if (ipu_init_channel_buffer(MEM_ROT_VF_MEM,
+ IPU_OUTPUT_BUFFER,
+ params.mem_prp_vf_mem.
+ out_pixel_fmt, out_width,
+ out_height, out_width,
+ IPU_ROTATE_NONE,
+ vout->display_bufs[0],
+ vout->display_bufs[1], 0, 0) != 0) {
+ dev_err(dev,
+ "Error initializing PP-VDI output buffer\n");
+ return -EINVAL;
+ }
+
+ if (ipu_link_channels(vout->post_proc_ch, MEM_ROT_VF_MEM) < 0)
+ return -EINVAL;
+
+ vout->display_input_ch = MEM_ROT_VF_MEM;
+ ipu_enable_channel(MEM_ROT_VF_MEM);
+ ipu_select_buffer(MEM_ROT_VF_MEM, IPU_OUTPUT_BUFFER, 0);
+ ipu_select_buffer(MEM_ROT_VF_MEM, IPU_OUTPUT_BUFFER, 1);
+ } else {
+ if (ipu_init_channel_buffer(vout->post_proc_ch,
+ IPU_OUTPUT_BUFFER,
+ params.mem_prp_vf_mem.
+ out_pixel_fmt, out_width,
+ out_height, out_width,
+ vout->rotate,
+ vout->display_bufs[0],
+ vout->display_bufs[1], 0, 0) != 0) {
+ dev_err(dev,
+ "Error initializing PP-VDI output buffer\n");
+ return -EINVAL;
+ }
+ }
+ return 0;
+}
+
+/*!
+ * Initialize PP path
+ *
+ * @param params structure ipu_channel_params_t
+ *
+ * @param vout structure vout_data *
+ *
+ * @return status 0 Success
+ */
+static int init_PP(ipu_channel_params_t *params, vout_data *vout,
+ struct device *dev, struct fb_info *fbi,
+ u16 out_width, u16 out_height)
+{
+ u16 in_width, out_stride; /* stride of output channel */
+ u32 eba_offset;
+ u16 x_pos;
+ u16 y_pos;
+ dma_addr_t phy_addr0;
+ dma_addr_t phy_addr1;
+
+ eba_offset = 0;
+ x_pos = 0;
+ y_pos = 0;
+
+ params->mem_pp_mem.out_pixel_fmt = bpp_to_fmt(fbi);
+
+ if (vout->cur_disp_output == 5) {
+ x_pos = (vout->crop_current.left / 8) * 8;
+ y_pos = vout->crop_current.top;
+ eba_offset = (vout->xres*y_pos + x_pos) *
+ bytes_per_pixel(params->mem_pp_mem.out_pixel_fmt);
+ }
+
+ vout->bpp = fmt_to_bpp(params->mem_pp_mem.out_pixel_fmt);
+ out_stride = vout->xres *
+ bytes_per_pixel(params->mem_pp_mem.out_pixel_fmt);
+ in_width = params->mem_pp_mem.in_width = vout->v2f.fmt.pix.width;
+ params->mem_pp_mem.in_height = vout->v2f.fmt.pix.height;
+ params->mem_pp_mem.in_pixel_fmt = vout->v2f.fmt.pix.pixelformat;
+ params->mem_pp_mem.out_width = out_width;
+ params->mem_pp_mem.out_height = out_height;
+ params->mem_pp_mem.outh_resize_ratio = 0; /* 0 means unused */
+ params->mem_pp_mem.outv_resize_ratio = 0; /* 0 means unused */
+ /* split IC by two stripes, the by pass is impossible*/
+ if (vout->pp_split) {
+ vout->pp_left_stripe.input_column = 0;
+ vout->pp_left_stripe.output_column = 0;
+ vout->pp_right_stripe.input_column = 0;
+ vout->pp_right_stripe.output_column = 0;
+ vout->pp_up_stripe.input_column = 0;
+ vout->pp_up_stripe.output_column = 0;
+ vout->pp_down_stripe.input_column = 0;
+ vout->pp_down_stripe.output_column = 0;
+ if (vout->pp_split != 3) {
+ ipu_calc_stripes_sizes(
+ params->mem_pp_mem.in_width, /* input frame width;>1 */
+ params->mem_pp_mem.out_width, /* output frame width; >1 */
+ ipu_ic_out_max_width_size,
+ (((unsigned long long)1) << 32), /* 32bit for fractional*/
+ 1, /* equal stripes */
+ params->mem_pp_mem.in_pixel_fmt,
+ params->mem_pp_mem.out_pixel_fmt,
+ &(vout->pp_left_stripe),
+ &(vout->pp_right_stripe));
+
+ vout->pp_left_stripe.input_column = vout->pp_left_stripe.input_column *
+ fmt_to_bpp(vout->v2f.fmt.pix.pixelformat) / 8;
+ vout->pp_left_stripe.output_column = vout->pp_left_stripe.output_column *
+ fmt_to_bpp(params->mem_pp_mem.out_pixel_fmt) / 8;
+ vout->pp_right_stripe.input_column = vout->pp_right_stripe.input_column *
+ fmt_to_bpp(vout->v2f.fmt.pix.pixelformat) / 8;
+ vout->pp_right_stripe.output_column = vout->pp_right_stripe.output_column *
+ fmt_to_bpp(params->mem_pp_mem.out_pixel_fmt) / 8;
+
+
+ /* updare parameters */
+ params->mem_pp_mem.in_width = vout->pp_left_stripe.input_width;
+ params->mem_pp_mem.out_width = vout->pp_left_stripe.output_width;
+ out_width = vout->pp_left_stripe.output_width;
+ /* for using in ic_init*/
+ params->mem_pp_mem.outh_resize_ratio = vout->pp_left_stripe.irr;
+ }
+ if (vout->pp_split != 2) {
+ ipu_calc_stripes_sizes(
+ params->mem_pp_mem.in_height, /* input frame width;>1 */
+ params->mem_pp_mem.out_height, /* output frame width; >1 */
+ ipu_ic_out_max_height_size,
+ (((unsigned long long)1) << 32),/* 32bit for fractional */
+ 1, /* equal stripes */
+ params->mem_pp_mem.in_pixel_fmt,
+ params->mem_pp_mem.out_pixel_fmt,
+ &(vout->pp_up_stripe),
+ &(vout->pp_down_stripe));
+ vout->pp_down_stripe.output_column = vout->pp_down_stripe.output_column * out_stride;
+ vout->pp_up_stripe.output_column = vout->pp_up_stripe.output_column * out_stride;
+ params->mem_pp_mem.outv_resize_ratio = vout->pp_up_stripe.irr;
+ params->mem_pp_mem.in_height = vout->pp_up_stripe.input_width;/*height*/
+ out_height = vout->pp_up_stripe.output_width;/*height*/
+ if (vout->pp_split == 3)
+ vout->pp_split = 2;/*2 vertical stripe as two horizontal stripes */
+ }
+ vout->pp_split_buf_num = 0;
+ }
+
+ if (ipu_init_channel(vout->post_proc_ch, params) != 0) {
+ dev_err(dev, "Error initializing PP channel\n");
+ return -EINVAL;
+ }
+
+ /* always enable double buffer */
+ phy_addr0 = vout->v4l2_bufs[vout->ipu_buf[0]].m.offset;
+ if (vout->ipu_buf[1] == -1)
+ phy_addr1 = phy_addr0;
+ else
+ phy_addr1 = vout->v4l2_bufs[vout->ipu_buf[1]].m.offset;
+ if (ipu_init_channel_buffer(vout->post_proc_ch,
+ IPU_INPUT_BUFFER,
+ params->mem_pp_mem.in_pixel_fmt,
+ params->mem_pp_mem.in_width,
+ params->mem_pp_mem.in_height,
+ vout->v2f.fmt.pix.bytesperline /
+ bytes_per_pixel(params->mem_pp_mem.
+ in_pixel_fmt),
+ IPU_ROTATE_NONE,
+ phy_addr0,
+ phy_addr1,
+ vout->offset.u_offset,
+ vout->offset.v_offset) != 0) {
+ dev_err(dev, "Error initializing PP input buffer\n");
+ return -EINVAL;
+ }
+
+ if (!ipu_can_rotate_in_place(vout->rotate)) {
+ if (vout->rot_pp_bufs[0]) {
+ mxc_free_buffers(vout->rot_pp_bufs,
+ vout->rot_pp_bufs_vaddr, 2,
+ vout->display_buf_size);
+ }
+ if (mxc_allocate_buffers
+ (vout->rot_pp_bufs, vout->rot_pp_bufs_vaddr, 2,
+ vout->display_buf_size) < 0) {
+ return -ENOBUFS;
+ }
+
+ if (ipu_init_channel_buffer(vout->post_proc_ch,
+ IPU_OUTPUT_BUFFER,
+ params->mem_pp_mem.
+ out_pixel_fmt, out_width,
+ out_height, out_stride,
+ IPU_ROTATE_NONE,
+ vout->rot_pp_bufs[0] + eba_offset,
+ vout->rot_pp_bufs[1] + eba_offset, 0, 0) != 0) {
+ dev_err(dev, "Error initializing PP output buffer\n");
+ return -EINVAL;
+ }
+
+ if (ipu_init_channel(MEM_ROT_PP_MEM, NULL) != 0) {
+ dev_err(dev, "Error initializing PP ROT channel\n");
+ return -EINVAL;
+ }
+ if (ipu_init_channel_buffer(MEM_ROT_PP_MEM,
+ IPU_INPUT_BUFFER,
+ params->mem_pp_mem.
+ out_pixel_fmt, out_width,
+ out_height, out_stride,
+ vout->rotate,
+ vout->rot_pp_bufs[0],
+ vout->rot_pp_bufs[1], 0, 0) != 0) {
+ dev_err(dev,
+ "Error initializing PP ROT input buffer\n");
+ return -EINVAL;
+ }
+
+ /* swap width and height */
+ if (vout->rotate >= IPU_ROTATE_90_RIGHT) {
+ out_width = vout->crop_current.width;
+ out_height = vout->crop_current.height;
+ }
+
+ if (ipu_init_channel_buffer(MEM_ROT_PP_MEM,
+ IPU_OUTPUT_BUFFER,
+ params->mem_pp_mem.
+ out_pixel_fmt, out_width,
+ out_height, out_stride,
+ IPU_ROTATE_NONE,
+ vout->display_bufs[0] + eba_offset,
+ vout->display_bufs[1] + eba_offset, 0, 0) != 0) {
+ dev_err(dev, "Error initializing PP output buffer\n");
+ return -EINVAL;
+ }
+
+ if (ipu_link_channels(vout->post_proc_ch, MEM_ROT_PP_MEM) < 0)
+ return -EINVAL;
+
+ vout->display_input_ch = MEM_ROT_PP_MEM;
+ ipu_enable_channel(MEM_ROT_PP_MEM);
+ ipu_select_buffer(MEM_ROT_PP_MEM, IPU_OUTPUT_BUFFER, 0);
+ ipu_select_buffer(MEM_ROT_PP_MEM, IPU_OUTPUT_BUFFER, 1);
+ } else {
+ if (ipu_init_channel_buffer(vout->post_proc_ch,
+ IPU_OUTPUT_BUFFER,
+ params->mem_pp_mem.
+ out_pixel_fmt, out_width,
+ out_height, out_stride,
+ vout->rotate,
+ vout->display_bufs[0] + eba_offset,
+ vout->display_bufs[1] + eba_offset, 0, 0) != 0) {
+ dev_err(dev, "Error initializing PP output buffer\n");
+ return -EINVAL;
+ }
+ }
+
+ /* fix EBAs for IDMAC channels */
+ if (vout->pp_split) {
+ ipu_update_channel_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER,
+ 0,
+ vout->v4l2_bufs[vout->ipu_buf[0]].m.offset +
+ vout->pp_left_stripe.input_column +
+ vout->pp_up_stripe.input_column * vout->bytesperline);
+
+
+ ipu_update_channel_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER,
+ 1,
+ vout->v4l2_bufs[vout->ipu_buf[0]].m.offset +
+ vout->pp_right_stripe.input_column +
+ vout->pp_up_stripe.input_column * vout->bytesperline);
+
+ ipu_update_channel_buffer(vout->post_proc_ch, IPU_OUTPUT_BUFFER,
+ 0,
+ vout->display_bufs[0] + eba_offset +
+ vout->pp_left_stripe.output_column +
+ vout->pp_up_stripe.output_column);
+
+ ipu_update_channel_buffer(vout->post_proc_ch, IPU_OUTPUT_BUFFER,
+ 1,
+ vout->display_bufs[0] + eba_offset +
+ vout->pp_right_stripe.output_column +
+ vout->pp_up_stripe.output_column);
+ }
+
+ return 0;
+}
+
+/*!
+ * Start the output stream
+ *
+ * @param vout structure vout_data *
+ *
+ * @return status 0 Success
+ */
+static int mxc_v4l2out_streamon(vout_data *vout)
+{
+ struct device *dev = &vout->video_dev->dev;
+ ipu_channel_params_t params;
+ struct mxcfb_pos fb_pos;
+ struct fb_var_screeninfo fbvar;
+ struct fb_info *fbi =
+ registered_fb[vout->output_fb_num[vout->cur_disp_output]];
+ u16 out_width;
+ u16 out_height;
+ mm_segment_t old_fs;
+ unsigned int ipu_ch = CHAN_NONE;
+ unsigned int fb_fmt;
+ int rc = 0;
+
+ dev_dbg(dev, "mxc_v4l2out_streamon: field format=%d\n",
+ vout->field_fmt);
+
+ if (!vout)
+ return -EINVAL;
+
+ if (vout->state != STATE_STREAM_OFF)
+ return -EBUSY;
+
+ if (queue_size(&vout->ready_q) < 2) {
+ dev_err(dev, "2 buffers not been queued yet!\n");
+ return -EINVAL;
+ }
+
+ if ((vout->field_fmt == V4L2_FIELD_BOTTOM) || (vout->field_fmt == V4L2_FIELD_TOP)) {
+ dev_err(dev, "4 queued buffers need, not supported yet!\n");
+ return -EINVAL;
+ }
+
+ /*
+ * params init, check whether operation exceed the IC limitation:
+ * whether split mode used ( ipu version >= ipuv3 only)
+ */
+ g_irq_cnt = g_buf_output_cnt = g_buf_q_cnt = g_buf_dq_cnt = 0;
+ out_width = vout->crop_current.width;
+ out_height = vout->crop_current.height;
+ vout->disp_buf_num = 0;
+ vout->next_done_ipu_buf = 0;
+ vout->next_rdy_ipu_buf = vout->next_disp_ipu_buf = 1;
+ vout->pp_split = 0;
+ ipu_ic_out_max_height_size = 1024;
+#ifdef CONFIG_MXC_IPU_V1
+ if (cpu_is_mx35())
+ ipu_ic_out_max_width_size = 800;
+ else
+ ipu_ic_out_max_width_size = 720;
+#else
+ ipu_ic_out_max_width_size = 1024;
+#endif
+ if ((out_width > ipu_ic_out_max_width_size) ||
+ (out_height > ipu_ic_out_max_height_size))
+ vout->pp_split = 4;
+ if (!INTERLACED_CONTENT(vout)) {
+ vout->ipu_buf[0] = dequeue_buf(&vout->ready_q);
+ /* split IC by two stripes, the by pass is impossible*/
+ if ((out_width != vout->v2f.fmt.pix.width ||
+ out_height != vout->v2f.fmt.pix.height) &&
+ vout->pp_split) {
+ vout->next_done_ipu_buf = vout->next_rdy_ipu_buf = 0;
+ vout->ipu_buf[1] = vout->ipu_buf[0];
+ vout->frame_count = 1;
+ if ((out_width > ipu_ic_out_max_width_size) &&
+ (out_height > ipu_ic_out_max_height_size))
+ vout->pp_split = 1; /*4 stripes*/
+ else if (!(out_height > ipu_ic_out_max_height_size))
+ vout->pp_split = 2; /*two horizontal stripes */
+ else
+ vout->pp_split = 3; /*2 vertical stripes*/
+ } else {
+ vout->ipu_buf[1] = -1;
+ vout->frame_count = 1;
+ }
+ } else if (!LOAD_3FIELDS(vout)) {
+ vout->ipu_buf[0] = dequeue_buf(&vout->ready_q);
+ vout->ipu_buf[1] = -1;
+ vout->frame_count = 1;
+ } else {
+ vout->ipu_buf_p[0] = dequeue_buf(&vout->ready_q);
+ vout->ipu_buf[0] = dequeue_buf(&vout->ready_q);
+ vout->ipu_buf_n[0] = vout->ipu_buf[0];
+ vout->ipu_buf_p[1] = -1;
+ vout->ipu_buf[1] = -1;
+ vout->ipu_buf_n[1] = -1;
+ last_index_n = vout->ipu_buf_n[0];
+ vout->frame_count = 2;
+ }
+
+ /*
+ * Bypass IC if resizing and rotation are not needed
+ * Meanwhile, apply IC bypass to SDC only
+ */
+ fbvar = fbi->var;
+ vout->xres = fbvar.xres;
+ vout->yres = fbvar.yres;
+
+ if (vout->cur_disp_output == 3 || vout->cur_disp_output == 5) {
+ fb_fmt = vout->v2f.fmt.pix.pixelformat;
+
+ /* DC channel can not use CSC */
+ if (vout->cur_disp_output == 5) {
+ 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);
+ }
+ }
+
+ fbvar.bits_per_pixel = 16;
+ if (format_is_yuv(fb_fmt))
+ fbvar.nonstd = IPU_PIX_FMT_UYVY;
+ else
+ fbvar.nonstd = 0;
+ if (vout->cur_disp_output == 3) {
+ fbvar.xres = out_width;
+ fbvar.yres = out_height;
+ vout->xres = fbvar.xres;
+ vout->yres = fbvar.yres;
+ }
+
+ fbvar.xres_virtual = fbvar.xres;
+ fbvar.yres_virtual = fbvar.yres * 2;
+ }
+
+ if (out_width == vout->v2f.fmt.pix.width &&
+ out_height == vout->v2f.fmt.pix.height &&
+ vout->xres == out_width &&
+ vout->yres == out_height &&
+ ipu_can_rotate_in_place(vout->rotate) &&
+ (vout->bytesperline ==
+ bytes_per_pixel(vout->v2f.fmt.pix.pixelformat) * out_width) &&
+ !INTERLACED_CONTENT(vout)) {
+ vout->ic_bypass = 1;
+ } else {
+ vout->ic_bypass = 0;
+ }
+
+#ifdef CONFIG_MXC_IPU_V1
+ /* IPUv1 needs IC to do CSC */
+ if (format_is_yuv(vout->v2f.fmt.pix.pixelformat) !=
+ format_is_yuv(bpp_to_fmt(fbi)))
+#else
+ /* DC channel needs IC to do CSC */
+ if ((format_is_yuv(vout->v2f.fmt.pix.pixelformat) !=
+ format_is_yuv(fb_fmt)) &&
+ (vout->cur_disp_output == 5))
+ vout->ic_bypass = 0;
+#endif
+
+ 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);
+ }
+
+ if (ipu_ch == CHAN_NONE) {
+ dev_err(dev, "Can not get display ipu channel\n");
+ return -EINVAL;
+ }
+
+ vout->display_ch = ipu_ch;
+
+ if (vout->ic_bypass) {
+ dev_info(dev, "Bypassing IC\n");
+ vout->pp_split = 0;
+ switch (vout->v2f.fmt.pix.pixelformat) {
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_YVU420:
+ case V4L2_PIX_FMT_NV12:
+ fbvar.bits_per_pixel = 12;
+ break;
+ case V4L2_PIX_FMT_YUV422P:
+ fbvar.bits_per_pixel = 16;
+ break;
+ default:
+ fbvar.bits_per_pixel = 8*
+ bytes_per_pixel(vout->v2f.fmt.pix.pixelformat);
+ }
+ fbvar.nonstd = vout->v2f.fmt.pix.pixelformat;
+ }
+
+ /* Init display channel through fb API */
+ fbvar.yoffset = 0;
+ fbvar.activate |= FB_ACTIVATE_FORCE;
+ acquire_console_sem();
+ fbi->flags |= FBINFO_MISC_USEREVENT;
+ fb_set_var(fbi, &fbvar);
+ fbi->flags &= ~FBINFO_MISC_USEREVENT;
+ release_console_sem();
+
+ if (fbi->fbops->fb_ioctl && vout->display_ch == MEM_FG_SYNC) {
+ fb_pos.x = vout->crop_current.left;
+ fb_pos.y = vout->crop_current.top;
+ old_fs = get_fs();
+ set_fs(KERNEL_DS);
+ fbi->fbops->fb_ioctl(fbi, MXCFB_SET_OVERLAY_POS,
+ (unsigned long)&fb_pos);
+ set_fs(old_fs);
+ }
+
+ vout->display_bufs[1] = fbi->fix.smem_start;
+ vout->display_bufs[0] = fbi->fix.smem_start +
+ (fbi->fix.line_length * vout->yres);
+ vout->display_buf_size = vout->xres *
+ vout->yres * fbi->var.bits_per_pixel / 8;
+
+ /* fill black color for init fb, we assume fb has double buffer*/
+ if (format_is_yuv(vout->v2f.fmt.pix.pixelformat)) {
+ int i;
+
+ if ((vout->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_UYVY) ||
+ (vout->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV) ||
+ (!vout->ic_bypass)) {
+ short * tmp = (short *) fbi->screen_base;
+ short color;
+ if (vout->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV)
+ color = 0x8000;
+ else
+ color = 0x80;
+ for (i = 0; i < (fbi->fix.line_length * fbi->var.yres_virtual)/2;
+ i++, tmp++)
+ *tmp = color;
+ } else if ((vout->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUV420) ||
+ (vout->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YVU420) ||
+ (vout->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_NV12)) {
+ char * base = (char *)fbi->screen_base;
+ int j, screen_size = fbi->var.xres * fbi->var.yres;
+
+ for (j = 0; j < 2; j++) {
+ memset(base, 0, screen_size);
+ base += screen_size;
+ for (i = 0; i < screen_size/2; i++, base++)
+ *base = 0x80;
+ }
+ } else if (vout->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUV422P) {
+ char * base = (char *)fbi->screen_base;
+ int j, screen_size = fbi->var.xres * fbi->var.yres;
+
+ for (j = 0; j < 2; j++) {
+ memset(base, 0, screen_size);
+ base += screen_size;
+ for (i = 0; i < screen_size; i++, base++)
+ *base = 0x80;
+ }
+ }
+ } else
+ memset(fbi->screen_base, 0x0,
+ fbi->fix.line_length * fbi->var.yres_virtual);
+
+ if (INTERLACED_CONTENT(vout))
+ vout->post_proc_ch = MEM_VDI_PRP_VF_MEM;
+ else if (!vout->ic_bypass)
+ vout->post_proc_ch = MEM_PP_MEM;
+
+ /* Init IC channel */
+ if (!vout->ic_bypass) {
+ if (vout->rotate >= IPU_ROTATE_90_RIGHT) {
+ out_width = vout->crop_current.height;
+ out_height = vout->crop_current.width;
+ }
+ vout->display_input_ch = vout->post_proc_ch;
+ memset(&params, 0, sizeof(params));
+ if (INTERLACED_CONTENT(vout)) {
+ if (vout->pp_split) {
+ dev_err(&vout->video_dev->dev, "VDI split has not supported yet.\n");
+ return -1;
+ } else
+ rc = init_VDI(params, vout, dev, fbi, out_width, out_height);
+ } else {
+ rc = init_PP(&params, vout, dev, fbi, out_width, out_height);
+ }
+ if (rc < 0)
+ return rc;
+ }
+
+ if (!vout->ic_bypass) {
+ switch (vout->display_input_ch) {
+ case MEM_PP_MEM:
+ vout->work_irq = IPU_IRQ_PP_OUT_EOF;
+ break;
+ case MEM_VDI_PRP_VF_MEM:
+ vout->work_irq = IPU_IRQ_PRP_VF_OUT_EOF;
+ break;
+ case MEM_ROT_VF_MEM:
+ vout->work_irq = IPU_IRQ_PRP_VF_ROT_OUT_EOF;
+ break;
+ case MEM_ROT_PP_MEM:
+ vout->work_irq = IPU_IRQ_PP_ROT_OUT_EOF;
+ break;
+ default:
+ dev_err(&vout->video_dev->dev,
+ "not support channel, should not be here\n");
+ }
+ } else
+ vout->work_irq = -1;
+
+ if (!vout->ic_bypass && (vout->work_irq > 0)) {
+ ipu_clear_irq(vout->work_irq);
+ ipu_request_irq(vout->work_irq,
+ mxc_v4l2out_work_irq_handler,
+ 0, vout->video_dev->name, vout);
+ }
+
+ vout->state = STATE_STREAM_PAUSED;
+
+ /* Enable display and IC channels */
+ if (fbi) {
+ acquire_console_sem();
+ fb_blank(fbi, FB_BLANK_UNBLANK);
+ release_console_sem();
+ vout->fb_blank = FB_BLANK_UNBLANK;
+ } else {
+ ipu_enable_channel(vout->display_ch);
+ }
+
+ /* correct display ch buffer address */
+ ipu_update_channel_buffer(vout->display_ch, IPU_INPUT_BUFFER,
+ 0, vout->display_bufs[0]);
+ ipu_update_channel_buffer(vout->display_ch, IPU_INPUT_BUFFER,
+ 1, vout->display_bufs[1]);
+
+ if (!vout->ic_bypass) {
+#ifndef CONFIG_MXC_IPU_V1
+ ipu_enable_channel(vout->post_proc_ch);
+#endif
+ if (LOAD_3FIELDS(vout)) {
+ ipu_enable_channel(MEM_VDI_PRP_VF_MEM_P);
+ ipu_enable_channel(MEM_VDI_PRP_VF_MEM_N);
+ ipu_select_multi_vdi_buffer(0);
+ } else
+ ipu_select_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER, 0);
+ ipu_select_buffer(vout->post_proc_ch, IPU_OUTPUT_BUFFER, 0);
+ ipu_select_buffer(vout->post_proc_ch, IPU_OUTPUT_BUFFER, 1);
+#ifdef CONFIG_MXC_IPU_V1
+ ipu_enable_channel(vout->post_proc_ch);
+#endif
+ } else {
+ ipu_update_channel_buffer(vout->display_ch,
+ IPU_INPUT_BUFFER,
+ 0, vout->v4l2_bufs[vout->ipu_buf[0]].m.offset);
+ if (vout->offset.u_offset || vout->offset.v_offset)
+ /* only update u/v offset */
+ ipu_update_channel_offset(vout->display_ch,
+ IPU_INPUT_BUFFER,
+ vout->v2f.fmt.pix.pixelformat,
+ vout->v2f.fmt.pix.width,
+ vout->v2f.fmt.pix.height,
+ vout->bytesperline,
+ vout->offset.u_offset,
+ vout->offset.v_offset,
+ 0,
+ 0);
+ ipu_select_buffer(vout->display_ch, IPU_INPUT_BUFFER, 0);
+ queue_work(vout->v4l_wq, &vout->icbypass_work);
+ }
+
+ vout->start_jiffies = jiffies;
+
+ msleep(1);
+
+ dev_dbg(dev,
+ "streamon: start time = %lu jiffies\n", vout->start_jiffies);
+
+ return 0;
+}
+
+/*!
+ * Shut down the voutera
+ *
+ * @param vout structure vout_data *
+ *
+ * @return status 0 Success
+ */
+static int mxc_v4l2out_streamoff(vout_data *vout)
+{
+ struct fb_info *fbi =
+ registered_fb[vout->output_fb_num[vout->cur_disp_output]];
+ int i, retval = 0;
+ unsigned long lockflag = 0;
+
+ if (!vout)
+ return -EINVAL;
+
+ if (vout->state == STATE_STREAM_OFF) {
+ return 0;
+ }
+
+ if (!vout->ic_bypass)
+ ipu_free_irq(vout->work_irq, vout);
+
+ if (vout->ic_bypass)
+ cancel_work_sync(&vout->icbypass_work);
+
+ /* fill black color for fb, we assume fb has double buffer*/
+ if (format_is_yuv(vout->v2f.fmt.pix.pixelformat)) {
+ int i;
+
+ if ((vout->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_UYVY) ||
+ (vout->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV) ||
+ (!vout->ic_bypass)) {
+ short * tmp = (short *) fbi->screen_base;
+ short color;
+ if (vout->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV)
+ color = 0x8000;
+ else
+ color = 0x80;
+ for (i = 0; i < (fbi->fix.line_length * fbi->var.yres_virtual)/2;
+ i++, tmp++)
+ *tmp = color;
+ } else if ((vout->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUV420) ||
+ (vout->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YVU420) ||
+ (vout->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_NV12)) {
+ char * base = (char *)fbi->screen_base;
+ int j, screen_size = fbi->var.xres * fbi->var.yres;
+
+ for (j = 0; j < 2; j++) {
+ memset(base, 0, screen_size);
+ base += screen_size;
+ for (i = 0; i < screen_size/2; i++, base++)
+ *base = 0x80;
+ }
+ } else if (vout->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUV422P) {
+ char * base = (char *)fbi->screen_base;
+ int j, screen_size = fbi->var.xres * fbi->var.yres;
+
+ for (j = 0; j < 2; j++) {
+ memset(base, 0, screen_size);
+ base += screen_size;
+ for (i = 0; i < screen_size; i++, base++)
+ *base = 0x80;
+ }
+ }
+ } else
+ memset(fbi->screen_base, 0x0,
+ fbi->fix.line_length * fbi->var.yres_virtual);
+
+ spin_lock_irqsave(&g_lock, lockflag);
+
+ del_timer(&vout->output_timer);
+
+ if (vout->state == STATE_STREAM_ON) {
+ vout->state = STATE_STREAM_STOPPING;
+ }
+
+ spin_unlock_irqrestore(&g_lock, lockflag);
+
+ if (vout->display_ch == MEM_FG_SYNC) {
+ struct mxcfb_pos fb_pos;
+ mm_segment_t old_fs;
+
+ fb_pos.x = 0;
+ fb_pos.y = 0;
+ if (fbi->fbops->fb_ioctl) {
+ old_fs = get_fs();
+ set_fs(KERNEL_DS);
+ fbi->fbops->fb_ioctl(fbi, MXCFB_SET_OVERLAY_POS,
+ (unsigned long)&fb_pos);
+ set_fs(old_fs);
+ }
+ }
+
+ if (vout->ic_bypass) {
+ fbi->var.activate |= FB_ACTIVATE_FORCE;
+ acquire_console_sem();
+ fbi->flags |= FBINFO_MISC_USEREVENT;
+ fb_set_var(fbi, &fbi->var);
+ fbi->flags &= ~FBINFO_MISC_USEREVENT;
+ release_console_sem();
+
+ if (vout->display_ch == MEM_FG_SYNC) {
+ acquire_console_sem();
+ fb_blank(fbi, FB_BLANK_POWERDOWN);
+ release_console_sem();
+ }
+
+ vout->display_bufs[0] = 0;
+ vout->display_bufs[1] = 0;
+ } else if (vout->post_proc_ch == MEM_PP_MEM ||
+ vout->post_proc_ch == MEM_PRP_VF_MEM) {
+ /* SDC with Rotation */
+ if (!ipu_can_rotate_in_place(vout->rotate)) {
+ ipu_unlink_channels(MEM_PP_MEM, MEM_ROT_PP_MEM);
+ ipu_disable_channel(MEM_ROT_PP_MEM, true);
+
+ if (vout->rot_pp_bufs[0]) {
+ mxc_free_buffers(vout->rot_pp_bufs,
+ vout->rot_pp_bufs_vaddr, 2,
+ vout->display_buf_size);
+ }
+ }
+ ipu_disable_channel(MEM_PP_MEM, true);
+
+ fbi->var.activate |= FB_ACTIVATE_FORCE;
+ acquire_console_sem();
+ fbi->flags |= FBINFO_MISC_USEREVENT;
+ fb_set_var(fbi, &fbi->var);
+ fbi->flags &= ~FBINFO_MISC_USEREVENT;
+ release_console_sem();
+
+ if (vout->display_ch == MEM_FG_SYNC) {
+ acquire_console_sem();
+ fb_blank(fbi, FB_BLANK_POWERDOWN);
+ release_console_sem();
+ }
+
+ vout->display_bufs[0] = 0;
+ vout->display_bufs[1] = 0;
+
+ ipu_uninit_channel(MEM_PP_MEM);
+ if (!ipu_can_rotate_in_place(vout->rotate))
+ ipu_uninit_channel(MEM_ROT_PP_MEM);
+ } else if (INTERLACED_CONTENT(vout) &&
+ (vout->post_proc_ch == MEM_VDI_PRP_VF_MEM)) {
+ if (!ipu_can_rotate_in_place(vout->rotate)) {
+ ipu_unlink_channels(MEM_VDI_PRP_VF_MEM,
+ MEM_ROT_VF_MEM);
+ ipu_disable_channel(MEM_ROT_VF_MEM, true);
+
+ if (vout->rot_pp_bufs[0]) {
+ mxc_free_buffers(vout->rot_pp_bufs,
+ vout->rot_pp_bufs_vaddr, 2,
+ vout->display_buf_size);
+ }
+ }
+
+ ipu_disable_channel(MEM_VDI_PRP_VF_MEM, true);
+ if (LOAD_3FIELDS(vout)) {
+ ipu_disable_channel(MEM_VDI_PRP_VF_MEM_P, true);
+ ipu_disable_channel(MEM_VDI_PRP_VF_MEM_N, true);
+ }
+
+ fbi->var.activate |= FB_ACTIVATE_FORCE;
+ acquire_console_sem();
+ fbi->flags |= FBINFO_MISC_USEREVENT;
+ fb_set_var(fbi, &fbi->var);
+ fbi->flags &= ~FBINFO_MISC_USEREVENT;
+ release_console_sem();
+
+ if (vout->display_ch == MEM_FG_SYNC) {
+ acquire_console_sem();
+ fb_blank(fbi, FB_BLANK_POWERDOWN);
+ release_console_sem();
+ }
+
+ vout->display_bufs[0] = 0;
+ vout->display_bufs[1] = 0;
+
+ ipu_uninit_channel(MEM_VDI_PRP_VF_MEM);
+ if (LOAD_3FIELDS(vout)) {
+ ipu_uninit_channel(MEM_VDI_PRP_VF_MEM_P);
+ ipu_uninit_channel(MEM_VDI_PRP_VF_MEM_N);
+ }
+ if (!ipu_can_rotate_in_place(vout->rotate))
+ ipu_uninit_channel(MEM_ROT_VF_MEM);
+ }
+
+ vout->ready_q.head = vout->ready_q.tail = 0;
+ vout->done_q.head = vout->done_q.tail = 0;
+ for (i = 0; i < vout->buffer_cnt; i++) {
+ vout->v4l2_bufs[i].flags = 0;
+ vout->v4l2_bufs[i].timestamp.tv_sec = 0;
+ vout->v4l2_bufs[i].timestamp.tv_usec = 0;
+ }
+
+ vout->post_proc_ch = CHAN_NONE;
+ vout->state = STATE_STREAM_OFF;
+
+ return retval;
+}
+
+/*
+ * Valid whether the palette is supported
+ *
+ * @param palette V4L2_PIX_FMT_RGB565, V4L2_PIX_FMT_BGR24 or V4L2_PIX_FMT_BGR32
+ *
+ * @return 1 if supported, 0 if failed
+ */
+static inline int valid_mode(u32 palette)
+{
+ return ((palette == V4L2_PIX_FMT_RGB565) ||
+ (palette == V4L2_PIX_FMT_BGR24) ||
+ (palette == V4L2_PIX_FMT_RGB24) ||
+ (palette == V4L2_PIX_FMT_BGR32) ||
+ (palette == V4L2_PIX_FMT_RGB32) ||
+ (palette == V4L2_PIX_FMT_NV12) ||
+ (palette == V4L2_PIX_FMT_UYVY) ||
+ (palette == V4L2_PIX_FMT_YUYV) ||
+ (palette == V4L2_PIX_FMT_YUV422P) ||
+ (palette == V4L2_PIX_FMT_YUV444) ||
+ (palette == V4L2_PIX_FMT_YUV420));
+}
+
+/*
+ * V4L2 - Handles VIDIOC_G_FMT Ioctl
+ *
+ * @param vout structure vout_data *
+ *
+ * @param v4l2_format structure v4l2_format *
+ *
+ * @return status 0 success, EINVAL failed
+ */
+static int mxc_v4l2out_g_fmt(vout_data *vout, struct v4l2_format *f)
+{
+ if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) {
+ return -EINVAL;
+ }
+ *f = vout->v2f;
+ return 0;
+}
+
+/*
+ * V4L2 - Handles VIDIOC_S_FMT Ioctl
+ *
+ * @param vout structure vout_data *
+ *
+ * @param v4l2_format structure v4l2_format *
+ *
+ * @return status 0 success, EINVAL failed
+ */
+static int mxc_v4l2out_s_fmt(vout_data *vout, struct v4l2_format *f)
+{
+ int retval = 0;
+ u32 size = 0;
+ u32 bytesperline;
+
+ if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) {
+ retval = -EINVAL;
+ goto err0;
+ }
+ if (!valid_mode(f->fmt.pix.pixelformat)) {
+ dev_err(&vout->video_dev->dev, "pixel format not supported\n");
+ retval = -EINVAL;
+ goto err0;
+ }
+
+ bytesperline = (f->fmt.pix.width * fmt_to_bpp(f->fmt.pix.pixelformat)) /
+ 8;
+ if (f->fmt.pix.bytesperline < bytesperline) {
+ f->fmt.pix.bytesperline = bytesperline;
+ } else {
+ bytesperline = f->fmt.pix.bytesperline;
+ }
+ vout->bytesperline = bytesperline;
+
+ /* Based on http://v4l2spec.bytesex.org/spec/x6386.htm#V4L2-FIELD */
+ vout->field_fmt = f->fmt.pix.field;
+ switch (vout->field_fmt) {
+ /* Images are in progressive format, not interlaced */
+ case V4L2_FIELD_NONE:
+ break;
+ /* The two fields of a frame are passed in separate buffers,
+ in temporal order, i. e. the older one first. */
+ case V4L2_FIELD_ALTERNATE:
+ dev_err(&vout->video_dev->dev,
+ "V4L2_FIELD_ALTERNATE field format not supported yet!\n");
+ break;
+ case V4L2_FIELD_INTERLACED_TB:
+ case V4L2_FIELD_INTERLACED_BT:
+ if (cpu_is_mx51() || cpu_is_mx53())
+ break;
+ dev_err(&vout->video_dev->dev,
+ "De-interlacing not supported in this device!\n");
+ vout->field_fmt = V4L2_FIELD_NONE;
+ break;
+ default:
+ vout->field_fmt = V4L2_FIELD_NONE;
+ break;
+ }
+
+ switch (f->fmt.pix.pixelformat) {
+ case V4L2_PIX_FMT_YUV422P:
+ /* byteperline for YUV planar formats is for
+ Y plane only */
+ size = bytesperline * f->fmt.pix.height * 2;
+ break;
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_NV12:
+ size = (bytesperline * f->fmt.pix.height * 3) / 2;
+ break;
+ default:
+ size = bytesperline * f->fmt.pix.height;
+ break;
+ }
+
+ /* Return the actual size of the image to the app */
+ if (f->fmt.pix.sizeimage < size) {
+ f->fmt.pix.sizeimage = size;
+ } else {
+ size = f->fmt.pix.sizeimage;
+ }
+
+ vout->v2f.fmt.pix = f->fmt.pix;
+ if (vout->v2f.fmt.pix.priv != 0) {
+ if (copy_from_user(&vout->offset,
+ (void *)vout->v2f.fmt.pix.priv,
+ sizeof(vout->offset))) {
+ retval = -EFAULT;
+ goto err0;
+ }
+ } else {
+ vout->offset.u_offset = 0;
+ vout->offset.v_offset = 0;
+ }
+
+ retval = 0;
+ err0:
+ return retval;
+}
+
+/*
+ * V4L2 - Handles VIDIOC_G_CTRL Ioctl
+ *
+ * @param vout structure vout_data *
+ *
+ * @param c structure v4l2_control *
+ *
+ * @return status 0 success, EINVAL failed
+ */
+static int mxc_get_v42lout_control(vout_data *vout, struct v4l2_control *c)
+{
+ switch (c->id) {
+ case V4L2_CID_HFLIP:
+ return (vout->rotate & IPU_ROTATE_HORIZ_FLIP) ? 1 : 0;
+ case V4L2_CID_VFLIP:
+ return (vout->rotate & IPU_ROTATE_VERT_FLIP) ? 1 : 0;
+ case (V4L2_CID_PRIVATE_BASE + 1):
+ return vout->rotate;
+ default:
+ return -EINVAL;
+ }
+}
+
+/*
+ * V4L2 - Handles VIDIOC_S_CTRL Ioctl
+ *
+ * @param vout structure vout_data *
+ *
+ * @param c structure v4l2_control *
+ *
+ * @return status 0 success, EINVAL failed
+ */
+static int mxc_set_v42lout_control(vout_data *vout, struct v4l2_control *c)
+{
+ switch (c->id) {
+ case V4L2_CID_HFLIP:
+ vout->rotate |= c->value ? IPU_ROTATE_HORIZ_FLIP :
+ IPU_ROTATE_NONE;
+ break;
+ case V4L2_CID_VFLIP:
+ vout->rotate |= c->value ? IPU_ROTATE_VERT_FLIP :
+ IPU_ROTATE_NONE;
+ break;
+ case V4L2_CID_MXC_ROT:
+ vout->rotate = c->value;
+ break;
+ case V4L2_CID_MXC_MOTION:
+ vout->motion_sel = c->value;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/*!
+ * V4L2 interface - open function
+ *
+ * @param file structure file *
+ *
+ * @return status 0 success, ENODEV invalid device instance,
+ * ENODEV timeout, ERESTARTSYS interrupted by user
+ */
+static int mxc_v4l2out_open(struct file *file)
+{
+ struct video_device *dev = video_devdata(file);
+ vout_data *vout = video_get_drvdata(dev);
+ int err;
+
+ if (!vout) {
+ return -ENODEV;
+ }
+
+ down(&vout->busy_lock);
+
+ err = -EINTR;
+ if (signal_pending(current))
+ goto oops;
+
+
+ if (vout->open_count++ == 0) {
+ init_waitqueue_head(&vout->v4l_bufq);
+
+ init_timer(&vout->output_timer);
+ vout->output_timer.function = mxc_v4l2out_timer_handler;
+ vout->output_timer.data = (unsigned long)vout;
+
+ vout->state = STATE_STREAM_OFF;
+ vout->rotate = IPU_ROTATE_NONE;
+
+ vout->v4l_wq = create_singlethread_workqueue("v4l2q");
+ if (!vout->v4l_wq) {
+ dev_dbg(&dev->dev,
+ "Could not create work queue\n");
+ err = -ENOMEM;
+ goto oops;
+ }
+
+ INIT_WORK(&vout->icbypass_work, icbypass_work_func);
+ }
+
+ file->private_data = dev;
+
+ up(&vout->busy_lock);
+
+ return 0;
+
+ oops:
+ up(&vout->busy_lock);
+ return err;
+}
+
+/*!
+ * V4L2 interface - close function
+ *
+ * @param file struct file *
+ *
+ * @return 0 success
+ */
+static int mxc_v4l2out_close(struct file *file)
+{
+ struct video_device *dev = video_devdata(file);
+ vout_data *vout = video_get_drvdata(dev);
+
+ if (--vout->open_count == 0) {
+ if (vout->state != STATE_STREAM_OFF)
+ mxc_v4l2out_streamoff(vout);
+
+ file->private_data = NULL;
+
+ mxc_free_buffers(vout->queue_buf_paddr, vout->queue_buf_vaddr,
+ vout->buffer_cnt, vout->queue_buf_size);
+ vout->buffer_cnt = 0;
+ mxc_free_buffers(vout->rot_pp_bufs, vout->rot_pp_bufs_vaddr, 2,
+ vout->display_buf_size);
+
+ /* capture off */
+ wake_up_interruptible(&vout->v4l_bufq);
+
+ flush_workqueue(vout->v4l_wq);
+ destroy_workqueue(vout->v4l_wq);
+ }
+
+ return 0;
+}
+
+/*!
+ * V4L2 interface - ioctl function
+ *
+ * @param file struct file *
+ *
+ * @param ioctlnr unsigned int
+ *
+ * @param arg void *
+ *
+ * @return 0 success, ENODEV for invalid device instance,
+ * -1 for other errors.
+ */
+static long
+mxc_v4l2out_do_ioctl(struct file *file,
+ unsigned int ioctlnr, void *arg)
+{
+ struct video_device *vdev = file->private_data;
+ vout_data *vout = video_get_drvdata(vdev);
+ int retval = 0;
+ int i = 0;
+
+ if (!vout)
+ return -EBADF;
+
+ /* make this _really_ smp-safe */
+ if (down_interruptible(&vout->busy_lock))
+ return -EBUSY;
+
+ switch (ioctlnr) {
+ case VIDIOC_QUERYCAP:
+ {
+ struct v4l2_capability *cap = arg;
+ strcpy(cap->driver, "mxc_v4l2_output");
+ cap->version = 0;
+ cap->capabilities =
+ V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING;
+ cap->card[0] = '\0';
+ cap->bus_info[0] = '\0';
+ retval = 0;
+ break;
+ }
+ case VIDIOC_G_FMT:
+ {
+ struct v4l2_format *gf = arg;
+ retval = mxc_v4l2out_g_fmt(vout, gf);
+ break;
+ }
+ case VIDIOC_S_FMT:
+ {
+ struct v4l2_format *sf = arg;
+ if (vout->state != STATE_STREAM_OFF) {
+ retval = -EBUSY;
+ break;
+ }
+ retval = mxc_v4l2out_s_fmt(vout, sf);
+ break;
+ }
+ case VIDIOC_REQBUFS:
+ {
+ struct v4l2_requestbuffers *req = arg;
+ if ((req->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) ||
+ (req->memory != V4L2_MEMORY_MMAP)) {
+ dev_dbg(&vdev->dev,
+ "VIDIOC_REQBUFS: incorrect buffer type\n");
+ retval = -EINVAL;
+ break;
+ }
+
+ if (req->count == 0)
+ mxc_v4l2out_streamoff(vout);
+
+ if (vout->state == STATE_STREAM_OFF) {
+ if (vout->queue_buf_paddr[0] != 0) {
+ mxc_free_buffers(vout->queue_buf_paddr,
+ vout->queue_buf_vaddr,
+ vout->buffer_cnt,
+ vout->queue_buf_size);
+ dev_dbg(&vdev->dev,
+ "VIDIOC_REQBUFS: freed buffers\n");
+ }
+ vout->buffer_cnt = 0;
+ } else {
+ dev_dbg(&vdev->dev,
+ "VIDIOC_REQBUFS: Buffer is in use\n");
+ retval = -EBUSY;
+ break;
+ }
+
+ if (req->count == 0)
+ break;
+
+ if (req->count < MIN_FRAME_NUM) {
+ req->count = MIN_FRAME_NUM;
+ } else if (req->count > MAX_FRAME_NUM) {
+ req->count = MAX_FRAME_NUM;
+ }
+ vout->buffer_cnt = req->count;
+ vout->queue_buf_size =
+ PAGE_ALIGN(vout->v2f.fmt.pix.sizeimage);
+
+ retval = mxc_allocate_buffers(vout->queue_buf_paddr,
+ vout->queue_buf_vaddr,
+ vout->buffer_cnt,
+ vout->queue_buf_size);
+ if (retval < 0)
+ break;
+
+ /* Init buffer queues */
+ vout->done_q.head = 0;
+ vout->done_q.tail = 0;
+ vout->ready_q.head = 0;
+ vout->ready_q.tail = 0;
+
+ for (i = 0; i < vout->buffer_cnt; i++) {
+ memset(&(vout->v4l2_bufs[i]), 0,
+ sizeof(vout->v4l2_bufs[i]));
+ vout->v4l2_bufs[i].flags = 0;
+ vout->v4l2_bufs[i].memory = V4L2_MEMORY_MMAP;
+ vout->v4l2_bufs[i].index = i;
+ vout->v4l2_bufs[i].type =
+ V4L2_BUF_TYPE_VIDEO_OUTPUT;
+ vout->v4l2_bufs[i].length =
+ PAGE_ALIGN(vout->v2f.fmt.pix.sizeimage);
+ vout->v4l2_bufs[i].m.offset =
+ (unsigned long)vout->queue_buf_paddr[i];
+ vout->v4l2_bufs[i].timestamp.tv_sec = 0;
+ vout->v4l2_bufs[i].timestamp.tv_usec = 0;
+ }
+ break;
+ }
+ case VIDIOC_QUERYBUF:
+ {
+ struct v4l2_buffer *buf = arg;
+ u32 type = buf->type;
+ int index = buf->index;
+
+ if ((type != V4L2_BUF_TYPE_VIDEO_OUTPUT) ||
+ (index >= vout->buffer_cnt)) {
+ dev_dbg(&vdev->dev,
+ "VIDIOC_QUERYBUFS: incorrect buffer type\n");
+ retval = -EINVAL;
+ break;
+ }
+ down(&vout->param_lock);
+ memcpy(buf, &(vout->v4l2_bufs[index]), sizeof(*buf));
+ up(&vout->param_lock);
+ break;
+ }
+ case VIDIOC_QBUF:
+ {
+ struct v4l2_buffer *buf = arg;
+ int index = buf->index;
+ unsigned long lock_flags;
+ int param[5][3];
+
+ if ((buf->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) ||
+ (index >= vout->buffer_cnt)) {
+ retval = -EINVAL;
+ break;
+ }
+
+ dev_dbg(&vdev->dev, "VIDIOC_QBUF: %d field = %d\n", buf->index, buf->field);
+
+ /* mmapped buffers are L1 WB cached,
+ * so we need to clean them */
+ if (buf->memory & V4L2_MEMORY_MMAP) {
+ flush_cache_all();
+ }
+
+ spin_lock_irqsave(&g_lock, lock_flags);
+
+ memcpy(&(vout->v4l2_bufs[index]), buf, sizeof(*buf));
+ vout->v4l2_bufs[index].flags |= V4L2_BUF_FLAG_QUEUED;
+
+ g_buf_q_cnt++;
+ if (vout->v4l2_bufs[index].reserved)
+ if (!copy_from_user(&param[0][0],
+ (void *)vout->
+ v4l2_bufs[index]
+ .reserved, sizeof(param)))
+ ipu_set_csc_coefficients(vout->
+ display_ch,
+ param);
+ queue_buf(&vout->ready_q, index);
+ if (vout->state == STATE_STREAM_PAUSED) {
+ index = peek_next_buf(&vout->ready_q);
+ setup_next_buf_timer(vout, index);
+ vout->state = STATE_STREAM_ON;
+ }
+
+ spin_unlock_irqrestore(&g_lock, lock_flags);
+ break;
+ }
+ case VIDIOC_DQBUF:
+ {
+ struct v4l2_buffer *buf = arg;
+ int idx;
+
+ if ((queue_size(&vout->done_q) == 0) &&
+ (file->f_flags & O_NONBLOCK)) {
+ retval = -EAGAIN;
+ break;
+ }
+
+ if (!wait_event_interruptible_timeout(vout->v4l_bufq,
+ queue_size(&vout->
+ done_q)
+ != 0, 10 * HZ)) {
+ dev_dbg(&vdev->dev, "VIDIOC_DQBUF: timeout\n");
+ retval = -ETIME;
+ break;
+ } else if (signal_pending(current)) {
+ dev_dbg(&vdev->dev,
+ "VIDIOC_DQBUF: interrupt received\n");
+ retval = -ERESTARTSYS;
+ break;
+ }
+ idx = dequeue_buf(&vout->done_q);
+ if (idx == -1) { /* No frame free */
+ dev_dbg(&vdev->dev,
+ "VIDIOC_DQBUF: no free buffers, returning\n");
+ retval = -EAGAIN;
+ break;
+ }
+ if ((vout->v4l2_bufs[idx].flags & V4L2_BUF_FLAG_DONE) ==
+ 0)
+ dev_dbg(&vdev->dev,
+ "VIDIOC_DQBUF: buffer in done q, but not "
+ "flagged as done\n");
+
+ vout->v4l2_bufs[idx].flags = 0;
+ memcpy(buf, &(vout->v4l2_bufs[idx]), sizeof(*buf));
+ dev_dbg(&vdev->dev, "VIDIOC_DQBUF: %d\n", buf->index);
+ break;
+ }
+ case VIDIOC_STREAMON:
+ {
+ retval = mxc_v4l2out_streamon(vout);
+ break;
+ }
+ case VIDIOC_STREAMOFF:
+ {
+ retval = mxc_v4l2out_streamoff(vout);
+ break;
+ }
+ case VIDIOC_G_CTRL:
+ {
+ retval = mxc_get_v42lout_control(vout, arg);
+ break;
+ }
+ case VIDIOC_S_CTRL:
+ {
+ retval = mxc_set_v42lout_control(vout, arg);
+ break;
+ }
+ case VIDIOC_CROPCAP:
+ {
+ struct v4l2_cropcap *cap = arg;
+
+ if (cap->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) {
+ retval = -EINVAL;
+ break;
+ }
+
+ cap->bounds = vout->crop_bounds[vout->cur_disp_output];
+ cap->defrect = vout->crop_bounds[vout->cur_disp_output];
+ retval = 0;
+ break;
+ }
+ case VIDIOC_G_CROP:
+ {
+ struct v4l2_crop *crop = arg;
+
+ if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) {
+ retval = -EINVAL;
+ break;
+ }
+ crop->c = vout->crop_current;
+ break;
+ }
+ case VIDIOC_S_CROP:
+ {
+ struct v4l2_crop *crop = arg;
+ struct v4l2_rect *b =
+ &(vout->crop_bounds[vout->cur_disp_output]);
+
+ if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) {
+ retval = -EINVAL;
+ break;
+ }
+ if (crop->c.height < 0) {
+ retval = -EINVAL;
+ break;
+ }
+ if (crop->c.width < 0) {
+ retval = -EINVAL;
+ break;
+ }
+
+ /* only full screen supported for SDC BG and SDC DC */
+ if (vout->cur_disp_output == 4) {
+ crop->c = vout->crop_current;
+ break;
+ }
+
+ if (crop->c.top < b->top)
+ crop->c.top = b->top;
+ if (crop->c.top >= b->top + b->height)
+ crop->c.top = b->top + b->height - 1;
+ if (crop->c.height > b->top - crop->c.top + b->height)
+ crop->c.height =
+ b->top - crop->c.top + b->height;
+
+ if (crop->c.left < b->left)
+ crop->c.left = b->left;
+ if (crop->c.left >= b->left + b->width)
+ crop->c.left = b->left + b->width - 1;
+ if (crop->c.width > b->left - crop->c.left + b->width)
+ crop->c.width =
+ b->left - crop->c.left + b->width;
+
+ /* stride line limitation */
+ crop->c.height -= crop->c.height % 8;
+ crop->c.width -= crop->c.width % 8;
+
+ vout->crop_current = crop->c;
+ break;
+ }
+ case VIDIOC_ENUMOUTPUT:
+ {
+ struct v4l2_output *output = arg;
+
+ if ((output->index >= 5) ||
+ (vout->output_enabled[output->index] == false)) {
+ retval = -EINVAL;
+ break;
+ }
+
+ if (output->index >= 3)
+ *output = mxc_outputs[MXC_V4L2_OUT_2_SDC];
+ break;
+ }
+ case VIDIOC_G_OUTPUT:
+ {
+ int *p_output_num = arg;
+
+ *p_output_num = vout->cur_disp_output;
+ break;
+ }
+ case VIDIOC_S_OUTPUT:
+ {
+ int *p_output_num = arg;
+ int fbnum;
+ struct v4l2_rect *b;
+
+ if ((*p_output_num >= MXC_V4L2_OUT_NUM_OUTPUTS) ||
+ (vout->output_enabled[*p_output_num] == false)) {
+ retval = -EINVAL;
+ break;
+ }
+
+ if (vout->state != STATE_STREAM_OFF) {
+ retval = -EBUSY;
+ break;
+ }
+
+ vout->cur_disp_output = *p_output_num;
+
+ /* Update bounds in case they have changed */
+ b = &vout->crop_bounds[vout->cur_disp_output];
+
+ fbnum = vout->output_fb_num[vout->cur_disp_output];
+
+ /*
+ * For FG overlay, it uses BG window parameter as
+ * limitation reference; and BG must be enabled to
+ * support FG.
+ */
+ if (vout->cur_disp_output == 3) {
+ unsigned int i, ipu_ch = CHAN_NONE;
+ struct fb_info *fbi;
+ mm_segment_t old_fs;
+
+ for (i = 0; i < num_registered_fb; i++) {
+ fbi = registered_fb[i];
+ 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);
+ }
+ if (ipu_ch == CHAN_NONE) {
+ dev_err(&vdev->dev,
+ "Can't get disp ipu channel\n");
+ retval = -EINVAL;
+ break;
+ }
+
+ if (ipu_ch == MEM_BG_SYNC) {
+ fbnum = i;
+ break;
+ }
+ }
+ }
+
+ b->width = registered_fb[fbnum]->var.xres;
+ b->height = registered_fb[fbnum]->var.yres;
+
+ vout->crop_current = *b;
+ break;
+ }
+ case VIDIOC_ENUM_FMT:
+ case VIDIOC_TRY_FMT:
+ case VIDIOC_QUERYCTRL:
+ case VIDIOC_G_PARM:
+ case VIDIOC_ENUMSTD:
+ case VIDIOC_G_STD:
+ case VIDIOC_S_STD:
+ case VIDIOC_G_TUNER:
+ case VIDIOC_S_TUNER:
+ case VIDIOC_G_FREQUENCY:
+ case VIDIOC_S_FREQUENCY:
+ default:
+ retval = -EINVAL;
+ break;
+ }
+
+ up(&vout->busy_lock);
+ return retval;
+}
+
+/*
+ * V4L2 interface - ioctl function
+ *
+ * @return None
+ */
+static long
+mxc_v4l2out_ioctl(struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ return video_usercopy(file, cmd, arg, mxc_v4l2out_do_ioctl);
+}
+
+/*!
+ * V4L2 interface - mmap function
+ *
+ * @param file structure file *
+ *
+ * @param vma structure vm_area_struct *
+ *
+ * @return status 0 Success, EINTR busy lock error,
+ * ENOBUFS remap_page error
+ */
+static int mxc_v4l2out_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ struct video_device *vdev = video_devdata(file);
+ unsigned long size = vma->vm_end - vma->vm_start;
+ int res = 0;
+ int i;
+ vout_data *vout = video_get_drvdata(vdev);
+
+ dev_dbg(&vdev->dev, "pgoff=0x%lx, start=0x%lx, end=0x%lx\n",
+ vma->vm_pgoff, vma->vm_start, vma->vm_end);
+
+ /* make this _really_ smp-safe */
+ if (down_interruptible(&vout->busy_lock))
+ return -EINTR;
+
+ for (i = 0; i < vout->buffer_cnt; i++) {
+ if ((vout->v4l2_bufs[i].m.offset ==
+ (vma->vm_pgoff << PAGE_SHIFT)) &&
+ (vout->v4l2_bufs[i].length >= size)) {
+ vout->v4l2_bufs[i].flags |= V4L2_BUF_FLAG_MAPPED;
+ break;
+ }
+ }
+ if (i == vout->buffer_cnt) {
+ res = -ENOBUFS;
+ goto mxc_mmap_exit;
+ }
+
+ /* make buffers inner write-back, outer write-thru cacheable */
+ /* vma->vm_page_prot = pgprot_outer_wrthru(vma->vm_page_prot);*/
+
+ if (remap_pfn_range(vma, vma->vm_start,
+ vma->vm_pgoff, size, vma->vm_page_prot)) {
+ dev_dbg(&vdev->dev, "mmap remap_pfn_range failed\n");
+ res = -ENOBUFS;
+ goto mxc_mmap_exit;
+ }
+
+ vma->vm_flags &= ~VM_IO; /* using shared anonymous pages */
+
+ mxc_mmap_exit:
+ up(&vout->busy_lock);
+ return res;
+}
+
+/*!
+ * V4L2 interface - poll function
+ *
+ * @param file structure file *
+ *
+ * @param wait structure poll_table_struct *
+ *
+ * @return status POLLIN | POLLRDNORM
+ */
+static unsigned int mxc_v4l2out_poll(struct file *file, struct poll_table_struct * wait)
+{
+ struct video_device *dev = video_devdata(file);
+ vout_data *vout = video_get_drvdata(dev);
+
+ wait_queue_head_t *queue = NULL;
+ int res = POLLIN | POLLRDNORM;
+
+ if (down_interruptible(&vout->busy_lock))
+ return -EINTR;
+
+ queue = &vout->v4l_bufq;
+ poll_wait(file, queue, wait);
+
+ up(&vout->busy_lock);
+ return res;
+}
+
+static struct
+v4l2_file_operations mxc_v4l2out_fops = {
+ .owner = THIS_MODULE,
+ .open = mxc_v4l2out_open,
+ .release = mxc_v4l2out_close,
+ .ioctl = mxc_v4l2out_ioctl,
+ .mmap = mxc_v4l2out_mmap,
+ .poll = mxc_v4l2out_poll,
+};
+
+static struct video_device mxc_v4l2out_template = {
+ .name = "MXC Video Output",
+ .vfl_type = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING,
+ .fops = &mxc_v4l2out_fops,
+ .release = video_device_release,
+};
+
+static ssize_t show_streaming(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct video_device *video_dev = container_of(dev,
+ struct video_device, dev);
+ vout_data *vout = video_get_drvdata(video_dev);
+
+ if (vout->state == STATE_STREAM_OFF)
+ return sprintf(buf, "stream off\n");
+ else if (vout->state == STATE_STREAM_PAUSED)
+ return sprintf(buf, "stream paused\n");
+ else
+ return sprintf(buf, "stream on\n");
+}
+static DEVICE_ATTR(fsl_v4l2_output_property, S_IRUGO, show_streaming, NULL);
+
+/*!
+ * Probe routine for the framebuffer driver. It is called during the
+ * driver binding process. The following functions are performed in
+ * this routine: Framebuffer initialization, Memory allocation and
+ * mapping, Framebuffer registration, IPU initialization.
+ *
+ * @return Appropriate error code to the kernel common code
+ */
+static int mxc_v4l2out_probe(struct platform_device *pdev)
+{
+ int i;
+ vout_data *vout;
+
+ /*
+ * Allocate sufficient memory for the fb structure
+ */
+ vout = kmalloc(sizeof(vout_data), GFP_KERNEL);
+
+ if (!vout)
+ return 0;
+
+ memset(vout, 0, sizeof(vout_data));
+
+ vout->video_dev = video_device_alloc();
+ if (vout->video_dev == NULL)
+ return -1;
+ vout->video_dev->minor = -1;
+
+ *(vout->video_dev) = mxc_v4l2out_template;
+
+ /* register v4l device */
+ if (video_register_device(vout->video_dev,
+ VFL_TYPE_GRABBER, video_nr) == -1) {
+ dev_dbg(&pdev->dev, "video_register_device failed\n");
+ return 0;
+ }
+ dev_info(&pdev->dev, "Registered device video%d\n",
+ vout->video_dev->minor & 0x1f);
+ /*vout->video_dev->dev = &pdev->dev;*/
+
+ video_set_drvdata(vout->video_dev, vout);
+
+ init_MUTEX(&vout->param_lock);
+ init_MUTEX(&vout->busy_lock);
+
+ /* setup outputs and cropping */
+ vout->cur_disp_output = -1;
+ for (i = 0; i < num_registered_fb; i++) {
+ char *idstr = registered_fb[i]->fix.id;
+ if (strncmp(idstr, "DISP", 4) == 0) {
+ int disp_num = idstr[4] - '0';
+ if (disp_num == 3) {
+ if (strcmp(idstr, "DISP3 BG - DI1") == 0)
+ disp_num = 5;
+ else if (strncmp(idstr, "DISP3 BG", 8) == 0)
+ disp_num = 4;
+ }
+ vout->crop_bounds[disp_num].left = 0;
+ vout->crop_bounds[disp_num].top = 0;
+ vout->crop_bounds[disp_num].width =
+ registered_fb[i]->var.xres;
+ vout->crop_bounds[disp_num].height =
+ registered_fb[i]->var.yres;
+ vout->output_enabled[disp_num] = true;
+ vout->output_fb_num[disp_num] = i;
+ if (vout->cur_disp_output == -1) {
+ vout->cur_disp_output = disp_num;
+ }
+ }
+
+ }
+ vout->crop_current = vout->crop_bounds[vout->cur_disp_output];
+
+ platform_set_drvdata(pdev, vout);
+
+ if (device_create_file(&vout->video_dev->dev,
+ &dev_attr_fsl_v4l2_output_property))
+ dev_err(&pdev->dev, "Error on creating file\n");
+
+ return 0;
+}
+
+static int mxc_v4l2out_remove(struct platform_device *pdev)
+{
+ vout_data *vout = platform_get_drvdata(pdev);
+
+ if (vout->video_dev) {
+ device_remove_file(&vout->video_dev->dev,
+ &dev_attr_fsl_v4l2_output_property);
+ video_unregister_device(vout->video_dev);
+ vout->video_dev = NULL;
+ }
+
+ platform_set_drvdata(pdev, NULL);
+
+ kfree(vout);
+
+ return 0;
+}
+
+/*!
+ * This structure contains pointers to the power management callback functions.
+ */
+static struct platform_driver mxc_v4l2out_driver = {
+ .driver = {
+ .name = "mxc_v4l2_output",
+ },
+ .probe = mxc_v4l2out_probe,
+ .remove = mxc_v4l2out_remove,
+};
+
+/*!
+ * mxc v4l2 init function
+ *
+ */
+static int mxc_v4l2out_init(void)
+{
+ return platform_driver_register(&mxc_v4l2out_driver);
+}
+
+/*!
+ * mxc v4l2 cleanup function
+ *
+ */
+static void mxc_v4l2out_clean(void)
+{
+ platform_driver_unregister(&mxc_v4l2out_driver);
+}
+
+module_init(mxc_v4l2out_init);
+module_exit(mxc_v4l2out_clean);
+
+module_param(video_nr, int, 0444);
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("V4L2-driver for MXC video output");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("video");
diff --git a/drivers/media/video/mxc/output/mxc_v4l2_output.h b/drivers/media/video/mxc/output/mxc_v4l2_output.h
new file mode 100644
index 000000000000..c26194f5ff90
--- /dev/null
+++ b/drivers/media/video/mxc/output/mxc_v4l2_output.h
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2005-2010 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
+ */
+
+/*!
+ * @defgroup MXC_V4L2_OUTPUT MXC V4L2 Video Output Driver
+ */
+/*!
+ * @file mxc_v4l2_output.h
+ *
+ * @brief MXC V4L2 Video Output Driver Header file
+ *
+ * Video4Linux2 Output Device using MXC IPU Post-processing functionality.
+ *
+ * @ingroup MXC_V4L2_OUTPUT
+ */
+#ifndef __MXC_V4L2_OUTPUT_H__
+#define __MXC_V4L2_OUTPUT_H__
+
+#include <media/v4l2-dev.h>
+
+#ifdef __KERNEL__
+
+#include <linux/ipu.h>
+#include <linux/mxc_v4l2.h>
+#include <linux/videodev2.h>
+
+#define MIN_FRAME_NUM 2
+#define MAX_FRAME_NUM 30
+
+#define MXC_V4L2_OUT_NUM_OUTPUTS 6
+#define MXC_V4L2_OUT_2_SDC 0
+
+
+typedef struct {
+ int list[MAX_FRAME_NUM + 1];
+ int head;
+ int tail;
+} v4l_queue;
+
+/*!
+ * States for the video stream
+ */
+typedef enum {
+ STATE_STREAM_OFF,
+ STATE_STREAM_ON,
+ STATE_STREAM_PAUSED,
+ STATE_STREAM_STOPPING,
+} v4lout_state;
+
+/*!
+ * common v4l2 driver structure.
+ */
+typedef struct _vout_data {
+ struct video_device *video_dev;
+ /*!
+ * semaphore guard against SMP multithreading
+ */
+ struct semaphore busy_lock;
+
+ /*!
+ * number of process that have device open
+ */
+ int open_count;
+
+ /*!
+ * params lock for this camera
+ */
+ struct semaphore param_lock;
+
+ struct timer_list output_timer;
+ struct workqueue_struct *v4l_wq;
+ struct work_struct icbypass_work;
+ int disp_buf_num;
+ int fb_blank;
+ unsigned long start_jiffies;
+ u32 frame_count;
+
+ v4l_queue ready_q;
+ v4l_queue done_q;
+
+ s8 next_rdy_ipu_buf;
+ s8 next_done_ipu_buf;
+ s8 next_disp_ipu_buf;
+ s8 ipu_buf[2];
+ s8 ipu_buf_p[2];
+ s8 ipu_buf_n[2];
+ volatile v4lout_state state;
+
+ int cur_disp_output;
+ int output_fb_num[MXC_V4L2_OUT_NUM_OUTPUTS];
+ int output_enabled[MXC_V4L2_OUT_NUM_OUTPUTS];
+ struct v4l2_framebuffer v4l2_fb;
+ int ic_bypass;
+ u32 work_irq;
+ ipu_channel_t display_ch;
+ ipu_channel_t post_proc_ch;
+ ipu_channel_t display_input_ch;
+
+ /*!
+ * FRAME_NUM-buffering, so we need a array
+ */
+ int buffer_cnt;
+ dma_addr_t queue_buf_paddr[MAX_FRAME_NUM];
+ void *queue_buf_vaddr[MAX_FRAME_NUM];
+ u32 queue_buf_size;
+ struct v4l2_buffer v4l2_bufs[MAX_FRAME_NUM];
+ u32 display_buf_size;
+ dma_addr_t display_bufs[2];
+ void *display_bufs_vaddr[2];
+ dma_addr_t rot_pp_bufs[2];
+ void *rot_pp_bufs_vaddr[2];
+
+ /*!
+ * Poll wait queue
+ */
+ wait_queue_head_t v4l_bufq;
+
+ /*!
+ * v4l2 format
+ */
+ struct v4l2_format v2f;
+ struct v4l2_mxc_offset offset;
+ ipu_rotate_mode_t rotate;
+
+ /* crop */
+ struct v4l2_rect crop_bounds[MXC_V4L2_OUT_NUM_OUTPUTS];
+ struct v4l2_rect crop_current;
+ u32 bytesperline;
+ enum v4l2_field field_fmt;
+ ipu_motion_sel motion_sel;
+
+ /* PP split fot two stripes*/
+ int pp_split; /* 0,1 */
+ struct stripe_param pp_left_stripe;
+ struct stripe_param pp_right_stripe; /* struct for split parameters */
+ struct stripe_param pp_up_stripe;
+ struct stripe_param pp_down_stripe;
+ /* IC ouput buffer number. Counting from 0 to 7 */
+ int pp_split_buf_num; /* 0..7 */
+ u16 bpp ; /* bit per pixel */
+ u16 xres; /* width of physical frame (BGs) */
+ u16 yres; /* heigth of physical frame (BGs)*/
+
+} vout_data;
+
+#endif
+#endif /* __MXC_V4L2_OUTPUT_H__ */