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/Makefile11
-rw-r--r--drivers/media/video/mxc/output/mx27_pp.c904
-rw-r--r--drivers/media/video/mxc/output/mx27_pp.h180
-rw-r--r--drivers/media/video/mxc/output/mx27_v4l2_output.c1442
-rw-r--r--drivers/media/video/mxc/output/mx31_v4l2_wvga_output.c1926
-rw-r--r--drivers/media/video/mxc/output/mxc_v4l2_output.c2614
-rw-r--r--drivers/media/video/mxc/output/mxc_v4l2_output.h154
8 files changed, 7259 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..1713fa3bf3ab
--- /dev/null
+++ b/drivers/media/video/mxc/output/Makefile
@@ -0,0 +1,11 @@
+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_IPUV1_WVGA_OUTPUT),y)
+ obj-$(CONFIG_VIDEO_MXC_OUTPUT) += mx31_v4l2_wvga_output.o
+endif
diff --git a/drivers/media/video/mxc/output/mx27_pp.c b/drivers/media/video/mxc/output/mx27_pp.c
new file mode 100644
index 000000000000..a82328015fe2
--- /dev/null
+++ b/drivers/media/video/mxc/output/mx27_pp.c
@@ -0,0 +1,904 @@
+/*
+ * Copyright 2005-2007 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 mx27_pp.c
+ *
+ * @brief MX27 V4L2 Video Output Driver
+ *
+ * Video4Linux2 Output Device using MX27 eMMA Post-processing functionality.
+ *
+ * @ingroup MXC_V4L2_OUTPUT
+ */
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/module.h>
+#include <linux/fb.h>
+#include <linux/clk.h>
+#include <linux/interrupt.h>
+#include <asm/io.h>
+
+#include "mx27_pp.h"
+#include "mxc_v4l2_output.h"
+
+#define SCALE_RETRY 32 /* to be more relax, less precise */
+#define PP_SKIP 1
+#define PP_TBL_MAX 40
+
+static unsigned short scale_tbl[PP_TBL_MAX];
+static int g_hlen, g_vlen;
+
+static emma_pp_cfg g_pp_cfg;
+static int g_disp_num = 0;
+static char pp_dev[] = "emma_pp";
+
+/*!
+ * @brief PP resizing routines
+ */
+static int gcd(int x, int y);
+static int ratio(int x, int y, int *den);
+static int scale_0d(int k, int coeff, int base, int nxt);
+static int scale_1d(int inv, int outv, int k);
+static int scale_1d_smart(int *inv, int *outv, int index);
+static int scale_2d(emma_pp_scale * sz);
+
+static irqreturn_t pp_isr(int irq, void *dev_id);
+static int set_output_addr(emma_pp_cfg * cfg, vout_data * vout);
+static int pphw_reset(void);
+static int pphw_enable(int flag);
+static int pphw_ptr(emma_pp_cfg * cfg);
+static int pphw_outptr(emma_pp_cfg * cfg);
+static int pphw_cfg(emma_pp_cfg * cfg);
+static int pphw_isr(void);
+static void pphw_init(void);
+static void pphw_exit(void);
+
+#define PP_DUMP(reg) pr_debug("%s\t = 0x%08X\n", #reg, __raw_readl(reg))
+void pp_dump(void)
+{
+ PP_DUMP(PP_CNTL);
+ PP_DUMP(PP_INTRCNTL);
+ PP_DUMP(PP_INTRSTATUS);
+ PP_DUMP(PP_SOURCE_Y_PTR);
+ PP_DUMP(PP_SOURCE_CB_PTR);
+ PP_DUMP(PP_SOURCE_CR_PTR);
+ PP_DUMP(PP_DEST_RGB_PTR);
+ PP_DUMP(PP_QUANTIZER_PTR);
+ PP_DUMP(PP_PROCESS_FRAME_PARA);
+ PP_DUMP(PP_SOURCE_FRAME_WIDTH);
+ PP_DUMP(PP_DEST_DISPLAY_WIDTH);
+ PP_DUMP(PP_DEST_IMAGE_SIZE);
+ PP_DUMP(PP_DEST_FRAME_FMT_CNTL);
+ PP_DUMP(PP_RESIZE_INDEX);
+ PP_DUMP(PP_CSC_COEF_0123);
+ PP_DUMP(PP_CSC_COEF_4);
+}
+
+/*!
+ * @brief Set PP input address.
+ * @param ptr The pointer to the Y value of input
+ * @return Zero on success, others on failure
+ */
+int pp_ptr(unsigned long ptr)
+{
+ g_pp_cfg.ptr.y = ptr;
+ g_pp_cfg.ptr.u = g_pp_cfg.ptr.v = g_pp_cfg.ptr.qp = 0;
+
+ return pphw_ptr(&g_pp_cfg);
+}
+
+/*!
+ * @brief Enable or disable PP.
+ * @param flag Zero to disable PP, others to enable PP
+ * @return Zero on success, others on failure
+ */
+int pp_enable(int flag)
+{
+ return pphw_enable(flag);
+}
+
+/*!
+ * @brief Get the display No. of last completed PP frame.
+ * @return The display No. of last completed PP frame.
+ */
+int pp_num_last(void)
+{
+ return (g_disp_num ? 0 : 1);
+}
+
+/*!
+ * @brief Initialize PP.
+ * @param vout Pointer to _vout_data structure
+ * @return Zero on success, others on failure
+ */
+int pp_init(vout_data * vout)
+{
+ pphw_init();
+ pphw_enable(0);
+ enable_irq(MXC_INT_EMMAPP);
+ return request_irq(MXC_INT_EMMAPP, pp_isr, 0, pp_dev, vout);
+}
+
+/*!
+ * @brief Deinitialize PP.
+ * @param vout Pointer to _vout_data structure
+ */
+void pp_exit(vout_data * vout)
+{
+ disable_irq(MXC_INT_EMMAPP);
+ free_irq(MXC_INT_EMMAPP, vout);
+ pphw_enable(0);
+ pphw_exit();
+}
+
+/*!
+ * @brief Configure PP.
+ * @param vout Pointer to _vout_data structure
+ * @return Zero on success, others on failure
+ */
+int pp_cfg(vout_data * vout)
+{
+ if (!vout)
+ return -1;
+
+ /* PP accepts YUV420 input only */
+ if (vout->v2f.fmt.pix.pixelformat != V4L2_PIX_FMT_YUV420) {
+ pr_debug("unsupported pixel format.\n");
+ return -1;
+ }
+
+ g_pp_cfg.operation = 0;
+
+ memset(g_pp_cfg.csc_table, 0, sizeof(g_pp_cfg.csc_table));
+
+ /* Convert output pixel format to PP required format */
+ switch (vout->v4l2_fb.fmt.pixelformat) {
+ case V4L2_PIX_FMT_BGR32:
+ g_pp_cfg.red_width = 8;
+ g_pp_cfg.green_width = 8;
+ g_pp_cfg.blue_width = 8;
+ g_pp_cfg.red_offset = 8;
+ g_pp_cfg.green_offset = 16;
+ g_pp_cfg.blue_offset = 24;
+ g_pp_cfg.rgb_resolution = 32;
+ break;
+ case V4L2_PIX_FMT_RGB32:
+ g_pp_cfg.red_width = 8;
+ g_pp_cfg.green_width = 8;
+ g_pp_cfg.blue_width = 8;
+ g_pp_cfg.red_offset = 24;
+ g_pp_cfg.green_offset = 16;
+ g_pp_cfg.blue_offset = 8;
+ g_pp_cfg.rgb_resolution = 32;
+ break;
+ case V4L2_PIX_FMT_YUYV:
+ g_pp_cfg.red_width = 0;
+ g_pp_cfg.green_width = 0;
+ g_pp_cfg.blue_width = 0;
+ g_pp_cfg.red_offset = 0;
+ g_pp_cfg.green_offset = 0;
+ g_pp_cfg.blue_offset = PP_PIX_YUYV;
+ g_pp_cfg.rgb_resolution = 16;
+ break;
+ case V4L2_PIX_FMT_UYVY:
+ g_pp_cfg.red_width = 0;
+ g_pp_cfg.green_width = 0;
+ g_pp_cfg.blue_width = 0;
+ g_pp_cfg.red_offset = 0;
+ g_pp_cfg.green_offset = 0;
+ g_pp_cfg.blue_offset = PP_PIX_UYVY;
+ g_pp_cfg.rgb_resolution = 16;
+ break;
+ case V4L2_PIX_FMT_RGB565:
+ default:
+ g_pp_cfg.red_width = 5;
+ g_pp_cfg.green_width = 6;
+ g_pp_cfg.blue_width = 5;
+ g_pp_cfg.red_offset = 11;
+ g_pp_cfg.green_offset = 5;
+ g_pp_cfg.blue_offset = 0;
+ g_pp_cfg.rgb_resolution = 16;
+ break;
+ }
+
+ if (vout->ipu_buf[0] != -1)
+ g_pp_cfg.ptr.y =
+ (unsigned int)vout->queue_buf_paddr[vout->ipu_buf[0]];
+ else
+ g_pp_cfg.ptr.y = 0;
+
+ g_pp_cfg.ptr.u = g_pp_cfg.ptr.v = g_pp_cfg.ptr.qp = 0;
+
+ g_pp_cfg.dim.in.width = vout->v2f.fmt.pix.width;
+ g_pp_cfg.dim.in.height = vout->v2f.fmt.pix.height;
+ g_pp_cfg.dim.out.width = vout->crop_current.width;
+ g_pp_cfg.dim.out.height = vout->crop_current.height;
+ g_pp_cfg.dim.num.width = 0;
+ g_pp_cfg.dim.num.height = 0;
+ g_pp_cfg.dim.den.width = 0;
+ g_pp_cfg.dim.den.height = 0;
+
+ if (scale_2d(&g_pp_cfg.dim)) {
+ pr_debug("unsupported resize ratio.\n");
+ return -1;
+ }
+
+ g_pp_cfg.dim.out.width = vout->crop_current.width;
+ g_pp_cfg.dim.out.height = vout->crop_current.height;
+
+ g_pp_cfg.in_y_stride = 0;
+ if (set_output_addr(&g_pp_cfg, vout)) {
+ pr_debug("failed to set pp output address.\n");
+ return -1;
+ }
+
+ return pphw_cfg(&g_pp_cfg);
+}
+
+irqreturn_t mxc_v4l2out_pp_in_irq_handler(int irq, void *dev_id);
+
+/*!
+ * @brief PP IRQ handler.
+ */
+static irqreturn_t pp_isr(int irq, void *dev_id)
+{
+ int status;
+ vout_data *vout = dev_id;
+
+ status = pphw_isr();
+ if ((status & 0x1) == 0) { /* Not frame complete interrupt */
+ pr_debug("not pp frame complete interrupt\n");
+ return IRQ_HANDLED;
+ }
+
+ if (vout->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) {
+ g_disp_num = g_disp_num ? 0 : 1;
+ g_pp_cfg.outptr = (unsigned int)vout->display_bufs[g_disp_num];
+ pphw_outptr(&g_pp_cfg);
+ }
+
+ return mxc_v4l2out_pp_in_irq_handler(irq, dev_id);
+}
+
+/*!
+ * @brief Set PP output address.
+ * @param cfg Pointer to emma_pp_cfg structure
+ * @param vout Pointer to _vout_data structure
+ * @return Zero on success, others on failure
+ */
+static int set_output_addr(emma_pp_cfg * cfg, vout_data * vout)
+{
+ if (vout->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) {
+ g_disp_num = 0;
+ cfg->outptr = (unsigned int)vout->display_bufs[g_disp_num];
+ cfg->out_stride = vout->crop_current.width;
+ return 0;
+ } else {
+ struct fb_info *fb;
+
+ fb = registered_fb[vout->output_fb_num[vout->cur_disp_output]];
+ if (!fb)
+ return -1;
+
+ cfg->outptr = fb->fix.smem_start;
+ cfg->outptr += vout->crop_current.top * fb->var.xres_virtual
+ * (fb->var.bits_per_pixel >> 3)
+ + vout->crop_current.left * (fb->var.bits_per_pixel >> 3);
+ cfg->out_stride = fb->var.xres_virtual;
+
+ return 0;
+ }
+}
+
+/*!
+ * @brief Get maximum common divisor.
+ * @param x First input value
+ * @param y Second input value
+ * @return Maximum common divisor of x and y
+ */
+static int gcd(int x, int y)
+{
+ int k;
+
+ if (x < y) {
+ k = x;
+ x = y;
+ y = k;
+ }
+
+ while ((k = x % y)) {
+ x = y;
+ y = k;
+ }
+
+ return y;
+}
+
+/*!
+ * @brief Get ratio.
+ * @param x First input value
+ * @param y Second input value
+ * @param den Denominator of the ratio (corresponding to y)
+ * @return Numerator of the ratio (corresponding to x)
+ */
+static int ratio(int x, int y, int *den)
+{
+ int g;
+
+ if (!x || !y)
+ return 0;
+
+ g = gcd(x, y);
+ *den = y / g;
+
+ return x / g;
+}
+
+/*!
+ * @brief Build PP coefficient entry
+ * Build one or more coefficient entries for PP coefficient table based
+ * on given coefficient.
+ *
+ * @param k The index of the coefficient in coefficient table
+ * @param coeff The weighting coefficient
+ * @param base The base of the coefficient
+ * @param nxt Number of pixels to be read
+ *
+ * @return The index of the next coefficient entry on success
+ * -1 on failure
+ */
+static int scale_0d(int k, int coeff, int base, int nxt)
+{
+ if (k >= PP_TBL_MAX) {
+ /* no more space in table */
+ pr_debug("no space in scale table, k = %d\n", k);
+ return -1;
+ }
+
+ coeff = ((coeff << BC_COEF) + (base >> 1)) / base;
+
+ /*
+ * Valid values for weighting coefficient are 0, 2 to 30, and 31.
+ * A value of 31 is treated as 32 and therefore 31 is an
+ * invalid co-efficient.
+ */
+ if (coeff >= SZ_COEF - 1)
+ coeff--;
+ else if (coeff == 1)
+ coeff++;
+ coeff = coeff << BC_NXT;
+
+ if (nxt < SZ_NXT) {
+ coeff |= nxt;
+ coeff <<= 1;
+ coeff |= 1;
+ } else {
+ /*
+ * src inc field is 2 bit wide, for 4+, use special
+ * code 0:0:1 to prevent dest inc
+ */
+ coeff |= PP_SKIP;
+ coeff <<= 1;
+ coeff |= 1;
+ nxt -= PP_SKIP;
+ do {
+ pr_debug("tbl = %03X\n", coeff);
+ scale_tbl[k++] = coeff;
+ coeff = (nxt > PP_SKIP) ? PP_SKIP : nxt;
+ coeff <<= 1;
+ } while ((nxt -= PP_SKIP) > 0);
+ }
+ pr_debug("tbl = %03X\n", coeff);
+ scale_tbl[k++] = coeff;
+
+ return k;
+}
+
+/*
+ * @brief Build PP coefficient table
+ * Build PP coefficient table for one dimension (width or height)
+ * based on given input and output resolution
+ *
+ * @param inv input resolution
+ * @param outv output resolution
+ * @param k index of free table entry
+ *
+ * @return The index of the next free coefficient entry on success
+ * -1 on failure
+ */
+static int scale_1d(int inv, int outv, int k)
+{
+ int v; /* overflow counter */
+ int coeff, nxt; /* table output */
+
+ if (inv == outv)
+ return scale_0d(k, 1, 1, 1); /* force scaling */
+
+ if (inv * 4 < outv) {
+ pr_debug("upscale err: ratio should be in range 1:1 to 1:4\n");
+ return -1;
+ }
+
+ v = 0;
+ if (inv < outv) {
+ /* upscale: mix <= 2 input pixels per output pixel */
+ do {
+ coeff = outv - v;
+ v += inv;
+ if (v >= outv) {
+ v -= outv;
+ nxt = 1;
+ } else
+ nxt = 0;
+ pr_debug("upscale: coeff = %d/%d nxt = %d\n", coeff,
+ outv, nxt);
+ k = scale_0d(k, coeff, outv, nxt);
+ if (k < 0)
+ return -1;
+ } while (v);
+ } else if (inv >= 2 * outv) {
+ /* PP doesn't support resize ratio > 2:1 except 4:1. */
+ if ((inv != 2 * outv) && (inv != 4 * outv))
+ return -1;
+ /* downscale: >=2:1 bilinear approximation */
+ coeff = inv - 2 * outv;
+ v = 0;
+ nxt = 0;
+ do {
+ v += coeff;
+ nxt = 2;
+ while (v >= outv) {
+ v -= outv;
+ nxt++;
+ }
+ pr_debug("downscale: coeff = 1/2 nxt = %d\n", nxt);
+ k = scale_0d(k, 1, 2, nxt);
+ if (k < 0)
+ return -1;
+ } while (v);
+ } else {
+ /* downscale: bilinear */
+ int in_pos_inc = 2 * outv;
+ int out_pos = inv;
+ int out_pos_inc = 2 * inv;
+ int init_carry = inv - outv;
+ int carry = init_carry;
+
+ v = outv + in_pos_inc;
+ do {
+ coeff = v - out_pos;
+ out_pos += out_pos_inc;
+ carry += out_pos_inc;
+ for (nxt = 0; v < out_pos; nxt++) {
+ v += in_pos_inc;
+ carry -= in_pos_inc;
+ }
+ pr_debug("downscale: coeff = %d/%d nxt = %d\n", coeff,
+ in_pos_inc, nxt);
+ k = scale_0d(k, coeff, in_pos_inc, nxt);
+ if (k < 0)
+ return -1;
+ } while (carry != init_carry);
+ }
+ return k;
+}
+
+/*
+ * @brief Build PP coefficient table
+ * Build PP coefficient table for one dimension (width or height)
+ * based on given input and output resolution. The given input
+ * and output resolution might be not supported due to hardware
+ * limits. In this case this functin rounds the input and output
+ * to closest possible values and return them to caller.
+ *
+ * @param inv input resolution, might be modified after the call
+ * @param outv output resolution, might be modified after the call
+ * @param k index of free table entry
+ *
+ * @return The index of the next free coefficient entry on success
+ * -1 on failure
+ */
+static int scale_1d_smart(int *inv, int *outv, int index)
+{
+ int len, num, den, retry;
+ static int num1, den1;
+
+ if (!inv || !outv)
+ return -1;
+
+ /* Both should be non-zero */
+ if (!(*inv) || !(*outv))
+ return -1;
+
+ retry = SCALE_RETRY;
+
+ do {
+ num = ratio(*inv, *outv, &den);
+ pr_debug("num = %d, den = %d\n", num, den);
+ if (!num)
+ continue;
+
+ if (index != 0) {
+ /*
+ * We are now resizing height. Check to see if the
+ * resize ratio for width can be reused by height
+ */
+ if ((num == num1) && (den == den1))
+ return index;
+ }
+
+ if ((len = scale_1d(num, den, index)) < 0)
+ /* increase output dimension to try another ratio */
+ (*outv)++;
+ else {
+ if (index == 0) {
+ /*
+ * We are now resizing width. The same resize
+ * ratio may be reused by height, so save the
+ * ratio.
+ */
+ num1 = num;
+ den1 = den;
+ }
+ return len;
+ }
+ } while (retry--);
+
+ pr_debug("pp scale err\n");
+ return -1;
+}
+
+/*
+ * @brief Build PP coefficient table for both width and height
+ * Build PP coefficient table for both width and height based on
+ * given resizing ratios.
+ *
+ * @param sz Structure contains resizing ratio informations
+ *
+ * @return 0 on success, others on failure
+ */
+static int scale_2d(emma_pp_scale * sz)
+{
+ int inv, outv;
+
+ /* horizontal resizing. parameter check - must provide in size */
+ if (!sz->in.width)
+ return -1;
+
+ /* Resizing based on num:den */
+ inv = sz->num.width;
+ outv = sz->den.width;
+
+ if ((g_hlen = scale_1d_smart(&inv, &outv, 0)) > 0) {
+ /* Resizing succeeded */
+ sz->den.width = outv;
+ sz->out.width = (sz->in.width * outv) / inv;
+ } else {
+ /* Resizing based on in:out */
+ inv = sz->in.width;
+ outv = sz->out.width;
+
+ if ((g_hlen = scale_1d_smart(&inv, &outv, 0)) > 0) {
+ /* Resizing succeeded */
+ sz->out.width = outv;
+ sz->num.width = ratio(sz->in.width, sz->out.width,
+ &sz->den.width);
+ } else
+ return -1;
+ }
+
+ sz->out.width &= ~1;
+
+ /* vertical resizing. parameter check - must provide in size */
+ if (!sz->in.height)
+ return -1;
+
+ /* Resizing based on num:den */
+ inv = sz->num.height;
+ outv = sz->den.height;
+
+ if ((g_vlen = scale_1d_smart(&inv, &outv, g_hlen)) > 0) {
+ /* Resizing succeeded */
+ sz->den.height = outv;
+ sz->out.height = (sz->in.height * outv) / inv;
+ } else {
+ /* Resizing based on in:out */
+ inv = sz->in.height;
+ outv = sz->out.height;
+
+ if ((g_vlen = scale_1d_smart(&inv, &outv, g_hlen)) > 0) {
+ /* Resizing succeeded */
+ sz->out.height = outv;
+ sz->num.height = ratio(sz->in.height, sz->out.height,
+ &sz->den.height);
+ } else
+ return -1;
+ }
+
+ return 0;
+}
+
+/*!
+ * @brief Set PP resizing registers.
+ * @param sz Pointer to pp scaling structure
+ * @return Zero on success, others on failure
+ */
+static int pphw_scale(emma_pp_scale * sz)
+{
+ __raw_writel((sz->out.width << 16) | sz->out.height,
+ PP_DEST_IMAGE_SIZE);
+ __raw_writel(((g_hlen - 1) << 16) | (g_vlen ==
+ g_hlen ? 0 : (g_hlen << 8)) |
+ (g_vlen - 1), PP_RESIZE_INDEX);
+ for (g_hlen = 0; g_hlen < g_vlen; g_hlen++)
+ __raw_writel(scale_tbl[g_hlen],
+ PP_RESIZE_COEF_TBL + g_hlen * 4);
+
+ return 0;
+}
+
+/*!
+ * @brief Reset PP.
+ * @return Zero on success, others on failure
+ */
+static int pphw_reset(void)
+{
+ int i;
+
+ __raw_writel(0x100, PP_CNTL);
+
+ /* timeout */
+ for (i = 0; i < 1000; i++) {
+ if (!(__raw_readl(PP_CNTL) & 0x100)) {
+ pr_debug("pp reset over\n");
+ break;
+ }
+ }
+
+ /* check reset value */
+ if (__raw_readl(PP_CNTL) != 0x876) {
+ pr_debug("pp reset value err = 0x%08X\n", __raw_readl(PP_CNTL));
+ return -1;
+ }
+
+ return 0;
+}
+
+/*!
+ * @brief Enable or disable PP.
+ * @param flag Zero to disable PP, others to enable PP
+ * @return Zero on success, others on failure
+ */
+static int pphw_enable(int flag)
+{
+ int ret = 0;
+
+ if (flag)
+ __raw_writel(__raw_readl(PP_CNTL) | 1, PP_CNTL);
+ else
+ ret = pphw_reset();
+
+ return ret;
+}
+
+/*!
+ * @brief Set PP input address.
+ * @param cfg The pointer to PP configuration parameter
+ * @return Zero on success, others on failure
+ */
+static int pphw_ptr(emma_pp_cfg * cfg)
+{
+ if (!cfg->ptr.u) {
+ int size;
+
+ /* yuv - packed */
+ size = PP_CALC_Y_SIZE(cfg);
+ cfg->ptr.u = cfg->ptr.y + size;
+ cfg->ptr.v = cfg->ptr.u + (size >> 2);
+
+ /* yuv packed with qp appended */
+ if (!cfg->ptr.qp)
+ cfg->ptr.qp = cfg->ptr.v + (size >> 2);
+ }
+ __raw_writel(cfg->ptr.y, PP_SOURCE_Y_PTR);
+ __raw_writel(cfg->ptr.u, PP_SOURCE_CB_PTR);
+ __raw_writel(cfg->ptr.v, PP_SOURCE_CR_PTR);
+ __raw_writel(cfg->ptr.qp, PP_QUANTIZER_PTR);
+
+ return 0;
+}
+
+/*!
+ * @brief Set PP output address.
+ * @param cfg The pointer to PP configuration parameter
+ * @return Zero on success, others on failure
+ */
+static int pphw_outptr(emma_pp_cfg * cfg)
+{
+ __raw_writel(cfg->outptr, PP_DEST_RGB_PTR);
+ return 0;
+}
+
+/*!
+ * @brief Configuration PP.
+ * @param cfg The pointer to PP configuration parameter
+ * @return Zero on success, others on failure
+ */
+static int pphw_cfg(emma_pp_cfg * cfg)
+{
+ int rt;
+ register int r;
+
+ pphw_scale(&cfg->dim);
+
+ if (!cfg->in_y_stride)
+ cfg->in_y_stride = cfg->dim.in.width;
+
+ if (!cfg->out_stride)
+ cfg->out_stride = cfg->dim.out.width;
+
+ r = __raw_readl(PP_CNTL) & ~EN_MASK;
+
+ /* config parms */
+ r |= cfg->operation & EN_MASK;
+ if (cfg->operation & EN_MACROBLOCK) {
+ /* Macroblock Mode */
+ r |= 0x0200;
+ __raw_writel(0x06, PP_INTRCNTL);
+ } else {
+ /* Frame mode */
+ __raw_writel(0x05, PP_INTRCNTL);
+ }
+
+ if (cfg->red_width | cfg->green_width | cfg->blue_width) {
+ /* color conversion to be performed */
+ r |= EN_CSC;
+ if (!(cfg->red_offset | cfg->green_offset)) {
+ /* auto offset B:G:R LSb to Msb */
+ cfg->green_offset = cfg->blue_offset + cfg->blue_width;
+ cfg->red_offset = cfg->green_offset + cfg->green_width;
+ }
+ if (!cfg->rgb_resolution) {
+ /* derive minimum resolution required */
+ int w, w2;
+
+ w = cfg->red_offset + cfg->red_width;
+ w2 = cfg->blue_offset + cfg->blue_width;
+ if (w < w2)
+ w = w2;
+ w2 = cfg->green_offset + cfg->green_width;
+ if (w < w2)
+ w = w2;
+ if (w > 16)
+ w = 24;
+ else if (w > 8)
+ w = 16;
+ else
+ w = 8;
+ cfg->rgb_resolution = w;
+ }
+ /* 00,11 - 32 bpp, 10 - 16 bpp, 01 - 8 bpp */
+ r &= ~0xC00;
+ if (cfg->rgb_resolution < 32)
+ r |= (cfg->rgb_resolution << 7);
+ __raw_writel((cfg->red_offset << 26) |
+ (cfg->green_offset << 21) |
+ (cfg->blue_offset << 16) |
+ (cfg->red_width << 8) |
+ (cfg->green_width << 4) |
+ cfg->blue_width, PP_DEST_FRAME_FMT_CNTL);
+ } else {
+ /* add YUV422 formatting */
+ static const unsigned int _422[] = {
+ 0x62000888,
+ 0x60100888,
+ 0x43080888,
+ 0x41180888
+ };
+
+ __raw_writel(_422[(cfg->blue_offset >> 3) & 3],
+ PP_DEST_FRAME_FMT_CNTL);
+ cfg->rgb_resolution = 16;
+ r &= ~0xC00;
+ r |= (cfg->rgb_resolution << 7);
+ }
+
+ /* add csc formatting */
+ if (!cfg->csc_table[1]) {
+ static const unsigned short _csc[][6] = {
+ {0x80, 0xb4, 0x2c, 0x5b, 0x0e4, 0},
+ {0x95, 0xcc, 0x32, 0x68, 0x104, 1},
+ {0x80, 0xca, 0x18, 0x3c, 0x0ec, 0},
+ {0x95, 0xe5, 0x1b, 0x44, 0x10e, 1},
+ };
+ memcpy(cfg->csc_table, _csc[cfg->csc_table[0]],
+ sizeof(_csc[0]));
+ }
+ __raw_writel((cfg->csc_table[0] << 24) |
+ (cfg->csc_table[1] << 16) |
+ (cfg->csc_table[2] << 8) |
+ cfg->csc_table[3], PP_CSC_COEF_0123);
+ __raw_writel((cfg->csc_table[5] ? (1 << 9) : 0) | cfg->csc_table[4],
+ PP_CSC_COEF_4);
+
+ __raw_writel(r, PP_CNTL);
+
+ pphw_ptr(cfg);
+ pphw_outptr(cfg);
+
+ /*
+ * #MB in a row = input_width / 16pix
+ * 1 byte per QP per MB
+ * QP must be formatted to be 4-byte aligned
+ * YUV lines are to be 4-byte aligned as well
+ * So Y is 8 byte aligned, as U = V = Y/2 for 420
+ * MPEG MBs are 16x16 anyway
+ */
+ __raw_writel((cfg->dim.in.width << 16) | cfg->dim.in.height,
+ PP_PROCESS_FRAME_PARA);
+ __raw_writel(cfg->in_y_stride | (PP_CALC_QP_WIDTH(cfg) << 16),
+ PP_SOURCE_FRAME_WIDTH);
+
+ /* in bytes */
+ rt = cfg->rgb_resolution >> 3;
+ if (rt == 3)
+ rt = 4;
+ __raw_writel(cfg->out_stride * rt, PP_DEST_DISPLAY_WIDTH);
+
+ pp_dump();
+ return 0;
+}
+
+/*!
+ * @brief Check PP interrupt status.
+ * @return PP interrupt status
+ */
+static int pphw_isr(void)
+{
+ unsigned long status;
+
+ pr_debug("pp: in isr.\n");
+ status = __raw_readl(PP_INTRSTATUS) & 7;
+ if (!status) {
+ pr_debug("pp: not my isr err.\n");
+ return status;
+ }
+
+ if (status & 4)
+ pr_debug("pp: isr state error.\n");
+
+ /* clear interrupt status */
+ __raw_writel(status, PP_INTRSTATUS);
+
+ return status;
+}
+
+static struct clk *emma_clk;
+
+/*!
+ * @brief PP module clock enable
+ */
+static void pphw_init(void)
+{
+ emma_clk = clk_get(NULL, "emma_clk");
+ clk_enable(emma_clk);
+}
+
+/*!
+ * @brief PP module clock disable
+ */
+static void pphw_exit(void)
+{
+ clk_disable(emma_clk);
+ clk_put(emma_clk);
+}
diff --git a/drivers/media/video/mxc/output/mx27_pp.h b/drivers/media/video/mxc/output/mx27_pp.h
new file mode 100644
index 000000000000..7bc65ddda3a1
--- /dev/null
+++ b/drivers/media/video/mxc/output/mx27_pp.h
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2005-2007 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 mx27_pp.h
+ *
+ * @brief Header file for MX27 V4L2 Video Output Driver
+ *
+ * @ingroup MXC_V4L2_OUTPUT
+ */
+#ifndef __MX27_PP_H__
+#define __MX27_PP_H__
+
+#include "mxc_v4l2_output.h"
+
+/* PP register definitions */
+#define PP_REG(ofs) (IO_ADDRESS(EMMA_BASE_ADDR) - 0x400 + ofs)
+
+/* Register offsets */
+#define PP_CNTL PP_REG(0x00)
+#define PP_INTRCNTL PP_REG(0x04)
+#define PP_INTRSTATUS PP_REG(0x08)
+#define PP_SOURCE_Y_PTR PP_REG(0x0C)
+#define PP_SOURCE_CB_PTR PP_REG(0x10)
+#define PP_SOURCE_CR_PTR PP_REG(0x14)
+#define PP_DEST_RGB_PTR PP_REG(0x18)
+#define PP_QUANTIZER_PTR PP_REG(0x1C)
+#define PP_PROCESS_FRAME_PARA PP_REG(0x20)
+#define PP_SOURCE_FRAME_WIDTH PP_REG(0x24)
+#define PP_DEST_DISPLAY_WIDTH PP_REG(0x28)
+#define PP_DEST_IMAGE_SIZE PP_REG(0x2C)
+#define PP_DEST_FRAME_FMT_CNTL PP_REG(0x30)
+#define PP_RESIZE_INDEX PP_REG(0x34)
+#define PP_CSC_COEF_0123 PP_REG(0x38)
+#define PP_CSC_COEF_4 PP_REG(0x3C)
+#define PP_RESIZE_COEF_TBL PP_REG(0x100)
+
+/* resize table dimensions
+ dest pixel index left/32 right/32 #src pixels to read
+ 0 [BC_COEF] [BC_COEF] [BC_NXT]
+ :
+ pp_tbl_max-1
+*/
+#define BC_NXT 2
+#define BC_COEF 5
+#define SZ_COEF (1 << BC_COEF)
+#define SZ_NXT (1 << BC_NXT)
+
+/* PP operations */
+#define EN_DEBLOCK 0x02
+#define EN_DERING 0x04
+#define EN_CSC 0x10
+#define EN_MACROBLOCK 0x20
+#define EN_DEF 0x16
+#define EN_MASK 0x36
+#define EN_BIGDATA 0x1000
+#define EN_BIGQP 0x2000
+
+/* PP CSC tables */
+#define CSC_TBL_NONE 0x80
+#define CSC_TBL_REUSE 0x81
+#define CSC_TBL_A1 0x00
+#define CSC_TBL_A0 0x20
+#define CSC_TBL_B1 0x40
+#define CSC_TBL_B0 0x60
+/* converts from 4 decimal fixed point to hw setting & vice versa */
+#define PP_CSC_FP4_2_HW(coeff) ((((coeff) << 7) + 5000) / 10000)
+#define PP_CSC_HW_2_FP4(coeff) ((((coeff) * 10000) + 64) >> 7)
+
+#define PP_PIX_YUYV 0
+#define PP_PIX_YVYU 8
+#define PP_PIX_UYVY 16
+#define PP_PIX_VYUY 24
+
+/* PP size & width calculation macros */
+#define PP_CALC_QP_WIDTH(cfg) \
+ (!((cfg)->operation & (EN_DEBLOCK | EN_DERING)) ? 0 : \
+ (((((cfg)->dim.in.width + 15) >> 4) + 3) & ~3))
+#define PP_CALC_Y_SIZE(cfg) \
+ ((cfg)->in_y_stride * (cfg)->dim.in.height)
+#define PP_CALC_CH_SIZE(cfg) (PP_CALC_Y_SIZE(cfg) >> 2)
+#define PP_CALC_BPP(cfg) \
+ ((cfg)->rgb_resolution > 16 ? 4 : ((cfg)->rgb_resolution >> 3))
+#define PP_CALC_YUV_SIZE(cfg) \
+ ((PP_CALC_Y_SIZE(cfg) * 3) >> 1)
+#define PP_CALC_QP_SIZE(cfg) \
+ (PP_CALC_QP_WIDTH(cfg) * (((cfg)->dim.in.height + 15) >> 4))
+#define PP_CALC_DEST_WIDTH(cfg) \
+ (((cfg)->out_stride & ~1) * PP_CALC_BPP(cfg))
+#define PP_CALC_DEST_SIZE(cfg) \
+ ((cfg)->dim.out.height * PP_CALC_DEST_WIDTH(cfg))
+
+/*
+ * physical addresses for bus mastering
+ * v=0 -> yuv packed
+ * v=0 & qp=0 -> yuv packed with qp appended
+ */
+typedef struct _emma_pp_ptr {
+ unsigned int y; /* Y data (line align8) */
+ unsigned int u; /* U data (line align4) */
+ unsigned int v; /* V data (line align4) */
+ unsigned int qp; /* Quantization (line align4) */
+} emma_pp_ptr;
+
+typedef struct _emma_pp_size {
+ int width;
+ int height;
+} emma_pp_size;
+
+/*
+ * if num.width != 0
+ * resize ratio = num.width : den.width
+ * else
+ * resize ratio = in.width : out.width
+ * same for height
+ */
+typedef struct _emma_pp_scale {
+ emma_pp_size num;
+ emma_pp_size den;
+ emma_pp_size in; /* clip */
+ emma_pp_size out; /* 0 -> same as in */
+} emma_pp_scale;
+
+typedef struct _emma_pp_cfg {
+ unsigned char operation; /* OR of EN_xx defines */
+
+ /*
+ * input color coeff
+ * fixed pt 8 bits, steps of 1/128
+ * csc[5] is 1 or 0 to indicate Y + 16
+ * csc[0] is matrix id 0-3 while csc[1-5]=0
+ */
+ unsigned short csc_table[6];
+
+ /*
+ * Output color (shade width, shade offset, pixel resolution)
+ * Eg. 16bpp RGB565 resolution, the values could be:
+ * red_width = 5, green_width = 6, blue_width = 6
+ * red_offset = 11, green_offset = 5, blue_offset = 0 (defaults)
+ * rgb_resolution = 16 (default)
+ * For YUV422: xxx_width=0, blue_offset=PP_PIX_xxx
+ */
+ unsigned short red_width;
+ unsigned short green_width;
+ unsigned short blue_width;
+ /* if offsets are 0, the offsets are by width LSb to MSb B:G:R */
+ unsigned short red_offset;
+ unsigned short blue_offset;
+ unsigned short green_offset;
+ /* if resolution is 0, the minimum for the sum of widths is chosen */
+ short rgb_resolution; /* 8,16,24 bpp only */
+
+ emma_pp_ptr ptr; /* dma buffer pointers */
+ unsigned int outptr; /* RGB/YUV output */
+ emma_pp_scale dim; /* in/out dimensions */
+
+ /* pixels between two adjacent input Y rows */
+ unsigned short in_y_stride; /* 0 = in_width */
+ /* PIXELS between two adjacent output rows */
+ unsigned short out_stride; /* 0 = out_width */
+} emma_pp_cfg;
+
+int pp_ptr(unsigned long ptr);
+int pp_enable(int flag);
+int pp_cfg(vout_data * vout);
+int pp_init(vout_data * vout);
+int pp_num_last(void);
+void pp_exit(vout_data * vout);
+
+#endif /* __MX27_PP_H__ */
diff --git a/drivers/media/video/mxc/output/mx27_v4l2_output.c b/drivers/media/video/mxc/output/mx27_v4l2_output.c
new file mode 100644
index 000000000000..a00f92d8e979
--- /dev/null
+++ b/drivers/media/video/mxc/output/mx27_v4l2_output.c
@@ -0,0 +1,1442 @@
+/*
+ * Copyright 2005-2009 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 mx27_v4l2_output.c
+ *
+ * @brief MX27 V4L2 Video Output Driver
+ *
+ * Video4Linux2 Output Device using MX27 eMMA Post-processing functionality.
+ *
+ * @ingroup MXC_V4L2_OUTPUT
+ */
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/delay.h>
+#include <linux/fb.h>
+#include <linux/pci.h>
+#include <linux/platform_device.h>
+#include <linux/semaphore.h>
+#include <linux/poll.h>
+#include <media/v4l2-dev.h>
+#include <media/v4l2-ioctl.h>
+
+#include "mxc_v4l2_output.h"
+#include "mx27_pp.h"
+#include "../drivers/video/mxc/mx2fb.h"
+
+#define SDC_FG_FB_FORMAT V4L2_PIX_FMT_RGB565
+
+struct v4l2_output mxc_outputs[1] = {
+ {
+ .index = 0,
+ .name = "DISP0 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 spinlock_t g_lock = SPIN_LOCK_UNLOCKED;
+vout_data *g_vout;
+
+/* 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;
+
+#ifdef CONFIG_VIDEO_MXC_OUTPUT_FBSYNC
+static uint32_t g_output_fb = -1;
+static uint32_t g_fb_enabled = 0;
+static uint32_t g_pp_ready = 0;
+
+static int fb_event_notify(struct notifier_block *self,
+ unsigned long action, void *data)
+{
+ struct fb_event *event = data;
+ struct fb_info *info = event->info;
+ unsigned long lock_flags;
+ int blank, i;
+
+ for (i = 0; i < num_registered_fb; i++)
+ if (registered_fb[i] == info)
+ break;
+
+ /*
+ * Check if the event is sent by the framebuffer in which
+ * the video is displayed.
+ */
+ if (i != g_output_fb)
+ return 0;
+
+ switch (action) {
+ case FB_EVENT_BLANK:
+ blank = *(int *)event->data;
+ spin_lock_irqsave(&g_lock, lock_flags);
+ g_fb_enabled = !blank;
+ if (blank && g_pp_ready) {
+ if (pp_enable(1))
+ pr_debug("unable to enable PP\n");
+ g_pp_ready = 0;
+ }
+ spin_unlock_irqrestore(&g_lock, lock_flags);
+ break;
+ case FB_EVENT_MXC_EOF:
+ spin_lock_irqsave(&g_lock, lock_flags);
+ g_fb_enabled = 1;
+ if (g_pp_ready) {
+ if (pp_enable(1))
+ pr_debug("unable to enable PP\n");
+ g_pp_ready = 0;
+ }
+ spin_unlock_irqrestore(&g_lock, lock_flags);
+ break;
+ }
+
+ return 0;
+}
+
+static struct notifier_block fb_event_notifier = {
+ .notifier_call = fb_event_notify,
+};
+
+static struct notifier_block mx2fb_event_notifier = {
+ .notifier_call = fb_event_notify,
+};
+#endif
+
+#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);
+ pr_debug("dma_alloc_coherent failed.\n");
+ return -ENOBUFS;
+ }
+ pr_debug("allocated @ paddr=0x%08X, size=%d.\n",
+ (u32) bufs_paddr[i], size);
+ }
+
+ return 0;
+}
+
+static void mxc_v4l2out_timer_handler(unsigned long arg)
+{
+ int index;
+ unsigned long timeout;
+ unsigned long lock_flags;
+ vout_data *vout = (vout_data *) arg;
+
+ pr_debug("timer handler: %lu\n", jiffies);
+
+ spin_lock_irqsave(&g_lock, lock_flags);
+
+ if ((vout->state == STATE_STREAM_OFF)
+ || (vout->state == STATE_STREAM_STOPPING)) {
+ pr_debug("stream has stopped\n");
+ goto exit0;
+ }
+
+ /*
+ * If timer occurs before PP h/w is ready, then set the state to
+ * paused and the timer will be set again when next buffer is queued
+ * or PP completes.
+ */
+ if (vout->ipu_buf[0] != -1) {
+ pr_debug("buffer is busy\n");
+ vout->state = STATE_STREAM_PAUSED;
+ goto exit0;
+ }
+
+ /* Dequeue buffer and pass to PP */
+ index = dequeue_buf(&vout->ready_q);
+ if (index == -1) { /* no buffers ready, should never occur */
+ pr_debug("mxc_v4l2out: timer - no queued buffers ready\n");
+ goto exit0;
+ }
+
+ g_buf_dq_cnt++;
+ vout->frame_count++;
+ vout->ipu_buf[0] = index;
+
+ if (pp_ptr((unsigned int)vout->queue_buf_paddr[index])) {
+ pr_debug("unable to update buffer\n");
+ goto exit0;
+ }
+#ifdef CONFIG_VIDEO_MXC_OUTPUT_FBSYNC
+ if (g_fb_enabled && (vout->v4l2_fb.flags != V4L2_FBUF_FLAG_OVERLAY))
+ g_pp_ready = 1;
+ else if (pp_enable(1)) {
+ pr_debug("unable to enable PP\n");
+ goto exit0;
+ }
+#else
+ if (pp_enable(1)) {
+ pr_debug("unable to enable PP\n");
+ goto exit0;
+ }
+#endif
+ pr_debug("enabled index %d\n", index);
+
+ /* Setup timer for next buffer */
+ index = peek_next_buf(&vout->ready_q);
+ pr_debug("next index %d\n", index);
+ 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) {
+ pr_debug("warning: timer timeout already expired.\n");
+ }
+
+ if (mod_timer(&vout->output_timer, timeout))
+ pr_debug("warning: timer was already set\n");
+
+ pr_debug("timer handler next schedule: %lu\n", timeout);
+ } else {
+ vout->state = STATE_STREAM_PAUSED;
+ pr_debug("timer handler paused\n");
+ }
+
+ exit0:
+ spin_unlock_irqrestore(&g_lock, lock_flags);
+}
+
+irqreturn_t mxc_v4l2out_pp_in_irq_handler(int irq, void *dev_id)
+{
+ int last_buf;
+ int index;
+ unsigned long timeout;
+ unsigned long lock_flags;
+ vout_data *vout = dev_id;
+
+ spin_lock_irqsave(&g_lock, lock_flags);
+
+ g_irq_cnt++;
+
+ if ((vout->state == STATE_STREAM_OFF)
+ || (vout->state == STATE_STREAM_STOPPING)) {
+ spin_unlock_irqrestore(&g_lock, lock_flags);
+ return IRQ_HANDLED;
+ }
+
+ if (vout->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) {
+ struct fb_gwinfo gwinfo;
+
+ gwinfo.enabled = 1;
+ gwinfo.alpha_value = 255;
+ gwinfo.ck_enabled = 0;
+ gwinfo.xpos = vout->crop_current.left;
+ gwinfo.ypos = vout->crop_current.top;
+ gwinfo.base = (unsigned long)vout->display_bufs[pp_num_last()];
+ gwinfo.xres = vout->crop_current.width;
+ gwinfo.yres = vout->crop_current.height;
+ gwinfo.xres_virtual = vout->crop_current.width;
+ gwinfo.vs_reversed = 0;
+
+ mx2_gw_set(&gwinfo);
+ }
+
+ /* Process previous buffer */
+ last_buf = vout->ipu_buf[0];
+ pr_debug("last_buf %d g_irq_cnt %d\n", last_buf, g_irq_cnt);
+ 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);
+ vout->ipu_buf[0] = -1;
+ wake_up_interruptible(&vout->v4l_bufq);
+ }
+
+ /* Setup timer for next buffer, when stream has been paused */
+ if ((vout->state == STATE_STREAM_PAUSED)
+ && ((index = peek_next_buf(&vout->ready_q)) != -1)) {
+ 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);
+ }
+
+ 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)
+{
+ unsigned long timeout;
+ int index;
+
+ if (!vout)
+ return -EINVAL;
+
+ if (vout->state != STATE_STREAM_OFF)
+ return -EBUSY;
+
+ if (queue_size(&vout->ready_q) < 1) {
+ pr_debug("no buffers queued yet!\n");
+ return -EINVAL;
+ }
+
+ vout->ipu_buf[0] = -1;
+
+ if (vout->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) {
+ /* Free previously allocated buffer */
+ mxc_free_buffers(vout->display_bufs, vout->display_bufs_vaddr,
+ 2, vout->display_buf_size);
+ /* Allocate buffers for foreground */
+ if (mxc_allocate_buffers(vout->display_bufs,
+ vout->display_bufs_vaddr, 2,
+ vout->display_buf_size) < 0) {
+ pr_debug("unable to allocate SDC FG buffers\n");
+ return -ENOMEM;
+ }
+ }
+
+ /* Configure PP */
+ if (pp_cfg(vout)) {
+ /* Free previously allocated buffer */
+ mxc_free_buffers(vout->display_bufs, vout->display_bufs_vaddr,
+ 2, vout->display_buf_size);
+ pr_debug("failed to config PP.\n");
+ return -EINVAL;
+ }
+#ifdef CONFIG_VIDEO_MXC_OUTPUT_FBSYNC
+ g_output_fb = vout->output_fb_num[vout->cur_disp_output];
+ g_fb_enabled = 0;
+ g_pp_ready = 0;
+ fb_register_client(&fb_event_notifier);
+ mx2fb_register_client(&mx2fb_event_notifier);
+#endif
+ vout->frame_count = 0;
+ vout->state = STATE_STREAM_ON;
+ 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 = jiffies;
+ else
+ timeout = get_jiffies(&vout->v4l2_bufs[index].timestamp);
+
+ if (jiffies >= timeout) {
+ pr_debug("warning: timer timeout already expired.\n");
+ }
+
+ vout->start_jiffies = vout->output_timer.expires = timeout;
+ pr_debug("STREAMON:Add timer %d timeout @ %lu jiffies, current = %lu\n",
+ index, timeout, jiffies);
+ add_timer(&vout->output_timer);
+
+ return 0;
+}
+
+/*!
+ * Shut down the voutera
+ *
+ * @param vout structure vout_data *
+ *
+ * @return status 0 Success
+ */
+static int mxc_v4l2out_streamoff(vout_data * vout)
+{
+ int i, retval = 0;
+ unsigned long lock_flag = 0;
+
+ if (!vout)
+ return -EINVAL;
+
+ if (vout->state == STATE_STREAM_OFF) {
+ return 0;
+ }
+
+ spin_lock_irqsave(&g_lock, lock_flag);
+
+ del_timer(&vout->output_timer);
+ pp_enable(0); /* Disable PP */
+
+ if (vout->state == STATE_STREAM_ON) {
+ vout->state = STATE_STREAM_STOPPING;
+ }
+
+ spin_unlock_irqrestore(&g_lock, lock_flag);
+
+ 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;
+
+ if (vout->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) {
+ struct fb_gwinfo gwinfo;
+
+ /* Disable graphic window */
+ gwinfo.enabled = 0;
+ mx2_gw_set(&gwinfo);
+ }
+#ifdef CONFIG_VIDEO_MXC_OUTPUT_FBSYNC
+ g_output_fb = -1;
+ g_fb_enabled = 0;
+ g_pp_ready = 0;
+ fb_unregister_client(&fb_event_notifier);
+ mx2fb_unregister_client(&mx2fb_event_notifier);
+#endif
+
+ mxc_free_buffers(vout->display_bufs, vout->display_bufs_vaddr,
+ 2, vout->display_buf_size);
+
+ 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_YUV420);
+}
+
+/*
+ * 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;
+}
+
+/*
+ * 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)) {
+ pr_debug("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:
+ 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 */
+ f->fmt.pix.sizeimage = size;
+
+ vout->v2f.fmt.pix.sizeimage = size;
+ vout->v2f.fmt.pix.width = f->fmt.pix.width;
+ vout->v2f.fmt.pix.height = f->fmt.pix.height;
+ vout->v2f.fmt.pix.pixelformat = f->fmt.pix.pixelformat;
+ vout->v2f.fmt.pix.bytesperline = f->fmt.pix.bytesperline;
+
+ 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:
+ case V4L2_CID_VFLIP:
+ case V4L2_CID_MXC_ROT:
+ return 0;
+ 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) {
+ pr_info("Internal error, vout_data not found!\n");
+ return -ENODEV;
+ }
+
+ down(&vout->busy_lock);
+
+ err = -EINTR;
+ if (signal_pending(current))
+ goto oops;
+
+ if (vout->open_count++ == 0) {
+ pp_init(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;
+ 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 = file->private_data;
+ vout_data *vout = video_get_drvdata(dev);
+
+ if (--vout->open_count == 0) {
+ pr_debug("release resource\n");
+
+ pp_exit(vout);
+ 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->display_bufs,
+ vout->display_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 *dev = file->private_data;
+ vout_data *vout = video_get_drvdata(dev);
+ 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)) {
+ pr_debug
+ ("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);
+ pr_debug
+ ("VIDIOC_REQBUFS: freed buffers\n");
+ }
+ vout->buffer_cnt = 0;
+ } else {
+ pr_debug("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)) {
+ pr_debug
+ ("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;
+ unsigned long timeout;
+
+ if ((buf->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) ||
+ (index >= vout->buffer_cnt) || (buf->flags != 0)) {
+ retval = -EINVAL;
+ break;
+ }
+
+ pr_debug("VIDIOC_QBUF: %d\n", buf->index);
+
+ 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) {
+ 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) {
+ pr_debug
+ ("warning: timer timeout already expired.\n");
+ }
+
+ vout->output_timer.expires = timeout;
+ pr_debug
+ ("QBUF:Add timer %d timeout @ %lu jiffies, "
+ "current = %lu\n", index, 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;
+
+ pr_debug("VIDIOC_DQBUF: q size = %d\n",
+ queue_size(&vout->done_q));
+
+ 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)) {
+ pr_debug("VIDIOC_DQBUF: timeout\n");
+ retval = -ETIME;
+ break;
+ } else if (signal_pending(current)) {
+ pr_debug("VIDIOC_DQBUF: interrupt received\n");
+ retval = -ERESTARTSYS;
+ break;
+ }
+ idx = dequeue_buf(&vout->done_q);
+ if (idx == -1) { /* No frame free */
+ pr_debug
+ ("VIDIOC_DQBUF: no free buffers, returning\n");
+ retval = -EAGAIN;
+ break;
+ }
+ if ((vout->v4l2_bufs[idx].flags & V4L2_BUF_FLAG_DONE) ==
+ 0)
+ pr_debug
+ ("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));
+ pr_debug("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;
+ }
+
+ 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;
+ 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.top = b->left;
+ if (crop->c.left > b->left + b->width)
+ crop->c.top = b->left + b->width;
+ 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;
+
+ vout->display_buf_size = vout->crop_current.width *
+ vout->crop_current.height;
+ vout->display_buf_size *=
+ fmt_to_bpp(SDC_FG_FB_FORMAT) / 8;
+ break;
+ }
+ case VIDIOC_ENUMOUTPUT:
+ {
+ struct v4l2_output *output = arg;
+
+ if ((output->index >= 2) ||
+ (vout->output_enabled[output->index] == false)) {
+ retval = -EINVAL;
+ break;
+ }
+
+ *output = mxc_outputs[0];
+ output->name[4] = '0' + output->index;
+ 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;
+
+ if ((*p_output_num >= 2) ||
+ (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;
+ break;
+ }
+ case VIDIOC_G_FBUF:
+ {
+ struct v4l2_framebuffer *fb = arg;
+ *fb = vout->v4l2_fb;
+ break;
+ }
+ case VIDIOC_S_FBUF:
+ {
+ struct v4l2_framebuffer *fb = arg;
+ vout->v4l2_fb = *fb;
+ vout->v4l2_fb.capability = V4L2_FBUF_CAP_EXTERNOVERLAY;
+ 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 *dev = file->private_data;
+ unsigned long start = vma->vm_start;
+ unsigned long size = vma->vm_end - vma->vm_start;
+ int res = 0;
+ vout_data *vout = video_get_drvdata(dev);
+
+ /* make this _really_ smp-safe */
+ if (down_interruptible(&vout->busy_lock))
+ return -EINTR;
+
+ /* make buffers write-thru cacheable */
+ vma->vm_page_prot = __pgprot(pgprot_val(vma->vm_page_prot) &
+ ~L_PTE_BUFFERABLE);
+
+ if (remap_pfn_range(vma, start, vma->vm_pgoff, size, vma->vm_page_prot)) {
+ pr_debug("mxc_mmap(V4L)i - remap_pfn_range failed\n");
+ res = -ENOBUFS;
+ goto mxc_mmap_exit;
+ }
+
+ 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 = file->private_data;
+ 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 = {
+ .name = "MXC Video Output",
+ .vfl_type = 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->minor = -1;
+
+ *(vout->video_dev) = mxc_v4l2out_template;
+
+ /* register v4l device */
+ if (video_register_device(vout->video_dev,
+ VFL_TYPE_GRABBER, video_nr) == -1) {
+ pr_debug("video_register_device failed\n");
+ return 0;
+ }
+ pr_debug("mxc_v4l2out: registered device video%d\n",
+ vout->video_dev->minor & 0x1f);
+
+ 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 = i;
+ 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];
+
+ /* Setup framebuffer parameters */
+ vout->v4l2_fb.capability = V4L2_FBUF_CAP_EXTERNOVERLAY;
+ vout->v4l2_fb.flags = V4L2_FBUF_FLAG_PRIMARY;
+
+ return 0;
+}
+
+/*!
+ * This structure contains pointers to the power management callback functions.
+ */
+static struct platform_driver mxc_v4l2out_driver = {
+ .driver = {
+ .name = "MXC Video Output",
+ .owner = THIS_MODULE,
+ .bus = &platform_bus_type,
+ },
+ .probe = mxc_v4l2out_probe,
+ .remove = NULL,
+};
+
+static void camera_platform_release(struct device *device)
+{
+}
+
+static struct platform_device mxc_v4l2out_device = {
+ .name = "MXC Video Output",
+ .dev = {
+ .release = camera_platform_release,
+ },
+ .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)
+{
+ pr_debug("unregistering video\n");
+
+ 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/mx31_v4l2_wvga_output.c b/drivers/media/video/mxc/output/mx31_v4l2_wvga_output.c
new file mode 100644
index 000000000000..4a82da2d7d00
--- /dev/null
+++ b/drivers/media/video/mxc/output/mx31_v4l2_wvga_output.c
@@ -0,0 +1,1926 @@
+/*
+ * Copyright 2005-2009 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/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;
+ }
+ 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 = peek_next_buf(&vout->ready_q)) != -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_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 *
+ *
+ * @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_v4l2_output.c b/drivers/media/video/mxc/output/mxc_v4l2_output.c
new file mode 100644
index 000000000000..ce577c4c27d3
--- /dev/null
+++ b/drivers/media/video/mxc/output/mxc_v4l2_output.c
@@ -0,0 +1,2614 @@
+/*
+ * 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/module.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/semaphore.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 spinlock_t g_lock = SPIN_LOCK_UNLOCKED;
+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_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;
+
+ 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;
+
+ /* make sure buf[next_done_ipu_buf] showed */
+ while (ipu_check_buffer_busy(vout->display_ch,
+ IPU_INPUT_BUFFER, vout->next_done_ipu_buf)) {
+ /* 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);
+
+ if (ret < 0) {
+ /* ic_bypass need clear display buffer ready for next update*/
+ ipu_clear_buffer_ready(vout->display_ch, IPU_INPUT_BUFFER,
+ vout->next_done_ipu_buf);
+ }
+ }
+ }
+
+ 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[next_rdy_ipu_buf] begin to show */
+ if (ipu_get_cur_buffer_idx(vout->display_ch, IPU_INPUT_BUFFER)
+ != vout->next_rdy_ipu_buf) {
+ /* 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 timer_work_func(struct work_struct *work)
+{
+ vout_data *vout =
+ container_of(work, vout_data, timer_work);
+ int index, ret;
+ int last_buf;
+ unsigned long lock_flags = 0;
+
+ finish_previous_frame(vout);
+
+ spin_lock_irqsave(&g_lock, lock_flags);
+
+ if (g_buf_output_cnt == 0) {
+ ipu_select_buffer(vout->display_ch, IPU_INPUT_BUFFER, 1);
+ } else {
+ 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 void mxc_v4l2out_timer_handler(unsigned long arg)
+{
+ int index, ret;
+ unsigned long lock_flags = 0;
+ vout_data *vout = (vout_data *) arg;
+
+ 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->timer_work) == 0) {
+ dev_err(&vout->video_dev->dev, "work was in queue already!\n ");
+ vout->state = STATE_STREAM_PAUSED;
+ }
+ goto exit0;
+ }
+
+ /* 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;
+ }
+
+ /* Non IC split action */
+ if (!vout->pp_split)
+ vout->next_rdy_ipu_buf = !vout->next_rdy_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");
+ vout->next_rdy_ipu_buf = !vout->next_rdy_ipu_buf;
+
+ } else {/* last stripe is done, run display refresh */
+ select_display_buffer(vout, disp_buf_num);
+ vout->ipu_buf[vout->next_done_ipu_buf] = -1;
+ vout->next_done_ipu_buf = !vout->next_done_ipu_buf;
+ vout->next_rdy_ipu_buf = !vout->next_rdy_ipu_buf;
+ }
+
+ /* 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 {
+ /* show to display */
+ select_display_buffer(vout, 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) {
+ 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;
+ }
+ vout->next_done_ipu_buf = !vout->next_done_ipu_buf;
+ }
+ } /* end of last_buf != -1 */
+
+ 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 = peek_next_buf(&vout->ready_q)) != -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;
+ 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;
+ }
+
+ 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[vout->ipu_buf[0]].m.offset,
+ vout->v4l2_bufs[vout->ipu_buf[1]].m.offset,
+ 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;
+ 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->next_done_ipu_buf = 0;
+ vout->next_rdy_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->next_done_ipu_buf = vout->next_rdy_ipu_buf = 0;
+ 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->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] = dequeue_buf(&vout->ready_q);
+ vout->frame_count = 2;
+ }
+ } 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) {
+ fbvar.bits_per_pixel = 16;
+ if (format_is_yuv(vout->v2f.fmt.pix.pixelformat))
+ 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)))
+ 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) {
+ pr_debug("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.activate |= FB_ACTIVATE_FORCE;
+ fb_set_var(fbi, &fbvar);
+
+ 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();
+ } else {
+ ipu_enable_channel(vout->display_ch);
+ }
+ 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 if (INTERLACED_CONTENT(vout)) {
+ ipu_select_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER, 0);
+ } else {
+ ipu_select_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER, 0);
+ if (!vout->pp_split)
+ ipu_select_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER, 1);
+ }
+ 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);
+ ipu_update_channel_buffer(vout->display_ch,
+ IPU_INPUT_BUFFER,
+ 1, vout->v4l2_bufs[vout->ipu_buf[1]].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->timer_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->timer_work);
+
+ 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;
+ 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;
+ } 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;
+ 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 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;
+ 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_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_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:
+ 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;
+ case V4L2_FIELD_INTERLACED_BT:
+ dev_err(&vout->video_dev->dev,
+ "V4L2_FIELD_INTERLACED_BT field format not supported yet!\n");
+ 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->timer_work, timer_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,
+};
+
+/*!
+ * 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);
+
+ return 0;
+}
+
+static int mxc_v4l2out_remove(struct platform_device *pdev)
+{
+ vout_data *vout = platform_get_drvdata(pdev);
+
+ if (vout->video_dev) {
+ 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..096dc3b17a06
--- /dev/null
+++ b/drivers/media/video/mxc/output/mxc_v4l2_output.h
@@ -0,0 +1,154 @@
+/*
+ * 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 timer_work;
+ 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 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__ */