From 438feaa4594948fe66ed88232d56223229f9bffc Mon Sep 17 00:00:00 2001
From: Yong Deng <yong.deng@magewell.com>
Date: Thu, 27 Jul 2017 13:01:35 +0800
Subject: [PATCH 28/82] media: sun6i-csi: Add support for Allwinner CSI

Allwinner V3s SoC has two CSI modules. CSI0 is used for MIPI interface
and CSI1 is used for parallel interface. This is not documented in
datasheet but it was found by testing and guessing.

This patch implements a v4l2 framework driver for it.

Currently, the driver only support the parallel interface. MIPI-CSI2,
ISP's support are not included in this patch.

This patch was cleaned up and extended to support A83T by Ondrej Jirman.

Signed-off-by: Yong Deng <yong.deng@magewell.com>
Signed-off-by: Ondrej Jirman <megous@megous.com>
---
 drivers/media/platform/Kconfig                |    1 +
 drivers/media/platform/Makefile               |    2 +
 drivers/media/platform/sun6i-csi/Kconfig      |    9 +
 drivers/media/platform/sun6i-csi/Makefile     |    3 +
 drivers/media/platform/sun6i-csi/sun6i_csi.c  | 1077 +++++++++++++++++
 drivers/media/platform/sun6i-csi/sun6i_csi.h  |  202 ++++
 .../media/platform/sun6i-csi/sun6i_csi_v3s.c  |  776 ++++++++++++
 .../media/platform/sun6i-csi/sun6i_csi_v3s.h  |  243 ++++
 8 files changed, 2313 insertions(+)
 create mode 100644 drivers/media/platform/sun6i-csi/Kconfig
 create mode 100644 drivers/media/platform/sun6i-csi/Makefile
 create mode 100644 drivers/media/platform/sun6i-csi/sun6i_csi.c
 create mode 100644 drivers/media/platform/sun6i-csi/sun6i_csi.h
 create mode 100644 drivers/media/platform/sun6i-csi/sun6i_csi_v3s.c
 create mode 100644 drivers/media/platform/sun6i-csi/sun6i_csi_v3s.h

diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
index 54fe90acb5b2..643c8671a621 100644
--- a/drivers/media/platform/Kconfig
+++ b/drivers/media/platform/Kconfig
@@ -137,6 +137,7 @@ source "drivers/media/platform/am437x/Kconfig"
 source "drivers/media/platform/xilinx/Kconfig"
 source "drivers/media/platform/rcar-vin/Kconfig"
 source "drivers/media/platform/atmel/Kconfig"
+source "drivers/media/platform/sun6i-csi/Kconfig"
 
 config VIDEO_TI_CAL
 	tristate "TI CAL (Camera Adaptation Layer) driver"
diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
index 41322ab65802..e7938f293e0c 100644
--- a/drivers/media/platform/Makefile
+++ b/drivers/media/platform/Makefile
@@ -96,3 +96,5 @@ obj-$(CONFIG_VIDEO_QCOM_VENUS)		+= qcom/venus/
 obj-y					+= meson/
 
 obj-y					+= cros-ec-cec/
+
+obj-$(CONFIG_VIDEO_SUN6I_CSI)		+= sun6i-csi/
diff --git a/drivers/media/platform/sun6i-csi/Kconfig b/drivers/media/platform/sun6i-csi/Kconfig
new file mode 100644
index 000000000000..314188aae2c2
--- /dev/null
+++ b/drivers/media/platform/sun6i-csi/Kconfig
@@ -0,0 +1,9 @@
+config VIDEO_SUN6I_CSI
+	tristate "Allwinner V3s Camera Sensor Interface driver"
+	depends on VIDEO_V4L2 && COMMON_CLK && VIDEO_V4L2_SUBDEV_API && HAS_DMA
+	depends on ARCH_SUNXI || COMPILE_TEST
+	select VIDEOBUF2_DMA_CONTIG
+	select REGMAP_MMIO
+	select V4L2_FWNODE
+	---help---
+	   Support for the Allwinner Camera Sensor Interface Controller on V3s.
diff --git a/drivers/media/platform/sun6i-csi/Makefile b/drivers/media/platform/sun6i-csi/Makefile
new file mode 100644
index 000000000000..7def1914cae1
--- /dev/null
+++ b/drivers/media/platform/sun6i-csi/Makefile
@@ -0,0 +1,3 @@
+sun6i-csi-y += sun6i_csi.o sun6i_csi_v3s.o
+
+obj-$(CONFIG_VIDEO_SUN6I_CSI) += sun6i-csi.o
diff --git a/drivers/media/platform/sun6i-csi/sun6i_csi.c b/drivers/media/platform/sun6i-csi/sun6i_csi.c
new file mode 100644
index 000000000000..8cfccc5782b1
--- /dev/null
+++ b/drivers/media/platform/sun6i-csi/sun6i_csi.c
@@ -0,0 +1,1077 @@
+/*
+ * Copyright (c) 2017 Magewell Electronics Co., Ltd. (Nanjing).
+ * All rights reserved.
+ * Author: Yong Deng <yong.deng@magewell.com>
+ * Copyright (c) 2017 Ondrej Jirman <megous@megous.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#define DEBUG
+
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_graph.h>
+#include <linux/slab.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-mc.h>
+#include <media/videobuf2-dma-contig.h>
+#include <media/videobuf2-v4l2.h>
+
+#include "sun6i_csi.h"
+
+// {{{ utils
+
+static struct sun6i_csi_subdev *
+sun6i_get_enabled_subdev(struct sun6i_csi *csi)
+{
+	struct media_pad *remote;
+
+	remote = media_entity_remote_pad(&csi->pad);
+
+	if (!remote || !is_media_entity_v4l2_subdev(remote->entity))
+		return NULL;
+
+	return v4l2_get_subdev_hostdata(
+		media_entity_to_v4l2_subdev(remote->entity));
+}
+
+static struct sun6i_csi_format *
+sun6i_find_format_by_fourcc(struct sun6i_csi *csi, u32 fourcc)
+{
+	int i;
+
+	for (i = 0; i < csi->num_formats; i++)
+		if (csi->formats[i].fourcc == fourcc)
+			return &csi->formats[i];
+
+	return NULL;
+}
+
+// }}}
+// {{{ vb2
+
+struct sun6i_csi_buffer {
+	struct vb2_v4l2_buffer		vb;
+	struct list_head		list;
+
+	dma_addr_t			dma_addr;
+};
+
+static int sun6i_video_queue_setup(struct vb2_queue *vq,
+				 unsigned int *nbuffers, unsigned int *nplanes,
+				 unsigned int sizes[],
+				 struct device *alloc_devs[])
+{
+	struct sun6i_csi *csi = vb2_get_drv_priv(vq);
+	unsigned int size = csi->fmt.fmt.pix.sizeimage;
+
+	if (*nplanes)
+		return sizes[0] < size ? -EINVAL : 0;
+
+	*nplanes = 1;
+	sizes[0] = size;
+
+	return 0;
+}
+
+static int sun6i_video_buffer_prepare(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct sun6i_csi_buffer *buf =
+			container_of(vbuf, struct sun6i_csi_buffer, vb);
+	struct sun6i_csi *csi = vb2_get_drv_priv(vb->vb2_queue);
+	unsigned long size = csi->fmt.fmt.pix.sizeimage;
+
+	if (vb2_plane_size(vb, 0) < size) {
+		v4l2_err(csi->vdev.v4l2_dev, "buffer too small (%lu < %lu)\n",
+			 vb2_plane_size(vb, 0), size);
+		return -EINVAL;
+	}
+
+	vb2_set_plane_payload(vb, 0, size);
+	buf->dma_addr = vb2_dma_contig_plane_dma_addr(vb, 0);
+	vbuf->field = csi->fmt.fmt.pix.field;
+
+	return 0;
+}
+
+static int sun6i_sources_set_stream(struct sun6i_csi *csi, bool enable)
+{
+	struct media_entity *entity;
+	struct media_pad *pad;
+	struct v4l2_subdev *subdev;
+	int ret;
+
+	entity = &csi->vdev.entity;
+	while (1) {
+		pad = &entity->pads[0];
+		if (!(pad->flags & MEDIA_PAD_FL_SINK))
+			break;
+
+		pad = media_entity_remote_pad(pad);
+		if (!pad || !is_media_entity_v4l2_subdev(pad->entity))
+			break;
+
+		entity = pad->entity;
+		subdev = media_entity_to_v4l2_subdev(entity);
+
+		ret = v4l2_subdev_call(subdev, video, s_stream, enable);
+		if (enable && ret < 0 && ret != -ENOIOCTLCMD)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int sun6i_csi_apply_config(struct sun6i_csi *csi)
+{
+	struct sun6i_csi_subdev *csi_sd;
+
+	csi_sd = sun6i_get_enabled_subdev(csi);
+	if (csi_sd == NULL)
+		return -ENXIO;
+
+	if (csi->ops != NULL && csi->ops->apply_config != NULL)
+		return csi->ops->apply_config(csi, csi_sd);
+
+	return -ENOIOCTLCMD;
+}
+
+static int sun6i_video_start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+	struct sun6i_csi *csi = vb2_get_drv_priv(vq);
+	struct sun6i_csi_buffer *buf;
+	unsigned long flags;
+	int ret;
+
+	ret = media_pipeline_start(&csi->vdev.entity, &csi->vdev.pipe);
+	if (ret < 0)
+		goto err_queue_buffers;
+
+	ret = sun6i_csi_apply_config(csi);
+	if (ret < 0)
+		goto err_stop_media_pipeline;
+
+	spin_lock_irqsave(&csi->dma_queue_lock, flags);
+	csi->sequence = 0;
+	csi->skip_first_interrupt = true;
+	buf = list_first_entry(&csi->dma_queue, struct sun6i_csi_buffer, list);
+	ret = sun6i_csi_update_buf_addr(csi, buf->dma_addr);
+	spin_unlock_irqrestore(&csi->dma_queue_lock, flags);
+	if (ret < 0)
+		goto err_stop_media_pipeline;
+
+	ret = sun6i_csi_set_stream(csi, true);
+	if (ret < 0)
+		goto err_stop_media_pipeline;
+
+	ret = sun6i_sources_set_stream(csi, true);
+	if (ret < 0)
+		goto err_stop_stream;
+
+	return 0;
+
+err_stop_stream:
+	sun6i_sources_set_stream(csi, false);
+err_stop_media_pipeline:
+	media_pipeline_stop(&csi->vdev.entity);
+err_queue_buffers:
+	spin_lock_irqsave(&csi->dma_queue_lock, flags);
+	list_for_each_entry(buf, &csi->dma_queue, list)
+		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_QUEUED);
+	INIT_LIST_HEAD(&csi->dma_queue);
+	spin_unlock_irqrestore(&csi->dma_queue_lock, flags);
+
+	return ret;
+}
+
+static void sun6i_video_stop_streaming(struct vb2_queue *vq)
+{
+	struct sun6i_csi *csi = vb2_get_drv_priv(vq);
+	unsigned long flags;
+	struct sun6i_csi_buffer *buf;
+
+	sun6i_csi_set_stream(csi, false);
+	sun6i_sources_set_stream(csi, false);
+	media_pipeline_stop(&csi->vdev.entity);
+
+	/* Release all active buffers */
+	spin_lock_irqsave(&csi->dma_queue_lock, flags);
+	list_for_each_entry(buf, &csi->dma_queue, list)
+		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+	INIT_LIST_HEAD(&csi->dma_queue);
+	spin_unlock_irqrestore(&csi->dma_queue_lock, flags);
+}
+
+static void sun6i_video_buffer_queue(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct sun6i_csi_buffer *buf =
+			container_of(vbuf, struct sun6i_csi_buffer, vb);
+	struct sun6i_csi *csi = vb2_get_drv_priv(vb->vb2_queue);
+	unsigned long flags;
+
+	spin_lock_irqsave(&csi->dma_queue_lock, flags);
+	list_add_tail(&buf->list, &csi->dma_queue);
+	spin_unlock_irqrestore(&csi->dma_queue_lock, flags);
+}
+
+void sun6i_video_frame_done(struct sun6i_csi *csi)
+{
+	struct sun6i_csi_buffer *buf;
+	struct sun6i_csi_buffer *next_buf;
+	bool can_dequeue;
+
+	spin_lock(&csi->dma_queue_lock);
+
+	if (!vb2_is_streaming(&csi->vb2_vidq))
+		goto out_unlock;
+
+	/* we always keep two buffers in the queue and expect the third */
+	buf = list_first_entry(&csi->dma_queue, struct sun6i_csi_buffer, list);
+	next_buf = list_next_entry(buf, list);
+	can_dequeue = !list_is_last(&next_buf->list, &csi->dma_queue);
+
+	if (csi->skip_first_interrupt) {
+		csi->skip_first_interrupt = false;
+		sun6i_csi_update_buf_addr(csi, next_buf->dma_addr);
+		goto out_unlock;
+	} else if (!can_dequeue) {
+		csi->skip_first_interrupt = true;
+	} else {
+		struct vb2_v4l2_buffer *vbuf = &buf->vb;
+		struct vb2_buffer *vb = &vbuf->vb2_buf;
+
+		vb->timestamp = ktime_get_ns();
+		vbuf->sequence = csi->sequence++;
+		vb2_buffer_done(vb, VB2_BUF_STATE_DONE);
+
+		list_del(&buf->list);
+
+		next_buf = list_next_entry(next_buf, list);
+		sun6i_csi_update_buf_addr(csi, next_buf->dma_addr);
+	}
+
+out_unlock:
+	spin_unlock(&csi->dma_queue_lock);
+}
+
+static struct vb2_ops sun6i_csi_vb2_ops = {
+	.queue_setup		= sun6i_video_queue_setup,
+	.wait_prepare		= vb2_ops_wait_prepare,
+	.wait_finish		= vb2_ops_wait_finish,
+	.buf_prepare		= sun6i_video_buffer_prepare,
+	.start_streaming	= sun6i_video_start_streaming,
+	.stop_streaming		= sun6i_video_stop_streaming,
+	.buf_queue		= sun6i_video_buffer_queue,
+};
+
+// }}}
+// {{{ videodev ioct
+
+static int sun6i_video_set_fmt(struct sun6i_csi *csi, struct v4l2_format *f,
+			       bool try_only)
+{
+	struct v4l2_subdev_format sd_fmt;
+	struct v4l2_subdev_pad_config padconf;
+	struct v4l2_pix_format *pixfmt = &f->fmt.pix;
+	struct sun6i_csi_format *csi_fmt;
+	struct sun6i_csi_subdev *csi_sd;
+	int ret;
+
+	if (csi->num_formats == 0)
+		return -EINVAL;
+
+	csi_fmt = sun6i_find_format_by_fourcc(csi, pixfmt->pixelformat);
+	if (csi_fmt == NULL)
+		csi_fmt = &csi->formats[0];
+	pixfmt->pixelformat = csi_fmt->fourcc;
+
+	csi_sd = sun6i_get_enabled_subdev(csi);
+	if (csi_sd == NULL)
+		return -ENXIO;
+
+	sd_fmt.pad = csi_sd->pad;
+	sd_fmt.which = V4L2_SUBDEV_FORMAT_TRY;
+	v4l2_fill_mbus_format(&sd_fmt.format, pixfmt, csi_fmt->mbus_code);
+	ret = v4l2_subdev_call(csi_sd->sd, pad, set_fmt, &padconf, &sd_fmt);
+	if (ret)
+		return ret;
+
+	v4l2_fill_pix_format(pixfmt, &sd_fmt.format);
+	pixfmt->bytesperline = (pixfmt->width * csi_fmt->bpp) / 8;
+	pixfmt->sizeimage = pixfmt->bytesperline * pixfmt->height;
+	pixfmt->flags = 0;
+
+	if (!try_only) {
+		sd_fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+		ret = v4l2_subdev_call(csi_sd->sd, pad, set_fmt, NULL, &sd_fmt);
+		if (ret)
+			return ret;
+
+		//XXX: check that we got an expected format
+
+		csi->fmt = *f;
+		csi->current_fmt = csi_fmt;
+	}
+
+	return 0;
+}
+
+static int sun6i_querycap(struct file *file, void *priv,
+			  struct v4l2_capability *cap)
+{
+	struct sun6i_csi *csi = video_drvdata(file);
+
+	strlcpy(cap->driver, "sun6i-video", sizeof(cap->driver));
+	strlcpy(cap->card, csi->vdev.name, sizeof(cap->card));
+	snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s",
+		 csi->dev->of_node->name);
+
+	return 0;
+}
+
+static int sun6i_try_fmt_vid_cap(struct file *file, void *priv,
+				 struct v4l2_format *f)
+{
+	struct sun6i_csi *csi = video_drvdata(file);
+
+	return sun6i_video_set_fmt(csi, f, true);
+}
+
+static int sun6i_g_fmt_vid_cap(struct file *file, void *priv,
+				struct v4l2_format *fmt)
+{
+	struct sun6i_csi *csi = video_drvdata(file);
+
+	*fmt = csi->fmt;
+
+	return 0;
+}
+
+static int sun6i_s_fmt_vid_cap(struct file *file, void *priv,
+			       struct v4l2_format *f)
+{
+	struct sun6i_csi *csi = video_drvdata(file);
+
+	if (vb2_is_streaming(&csi->vb2_vidq))
+		return -EBUSY;
+
+	return sun6i_video_set_fmt(csi, f, false);
+}
+
+static int sun6i_enum_fmt_vid_cap(struct file *file, void *priv,
+				  struct v4l2_fmtdesc *f)
+{
+	struct sun6i_csi *csi = video_drvdata(file);
+
+	if (f->index >= csi->num_formats)
+		return -EINVAL;
+
+	f->flags = 0;
+	f->description[0] = '\0';
+	f->pixelformat = csi->formats[f->index].fourcc;
+
+	return 0;
+}
+
+static int sun6i_enum_framesizes(struct file *file, void *priv,
+				 struct v4l2_frmsizeenum *fsize)
+{
+	struct sun6i_csi *csi = video_drvdata(file);
+	struct sun6i_csi_format *csi_fmt;
+	struct sun6i_csi_subdev *csi_sd;
+	struct v4l2_subdev_frame_size_enum fse = {
+		.index = fsize->index,
+		.which = V4L2_SUBDEV_FORMAT_ACTIVE,
+	};
+	int ret;
+
+	csi_fmt = sun6i_find_format_by_fourcc(csi, fsize->pixel_format);
+	if (!csi_fmt)
+		return -EINVAL;
+
+	fse.code = csi_fmt->mbus_code;
+
+	csi_sd = sun6i_get_enabled_subdev(csi);
+	if (csi_sd == NULL)
+		return -ENXIO;
+
+	ret = v4l2_subdev_call(csi_sd->sd, pad, enum_frame_size, NULL, &fse);
+	if (ret)
+		return ret;
+
+	fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;
+	fsize->discrete.width = fse.max_width;
+	fsize->discrete.height = fse.max_height;
+
+	return 0;
+}
+
+static int sun6i_enum_frameintervals(struct file *file, void *priv,
+				     struct v4l2_frmivalenum *fival)
+{
+	struct sun6i_csi *csi = video_drvdata(file);
+	struct sun6i_csi_format *csi_fmt;
+	struct sun6i_csi_subdev *csi_sd;
+	struct v4l2_subdev_frame_interval_enum fie = {
+		.index = fival->index,
+		.width = fival->width,
+		.height = fival->height,
+		.which = V4L2_SUBDEV_FORMAT_ACTIVE,
+	};
+	int ret;
+
+	csi_fmt = sun6i_find_format_by_fourcc(csi, fival->pixel_format);
+	if (!csi_fmt)
+		return -EINVAL;
+
+	fie.code = csi_fmt->mbus_code;
+
+	csi_sd = sun6i_get_enabled_subdev(csi);
+	if (csi_sd == NULL)
+		return -ENXIO;
+
+	ret = v4l2_subdev_call(csi_sd->sd, pad,
+			       enum_frame_interval, NULL, &fie);
+	if (ret)
+		return ret;
+
+	fival->type = V4L2_FRMIVAL_TYPE_DISCRETE;
+	fival->discrete = fie.interval;
+
+	return 0;
+}
+
+
+static int sun6i_g_parm(struct file *file, void *fh, struct v4l2_streamparm *a)
+{
+	struct sun6i_csi *csi = video_drvdata(file);
+	struct sun6i_csi_subdev *csi_sd;
+
+	if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	csi_sd = sun6i_get_enabled_subdev(csi);
+	if (csi_sd == NULL)
+		return -ENXIO;
+
+	a->parm.capture.readbuffers = 0;
+	return v4l2_subdev_call(csi_sd->sd, video, g_parm, a);
+}
+
+static int sun6i_s_parm(struct file *file, void *fh, struct v4l2_streamparm *a)
+{
+	struct sun6i_csi *csi = video_drvdata(file);
+	struct sun6i_csi_subdev *csi_sd;
+
+	if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	csi_sd = sun6i_get_enabled_subdev(csi);
+	if (csi_sd == NULL)
+		return -ENXIO;
+
+	a->parm.capture.readbuffers = 0;
+	return v4l2_subdev_call(csi_sd->sd, video, s_parm, a);
+}
+
+static int sun6i_enum_input(struct file *file, void *priv,
+			   struct v4l2_input *i)
+{
+	struct sun6i_csi *csi = video_drvdata(file);
+	struct v4l2_subdev *subdev;
+	int ret, s, idx;
+
+	idx = 0;
+	for (s = 0; s < SUN6I_CSI_NUM_SENSORS; s++) {
+		subdev = csi->sensors[s].sd;
+		if (!subdev)
+			continue;
+
+		if (idx == i->index) {
+			ret = v4l2_subdev_call(subdev, video, g_input_status,
+					       &i->status);
+			if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV)
+				return ret;
+
+			i->type = V4L2_INPUT_TYPE_CAMERA;
+			strlcpy(i->name, "Camera", sizeof(i->name));
+			return 0;
+		}
+
+		idx++;
+	}
+
+	return -EINVAL;
+}
+
+static int sun6i_g_input(struct file *file, void *priv, unsigned int *i)
+{
+	//XXX: get index of the enabled input
+
+	*i = 0;
+
+	return 0;
+}
+
+static int sun6i_s_input(struct file *file, void *priv, unsigned int i)
+{
+	//XXX: enable link
+
+	if (i > 0)
+		return -EINVAL;
+
+	return 0;
+}
+
+static const struct v4l2_ioctl_ops sun6i_video_ioctl_ops = {
+	.vidioc_querycap		= sun6i_querycap,
+
+	.vidioc_try_fmt_vid_cap		= sun6i_try_fmt_vid_cap,
+	.vidioc_g_fmt_vid_cap		= sun6i_g_fmt_vid_cap,
+	.vidioc_s_fmt_vid_cap		= sun6i_s_fmt_vid_cap,
+	.vidioc_enum_fmt_vid_cap	= sun6i_enum_fmt_vid_cap,
+
+	.vidioc_g_parm			= sun6i_g_parm,
+	.vidioc_s_parm			= sun6i_s_parm,
+	.vidioc_enum_framesizes		= sun6i_enum_framesizes,
+	.vidioc_enum_frameintervals	= sun6i_enum_frameintervals,
+
+	.vidioc_enum_input		= sun6i_enum_input,
+	.vidioc_g_input			= sun6i_g_input,
+	.vidioc_s_input			= sun6i_s_input,
+
+	.vidioc_reqbufs			= vb2_ioctl_reqbufs,
+	.vidioc_querybuf		= vb2_ioctl_querybuf,
+	.vidioc_qbuf			= vb2_ioctl_qbuf,
+	.vidioc_expbuf			= vb2_ioctl_expbuf,
+	.vidioc_dqbuf			= vb2_ioctl_dqbuf,
+	.vidioc_create_bufs		= vb2_ioctl_create_bufs,
+	.vidioc_prepare_buf		= vb2_ioctl_prepare_buf,
+	.vidioc_streamon		= vb2_ioctl_streamon,
+	.vidioc_streamoff		= vb2_ioctl_streamoff,
+
+	.vidioc_log_status		= v4l2_ctrl_log_status,
+};
+
+// }}}
+// {{{ videodev fops
+
+/* -----------------------------------------------------------------------------
+ * V4L2 file operations
+ */
+static int sun6i_video_open(struct file *file)
+{
+	struct sun6i_csi *csi = video_drvdata(file);
+	struct v4l2_format format;
+	int ret;
+
+	if (mutex_lock_interruptible(&csi->lock))
+		return -ERESTARTSYS;
+
+	ret = v4l2_fh_open(file);
+	if (ret < 0)
+		goto unlock;
+
+	ret = v4l2_pipeline_pm_use(&csi->vdev.entity, 1);
+	if (ret < 0)
+		goto fh_release;
+
+	if (!v4l2_fh_is_singular_file(file))
+		goto unlock;
+
+	ret = sun6i_csi_set_power(csi, true);
+	if (ret < 0)
+		goto fh_release;
+
+	/* setup default format */
+	if (csi->num_formats > 0) {
+		format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+		format.fmt.pix.width = 1280;
+		format.fmt.pix.height = 720;
+		format.fmt.pix.pixelformat = csi->formats[0].fourcc;
+		sun6i_video_set_fmt(csi, &format, false);
+	}
+
+	mutex_unlock(&csi->lock);
+	return 0;
+
+fh_release:
+	v4l2_fh_release(file);
+unlock:
+	mutex_unlock(&csi->lock);
+	return ret;
+}
+
+static int sun6i_video_close(struct file *file)
+{
+	struct sun6i_csi *csi = video_drvdata(file);
+	bool last_fh;
+
+	mutex_lock(&csi->lock);
+
+	last_fh = v4l2_fh_is_singular_file(file);
+
+	_vb2_fop_release(file, NULL);
+
+	v4l2_pipeline_pm_use(&csi->vdev.entity, 0);
+
+	if (last_fh)
+		sun6i_csi_set_power(csi, false);
+
+	mutex_unlock(&csi->lock);
+
+	return 0;
+}
+
+static const struct v4l2_file_operations sun6i_video_fops = {
+	.owner		= THIS_MODULE,
+	.open		= sun6i_video_open,
+	.release	= sun6i_video_close,
+	.unlocked_ioctl	= video_ioctl2,
+	.mmap		= vb2_fop_mmap,
+	.poll		= vb2_fop_poll
+};
+
+// }}}
+// {{{ media ops
+
+/* -----------------------------------------------------------------------------
+ * Media Operations
+ */
+
+static bool
+sun6i_csi_is_format_support(struct sun6i_csi *csi, u32 pixformat, u32 mbus_code,
+		struct sun6i_csi_subdev *csi_sd)
+{
+	if (csi->ops != NULL && csi->ops->is_format_support != NULL)
+		return csi->ops->is_format_support(csi, pixformat, mbus_code,
+						   csi_sd);
+
+	return -ENOIOCTLCMD;
+}
+
+static int sun6i_video_formats_init(struct sun6i_csi *csi)
+{
+	struct v4l2_subdev_mbus_code_enum mbus_code = { 0 };
+	struct sun6i_csi_subdev *csi_sd;
+	const u32 *pixformats;
+	int pixformat_count = 0;
+	u32 subdev_codes[32];
+	int codes_count = 0;
+	int num_fmts = 0;
+	int i, j;
+
+	csi_sd = sun6i_get_enabled_subdev(csi);
+	if (csi_sd == NULL)
+		return -ENXIO;
+
+	/* Get supported pixformats of CSI */
+	pixformat_count = sun6i_csi_get_supported_pixformats(csi, &pixformats);
+	if (pixformat_count <= 0)
+		return -ENXIO;
+
+	/* Get subdev formats codes */
+	mbus_code.pad = csi_sd->pad;
+	mbus_code.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+	while (codes_count < ARRAY_SIZE(subdev_codes) &&
+	       !v4l2_subdev_call(csi_sd->sd, pad, enum_mbus_code, NULL,
+				 &mbus_code)) {
+		subdev_codes[codes_count] = mbus_code.code;
+		codes_count++;
+		mbus_code.index++;
+	}
+
+	if (!codes_count)
+		return -ENXIO;
+
+	/* Get supported formats count */
+	for (j = 0; j < pixformat_count; j++) {
+		for (i = 0; i < codes_count; i++) {
+			if (!sun6i_csi_is_format_support(csi, pixformats[j],
+					subdev_codes[i], csi_sd))
+				continue;
+			num_fmts++;
+			break;
+		}
+	}
+
+	if (!num_fmts)
+		return -ENXIO;
+
+	if (csi->formats)
+		devm_kfree(csi->dev, csi->formats);
+
+	csi->num_formats = num_fmts;
+	csi->formats = devm_kcalloc(csi->dev, num_fmts,
+			sizeof(struct sun6i_csi_format), GFP_KERNEL);
+	if (!csi->formats)
+		return -ENOMEM;
+
+	/* Get supported formats */
+	num_fmts = 0;
+	for (j = 0; j < pixformat_count; j++) {
+		for (i = 0; i < codes_count; i++) {
+			if (!sun6i_csi_is_format_support(csi, pixformats[j],
+					subdev_codes[i], csi_sd))
+				continue;
+
+			dev_dbg(csi->dev, "supported format: pix=%d:bus=%d\n",
+				pixformats[j], subdev_codes[i]);
+
+			csi->formats[num_fmts].fourcc = pixformats[j];
+			csi->formats[num_fmts].mbus_code = subdev_codes[i];
+			csi->formats[num_fmts].bpp =
+					v4l2_pixformat_get_bpp(pixformats[j]);
+			num_fmts++;
+			break;
+		}
+	}
+
+	return 0;
+}
+
+static int sun6i_video_link_setup(struct media_entity *entity,
+				  const struct media_pad *local,
+				  const struct media_pad *remote, u32 flags)
+{
+	struct video_device *vdev = media_entity_to_video_device(entity);
+	struct sun6i_csi *csi = video_get_drvdata(vdev);
+
+	if (WARN_ON(csi == NULL))
+		return 0;
+
+	return sun6i_video_formats_init(csi);
+}
+
+static const struct media_entity_operations sun6i_video_media_ops = {
+	.link_setup = sun6i_video_link_setup,
+};
+
+// }}}
+// {{{ sun6i_csi video init/cleanup
+
+static void sun6i_video_cleanup(struct sun6i_csi *csi)
+{
+	if (video_is_registered(&csi->vdev))
+		video_unregister_device(&csi->vdev);
+
+	media_entity_cleanup(&csi->vdev.entity);
+}
+
+static int sun6i_video_init(struct sun6i_csi *csi, const char *name)
+{
+	struct video_device *vdev = &csi->vdev;
+	struct vb2_queue *vidq = &csi->vb2_vidq;
+	int ret;
+
+	/* Initialize the media entity... */
+	csi->pad.flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT;
+	vdev->entity.ops = &sun6i_video_media_ops;
+	ret = media_entity_pads_init(&vdev->entity, 1, &csi->pad);
+	if (ret < 0)
+		return ret;
+
+	mutex_init(&csi->lock);
+
+	INIT_LIST_HEAD(&csi->dma_queue);
+	spin_lock_init(&csi->dma_queue_lock);
+
+	csi->sequence = 0;
+	csi->num_formats = 0;
+
+	/* Initialize videobuf2 queue */
+	vidq->type			= V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	vidq->io_modes			= VB2_MMAP | VB2_DMABUF;
+	vidq->drv_priv			= csi;
+	vidq->buf_struct_size		= sizeof(struct sun6i_csi_buffer);
+	vidq->ops			= &sun6i_csi_vb2_ops;
+	vidq->mem_ops			= &vb2_dma_contig_memops;
+	vidq->timestamp_flags		= V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+	vidq->lock			= &csi->lock;
+	vidq->min_buffers_needed	= 2;
+	vidq->dev			= csi->dev;
+
+	ret = vb2_queue_init(vidq);
+	if (ret) {
+		v4l2_err(&csi->v4l2_dev, "vb2_queue_init failed: %d\n", ret);
+		goto error;
+	}
+
+	/* Register video device */
+	strlcpy(vdev->name, name, sizeof(vdev->name));
+	vdev->release		= video_device_release_empty;
+	vdev->fops		= &sun6i_video_fops;
+	vdev->ioctl_ops		= &sun6i_video_ioctl_ops;
+	vdev->vfl_type		= VFL_TYPE_GRABBER;
+	vdev->vfl_dir		= VFL_DIR_RX;
+	vdev->v4l2_dev		= &csi->v4l2_dev;
+	vdev->queue		= vidq;
+	vdev->lock		= &csi->lock;
+	vdev->device_caps	= V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE;
+	video_set_drvdata(vdev, csi);
+
+	ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
+	if (ret < 0) {
+		v4l2_err(&csi->v4l2_dev,
+			 "video_register_device failed: %d\n", ret);
+		goto error;
+	}
+
+	return 0;
+
+error:
+	sun6i_video_cleanup(csi);
+	return ret;
+}
+
+// }}}
+// {{{ sun6i_csi init/cleanup - DT parsing/subdev setup
+
+struct sun6i_csi_async_subdev {
+	struct v4l2_async_subdev asd; /* must be first */
+	unsigned int ep_id;
+	enum v4l2_mbus_type bus_type;
+	struct v4l2_fwnode_bus_parallel parallel;
+};
+
+#define notifier_to_csi(n) container_of(n, struct sun6i_csi, notifier)
+#define asd_to_csi_asd(a) container_of(a, struct sun6i_csi_async_subdev, asd)
+
+static int sun6i_csi_notify_bound(struct v4l2_async_notifier *notifier,
+				     struct v4l2_subdev *subdev,
+				     struct v4l2_async_subdev *asd)
+{
+	struct sun6i_csi *csi = notifier_to_csi(notifier);
+	struct sun6i_csi_async_subdev *csi_asd = asd_to_csi_asd(asd);
+	unsigned int i;
+
+	dev_dbg(csi->dev, "bound subdev %s\n", subdev->name);
+
+	if (subdev->entity.function != MEDIA_ENT_F_CAM_SENSOR) {
+		dev_err(csi->dev, "subdev %s must be a camera sensor\n",
+			subdev->name);
+		return -EINVAL;
+	}
+
+	for (i = 0; i < SUN6I_CSI_NUM_SENSORS; i++) {
+		if (csi->sensors[i].sd == NULL) {
+			csi->sensors[i].sd = subdev;
+			csi->sensors[i].ep_id = csi_asd->ep_id;
+			csi->sensors[i].bus_type = csi_asd->bus_type;
+			csi->sensors[i].parallel = csi_asd->parallel;
+			v4l2_set_subdev_hostdata(subdev, &csi->sensors[i]);
+			return 0;
+		}
+	}
+
+	dev_err(csi->dev, "subdev %s not bound, not enough sensor slots\n",
+		subdev->name);
+	return -ENOMEM;
+}
+
+static void sun6i_csi_notify_unbind(struct v4l2_async_notifier *notifier,
+				       struct v4l2_subdev *subdev,
+				       struct v4l2_async_subdev *asd)
+{
+	struct sun6i_csi *csi = notifier_to_csi(notifier);
+	//struct sun6i_csi_async_subdev *csi_asd = asd_to_csi_asd(asd);
+	unsigned int i;
+
+	dev_err(csi->dev, "unbind subdev %s\n", subdev->name);
+
+	for (i = 0; i < SUN6I_CSI_NUM_SENSORS; i++) {
+		if (csi->sensors[i].sd == subdev) {
+			csi->sensors[i].sd = NULL;
+			v4l2_set_subdev_hostdata(subdev, NULL);
+			return;
+		}
+	}
+}
+
+static int sun6i_csi_notify_complete(struct v4l2_async_notifier *notifier)
+{
+	struct sun6i_csi *csi = notifier_to_csi(notifier);
+	struct v4l2_subdev *subdev;
+	struct media_entity *sink = &csi->vdev.entity;
+	struct media_entity *source;
+	unsigned int pad;
+	unsigned int i;
+	int ret;
+	bool first_sensor = true;
+
+	dev_dbg(csi->dev, "notify complete, all subdevs bound\n");
+
+	for (i = 0; i < SUN6I_CSI_NUM_SENSORS; i++) {
+		subdev = csi->sensors[i].sd;
+		if (subdev == NULL)
+			continue;
+
+		source = &subdev->entity;
+
+		for (pad = 0; pad < source->num_pads; pad++) {
+			if (!(source->pads[pad].flags & MEDIA_PAD_FL_SOURCE))
+				continue;
+
+			csi->sensors[i].pad = pad;
+
+			ret = media_create_pad_link(source, pad, sink,
+						    0, first_sensor ?
+						    MEDIA_LNK_FL_ENABLED : 0);
+			if (ret)
+				return ret;
+
+			dev_dbg(csi->dev, "created pad link %s:%u -> %s:0\n",
+				subdev->name, pad, csi->vdev.name);
+
+			if (first_sensor) {
+				ret = media_entity_call(sink, link_setup,
+							&sink->pads[0],
+							&source->pads[pad], 0);
+				if (ret)
+					return ret;
+			}
+
+			first_sensor = false;
+			goto next_sensor;
+		}
+
+		dev_err(csi->dev, "subdev %s - no source pad found\n",
+			subdev->name);
+		return -EINVAL;
+next_sensor:;
+	}
+
+	ret = v4l2_device_register_subdev_nodes(&csi->v4l2_dev);
+	if (ret < 0) {
+		dev_err(csi->dev, "failed to register subdev nodes\n");
+		return ret;
+	}
+
+	dev_dbg(csi->dev, "registering media device\n");
+
+	return media_device_register(&csi->media_dev);
+}
+
+// this is called for each controller endpoint
+static int sun6i_csi_parse_subdev_endpoint(struct device *dev,
+				   struct v4l2_fwnode_endpoint *vep,
+				   struct v4l2_async_subdev *asd)
+{
+	//struct sun6i_csi *csi = dev_get_drvdata(dev);
+	struct sun6i_csi_async_subdev *csi_asd = asd_to_csi_asd(asd);
+
+	if (vep->base.port) {
+		dev_err(dev, "CSI has only one port\n");
+		return -ENOTCONN;
+	}
+
+	switch (vep->bus_type) {
+	case V4L2_MBUS_PARALLEL:
+	case V4L2_MBUS_BT656:
+		csi_asd->ep_id = vep->base.id;
+		csi_asd->bus_type = vep->bus_type;
+		csi_asd->parallel = vep->bus.parallel;
+		return 0;
+	default:
+		dev_err(dev, "Unsupported media bus type\n");
+		return -ENOTCONN;
+	}
+}
+
+static const struct v4l2_async_notifier_operations sun6i_csi_notifier_ops = {
+	.bound = sun6i_csi_notify_bound,
+	.unbind = sun6i_csi_notify_unbind,
+	.complete = sun6i_csi_notify_complete,
+};
+
+int sun6i_csi_init(struct sun6i_csi *csi)
+{
+	int ret;
+
+	csi->media_dev.dev = csi->dev;
+	strlcpy(csi->media_dev.model, "Allwinner Video Capture Device",
+		sizeof(csi->media_dev.model));
+	media_device_init(&csi->media_dev);
+
+	ret = v4l2_ctrl_handler_init(&csi->ctrl_handler, 0);
+	if (ret) {
+		dev_err(csi->dev, "V4L2 controls handler init failed (%d)\n",
+			ret);
+		goto media_clean;
+	}
+
+	csi->v4l2_dev.mdev = &csi->media_dev;
+	csi->v4l2_dev.ctrl_handler = &csi->ctrl_handler;
+	ret = v4l2_device_register(csi->dev, &csi->v4l2_dev);
+	if (ret < 0) {
+		dev_err(csi->dev, "V4L2 device registration failed (%d)\n",
+			ret);
+		goto ctrls_clean;
+	}
+
+	ret = sun6i_video_init(csi, "sun6i-csi");
+	if (ret < 0)
+		goto v4l2_clean;
+
+	// Parse DT and build notifier.subdevs (struct v4l2_async_subdev) list
+	// that will be used to match and bind/unbind subdevices (sensors) to
+	// the csi->v4l2_dev when they are probed and registered by their own
+	// drivers. sun6i_csi_parse_subdev_endpoint callback can be used to:
+	// - exclude certain subdev endpoints from being watched
+	// - parse subdev DT endpoint properties and pass them later to bound
+	//   callback via internediate struct sun6i_csi_async_subdev
+	ret = v4l2_async_notifier_parse_fwnode_endpoints(
+		csi->dev, &csi->notifier, sizeof(struct sun6i_csi_async_subdev),
+		sun6i_csi_parse_subdev_endpoint);
+	if (ret)
+		goto video_clean;
+
+	csi->notifier.ops = &sun6i_csi_notifier_ops;
+	ret = v4l2_async_notifier_register(&csi->v4l2_dev, &csi->notifier);
+	if (ret < 0) {
+		dev_err(csi->dev, "Notifier registration failed\n");
+		goto notifier_clean;
+	}
+
+	return 0;
+
+notifier_clean:
+	v4l2_async_notifier_cleanup(&csi->notifier);
+video_clean:
+	sun6i_video_cleanup(csi);
+v4l2_clean:
+	v4l2_device_unregister(&csi->v4l2_dev);
+	media_device_unregister(&csi->media_dev);
+ctrls_clean:
+	v4l2_ctrl_handler_free(&csi->ctrl_handler);
+media_clean:
+	media_device_cleanup(&csi->media_dev);
+	return ret;
+}
+
+int sun6i_csi_cleanup(struct sun6i_csi *csi)
+{
+	v4l2_async_notifier_unregister(&csi->notifier);
+	v4l2_async_notifier_cleanup(&csi->notifier);
+	sun6i_video_cleanup(csi);
+	v4l2_device_unregister(&csi->v4l2_dev);
+	v4l2_ctrl_handler_free(&csi->ctrl_handler);
+	media_device_unregister(&csi->media_dev);
+	media_device_cleanup(&csi->media_dev);
+
+	return 0;
+}
+
+// }}}
diff --git a/drivers/media/platform/sun6i-csi/sun6i_csi.h b/drivers/media/platform/sun6i-csi/sun6i_csi.h
new file mode 100644
index 000000000000..f6c7d852b15f
--- /dev/null
+++ b/drivers/media/platform/sun6i-csi/sun6i_csi.h
@@ -0,0 +1,202 @@
+/*
+ * Copyright (c) 2017 Yong Deng <yong.deng@magewell.com>
+ * Copyright (c) 2017 Ondrej Jirman <megous@megous.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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __SUN6I_VIDEO_H__
+#define __SUN6I_VIDEO_H__
+
+#include <media/v4l2-dev.h>
+#include <media/videobuf2-core.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-ctrls.h>
+
+#define SUN6I_CSI_NUM_SENSORS 4
+
+/*
+ * struct sun6i_csi_format - CSI media bus format information
+ * @fourcc: Fourcc code for this format
+ * @mbus_code: V4L2 media bus format code.
+ * @bpp: Bytes per pixel (when stored in memory)
+ */
+struct sun6i_csi_format {
+	u32	fourcc;
+	u32	mbus_code;
+	u8	bpp;
+};
+
+struct sun6i_csi_subdev {
+	struct v4l2_subdev		*sd;
+	unsigned int			pad;
+	unsigned int			ep_id;
+	enum v4l2_mbus_type		bus_type;
+	struct v4l2_fwnode_bus_parallel	parallel;
+};
+
+struct sun6i_csi;
+
+struct sun6i_csi_ops {
+	int (*get_supported_pixformats)(struct sun6i_csi *csi,
+					const u32 **pixformats);
+	bool (*is_format_support)(struct sun6i_csi *csi, u32 pixformat,
+				  u32 mbus_code,
+				  struct sun6i_csi_subdev *csi_sd);
+	int (*s_power)(struct sun6i_csi *csi, bool enable);
+	int (*apply_config)(struct sun6i_csi *csi,
+			    struct sun6i_csi_subdev *csi_sd);
+	int (*update_buf_addr)(struct sun6i_csi *csi, dma_addr_t addr);
+	int (*s_stream)(struct sun6i_csi *csi, bool enable);
+};
+
+struct sun6i_csi {
+	struct v4l2_device		v4l2_dev;
+	struct v4l2_ctrl_handler	ctrl_handler;
+	struct v4l2_async_notifier	notifier;
+	struct video_device		vdev;
+	struct media_device		media_dev;
+	struct media_pad		pad;
+	struct device			*dev;
+
+	struct sun6i_csi_subdev		sensors[SUN6I_CSI_NUM_SENSORS];
+
+	struct sun6i_csi_ops		*ops;
+
+	struct mutex			lock;
+
+	struct vb2_queue		vb2_vidq;
+	spinlock_t			dma_queue_lock;
+	struct list_head		dma_queue;
+	unsigned int			sequence;
+	bool				skip_first_interrupt;
+
+	struct sun6i_csi_format		*formats;
+	unsigned int			num_formats;
+	struct sun6i_csi_format		*current_fmt;
+	struct v4l2_format		fmt;
+};
+
+void sun6i_video_frame_done(struct sun6i_csi *video);
+
+int sun6i_csi_init(struct sun6i_csi *csi);
+int sun6i_csi_cleanup(struct sun6i_csi *csi);
+
+/**
+ * sun6i_csi_get_supported_pixformats() - get csi supported pixformats
+ * @csi:	pointer to the csi
+ * @pixformats: supported pixformats return from csi
+ *
+ * @return the count of pixformats or error(< 0)
+ */
+static inline int
+sun6i_csi_get_supported_pixformats(struct sun6i_csi *csi,
+				   const u32 **pixformats)
+{
+	if (csi->ops != NULL && csi->ops->get_supported_pixformats != NULL)
+		return csi->ops->get_supported_pixformats(csi, pixformats);
+
+	return -ENOIOCTLCMD;
+}
+
+/**
+ * sun6i_csi_is_format_support() - check if the format supported by csi
+ * @csi:	pointer to the csi
+ * @pixformat:	v4l2 pixel format (V4L2_PIX_FMT_*)
+ * @mbus_code:	media bus format code (MEDIA_BUS_FMT_*)
+ */
+
+/**
+ * sun6i_csi_set_power() - power on/off the csi
+ * @csi:	pointer to the csi
+ * @enable:	on/off
+ */
+static inline int sun6i_csi_set_power(struct sun6i_csi *csi, bool enable)
+{
+	if (csi->ops != NULL && csi->ops->s_power != NULL)
+		return csi->ops->s_power(csi, enable);
+
+	return -ENOIOCTLCMD;
+}
+
+/**
+ * sun6i_csi_update_buf_addr() - update the csi frame buffer address
+ * @csi:	pointer to the csi
+ * @addr:	frame buffer's physical address
+ */
+static inline int sun6i_csi_update_buf_addr(struct sun6i_csi *csi,
+					    dma_addr_t addr)
+{
+	if (csi->ops != NULL && csi->ops->update_buf_addr != NULL)
+		return csi->ops->update_buf_addr(csi, addr);
+
+	return -ENOIOCTLCMD;
+}
+
+/**
+ * sun6i_csi_set_stream() - start/stop csi streaming
+ * @csi:	pointer to the csi
+ * @enable:	start/stop
+ */
+static inline int sun6i_csi_set_stream(struct sun6i_csi *csi, bool enable)
+{
+	if (csi->ops != NULL && csi->ops->s_stream != NULL)
+		return csi->ops->s_stream(csi, enable);
+
+	return -ENOIOCTLCMD;
+}
+
+static inline int v4l2_pixformat_get_bpp(unsigned int pixformat)
+{
+	switch (pixformat) {
+	case V4L2_PIX_FMT_SBGGR8:
+	case V4L2_PIX_FMT_SGBRG8:
+	case V4L2_PIX_FMT_SGRBG8:
+	case V4L2_PIX_FMT_SRGGB8:
+		return 8;
+	case V4L2_PIX_FMT_SBGGR10:
+	case V4L2_PIX_FMT_SGBRG10:
+	case V4L2_PIX_FMT_SGRBG10:
+	case V4L2_PIX_FMT_SRGGB10:
+		return 10;
+	case V4L2_PIX_FMT_SBGGR12:
+	case V4L2_PIX_FMT_SGBRG12:
+	case V4L2_PIX_FMT_SGRBG12:
+	case V4L2_PIX_FMT_SRGGB12:
+	case V4L2_PIX_FMT_HM12:
+	case V4L2_PIX_FMT_NV12:
+	case V4L2_PIX_FMT_NV21:
+	case V4L2_PIX_FMT_YUV420:
+	case V4L2_PIX_FMT_YVU420:
+		return 12;
+	case V4L2_PIX_FMT_YUYV:
+	case V4L2_PIX_FMT_YVYU:
+	case V4L2_PIX_FMT_UYVY:
+	case V4L2_PIX_FMT_VYUY:
+	case V4L2_PIX_FMT_RGB565:
+	case V4L2_PIX_FMT_RGB555:
+	case V4L2_PIX_FMT_NV16:
+	case V4L2_PIX_FMT_NV61:
+	case V4L2_PIX_FMT_YUV422P:
+		return 16;
+	case V4L2_PIX_FMT_RGB24:
+	case V4L2_PIX_FMT_BGR24:
+		return 24;
+	case V4L2_PIX_FMT_RGB32:
+	case V4L2_PIX_FMT_BGR32:
+		return 32;
+	}
+
+	return 0;
+}
+
+#endif /* __SUN6I_VIDEO_H__ */
diff --git a/drivers/media/platform/sun6i-csi/sun6i_csi_v3s.c b/drivers/media/platform/sun6i-csi/sun6i_csi_v3s.c
new file mode 100644
index 000000000000..0d882866b336
--- /dev/null
+++ b/drivers/media/platform/sun6i-csi/sun6i_csi_v3s.c
@@ -0,0 +1,776 @@
+/*
+ * Copyright (c) 2017 Magewell Electronics Co., Ltd. (Nanjing).
+ * All rights reserved.
+ * Author: Yong Deng <yong.deng@magewell.com>
+ * Copyright (c) 2017 Ondrej Jirman <megous@megous.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#define DEBUG
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/fs.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/ioctl.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/sched.h>
+#include <linux/sizes.h>
+#include <linux/slab.h>
+
+#include "sun6i_csi.h"
+#include "sun6i_csi_v3s.h"
+
+#define MODULE_NAME	"sun6i-csi"
+
+struct sun6i_csi_cfg {
+	bool has_bt1120_if;
+	bool has_16bit_yuv422_if;
+};
+
+struct sun6i_csi_dev {
+	struct sun6i_csi		csi; /* must be first */
+	const struct sun6i_csi_cfg	*cfg;
+
+	struct regmap			*regmap;
+	struct clk			*clk_ahb;
+	struct clk			*clk_mod;
+	struct clk			*clk_ram;
+	struct reset_control		*rstc_ahb;
+
+	int				planar_offset[3];
+};
+
+static inline struct sun6i_csi_dev *sun6i_csi_to_dev(struct sun6i_csi *csi)
+{
+	return container_of(csi, struct sun6i_csi_dev, csi);
+}
+
+static const u32 supported_pixformats[] = {
+	V4L2_PIX_FMT_SBGGR8,
+	V4L2_PIX_FMT_SGBRG8,
+	V4L2_PIX_FMT_SGRBG8,
+	V4L2_PIX_FMT_SRGGB8,
+	V4L2_PIX_FMT_SBGGR10,
+	V4L2_PIX_FMT_SGBRG10,
+	V4L2_PIX_FMT_SGRBG10,
+	V4L2_PIX_FMT_SRGGB10,
+	V4L2_PIX_FMT_SBGGR12,
+	V4L2_PIX_FMT_SGBRG12,
+	V4L2_PIX_FMT_SGRBG12,
+	V4L2_PIX_FMT_SRGGB12,
+	V4L2_PIX_FMT_YUYV,
+	V4L2_PIX_FMT_YVYU,
+	V4L2_PIX_FMT_UYVY,
+	V4L2_PIX_FMT_VYUY,
+	V4L2_PIX_FMT_RGB565,
+	V4L2_PIX_FMT_RGB555,
+	V4L2_PIX_FMT_HM12,
+	V4L2_PIX_FMT_NV12,
+	V4L2_PIX_FMT_NV21,
+	V4L2_PIX_FMT_YUV420,
+	V4L2_PIX_FMT_YVU420,
+	V4L2_PIX_FMT_NV16,
+	V4L2_PIX_FMT_NV61,
+	V4L2_PIX_FMT_YUV422P,
+};
+
+static int get_supported_pixformats(struct sun6i_csi *csi,
+				    const u32 **pixformats)
+{
+	if (pixformats != NULL)
+		*pixformats = supported_pixformats;
+
+	return ARRAY_SIZE(supported_pixformats);
+}
+
+/* TODO add 10&12 bit YUV, RGB support */
+static bool is_format_support(struct sun6i_csi *csi, u32 pixformat,
+			      u32 mbus_code, struct sun6i_csi_subdev *csi_sd)
+{
+	//struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
+	enum v4l2_mbus_type bus_type = csi_sd->bus_type;
+	bool is_parallel = bus_type == V4L2_MBUS_PARALLEL ||
+			   bus_type == V4L2_MBUS_BT656;
+
+	/*
+	 * Some video receiver have capability both 8bit and 16bit.
+	 * Identify the media bus format from device tree.
+	 */
+	if ((is_parallel && csi_sd->parallel.bus_width == 16) ||
+	    bus_type == V4L2_MBUS_CSI2) {
+		switch (pixformat) {
+		case V4L2_PIX_FMT_HM12:
+		case V4L2_PIX_FMT_NV12:
+		case V4L2_PIX_FMT_NV21:
+		case V4L2_PIX_FMT_NV16:
+		case V4L2_PIX_FMT_NV61:
+		case V4L2_PIX_FMT_YUV420:
+		case V4L2_PIX_FMT_YVU420:
+		case V4L2_PIX_FMT_YUV422P:
+			switch (mbus_code) {
+			case MEDIA_BUS_FMT_UYVY8_1X16:
+			case MEDIA_BUS_FMT_VYUY8_1X16:
+			case MEDIA_BUS_FMT_YUYV8_1X16:
+			case MEDIA_BUS_FMT_YVYU8_1X16:
+				return true;
+			}
+			break;
+		}
+		return false;
+	}
+
+	switch (pixformat) {
+	case V4L2_PIX_FMT_SBGGR8:
+		return mbus_code == MEDIA_BUS_FMT_SBGGR8_1X8;
+	case V4L2_PIX_FMT_SGBRG8:
+		return mbus_code == MEDIA_BUS_FMT_SGBRG8_1X8;
+	case V4L2_PIX_FMT_SGRBG8:
+		return mbus_code == MEDIA_BUS_FMT_SGRBG8_1X8;
+	case V4L2_PIX_FMT_SRGGB8:
+		return mbus_code == MEDIA_BUS_FMT_SRGGB8_1X8;
+	case V4L2_PIX_FMT_SBGGR10:
+		return mbus_code == MEDIA_BUS_FMT_SBGGR10_1X10;
+	case V4L2_PIX_FMT_SGBRG10:
+		return mbus_code == MEDIA_BUS_FMT_SGBRG10_1X10;
+	case V4L2_PIX_FMT_SGRBG10:
+		return mbus_code == MEDIA_BUS_FMT_SGRBG10_1X10;
+	case V4L2_PIX_FMT_SRGGB10:
+		return mbus_code == MEDIA_BUS_FMT_SRGGB10_1X10;
+	case V4L2_PIX_FMT_SBGGR12:
+		return mbus_code == MEDIA_BUS_FMT_SBGGR12_1X12;
+	case V4L2_PIX_FMT_SGBRG12:
+		return mbus_code == MEDIA_BUS_FMT_SGBRG12_1X12;
+	case V4L2_PIX_FMT_SGRBG12:
+		return mbus_code == MEDIA_BUS_FMT_SGRBG12_1X12;
+	case V4L2_PIX_FMT_SRGGB12:
+		return mbus_code == MEDIA_BUS_FMT_SRGGB12_1X12;
+
+	case V4L2_PIX_FMT_RGB565:
+		return mbus_code == MEDIA_BUS_FMT_RGB565_2X8_LE;
+	case V4L2_PIX_FMT_RGB555:
+		return mbus_code == MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE;
+
+	case V4L2_PIX_FMT_YUYV:
+		return mbus_code == MEDIA_BUS_FMT_YUYV8_2X8;
+	case V4L2_PIX_FMT_YVYU:
+		return mbus_code == MEDIA_BUS_FMT_YVYU8_2X8;
+	case V4L2_PIX_FMT_UYVY:
+		return mbus_code == MEDIA_BUS_FMT_UYVY8_2X8;
+	case V4L2_PIX_FMT_VYUY:
+		return mbus_code == MEDIA_BUS_FMT_VYUY8_2X8;
+
+	case V4L2_PIX_FMT_HM12:
+	case V4L2_PIX_FMT_NV12:
+	case V4L2_PIX_FMT_NV21:
+	case V4L2_PIX_FMT_NV16:
+	case V4L2_PIX_FMT_NV61:
+	case V4L2_PIX_FMT_YUV420:
+	case V4L2_PIX_FMT_YVU420:
+	case V4L2_PIX_FMT_YUV422P:
+		switch (mbus_code) {
+		case MEDIA_BUS_FMT_UYVY8_2X8:
+		case MEDIA_BUS_FMT_VYUY8_2X8:
+		case MEDIA_BUS_FMT_YUYV8_2X8:
+		case MEDIA_BUS_FMT_YVYU8_2X8:
+			return true;
+		}
+		break;
+	}
+
+	return false;
+}
+
+static enum csi_input_fmt get_csi_input_format(u32 mbus_code, u32 pixformat)
+{
+	/* bayer */
+	if ((mbus_code & 0xF000) == 0x3000)
+		return CSI_INPUT_FORMAT_RAW;
+
+	switch (pixformat) {
+	case V4L2_PIX_FMT_YUYV:
+	case V4L2_PIX_FMT_YVYU:
+	case V4L2_PIX_FMT_UYVY:
+	case V4L2_PIX_FMT_VYUY:
+	case V4L2_PIX_FMT_RGB565:
+	case V4L2_PIX_FMT_RGB555:
+		return CSI_INPUT_FORMAT_RAW;
+	}
+
+	/* not support YUV420 input format yet */
+	return CSI_INPUT_FORMAT_YUV422;
+}
+
+static enum csi_output_fmt get_csi_output_format(u32 pixformat, u32 field)
+{
+	bool buf_interlaced = false;
+
+	if (field == V4L2_FIELD_INTERLACED ||
+	    field == V4L2_FIELD_INTERLACED_TB ||
+	    field == V4L2_FIELD_INTERLACED_BT)
+		buf_interlaced = true;
+
+	switch (pixformat) {
+	case V4L2_PIX_FMT_SBGGR8:
+	case V4L2_PIX_FMT_SGBRG8:
+	case V4L2_PIX_FMT_SGRBG8:
+	case V4L2_PIX_FMT_SRGGB8:
+		return buf_interlaced ? CSI_FRAME_RAW_8 : CSI_FIELD_RAW_8;
+	case V4L2_PIX_FMT_SBGGR10:
+	case V4L2_PIX_FMT_SGBRG10:
+	case V4L2_PIX_FMT_SGRBG10:
+	case V4L2_PIX_FMT_SRGGB10:
+		return buf_interlaced ? CSI_FRAME_RAW_10 : CSI_FIELD_RAW_10;
+	case V4L2_PIX_FMT_SBGGR12:
+	case V4L2_PIX_FMT_SGBRG12:
+	case V4L2_PIX_FMT_SGRBG12:
+	case V4L2_PIX_FMT_SRGGB12:
+		return buf_interlaced ? CSI_FRAME_RAW_12 : CSI_FIELD_RAW_12;
+
+	case V4L2_PIX_FMT_YUYV:
+	case V4L2_PIX_FMT_YVYU:
+	case V4L2_PIX_FMT_UYVY:
+	case V4L2_PIX_FMT_VYUY:
+	case V4L2_PIX_FMT_RGB565:
+	case V4L2_PIX_FMT_RGB555:
+		return buf_interlaced ? CSI_FRAME_RAW_8 : CSI_FIELD_RAW_8;
+
+	case V4L2_PIX_FMT_HM12:
+		return buf_interlaced ? CSI_FRAME_MB_YUV420 :
+					CSI_FIELD_MB_YUV420;
+	case V4L2_PIX_FMT_NV12:
+	case V4L2_PIX_FMT_NV21:
+		return buf_interlaced ? CSI_FRAME_UV_CB_YUV420 :
+					CSI_FIELD_UV_CB_YUV420;
+	case V4L2_PIX_FMT_YUV420:
+	case V4L2_PIX_FMT_YVU420:
+		return buf_interlaced ? CSI_FRAME_PLANAR_YUV420 :
+					CSI_FIELD_PLANAR_YUV420;
+	case V4L2_PIX_FMT_NV16:
+	case V4L2_PIX_FMT_NV61:
+		return buf_interlaced ? CSI_FRAME_UV_CB_YUV422 :
+					CSI_FIELD_UV_CB_YUV422;
+	case V4L2_PIX_FMT_YUV422P:
+		return buf_interlaced ? CSI_FRAME_PLANAR_YUV422 :
+					CSI_FIELD_PLANAR_YUV422;
+	}
+
+	return 0;
+}
+
+static enum csi_input_seq get_csi_input_seq(u32 mbus_code, u32 pixformat)
+{
+
+	switch (pixformat) {
+	case V4L2_PIX_FMT_HM12:
+	case V4L2_PIX_FMT_NV12:
+	case V4L2_PIX_FMT_NV16:
+	case V4L2_PIX_FMT_YUV420:
+	case V4L2_PIX_FMT_YUV422P:
+		switch (mbus_code) {
+		case MEDIA_BUS_FMT_UYVY8_2X8:
+		case MEDIA_BUS_FMT_UYVY8_1X16:
+			return CSI_INPUT_SEQ_UYVY;
+		case MEDIA_BUS_FMT_VYUY8_2X8:
+		case MEDIA_BUS_FMT_VYUY8_1X16:
+			return CSI_INPUT_SEQ_VYUY;
+		case MEDIA_BUS_FMT_YUYV8_2X8:
+		case MEDIA_BUS_FMT_YUYV8_1X16:
+			return CSI_INPUT_SEQ_YUYV;
+		case MEDIA_BUS_FMT_YVYU8_1X16:
+		case MEDIA_BUS_FMT_YVYU8_2X8:
+			return CSI_INPUT_SEQ_YVYU;
+		}
+		break;
+	case V4L2_PIX_FMT_NV21:
+	case V4L2_PIX_FMT_NV61:
+	case V4L2_PIX_FMT_YVU420:
+		switch (mbus_code) {
+		case MEDIA_BUS_FMT_UYVY8_2X8:
+		case MEDIA_BUS_FMT_UYVY8_1X16:
+			return CSI_INPUT_SEQ_VYUY;
+		case MEDIA_BUS_FMT_VYUY8_2X8:
+		case MEDIA_BUS_FMT_VYUY8_1X16:
+			return CSI_INPUT_SEQ_UYVY;
+		case MEDIA_BUS_FMT_YUYV8_2X8:
+		case MEDIA_BUS_FMT_YUYV8_1X16:
+			return CSI_INPUT_SEQ_YVYU;
+		case MEDIA_BUS_FMT_YVYU8_1X16:
+		case MEDIA_BUS_FMT_YVYU8_2X8:
+			return CSI_INPUT_SEQ_YUYV;
+		}
+		break;
+	}
+
+	return CSI_INPUT_SEQ_YUYV;
+}
+
+static void sun6i_csi_setup_bus(struct sun6i_csi_dev *sdev,
+				struct sun6i_csi_subdev *csi_sd)
+{
+	unsigned char bus_width = csi_sd->parallel.bus_width;
+	u32 flags = csi_sd->parallel.flags;
+	u32 cfg;
+
+	regmap_read(sdev->regmap, CSI_IF_CFG_REG, &cfg);
+
+	cfg &= ~(CSI_IF_CFG_CSI_IF_MASK | CSI_IF_CFG_MIPI_IF_MASK |
+		 CSI_IF_CFG_IF_DATA_WIDTH_MASK |
+		 CSI_IF_CFG_CLK_POL_MASK | CSI_IF_CFG_VREF_POL_MASK |
+		 CSI_IF_CFG_HREF_POL_MASK | CSI_IF_CFG_FIELD_MASK);
+
+	switch (csi_sd->bus_type) {
+#if 0
+	case V4L2_MBUS_CSI2:
+		cfg |= CSI_IF_CFG_MIPI_IF_MIPI;
+		break;
+#endif
+	case V4L2_MBUS_PARALLEL:
+		cfg |= CSI_IF_CFG_MIPI_IF_CSI;
+
+		cfg |= (bus_width == 16) ? CSI_IF_CFG_CSI_IF_YUV422_16BIT :
+					   CSI_IF_CFG_CSI_IF_YUV422_INTLV;
+
+		if (flags & V4L2_MBUS_FIELD_EVEN_LOW)
+			cfg |= CSI_IF_CFG_FIELD_POSITIVE;
+
+		if (flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH)
+			cfg |= CSI_IF_CFG_VREF_POL_POSITIVE;
+		if (flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH)
+			cfg |= CSI_IF_CFG_HREF_POL_POSITIVE;
+
+		if (flags & V4L2_MBUS_PCLK_SAMPLE_FALLING)
+			cfg |= CSI_IF_CFG_CLK_POL_FALLING_EDGE;
+		break;
+	case V4L2_MBUS_BT656:
+		cfg |= CSI_IF_CFG_MIPI_IF_CSI;
+
+		cfg |= (bus_width == 16) ? CSI_IF_CFG_CSI_IF_BT1120 :
+					   CSI_IF_CFG_CSI_IF_BT656;
+
+		if (flags & V4L2_MBUS_FIELD_EVEN_LOW)
+			cfg |= CSI_IF_CFG_FIELD_POSITIVE;
+
+		if (flags & V4L2_MBUS_PCLK_SAMPLE_FALLING)
+			cfg |= CSI_IF_CFG_CLK_POL_FALLING_EDGE;
+		break;
+	default:
+		BUG();
+		break;
+	}
+
+	switch (bus_width) {
+	case 8:
+		cfg |= CSI_IF_CFG_IF_DATA_WIDTH_8BIT;
+		break;
+	case 10:
+		cfg |= CSI_IF_CFG_IF_DATA_WIDTH_10BIT;
+		break;
+	case 12:
+		cfg |= CSI_IF_CFG_IF_DATA_WIDTH_12BIT;
+		break;
+	default:
+		break;
+	}
+
+	regmap_write(sdev->regmap, CSI_IF_CFG_REG, cfg);
+}
+
+static void sun6i_csi_set_format(struct sun6i_csi_dev *sdev)
+{
+	struct sun6i_csi *csi = &sdev->csi;
+	u32 cfg;
+	u32 val;
+
+	regmap_read(sdev->regmap, CSI_CH_CFG_REG, &cfg);
+
+	cfg &= ~(CSI_CH_CFG_INPUT_FMT_MASK |
+		 CSI_CH_CFG_OUTPUT_FMT_MASK | CSI_CH_CFG_VFLIP_EN |
+		 CSI_CH_CFG_HFLIP_EN | CSI_CH_CFG_FIELD_SEL_MASK |
+		 CSI_CH_CFG_INPUT_SEQ_MASK);
+
+	val = get_csi_input_format(csi->current_fmt->mbus_code,
+				   csi->fmt.fmt.pix.pixelformat);
+	cfg |= CSI_CH_CFG_INPUT_FMT(val);
+
+	val = get_csi_output_format(csi->current_fmt->mbus_code,
+				    csi->fmt.fmt.pix.field);
+	cfg |= CSI_CH_CFG_OUTPUT_FMT(val);
+
+	val = get_csi_input_seq(csi->current_fmt->mbus_code,
+				csi->fmt.fmt.pix.pixelformat);
+	cfg |= CSI_CH_CFG_INPUT_SEQ(val);
+
+	if (csi->fmt.fmt.pix.field == V4L2_FIELD_TOP)
+		cfg |= CSI_CH_CFG_FIELD_SEL_FIELD0;
+	else if (csi->fmt.fmt.pix.field == V4L2_FIELD_BOTTOM)
+		cfg |= CSI_CH_CFG_FIELD_SEL_FIELD1;
+	else
+		cfg |= CSI_CH_CFG_FIELD_SEL_BOTH;
+
+	regmap_write(sdev->regmap, CSI_CH_CFG_REG, cfg);
+}
+
+static void sun6i_csi_set_window(struct sun6i_csi_dev *sdev)
+{
+	struct sun6i_csi *csi = &sdev->csi;
+	u32 bytesperline_y;
+	u32 bytesperline_c;
+	int *planar_offset = sdev->planar_offset;
+	u32 width = csi->fmt.fmt.pix.width;
+	u32 height = csi->fmt.fmt.pix.height;
+	u32 hor_len = width;
+
+	switch (csi->fmt.fmt.pix.pixelformat) {
+	case V4L2_PIX_FMT_YUYV:
+	case V4L2_PIX_FMT_YVYU:
+	case V4L2_PIX_FMT_UYVY:
+	case V4L2_PIX_FMT_VYUY:
+	case V4L2_PIX_FMT_RGB565:
+	case V4L2_PIX_FMT_RGB555:
+		hor_len *= 2;
+		break;
+	}
+
+	regmap_write(sdev->regmap, CSI_CH_HSIZE_REG,
+		     CSI_CH_HSIZE_HOR_LEN(hor_len) |
+		     CSI_CH_HSIZE_HOR_START(0));
+	regmap_write(sdev->regmap, CSI_CH_VSIZE_REG,
+		     CSI_CH_VSIZE_VER_LEN(height) |
+		     CSI_CH_VSIZE_VER_START(0));
+
+	planar_offset[0] = 0;
+
+	switch (csi->fmt.fmt.pix.pixelformat) {
+	case V4L2_PIX_FMT_HM12:
+	case V4L2_PIX_FMT_NV12:
+	case V4L2_PIX_FMT_NV21:
+	case V4L2_PIX_FMT_NV16:
+	case V4L2_PIX_FMT_NV61:
+		bytesperline_y = width;
+		bytesperline_c = width;
+		planar_offset[1] = bytesperline_y * height;
+		planar_offset[2] = -1;
+		break;
+	case V4L2_PIX_FMT_YUV420:
+	case V4L2_PIX_FMT_YVU420:
+		bytesperline_y = width;
+		bytesperline_c = width / 2;
+		planar_offset[1] = bytesperline_y * height;
+		planar_offset[2] = planar_offset[1] +
+				bytesperline_c * height / 2;
+		break;
+	case V4L2_PIX_FMT_YUV422P:
+		bytesperline_y = width;
+		bytesperline_c = width / 2;
+		planar_offset[1] = bytesperline_y * height;
+		planar_offset[2] = planar_offset[1] +
+				bytesperline_c * height;
+		break;
+	default: /* raw */
+		bytesperline_y = (csi->current_fmt->bpp * width) / 8;
+		bytesperline_c = 0;
+		planar_offset[1] = -1;
+		planar_offset[2] = -1;
+		break;
+	}
+
+	regmap_write(sdev->regmap, CSI_CH_BUF_LEN_REG,
+		     CSI_CH_BUF_LEN_BUF_LEN_C(bytesperline_c) |
+		     CSI_CH_BUF_LEN_BUF_LEN_Y(bytesperline_y));
+}
+
+static int set_power(struct sun6i_csi *csi, bool enable)
+{
+	struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
+	struct regmap *regmap = sdev->regmap;
+	int ret;
+
+	if (!enable) {
+		regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, 0);
+
+		clk_disable_unprepare(sdev->clk_ram);
+		clk_disable_unprepare(sdev->clk_mod);
+		clk_disable_unprepare(sdev->clk_ahb);
+		reset_control_assert(sdev->rstc_ahb);
+		return 0;
+	}
+
+	ret = clk_prepare_enable(sdev->clk_ahb);
+	if (ret) {
+		dev_err(csi->dev, "Enable ahb clk err %d\n", ret);
+		return ret;
+	}
+
+	ret = clk_prepare_enable(sdev->clk_mod);
+	if (ret) {
+		dev_err(csi->dev, "Enable csi clk err %d\n", ret);
+		return ret;
+	}
+
+	ret = clk_prepare_enable(sdev->clk_ram);
+	if (ret) {
+		dev_err(csi->dev, "Enable clk_dram_csi clk err %d\n", ret);
+		return ret;
+	}
+
+	if (!IS_ERR_OR_NULL(sdev->rstc_ahb)) {
+		ret = reset_control_deassert(sdev->rstc_ahb);
+		if (ret) {
+			dev_err(csi->dev, "reset err %d\n", ret);
+			return ret;
+		}
+	}
+
+	regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, CSI_EN_CSI_EN);
+
+	return 0;
+}
+
+static int apply_config(struct sun6i_csi *csi, struct sun6i_csi_subdev *csi_sd)
+{
+	struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
+
+	sun6i_csi_setup_bus(sdev, csi_sd);
+	sun6i_csi_set_format(sdev);
+	sun6i_csi_set_window(sdev);
+
+	return 0;
+}
+
+static int update_buf_addr(struct sun6i_csi *csi, dma_addr_t addr)
+{
+	struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
+	/* transform physical address to bus address */
+	dma_addr_t bus_addr = addr - 0x40000000;
+
+	regmap_write(sdev->regmap, CSI_CH_F0_BUFA_REG,
+		     (bus_addr + sdev->planar_offset[0]) >> 2);
+	if (sdev->planar_offset[1] != -1)
+		regmap_write(sdev->regmap, CSI_CH_F1_BUFA_REG,
+			     (bus_addr + sdev->planar_offset[1]) >> 2);
+	if (sdev->planar_offset[2] != -1)
+		regmap_write(sdev->regmap, CSI_CH_F2_BUFA_REG,
+			     (bus_addr + sdev->planar_offset[2]) >> 2);
+
+	return 0;
+}
+
+static int set_stream(struct sun6i_csi *csi, bool enable)
+{
+	struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
+	struct regmap *regmap = sdev->regmap;
+
+	if (!enable) {
+		regmap_update_bits(regmap, CSI_CAP_REG, CSI_CAP_CH0_VCAP_ON, 0);
+		regmap_write(regmap, CSI_CH_INT_EN_REG, 0);
+		regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, 0);
+		return 0;
+	}
+
+	/* reset */
+	regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, CSI_EN_CSI_EN);
+	regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, 0);
+	regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, CSI_EN_CSI_EN);
+
+	regmap_write(regmap, CSI_CH_INT_STA_REG, 0xFF);
+	regmap_write(regmap, CSI_CH_INT_EN_REG,
+		     CSI_CH_INT_EN_HB_OF_INT_EN |
+		     CSI_CH_INT_EN_FIFO2_OF_INT_EN |
+		     CSI_CH_INT_EN_FIFO1_OF_INT_EN |
+		     CSI_CH_INT_EN_FIFO0_OF_INT_EN |
+		     CSI_CH_INT_EN_FD_INT_EN |
+		     CSI_CH_INT_EN_CD_INT_EN);
+
+	regmap_update_bits(regmap, CSI_CAP_REG, CSI_CAP_CH0_VCAP_ON,
+			   CSI_CAP_CH0_VCAP_ON);
+
+	return 0;
+}
+
+static struct sun6i_csi_ops csi_ops = {
+	.get_supported_pixformats	= get_supported_pixformats,
+	.is_format_support		= is_format_support,
+	.s_power			= set_power,
+	.apply_config			= apply_config,
+	.update_buf_addr		= update_buf_addr,
+	.s_stream			= set_stream,
+};
+
+static irqreturn_t sun6i_csi_isr(int irq, void *dev_id)
+{
+	struct sun6i_csi_dev *sdev = (struct sun6i_csi_dev *)dev_id;
+	struct regmap *regmap = sdev->regmap;
+	u32 status;
+
+	regmap_read(regmap, CSI_CH_INT_STA_REG, &status);
+
+	if ((status & CSI_CH_INT_STA_FIFO0_OF_PD) ||
+	    (status & CSI_CH_INT_STA_FIFO1_OF_PD) ||
+	    (status & CSI_CH_INT_STA_FIFO2_OF_PD) ||
+	    (status & CSI_CH_INT_STA_HB_OF_PD)) {
+		regmap_write(regmap, CSI_CH_INT_STA_REG, 0xff);
+		regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, 0);
+		regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN,
+				   CSI_EN_CSI_EN);
+		return IRQ_HANDLED;
+	}
+
+	if (status & CSI_CH_INT_STA_FD_PD)
+		sun6i_video_frame_done(&sdev->csi);
+
+	regmap_write(regmap, CSI_CH_INT_STA_REG, 0xff);
+	return IRQ_HANDLED;
+}
+
+static const struct regmap_config sun6i_csi_regmap_config = {
+	.reg_bits       = 32,
+	.reg_stride     = 4,
+	.val_bits       = 32,
+	.max_register	= 0x1000,
+};
+
+static int sun6i_csi_resource_request(struct sun6i_csi_dev *sdev,
+				      struct platform_device *pdev)
+{
+	struct resource *res;
+	void __iomem *io_base;
+	int ret;
+	int irq;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	io_base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(io_base))
+		return PTR_ERR(io_base);
+
+	sdev->regmap = devm_regmap_init_mmio(&pdev->dev, io_base,
+					    &sun6i_csi_regmap_config);
+	if (IS_ERR(sdev->regmap)) {
+		dev_err(&pdev->dev, "Failed to init register map\n");
+		return PTR_ERR(sdev->regmap);
+	}
+
+	sdev->clk_ahb = devm_clk_get(&pdev->dev, "ahb");
+	if (IS_ERR(sdev->clk_ahb)) {
+		dev_err(&pdev->dev, "Unable to acquire ahb clock\n");
+		return PTR_ERR(sdev->clk_ahb);
+	}
+
+	sdev->clk_mod = devm_clk_get(&pdev->dev, "mod");
+	if (IS_ERR(sdev->clk_mod)) {
+		dev_err(&pdev->dev, "Unable to acquire csi clock\n");
+		return PTR_ERR(sdev->clk_mod);
+	}
+
+	sdev->clk_ram = devm_clk_get(&pdev->dev, "ram");
+	if (IS_ERR(sdev->clk_ram)) {
+		dev_err(&pdev->dev, "Unable to acquire dram-csi clock\n");
+		return PTR_ERR(sdev->clk_ram);
+	}
+
+	sdev->rstc_ahb = devm_reset_control_get_optional_shared(&pdev->dev,
+								NULL);
+	if (IS_ERR(sdev->rstc_ahb)) {
+		dev_err(&pdev->dev, "Cannot get reset controller\n");
+		return PTR_ERR(sdev->rstc_ahb);
+	}
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		dev_err(&pdev->dev, "No csi IRQ specified\n");
+		ret = -ENXIO;
+		return ret;
+	}
+
+	ret = devm_request_irq(&pdev->dev, irq, sun6i_csi_isr, 0, MODULE_NAME,
+			       sdev);
+	if (ret) {
+		dev_err(&pdev->dev, "Cannot request csi IRQ\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int sun6i_csi_probe(struct platform_device *pdev)
+{
+	struct sun6i_csi_dev *sdev;
+	int ret;
+
+	sdev = devm_kzalloc(&pdev->dev, sizeof(*sdev), GFP_KERNEL);
+	if (!sdev)
+		return -ENOMEM;
+
+	sdev->csi.dev = &pdev->dev;
+	platform_set_drvdata(pdev, sdev);
+
+	sdev->cfg = of_device_get_match_data(&pdev->dev);
+
+	ret = sun6i_csi_resource_request(sdev, pdev);
+	if (ret)
+		return ret;
+
+	sdev->csi.ops = &csi_ops;
+	ret = sun6i_csi_init(&sdev->csi);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int sun6i_csi_remove(struct platform_device *pdev)
+{
+	struct sun6i_csi_dev *sdev = platform_get_drvdata(pdev);
+
+	sun6i_csi_cleanup(&sdev->csi);
+
+	return 0;
+}
+
+static const struct sun6i_csi_cfg sun8i_v3s_cfg = {
+	.has_bt1120_if = true,
+	.has_16bit_yuv422_if = true,
+};
+
+static const struct sun6i_csi_cfg sun8i_a83t_cfg = {
+	.has_bt1120_if = false,
+	.has_16bit_yuv422_if = false,
+};
+
+static const struct of_device_id sun6i_csi_of_match[] = {
+	{ .compatible = "allwinner,sun8i-v3s-csi", .data = &sun8i_v3s_cfg },
+	{ .compatible = "allwinner,sun8i-a83t-csi", .data = &sun8i_a83t_cfg },
+	{},
+};
+MODULE_DEVICE_TABLE(of, sun6i_csi_of_match);
+
+static struct platform_driver sun6i_csi_platform_driver = {
+	.probe = sun6i_csi_probe,
+	.remove = sun6i_csi_remove,
+	.driver = {
+		.name = MODULE_NAME,
+		.of_match_table = of_match_ptr(sun6i_csi_of_match),
+	},
+};
+module_platform_driver(sun6i_csi_platform_driver);
+
+MODULE_DESCRIPTION("Allwinner V3s Camera Sensor Interface driver");
+MODULE_AUTHOR("Yong Deng <yong.deng@magewell.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/media/platform/sun6i-csi/sun6i_csi_v3s.h b/drivers/media/platform/sun6i-csi/sun6i_csi_v3s.h
new file mode 100644
index 000000000000..4916d23c064f
--- /dev/null
+++ b/drivers/media/platform/sun6i-csi/sun6i_csi_v3s.h
@@ -0,0 +1,243 @@
+/*
+ * Copyright (c) 2017 Yong Deng <yong.deng@magewell.com>
+ * Copyright (c) 2017 Ondrej Jirman <megous@megous.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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __SUN6I_CSI_V3S_H__
+#define __SUN6I_CSI_V3S_H__
+
+#include <linux/kernel.h>
+
+#define CSI_EN_REG			0x0
+#define CSI_EN_VER_EN				BIT(30)
+#define CSI_EN_CSI_EN				BIT(0)
+
+#define CSI_IF_CFG_REG			0x4
+
+#define CSI_IF_CFG_SRC_TYPE_MASK		BIT(21)
+#define CSI_IF_CFG_SRC_TYPE_PROGRESSED		0
+#define CSI_IF_CFG_SRC_TYPE_INTERLACED		BIT(21)
+
+#define CSI_IF_CFG_FPS_DS_EN			BIT(20)
+
+#define CSI_IF_CFG_FIELD_MASK			BIT(19)
+#define CSI_IF_CFG_FIELD_NEGATIVE		0
+#define CSI_IF_CFG_FIELD_POSITIVE		BIT(19)
+
+#define CSI_IF_CFG_VREF_POL_MASK		BIT(18)
+#define CSI_IF_CFG_VREF_POL_NEGATIVE		0
+#define CSI_IF_CFG_VREF_POL_POSITIVE		BIT(18)
+
+#define CSI_IF_CFG_HREF_POL_MASK		BIT(17)
+#define CSI_IF_CFG_HREF_POL_NEGATIVE		0
+#define CSI_IF_CFG_HREF_POL_POSITIVE		BIT(17)
+
+#define CSI_IF_CFG_CLK_POL_MASK			BIT(16)
+#define CSI_IF_CFG_CLK_POL_RISING_EDGE		0
+#define CSI_IF_CFG_CLK_POL_FALLING_EDGE		BIT(16)
+
+//megi: A83T does haveg GENMASK(9, 8) and fewer options, though it is compatible
+//#define CSI_IF_CFG_IF_DATA_WIDTH_MASK		GENMASK(10, 8)
+#define CSI_IF_CFG_IF_DATA_WIDTH_MASK		GENMASK(9, 8)
+#define CSI_IF_CFG_IF_DATA_WIDTH_8BIT		(0 << 8)
+#define CSI_IF_CFG_IF_DATA_WIDTH_10BIT		(1 << 8)
+#define CSI_IF_CFG_IF_DATA_WIDTH_12BIT		(2 << 8)
+//megi: A83T only
+#define CSI_IF_CFG_IF_DATA_WIDTH_8P2BIT		(3 << 8)
+
+#define CSI_IF_CFG_MIPI_IF_MASK			BIT(7)
+#define CSI_IF_CFG_MIPI_IF_CSI			0
+#define CSI_IF_CFG_MIPI_IF_MIPI			BIT(7)
+
+#define CSI_IF_CFG_CSI_IF_MASK			GENMASK(4, 0)
+#define CSI_IF_CFG_CSI_IF_YUV422_INTLV		0
+//megi: not supported by A83T:
+#define CSI_IF_CFG_CSI_IF_YUV422_16BIT		1
+#define CSI_IF_CFG_CSI_IF_BT656			4
+//megi: not supported by A83T:
+#define CSI_IF_CFG_CSI_IF_BT1120		5
+
+#define CSI_CAP_REG			0x8
+#define CSI_CAP_CH0_CAP_MASK_MASK		GENMASK(5, 2)
+#define CSI_CAP_CH0_CAP_MASK(count)		((count << 2) & \
+						 CSI_CAP_CH0_CAP_MASK_MASK)
+#define CSI_CAP_CH0_VCAP_ON			BIT(1)
+#define CSI_CAP_CH0_SCAP_ON			BIT(0)
+
+#define CSI_SYNC_CNT_REG		0xc
+#define CSI_FIFO_THRS_REG		0x10
+//megi: missing from A83T docs: (BT656 muti-channel mode not supported?)
+#define CSI_BT656_HEAD_CFG_REG		0x14
+#define CSI_PTN_LEN_REG			0x30
+#define CSI_PTN_ADDR_REG		0x34
+#define CSI_VER_REG			0x3c
+
+//megi: called CSI0_C0_* in A83T
+#define CSI_CH_CFG_REG			0x44
+#define CSI_CH_CFG_INPUT_FMT_MASK		GENMASK(23, 20)
+#define CSI_CH_CFG_INPUT_FMT(fmt)		((fmt << 20) & \
+						 CSI_CH_CFG_INPUT_FMT_MASK)
+#define CSI_CH_CFG_OUTPUT_FMT_MASK		GENMASK(19, 16)
+#define CSI_CH_CFG_OUTPUT_FMT(fmt)		((fmt << 16) & \
+						 CSI_CH_CFG_OUTPUT_FMT_MASK)
+#define CSI_CH_CFG_VFLIP_EN			BIT(13)
+#define CSI_CH_CFG_HFLIP_EN			BIT(12)
+#define CSI_CH_CFG_FIELD_SEL_MASK		GENMASK(11, 10)
+#define CSI_CH_CFG_FIELD_SEL_FIELD0		(0 << 10)
+#define CSI_CH_CFG_FIELD_SEL_FIELD1		(1 << 10)
+#define CSI_CH_CFG_FIELD_SEL_BOTH		(2 << 10)
+#define CSI_CH_CFG_INPUT_SEQ_MASK		GENMASK(9, 8)
+#define CSI_CH_CFG_INPUT_SEQ(seq)		((seq << 8) & \
+						 CSI_CH_CFG_INPUT_SEQ_MASK)
+
+#define CSI_CH_SCALE_REG		0x4c
+#define CSI_CH_SCALE_QUART_EN			BIT(0)
+
+#define CSI_CH_F0_BUFA_REG		0x50
+
+#define CSI_CH_F1_BUFA_REG		0x58
+
+#define CSI_CH_F2_BUFA_REG		0x60
+
+//megi: called CAP_STA register in A83T manual
+#define CSI_CH_STA_REG			0x6c
+#define CSI_CH_STA_FIELD_STA_MASK		BIT(2)
+#define CSI_CH_STA_FIELD_STA_FIELD0		0
+#define CSI_CH_STA_FIELD_STA_FIELD1		BIT(2)
+#define CSI_CH_STA_VCAP_STA			BIT(1)
+#define CSI_CH_STA_SCAP_STA			BIT(0)
+
+#define CSI_CH_INT_EN_REG		0x70
+#define CSI_CH_INT_EN_VS_INT_EN			BIT(7)
+#define CSI_CH_INT_EN_HB_OF_INT_EN		BIT(6)
+#define CSI_CH_INT_EN_MUL_ERR_INT_EN		BIT(5)
+#define CSI_CH_INT_EN_FIFO2_OF_INT_EN		BIT(4)
+#define CSI_CH_INT_EN_FIFO1_OF_INT_EN		BIT(3)
+#define CSI_CH_INT_EN_FIFO0_OF_INT_EN		BIT(2)
+#define CSI_CH_INT_EN_FD_INT_EN			BIT(1)
+#define CSI_CH_INT_EN_CD_INT_EN			BIT(0)
+
+#define CSI_CH_INT_STA_REG		0x74
+#define CSI_CH_INT_STA_VS_PD			BIT(7)
+#define CSI_CH_INT_STA_HB_OF_PD			BIT(6)
+#define CSI_CH_INT_STA_MUL_ERR_PD		BIT(5)
+#define CSI_CH_INT_STA_FIFO2_OF_PD		BIT(4)
+#define CSI_CH_INT_STA_FIFO1_OF_PD		BIT(3)
+#define CSI_CH_INT_STA_FIFO0_OF_PD		BIT(2)
+#define CSI_CH_INT_STA_FD_PD			BIT(1)
+#define CSI_CH_INT_STA_CD_PD			BIT(0)
+
+#define CSI_CH_FLD1_VSIZE_REG		0x78
+
+#define CSI_CH_HSIZE_REG		0x80
+#define CSI_CH_HSIZE_HOR_LEN_MASK		GENMASK(28, 16)
+#define CSI_CH_HSIZE_HOR_LEN(len)		((len << 16) & \
+						 CSI_CH_HSIZE_HOR_LEN_MASK)
+#define CSI_CH_HSIZE_HOR_START_MASK		GENMASK(12, 0)
+#define CSI_CH_HSIZE_HOR_START(start)		((start << 0) & \
+						 CSI_CH_HSIZE_HOR_START_MASK)
+
+#define CSI_CH_VSIZE_REG		0x84
+#define CSI_CH_VSIZE_VER_LEN_MASK		GENMASK(28, 16)
+#define CSI_CH_VSIZE_VER_LEN(len)		((len << 16) & \
+						 CSI_CH_VSIZE_VER_LEN_MASK)
+#define CSI_CH_VSIZE_VER_START_MASK		GENMASK(12, 0)
+#define CSI_CH_VSIZE_VER_START(start)		((start << 0) & \
+						 CSI_CH_VSIZE_VER_START_MASK)
+
+#define CSI_CH_BUF_LEN_REG		0x88
+#define CSI_CH_BUF_LEN_BUF_LEN_C_MASK		GENMASK(29, 16)
+#define CSI_CH_BUF_LEN_BUF_LEN_C(len)		((len << 16) & \
+						 CSI_CH_BUF_LEN_BUF_LEN_C_MASK)
+#define CSI_CH_BUF_LEN_BUF_LEN_Y_MASK		GENMASK(13, 0)
+#define CSI_CH_BUF_LEN_BUF_LEN_Y(len)		((len << 0) & \
+						 CSI_CH_BUF_LEN_BUF_LEN_Y_MASK)
+
+#define CSI_CH_FLIP_SIZE_REG		0x8c
+#define CSI_CH_FLIP_SIZE_VER_LEN_MASK		GENMASK(28, 16)
+#define CSI_CH_FLIP_SIZE_VER_LEN(len)		((len << 16) & \
+						 CSI_CH_FLIP_SIZE_VER_LEN_MASK)
+#define CSI_CH_FLIP_SIZE_VALID_LEN_MASK		GENMASK(12, 0)
+#define CSI_CH_FLIP_SIZE_VALID_LEN(len)		((len << 0) & \
+						CSI_CH_FLIP_SIZE_VALID_LEN_MASK)
+
+#define CSI_CH_FRM_CLK_CNT_REG		0x90
+#define CSI_CH_ACC_ITNL_CLK_CNT_REG	0x94
+#define CSI_CH_FIFO_STAT_REG		0x98
+#define CSI_CH_PCLK_STAT_REG		0x9c
+
+/*
+ * csi input data format
+ */
+enum csi_input_fmt {
+	CSI_INPUT_FORMAT_RAW		= 0,
+	CSI_INPUT_FORMAT_YUV422		= 3,
+	CSI_INPUT_FORMAT_YUV420		= 4,
+};
+
+/*
+ * csi output data format
+ */
+enum csi_output_fmt {
+	/* only when input format is RAW */
+	CSI_FIELD_RAW_8			= 0,
+	CSI_FIELD_RAW_10		= 1,
+	CSI_FIELD_RAW_12		= 2,
+	CSI_FIELD_RGB565		= 4,
+	CSI_FIELD_RGB888		= 5,
+	CSI_FIELD_PRGB888		= 6,
+
+	//megi: A83T only
+	CSI_FIELD_UV_COMBINED		= 7,
+
+	CSI_FRAME_RAW_8			= 8,
+	CSI_FRAME_RAW_10		= 9,
+	CSI_FRAME_RAW_12		= 10,
+	CSI_FRAME_RGB565		= 12,
+	CSI_FRAME_RGB888		= 13,
+	CSI_FRAME_PRGB888		= 14,
+
+	//megi: A83T only
+	CSI_FRAME_UV_COMBINED		= 15,
+
+	/* only when input format is YUV422/YUV420 */
+	//megi: when input format is 420, only 420 output formats are available
+	//from below
+	CSI_FIELD_PLANAR_YUV422		= 0,
+	CSI_FIELD_PLANAR_YUV420		= 1,
+	CSI_FRAME_PLANAR_YUV420		= 2,
+	CSI_FRAME_PLANAR_YUV422		= 3,
+	CSI_FIELD_UV_CB_YUV422		= 4,
+	CSI_FIELD_UV_CB_YUV420		= 5,
+	CSI_FRAME_UV_CB_YUV420		= 6,
+	CSI_FRAME_UV_CB_YUV422		= 7,
+	CSI_FIELD_MB_YUV422		= 8,
+	CSI_FIELD_MB_YUV420		= 9,
+	CSI_FRAME_MB_YUV420		= 10,
+	CSI_FRAME_MB_YUV422		= 11,
+	CSI_FIELD_UV_CB_YUV422_10	= 12,
+	CSI_FIELD_UV_CB_YUV420_10	= 13,
+};
+
+/*
+ * csi YUV input data sequence
+ */
+enum csi_input_seq {
+	/* only when input format is YUV422 */
+	CSI_INPUT_SEQ_YUYV = 0,
+	CSI_INPUT_SEQ_YVYU,
+	CSI_INPUT_SEQ_UYVY,
+	CSI_INPUT_SEQ_VYUY,
+};
+
+#endif /* __SUN6I_CSI_V3S_H__ */
-- 
2.20.1

