Adds a V4L2 mem2mem driver for i.MX5/6 SoC. Uses the IPU IC image
converter (post-processor task) to perform scaling, rotation, and
color space conversion.

Signed-off-by: Steve Longerbeam <steve_longerb...@mentor.com>
---
 Documentation/devicetree/bindings/media/imx.txt |   25 +-
 drivers/staging/media/imx/Kconfig               |   12 +
 drivers/staging/media/imx/Makefile              |    1 +
 drivers/staging/media/imx/m2m/Makefile          |    1 +
 drivers/staging/media/imx/m2m/imx-m2m.c         | 1049 +++++++++++++++++++++++
 5 files changed, 1087 insertions(+), 1 deletion(-)
 create mode 100644 drivers/staging/media/imx/m2m/Makefile
 create mode 100644 drivers/staging/media/imx/m2m/imx-m2m.c

diff --git a/Documentation/devicetree/bindings/media/imx.txt 
b/Documentation/devicetree/bindings/media/imx.txt
index 5a89b51..67d3817 100644
--- a/Documentation/devicetree/bindings/media/imx.txt
+++ b/Documentation/devicetree/bindings/media/imx.txt
@@ -1,4 +1,27 @@
-Freescale i.MX Video Capture
+Freescale i.MX Video Capture, Mem2Mem
+
+Video Mem2Mem node
+------------------
+
+This is the imx video mem2mem device node. The mem2mem node is an IPU
+client and uses the register-level primitives of the IPU, so it does
+not require reg or interrupt properties. Only a compatible property
+and the ipu phandle is required.
+
+Required properties:
+- compatible   : "fsl,imx-video-mem2mem";
+- ipu           : the ipu phandle;
+
+Example:
+
+/ {
+       ipum2m0: ipum2m@ipu1 {
+               compatible = "fsl,imx-video-mem2mem";
+               ipu = <&ipu1>;
+               status = "okay";
+       };
+};
+
 
 Video Capture node
 ------------------
diff --git a/drivers/staging/media/imx/Kconfig 
b/drivers/staging/media/imx/Kconfig
index 65e1645..3305daa 100644
--- a/drivers/staging/media/imx/Kconfig
+++ b/drivers/staging/media/imx/Kconfig
@@ -21,3 +21,15 @@ config VIDEO_IMX_CAMERA
 if VIDEO_IMX_CAMERA
 source "drivers/staging/media/imx/capture/Kconfig"
 endif
+
+config VIDEO_IMX_M2M
+       tristate "i.MX5/6 Mem2Mem driver"
+       depends on VIDEO_IMX && VIDEO_DEV
+       select VIDEOBUF2_DMA_CONTIG
+       select V4L2_MEM2MEM_DEV
+       default y
+       ---help---
+         Use the IPU IC Post-processor on the i.MX5/6 SoC for mem2mem
+         processing of buffers. Operations include scaling, rotation,
+         and color space conversion. The driver implements tiling to
+         support scaling up to 4096x4096.
diff --git a/drivers/staging/media/imx/Makefile 
b/drivers/staging/media/imx/Makefile
index 7c97629..b9e31d8 100644
--- a/drivers/staging/media/imx/Makefile
+++ b/drivers/staging/media/imx/Makefile
@@ -1 +1,2 @@
 obj-$(CONFIG_VIDEO_IMX_CAMERA) += capture/
+obj-$(CONFIG_VIDEO_IMX_M2M) += m2m/
diff --git a/drivers/staging/media/imx/m2m/Makefile 
b/drivers/staging/media/imx/m2m/Makefile
new file mode 100644
index 0000000..287d258
--- /dev/null
+++ b/drivers/staging/media/imx/m2m/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_VIDEO_IMX_M2M) += imx-m2m.o
diff --git a/drivers/staging/media/imx/m2m/imx-m2m.c 
b/drivers/staging/media/imx/m2m/imx-m2m.c
new file mode 100644
index 0000000..66b49ff
--- /dev/null
+++ b/drivers/staging/media/imx/m2m/imx-m2m.c
@@ -0,0 +1,1049 @@
+/*
+ * This is a mem2mem driver for the Freescale i.MX5/6 SOC. It carries out
+ * color-space conversion, downsizing, resizing, and rotation transformations
+ * on input buffers using the IPU Image Converter's Post-Processing task.
+ *
+ * Based on mem2mem_testdev.c by Pawel Osciak.
+ *
+ * Copyright (c) 2012-2013 Mentor Graphics Inc.
+ * Steve Longerbeam <steve_longerb...@mentor.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version
+ */
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/fs.h>
+#include <linux/timer.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/log2.h>
+
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <media/v4l2-mem2mem.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-ctrls.h>
+#include <media/videobuf2-dma-contig.h>
+#include <video/imx-ipu-v3.h>
+#include <media/imx.h>
+
+MODULE_DESCRIPTION("i.MX5/6 Post-Processing mem2mem device");
+MODULE_AUTHOR("Steve Longerbeam <steve_longerb...@mentor.com>");
+MODULE_LICENSE("GPL");
+MODULE_VERSION("0.1");
+
+static int instrument;
+module_param(instrument, int, 0);
+MODULE_PARM_DESC(instrument, "1 = enable conversion time measurement");
+
+/* Flags that indicate a format can be used for capture/output */
+#define MEM2MEM_CAPTURE BIT(0)
+#define MEM2MEM_OUTPUT  BIT(1)
+
+#define MEM2MEM_NAME           "imx-m2m"
+
+/* Per queue */
+#define MEM2MEM_DEF_NUM_BUFS   VIDEO_MAX_FRAME
+/* In bytes, per queue */
+#define MEM2MEM_VID_MEM_LIMIT  SZ_256M
+
+#define dprintk(dev, fmt, arg...) \
+       v4l2_dbg(1, 1, &dev->v4l2_dev, "%s: " fmt, __func__, ## arg)
+
+struct m2mx_ctx;
+
+struct m2mx_dev {
+       struct v4l2_device      v4l2_dev;
+       struct video_device     *vfd;
+       struct device           *ipu_dev;
+       struct ipu_soc          *ipu;
+
+       struct mutex            dev_mutex;
+
+       struct v4l2_m2m_dev     *m2m_dev;
+       struct vb2_alloc_ctx    *alloc_ctx;
+};
+
+struct m2mx_ctx {
+       struct v4l2_fh          fh;
+       struct m2mx_dev *dev;
+
+       struct ipu_ic           *ic;
+       struct image_converter_ctx *ic_ctx;
+
+       /* The rotation controls */
+       struct v4l2_ctrl_handler ctrl_hdlr;
+       int                     rotation; /* degrees */
+       bool                    hflip;
+       bool                    vflip;
+
+       /* derived from rotation, hflip, vflip controls */
+       enum ipu_rotate_mode    rot_mode;
+
+       /* has the IC image converter been preapred */
+       bool                    image_converter_ready;
+
+       /* Abort requested by m2m */
+       bool                    aborting;
+
+       /* Source and destination image data */
+       struct ipu_image  image[2];
+
+       /* for instrumenting */
+       struct timespec   start;
+};
+
+#define fh_to_ctx(p) container_of(p, struct m2mx_ctx, fh)
+
+enum {
+       V4L2_M2M_SRC = 0,
+       V4L2_M2M_DST = 1,
+};
+
+static const struct v4l2_ctrl_config m2mx_std_ctrl[] = {
+       {
+               .id     = V4L2_CID_HFLIP,
+               .type   = V4L2_CTRL_TYPE_BOOLEAN,
+               .name   = "Horizontal Flip",
+               .def    = 0,
+               .min    = 0,
+               .max    = 1,
+               .step   = 1,
+       }, {
+               .id     = V4L2_CID_VFLIP,
+               .type   = V4L2_CTRL_TYPE_BOOLEAN,
+               .name   = "Vertical Flip",
+               .def    = 0,
+               .min    = 0,
+               .max    = 1,
+               .step   = 1,
+       }, {
+               .id     = V4L2_CID_ROTATE,
+               .type   = V4L2_CTRL_TYPE_INTEGER,
+               .name   = "Rotation",
+               .def    = 0,
+               .min    = 0,
+               .max    = 270,
+               .step   = 90,
+       },
+};
+
+#define M2MX_NUM_STD_CONTROLS ARRAY_SIZE(m2mx_std_ctrl)
+
+static struct ipu_image *get_image_data(struct m2mx_ctx *ctx,
+                                       enum v4l2_buf_type type)
+{
+       switch (type) {
+       case V4L2_BUF_TYPE_VIDEO_OUTPUT:
+               return &ctx->image[V4L2_M2M_SRC];
+       case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+               return &ctx->image[V4L2_M2M_DST];
+       default:
+               break;
+       }
+       return NULL;
+}
+
+/*
+ * mem2mem callbacks
+ */
+
+static void m2mx_job_abort(void *priv)
+{
+       struct m2mx_ctx *ctx = priv;
+
+       /* Will cancel the transaction in the next interrupt handler */
+       ipu_image_convert_abort(ctx->ic_ctx);
+       ctx->aborting = true;
+}
+
+static void m2mx_lock(void *priv)
+{
+       struct m2mx_ctx *ctx = priv;
+       struct m2mx_dev *dev = ctx->dev;
+
+       mutex_lock(&dev->dev_mutex);
+}
+
+static void m2mx_unlock(void *priv)
+{
+       struct m2mx_ctx *ctx = priv;
+       struct m2mx_dev *dev = ctx->dev;
+
+       mutex_unlock(&dev->dev_mutex);
+}
+
+static void m2mx_device_run(void *priv)
+{
+       struct m2mx_ctx *ctx = priv;
+       struct m2mx_dev *dev = ctx->dev;
+       struct vb2_v4l2_buffer *src_buf, *dst_buf;
+       dma_addr_t s_phys, d_phys;
+
+       src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
+       dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx);
+
+       s_phys = vb2_dma_contig_plane_dma_addr(&src_buf->vb2_buf, 0);
+       d_phys = vb2_dma_contig_plane_dma_addr(&dst_buf->vb2_buf, 0);
+       if (!s_phys || !d_phys) {
+               v4l2_err(&dev->v4l2_dev,
+                        "Acquiring kernel pointers to buffers failed\n");
+               return;
+       }
+
+       if (instrument)
+               getnstimeofday(&ctx->start);
+
+       ipu_image_convert_run(ctx->ic_ctx, s_phys, d_phys);
+}
+
+static void m2mx_convert_complete(void *data,
+                                  struct image_converter_run *run,
+                                  int err)
+{
+       struct m2mx_ctx *ctx = data;
+       struct m2mx_dev *dev = ctx->dev;
+       struct vb2_v4l2_buffer *src_vb, *dst_vb;
+
+       if (!ctx->aborting) {
+               src_vb = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
+               dst_vb = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx);
+
+               v4l2_m2m_buf_done(src_vb, VB2_BUF_STATE_DONE);
+               v4l2_m2m_buf_done(dst_vb, VB2_BUF_STATE_DONE);
+
+               if (instrument) {
+                       struct timespec ts, diff;
+                       unsigned long interval;
+
+                       getnstimeofday(&ts);
+                       diff = timespec_sub(ts, ctx->start);
+                       interval = diff.tv_sec * 1000 * 1000 +
+                               diff.tv_nsec / 1000;
+                       v4l2_info(&ctx->dev->v4l2_dev,
+                                 "buf%d completed in %lu usec\n",
+                                 dst_vb->vb2_buf.index, interval);
+               }
+       }
+
+       v4l2_m2m_job_finish(dev->m2m_dev, ctx->fh.m2m_ctx);
+}
+
+/*
+ * video ioctls
+ */
+static int vidioc_querycap(struct file *file, void *fh,
+                          struct v4l2_capability *cap)
+{
+       strncpy(cap->driver, MEM2MEM_NAME, sizeof(cap->driver) - 1);
+       strncpy(cap->card, MEM2MEM_NAME, sizeof(cap->card) - 1);
+       cap->bus_info[0] = 0;
+       cap->device_caps = V4L2_CAP_VIDEO_M2M | V4L2_CAP_STREAMING;
+       cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
+
+       return 0;
+}
+
+static int enum_fmt(struct v4l2_fmtdesc *f, u32 type)
+{
+       const char *desc;
+       u32 fourcc;
+       int ret;
+
+       ret = ipu_image_convert_enum_format(f->index, &desc, &fourcc);
+       if (ret)
+               return ret;
+
+       strncpy(f->description, desc, sizeof(f->description) - 1);
+       f->pixelformat = fourcc;
+       return 0;
+}
+
+static int vidioc_enum_fmt_vid_cap(struct file *file, void *fh,
+                                  struct v4l2_fmtdesc *f)
+{
+       return enum_fmt(f, MEM2MEM_CAPTURE);
+}
+
+static int vidioc_enum_fmt_vid_out(struct file *file, void *fh,
+                                  struct v4l2_fmtdesc *f)
+{
+       return enum_fmt(f, MEM2MEM_OUTPUT);
+}
+
+static int m2mx_g_fmt(struct m2mx_ctx *ctx, struct v4l2_format *f)
+{
+       struct vb2_queue *vq;
+       struct ipu_image *image;
+
+       vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type);
+       if (!vq)
+               return -EINVAL;
+
+       image = get_image_data(ctx, f->type);
+       if (!image)
+               return -EINVAL;
+
+       f->fmt.pix = image->pix;
+
+       return 0;
+}
+
+static int vidioc_g_fmt_vid_out(struct file *file, void *fh,
+                               struct v4l2_format *f)
+{
+       struct m2mx_ctx *ctx = fh_to_ctx(fh);
+
+       return m2mx_g_fmt(ctx, f);
+}
+
+static int vidioc_g_fmt_vid_cap(struct file *file, void *fh,
+                               struct v4l2_format *f)
+{
+       struct m2mx_ctx *ctx = fh_to_ctx(fh);
+
+       return m2mx_g_fmt(ctx, f);
+}
+
+static int m2mx_try_fmt(struct m2mx_ctx *ctx, struct v4l2_format *f)
+{
+       struct ipu_image test_in, test_out;
+       struct ipu_image *pin, *pout;
+       int ret;
+
+       pin = get_image_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT);
+       pout = get_image_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE);
+
+       if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
+               test_out.pix = f->fmt.pix;
+               test_in = *pin;
+       } else {
+               test_in.pix = f->fmt.pix;
+               test_out = *pout;
+       }
+
+       ret = ipu_image_convert_adjust(&test_in, &test_out, ctx->rot_mode);
+       if (ret)
+               return ret;
+
+       f->fmt.pix = (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) ?
+               test_out.pix : test_in.pix;
+
+       return 0;
+}
+
+static int vidioc_try_fmt_vid_cap(struct file *file, void *fh,
+                                 struct v4l2_format *f)
+{
+       struct m2mx_ctx *ctx = fh_to_ctx(fh);
+
+       return m2mx_try_fmt(ctx, f);
+}
+
+static int vidioc_try_fmt_vid_out(struct file *file, void *fh,
+                                 struct v4l2_format *f)
+{
+       struct m2mx_ctx *ctx = fh_to_ctx(fh);
+
+       return m2mx_try_fmt(ctx, f);
+}
+
+static int m2mx_s_fmt(struct m2mx_ctx *ctx, struct v4l2_format *f)
+{
+       struct m2mx_dev *dev = ctx->dev;
+       struct ipu_image test_in, test_out;
+       struct ipu_image *pin, *pout;
+       struct vb2_queue *vq;
+       int ret;
+
+       vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type);
+       if (!vq)
+               return -EINVAL;
+
+       if (vb2_is_busy(vq)) {
+               v4l2_err(&dev->v4l2_dev, "%s: queue busy\n", __func__);
+               return -EBUSY;
+       }
+
+       pin = get_image_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT);
+       pout = get_image_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE);
+
+       if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
+               test_out.pix = f->fmt.pix;
+               test_in = *pin;
+       } else {
+               test_in.pix = f->fmt.pix;
+               test_out = *pout;
+       }
+
+       ret = ipu_image_convert_adjust(&test_in, &test_out, ctx->rot_mode);
+       if (ret)
+               return ret;
+
+       f->fmt.pix = (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) ?
+               test_out.pix : test_in.pix;
+       *pin = test_in;
+       *pout = test_out;
+
+       return 0;
+}
+
+static int vidioc_s_fmt_vid_cap(struct file *file, void *fh,
+                               struct v4l2_format *f)
+{
+       struct m2mx_ctx *ctx = fh_to_ctx(fh);
+
+       return m2mx_s_fmt(ctx, f);
+}
+
+static int vidioc_s_fmt_vid_out(struct file *file, void *fh,
+                               struct v4l2_format *f)
+{
+       struct m2mx_ctx *ctx = fh_to_ctx(fh);
+
+       return m2mx_s_fmt(ctx, f);
+}
+
+static int vidioc_reqbufs(struct file *file, void *fh,
+                         struct v4l2_requestbuffers *reqbufs)
+{
+       struct m2mx_ctx *ctx = fh_to_ctx(fh);
+
+       return v4l2_m2m_reqbufs(file, ctx->fh.m2m_ctx, reqbufs);
+}
+
+static int vidioc_querybuf(struct file *file, void *fh,
+                          struct v4l2_buffer *buf)
+{
+       struct m2mx_ctx *ctx = fh_to_ctx(fh);
+
+       return v4l2_m2m_querybuf(file, ctx->fh.m2m_ctx, buf);
+}
+
+static int vidioc_qbuf(struct file *file, void *fh, struct v4l2_buffer *buf)
+{
+       struct m2mx_ctx *ctx = fh_to_ctx(fh);
+
+       return v4l2_m2m_qbuf(file, ctx->fh.m2m_ctx, buf);
+}
+
+static int vidioc_dqbuf(struct file *file, void *fh, struct v4l2_buffer *buf)
+{
+       struct m2mx_ctx *ctx = fh_to_ctx(fh);
+
+       return v4l2_m2m_dqbuf(file, ctx->fh.m2m_ctx, buf);
+}
+
+static int vidioc_expbuf(struct file *file, void *fh,
+                        struct v4l2_exportbuffer *eb)
+{
+       struct m2mx_ctx *ctx = fh_to_ctx(fh);
+
+       return v4l2_m2m_expbuf(file, ctx->fh.m2m_ctx, eb);
+}
+
+static int vidioc_streamon(struct file *file, void *fh,
+                          enum v4l2_buf_type type)
+{
+       struct m2mx_ctx *ctx = fh_to_ctx(fh);
+
+       return v4l2_m2m_streamon(file, ctx->fh.m2m_ctx, type);
+}
+
+static int vidioc_streamoff(struct file *file, void *fh,
+                           enum v4l2_buf_type type)
+{
+       struct m2mx_ctx *ctx = fh_to_ctx(fh);
+
+       return v4l2_m2m_streamoff(file, ctx->fh.m2m_ctx, type);
+}
+
+static int m2mx_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+       struct m2mx_ctx *ctx = container_of(ctrl->handler,
+                                            struct m2mx_ctx, ctrl_hdlr);
+       struct m2mx_dev *dev = ctx->dev;
+       enum ipu_rotate_mode rot_mode;
+       struct ipu_image *in, *out;
+       struct vb2_queue *vq;
+       bool hflip, vflip;
+       int rotation;
+       int ret;
+
+       vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE);
+       if (!vq)
+               return -EINVAL;
+
+       /* can't change rotation mid-streaming */
+       if (vb2_is_streaming(vq)) {
+               v4l2_err(&dev->v4l2_dev, "%s: not allowed while streaming\n",
+                        __func__);
+               return -EBUSY;
+       }
+
+       in = get_image_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT);
+       out = get_image_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE);
+
+       rotation = ctx->rotation;
+       hflip = ctx->hflip;
+       vflip = ctx->vflip;
+
+       switch (ctrl->id) {
+       case V4L2_CID_HFLIP:
+               hflip = (ctrl->val == 1);
+               break;
+       case V4L2_CID_VFLIP:
+               vflip = (ctrl->val == 1);
+               break;
+       case V4L2_CID_ROTATE:
+               rotation = ctrl->val;
+               break;
+       default:
+               v4l2_err(&dev->v4l2_dev, "Invalid control\n");
+               return -EINVAL;
+       }
+
+       ret = ipu_degrees_to_rot_mode(&rot_mode, rotation, hflip, vflip);
+       if (ret)
+               return ret;
+
+       /*
+        * make sure this rotation will work with current src/dest
+        * rectangles before setting
+        */
+       ret = ipu_image_convert_verify(in, out, rot_mode);
+       if (ret)
+               return ret;
+
+       ctx->rotation = rotation;
+       ctx->hflip = hflip;
+       ctx->vflip = vflip;
+       ctx->rot_mode = rot_mode;
+
+       return 0;
+}
+
+static const struct v4l2_ctrl_ops m2mx_ctrl_ops = {
+       .s_ctrl = m2mx_s_ctrl,
+};
+
+static int m2mx_init_controls(struct m2mx_ctx *ctx)
+{
+       struct v4l2_ctrl_handler *hdlr = &ctx->ctrl_hdlr;
+       const struct v4l2_ctrl_config *c;
+       int i, ret;
+
+       v4l2_ctrl_handler_init(hdlr, M2MX_NUM_STD_CONTROLS);
+
+       for (i = 0; i < M2MX_NUM_STD_CONTROLS; i++) {
+               c = &m2mx_std_ctrl[i];
+               v4l2_ctrl_new_std(hdlr, &m2mx_ctrl_ops,
+                                 c->id, c->min, c->max, c->step, c->def);
+       }
+
+       if (hdlr->error) {
+               ret = hdlr->error;
+               goto free_ctrls;
+       }
+
+       ctx->fh.ctrl_handler = hdlr;
+       ret = v4l2_ctrl_handler_setup(hdlr);
+       if (ret)
+               goto free_ctrls;
+
+       return 0;
+
+free_ctrls:
+       v4l2_ctrl_handler_free(hdlr);
+       return ret;
+}
+
+static const struct v4l2_ioctl_ops m2mx_ioctl_ops = {
+       .vidioc_querycap        = vidioc_querycap,
+
+       .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
+       .vidioc_g_fmt_vid_cap   = vidioc_g_fmt_vid_cap,
+       .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap,
+       .vidioc_s_fmt_vid_cap   = vidioc_s_fmt_vid_cap,
+
+       .vidioc_enum_fmt_vid_out = vidioc_enum_fmt_vid_out,
+       .vidioc_g_fmt_vid_out   = vidioc_g_fmt_vid_out,
+       .vidioc_try_fmt_vid_out = vidioc_try_fmt_vid_out,
+       .vidioc_s_fmt_vid_out   = vidioc_s_fmt_vid_out,
+
+       .vidioc_reqbufs         = vidioc_reqbufs,
+       .vidioc_querybuf        = vidioc_querybuf,
+
+       .vidioc_qbuf            = vidioc_qbuf,
+       .vidioc_dqbuf           = vidioc_dqbuf,
+       .vidioc_expbuf          = vidioc_expbuf,
+
+       .vidioc_streamon        = vidioc_streamon,
+       .vidioc_streamoff       = vidioc_streamoff,
+};
+
+/*
+ * Queue operations
+ */
+
+static int m2mx_queue_setup(struct vb2_queue *vq,
+                            unsigned int *nbuffers, unsigned int *nplanes,
+                            unsigned int sizes[], void *alloc_ctxs[])
+{
+       struct m2mx_ctx *ctx = vb2_get_drv_priv(vq);
+       struct ipu_image *image;
+       unsigned int count = *nbuffers;
+
+       image = get_image_data(ctx, vq->type);
+       if (!image)
+               return -EINVAL;
+
+       while (image->pix.sizeimage * count > MEM2MEM_VID_MEM_LIMIT)
+               count--;
+
+       *nplanes = 1;
+       *nbuffers = count;
+       sizes[0] = image->pix.sizeimage;
+
+       alloc_ctxs[0] = ctx->dev->alloc_ctx;
+
+       return 0;
+}
+
+static int m2mx_buf_prepare(struct vb2_buffer *vb)
+{
+       struct m2mx_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
+       struct m2mx_dev *dev = ctx->dev;
+       struct ipu_image *image;
+
+       image = get_image_data(ctx, vb->vb2_queue->type);
+       if (!image)
+               return -EINVAL;
+
+       if (vb2_plane_size(vb, 0) < image->pix.sizeimage) {
+               v4l2_err(&dev->v4l2_dev,
+                        "%s: data will not fit into plane (%lu < %lu)\n",
+                        __func__, vb2_plane_size(vb, 0),
+                        (long)image->pix.sizeimage);
+               return -EINVAL;
+       }
+
+       vb2_set_plane_payload(vb, 0, image->pix.sizeimage);
+
+       return 0;
+}
+
+static void m2mx_buf_queue(struct vb2_buffer *vb)
+{
+       struct m2mx_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
+
+       v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, to_vb2_v4l2_buffer(vb));
+}
+
+static void m2mx_wait_prepare(struct vb2_queue *q)
+{
+       struct m2mx_ctx *ctx = vb2_get_drv_priv(q);
+
+       m2mx_unlock(ctx);
+}
+
+static void m2mx_wait_finish(struct vb2_queue *q)
+{
+       struct m2mx_ctx *ctx = vb2_get_drv_priv(q);
+
+       m2mx_lock(ctx);
+}
+
+static int m2mx_start_streaming(struct vb2_queue *q, unsigned int count)
+{
+       struct m2mx_ctx *ctx = vb2_get_drv_priv(q);
+       struct ipu_image *pin, *pout;
+
+       /*
+        * mem2mem requires streamon at both sides, capture and output,
+        * and we only want to prepare the ipu-ic image converter once.
+        */
+       if (ctx->image_converter_ready)
+               return 0;
+
+       pin = get_image_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT);
+       pout = get_image_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE);
+
+       ctx->ic_ctx = ipu_image_convert_prepare(ctx->ic, pin, pout,
+                                               ctx->rot_mode,
+                                               m2mx_convert_complete, ctx);
+       if (IS_ERR(ctx->ic_ctx))
+               return PTR_ERR(ctx->ic_ctx);
+
+       ctx->image_converter_ready = true;
+       return 0;
+}
+
+static void m2mx_stop_streaming(struct vb2_queue *q)
+{
+       struct m2mx_ctx *ctx = vb2_get_drv_priv(q);
+
+       if (ctx->image_converter_ready) {
+               ipu_image_convert_unprepare(ctx->ic_ctx);
+               ctx->image_converter_ready = false;
+       }
+}
+
+static struct vb2_ops m2mx_qops = {
+       .queue_setup     = m2mx_queue_setup,
+       .buf_prepare     = m2mx_buf_prepare,
+       .buf_queue       = m2mx_buf_queue,
+       .wait_prepare    = m2mx_wait_prepare,
+       .wait_finish     = m2mx_wait_finish,
+       .start_streaming = m2mx_start_streaming,
+       .stop_streaming  = m2mx_stop_streaming,
+};
+
+static int queue_init(void *priv, struct vb2_queue *src_vq,
+                     struct vb2_queue *dst_vq)
+{
+       struct m2mx_ctx *ctx = priv;
+       int ret;
+
+       memset(src_vq, 0, sizeof(*src_vq));
+       src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
+       src_vq->io_modes = VB2_MMAP | VB2_DMABUF;
+       src_vq->drv_priv = ctx;
+       src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer);
+       src_vq->ops = &m2mx_qops;
+       src_vq->mem_ops = &vb2_dma_contig_memops;
+       src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+
+       ret = vb2_queue_init(src_vq);
+       if (ret)
+               return ret;
+
+       memset(dst_vq, 0, sizeof(*dst_vq));
+       dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+       dst_vq->io_modes = VB2_MMAP | VB2_DMABUF;
+       dst_vq->drv_priv = ctx;
+       dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer);
+       dst_vq->ops = &m2mx_qops;
+       dst_vq->mem_ops = &vb2_dma_contig_memops;
+       dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+
+       return vb2_queue_init(dst_vq);
+}
+
+/*
+ * File operations
+ */
+static int m2mx_open(struct file *file)
+{
+       struct m2mx_dev *dev = video_drvdata(file);
+       struct ipu_image *in, *out;
+       struct m2mx_ctx *ctx;
+       int ret = 0;
+
+       if (mutex_lock_interruptible(&dev->dev_mutex))
+               return -ERESTARTSYS;
+
+       ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+       if (!ctx) {
+               ret = -ENOMEM;
+               goto unlock;
+       }
+       ctx->dev = dev;
+
+       ctx->ic = ipu_ic_get(dev->ipu, IC_TASK_POST_PROCESSOR);
+       if (IS_ERR(ctx->ic)) {
+               v4l2_err(&dev->v4l2_dev, "could not get IC PP\n");
+               ret = PTR_ERR(ctx->ic);
+               goto error_free;
+       }
+
+       v4l2_fh_init(&ctx->fh, dev->vfd);
+       file->private_data = &ctx->fh;
+       v4l2_fh_add(&ctx->fh);
+
+       ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(dev->m2m_dev, ctx, &queue_init);
+       if (IS_ERR(ctx->fh.m2m_ctx)) {
+               ret = PTR_ERR(ctx->fh.m2m_ctx);
+               goto error_fh;
+       }
+
+       /*
+        * set some defaults for output and capture image formats.
+        * default for both is 640x480 RGB565.
+        */
+       in = get_image_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT);
+       out = get_image_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE);
+       in->pix.width = out->pix.width = 640;
+       in->pix.height = out->pix.height = 480;
+       in->pix.pixelformat = V4L2_PIX_FMT_RGB565;
+       out->pix.pixelformat = V4L2_PIX_FMT_RGB565;
+       in->pix.sizeimage =
+               (in->pix.width * in->pix.height * 16) >> 3;
+       out->pix.sizeimage =
+               (out->pix.width * out->pix.height * 16) >> 3;
+
+       ret = m2mx_init_controls(ctx);
+       if (ret)
+               goto error_m2m_ctx;
+
+       mutex_unlock(&dev->dev_mutex);
+       return 0;
+
+error_m2m_ctx:
+       v4l2_m2m_ctx_release(ctx->fh.m2m_ctx);
+error_fh:
+       v4l2_fh_del(&ctx->fh);
+       v4l2_fh_exit(&ctx->fh);
+       ipu_ic_put(ctx->ic);
+error_free:
+       kfree(ctx);
+unlock:
+       mutex_unlock(&dev->dev_mutex);
+       return ret;
+}
+
+static int m2mx_release(struct file *file)
+{
+       struct m2mx_dev *dev = video_drvdata(file);
+       struct m2mx_ctx *ctx = fh_to_ctx(file->private_data);
+
+       mutex_lock(&dev->dev_mutex);
+
+       v4l2_m2m_ctx_release(ctx->fh.m2m_ctx);
+       v4l2_ctrl_handler_free(&ctx->ctrl_hdlr);
+       v4l2_fh_del(&ctx->fh);
+       v4l2_fh_exit(&ctx->fh);
+       ipu_ic_put(ctx->ic);
+       kfree(ctx);
+
+       mutex_unlock(&dev->dev_mutex);
+       return 0;
+}
+
+static unsigned int m2mx_poll(struct file *file,
+                              struct poll_table_struct *wait)
+{
+       struct m2mx_dev *dev = video_drvdata(file);
+       struct m2mx_ctx *ctx = fh_to_ctx(file->private_data);
+       int ret;
+
+       if (mutex_lock_interruptible(&dev->dev_mutex))
+               return -ERESTARTSYS;
+
+       ret = v4l2_m2m_poll(file, ctx->fh.m2m_ctx, wait);
+
+       mutex_unlock(&dev->dev_mutex);
+       return ret;
+}
+
+static int m2mx_mmap(struct file *file, struct vm_area_struct *vma)
+{
+       struct m2mx_dev *dev = video_drvdata(file);
+       struct m2mx_ctx *ctx = fh_to_ctx(file->private_data);
+       int ret;
+
+       if (mutex_lock_interruptible(&dev->dev_mutex))
+               return -ERESTARTSYS;
+
+       ret = v4l2_m2m_mmap(file, ctx->fh.m2m_ctx, vma);
+
+       mutex_unlock(&dev->dev_mutex);
+       return ret;
+}
+
+static const struct v4l2_file_operations m2mx_fops = {
+       .owner          = THIS_MODULE,
+       .open           = m2mx_open,
+       .release        = m2mx_release,
+       .poll           = m2mx_poll,
+       .unlocked_ioctl = video_ioctl2,
+       .mmap           = m2mx_mmap,
+};
+
+static struct video_device m2mx_videodev = {
+       .name           = MEM2MEM_NAME,
+       .fops           = &m2mx_fops,
+       .ioctl_ops      = &m2mx_ioctl_ops,
+       .minor          = -1,
+       .release        = video_device_release,
+       .vfl_dir        = VFL_DIR_M2M,
+};
+
+static struct v4l2_m2m_ops m2m_ops = {
+       .device_run     = m2mx_device_run,
+       .job_abort      = m2mx_job_abort,
+       .lock           = m2mx_lock,
+       .unlock         = m2mx_unlock,
+};
+
+static int of_dev_node_match(struct device *dev, void *data)
+{
+       return dev->of_node == data;
+}
+
+static struct ipu_soc *m2mx_get_ipu(struct m2mx_dev *dev,
+                                    struct device_node *node)
+{
+       struct device_node *ipu_node;
+       struct device *ipu_dev;
+       struct ipu_soc *ipu;
+
+       ipu_node = of_parse_phandle(node, "ipu", 0);
+       if (!ipu_node) {
+               v4l2_err(&dev->v4l2_dev, "missing ipu phandle!\n");
+               return ERR_PTR(-EINVAL);
+       }
+
+       ipu_dev = bus_find_device(&platform_bus_type, NULL,
+                                 ipu_node, of_dev_node_match);
+       of_node_put(ipu_node);
+
+       if (!ipu_dev) {
+               v4l2_err(&dev->v4l2_dev, "failed to find ipu device!\n");
+               return ERR_PTR(-ENODEV);
+       }
+
+       device_lock(ipu_dev);
+
+       if (!ipu_dev->driver || !try_module_get(ipu_dev->driver->owner)) {
+               ipu = ERR_PTR(-EPROBE_DEFER);
+               v4l2_warn(&dev->v4l2_dev, "IPU driver not loaded\n");
+               device_unlock(ipu_dev);
+               goto dev_put;
+       }
+
+       dev->ipu_dev = ipu_dev;
+       ipu = dev_get_drvdata(ipu_dev);
+
+       device_unlock(ipu_dev);
+       return ipu;
+dev_put:
+       put_device(ipu_dev);
+       return ipu;
+}
+
+static void m2mx_put_ipu(struct m2mx_dev *dev)
+{
+       if (!IS_ERR_OR_NULL(dev->ipu_dev)) {
+               module_put(dev->ipu_dev->driver->owner);
+               put_device(dev->ipu_dev);
+       }
+}
+
+static int m2mx_probe(struct platform_device *pdev)
+{
+       struct device_node *node = pdev->dev.of_node;
+       struct m2mx_dev *dev;
+       struct video_device *vfd;
+       int ret;
+
+       dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
+       if (!dev)
+               return -ENOMEM;
+
+       ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev);
+       if (ret)
+               return ret;
+
+       pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
+
+       /* get our IPU */
+       dev->ipu = m2mx_get_ipu(dev, node);
+       if (IS_ERR(dev->ipu)) {
+               v4l2_err(&dev->v4l2_dev, "could not get ipu\n");
+               ret = PTR_ERR(dev->ipu);
+               goto unreg_dev;
+       }
+
+       mutex_init(&dev->dev_mutex);
+
+       vfd = video_device_alloc();
+       if (!vfd) {
+               v4l2_err(&dev->v4l2_dev, "Failed to allocate video device\n");
+               ret = -ENOMEM;
+               goto unreg_dev;
+       }
+
+       *vfd = m2mx_videodev;
+       vfd->v4l2_dev = &dev->v4l2_dev;
+       vfd->lock = &dev->dev_mutex;
+
+       dev->m2m_dev = v4l2_m2m_init(&m2m_ops);
+       if (IS_ERR(dev->m2m_dev)) {
+               v4l2_err(&dev->v4l2_dev, "Failed to init mem2mem device\n");
+               ret = PTR_ERR(dev->m2m_dev);
+               video_device_release(vfd);
+               goto unreg_dev;
+       }
+
+       ret = video_register_device(vfd, VFL_TYPE_GRABBER, 0);
+       if (ret) {
+               v4l2_err(&dev->v4l2_dev, "Failed to register video device\n");
+               video_device_release(vfd);
+               goto rel_m2m;
+       }
+
+       video_set_drvdata(vfd, dev);
+       snprintf(vfd->name, sizeof(vfd->name), "%s", m2mx_videodev.name);
+       dev->vfd = vfd;
+       v4l2_info(&dev->v4l2_dev,
+                 "Device registered as /dev/video%d, on ipu%d\n",
+                 vfd->num, ipu_get_num(dev->ipu));
+
+       platform_set_drvdata(pdev, dev);
+
+       dev->alloc_ctx = vb2_dma_contig_init_ctx(&pdev->dev);
+       if (IS_ERR(dev->alloc_ctx)) {
+               v4l2_err(&dev->v4l2_dev, "Failed to alloc vb2 context\n");
+               ret = PTR_ERR(dev->alloc_ctx);
+               goto unreg_vdev;
+       }
+
+       return 0;
+
+unreg_vdev:
+       video_unregister_device(dev->vfd);
+rel_m2m:
+       v4l2_m2m_release(dev->m2m_dev);
+unreg_dev:
+       v4l2_device_unregister(&dev->v4l2_dev);
+       return ret;
+}
+
+static int m2mx_remove(struct platform_device *pdev)
+{
+       struct m2mx_dev *dev =
+               (struct m2mx_dev *)platform_get_drvdata(pdev);
+
+       v4l2_info(&dev->v4l2_dev, "Removing " MEM2MEM_NAME "\n");
+       m2mx_put_ipu(dev);
+       v4l2_m2m_release(dev->m2m_dev);
+       vb2_dma_contig_cleanup_ctx(dev->alloc_ctx);
+       video_unregister_device(dev->vfd);
+
+       v4l2_device_unregister(&dev->v4l2_dev);
+
+       return 0;
+}
+
+static const struct of_device_id m2mx_dt_ids[] = {
+       { .compatible = "fsl,imx-video-mem2mem" },
+       { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, m2mx_dt_ids);
+
+static struct platform_driver m2mx_pdrv = {
+       .probe          = m2mx_probe,
+       .remove         = m2mx_remove,
+       .driver         = {
+               .name   = MEM2MEM_NAME,
+               .owner  = THIS_MODULE,
+               .of_match_table = m2mx_dt_ids,
+       },
+};
+
+module_platform_driver(m2mx_pdrv);
-- 
1.9.1

--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to