diff --git a/drivers/media/platform/mtk-isp/isp_50/cam/Makefile b/drivers/media/platform/mtk-isp/isp_50/cam/Makefile
new file mode 100644
index 0000000..8ddc34b
--- /dev/null
+++ b/drivers/media/platform/mtk-isp/isp_50/cam/Makefile
@@ -0,0 +1,19 @@
+#
+# Copyright (C) 2018 MediaTek Inc.
+#
+# 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.
+#
+
+mtk-cam-isp-objs += \
+	mtk_cam.o mtk_cam-dev.o mtk_cam-dev-ctx-core.o \
+	mtk_cam-ctrl.o mtk_cam-scp.o \
+	mtk_cam-v4l2-util.o mtk_cam-smem-drv.o
+
+obj-$(CONFIG_VIDEO_MEDIATEK_ISP_PASS1_SUPPORT) += mtk-cam-isp.o
diff --git a/drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam-ctx.h b/drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam-ctx.h
new file mode 100644
index 0000000..5f3b807
--- /dev/null
+++ b/drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam-ctx.h
@@ -0,0 +1,116 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2018 MediaTek Inc.
+ * Author: Frederic Chen <frederic.chen@mediatek.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.
+ */
+
+#ifndef __MTK_CAM_CTX_H__
+#define __MTK_CAM_CTX_H__
+
+#include <linux/device.h>
+#include <linux/types.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-ctrls.h>
+#include <media/videobuf2-core.h>
+#include <media/v4l2-subdev.h>
+
+#define MTK_CAM_DEV_NODES			11
+#define MTK_CAM_DEV_FRAME_BUNDLE_BUFFER_MAX	MTK_CAM_DEV_NODES
+
+struct mtk_cam_dev;
+
+/*
+ * struct mtk_cam_dev_node_desc - node attributes
+ *
+ * @id:		 id of the context queue
+ * @name:	 media entity name
+ * @description: descritpion of node
+ * @cap:	 mapped to V4L2 capabilities
+ * @buf_type:	 mapped to V4L2 buffer type
+ * @dma_port:	 the dma port associated to the buffer
+ * @link_flags:	 default media link flags
+ * @smem_alloc:	 using the cam_smem_drv as alloc ctx or not
+ * @capture:	 true for capture queue (device to user)
+ *		 false for output queue (from user to device)
+ * @image:	 true for image node, false for meta node
+ * @num_fmts:	 the number of supported formats
+ * @default_fmt_idx: default format of this node
+ * @max_buf_count: maximum V4L2 buffer count
+ * @ioctl_ops:  mapped to v4l2_ioctl_ops
+ * @fmts:	supported format
+ *
+ */
+struct mtk_cam_dev_node_desc {
+	u8 id;
+	char *name;
+	char *description;
+	u32 cap;
+	u32 buf_type;
+	u32 dma_port;
+	u32 link_flags;
+	u8 smem_alloc:1;
+	u8 capture:1;
+	u8 image:1;
+	u8 num_fmts;
+	u8 default_fmt_idx;
+	u8 max_buf_count;
+	const struct v4l2_ioctl_ops *ioctl_ops;
+	struct v4l2_format *fmts;
+};
+
+/* Attributes setup by device owner */
+struct mtk_cam_dev_queues_setting {
+	struct mtk_cam_dev_node_desc *output_node_descs;
+	unsigned int total_output_nodes;
+	struct mtk_cam_dev_node_desc *capture_node_descs;
+	unsigned int total_capture_nodes;
+};
+
+struct mtk_cam_dev_start_param {
+	int request_fd;
+	struct mtk_cam_dev_buffer*
+		buffers[MTK_CAM_DEV_FRAME_BUNDLE_BUFFER_MAX];
+};
+
+struct mtk_cam_dev_finish_param {
+	int request_fd;
+	unsigned int frame_seq_no;
+	unsigned int state;
+	struct list_head *list_buf;
+};
+
+/* For v4l2 event data, must smaller than 64 bytes */
+struct mtk_cam_dev_stat_event_data {
+	__u32 frame_seq_no;
+	__u32 irq_status_mask;
+	__u32 dma_status_mask;
+};
+
+int mtk_cam_dev_core_queue_setup(struct mtk_cam_dev *cam_dev,
+				 struct mtk_cam_dev_queues_setting *setting);
+int mtk_cam_dev_core_job_finish(struct mtk_cam_dev *cam_dev,
+				struct mtk_cam_dev_finish_param *param);
+int mtk_cam_dev_queue_event_dev_state(struct mtk_cam_dev *cam_dev,
+				      struct mtk_cam_dev_stat_event_data *stat);
+void mtk_cam_dev_fmt_set_img(struct device *dev,
+			     struct v4l2_pix_format_mplane *dest_fmt,
+			     struct v4l2_pix_format_mplane *src_fmt,
+			     unsigned int node_id);
+struct v4l2_format *
+mtk_cam_dev_find_fmt(struct mtk_cam_dev_node_desc *queue_desc, u32 format);
+void mtk_cam_dev_load_default_fmt(struct device *dev,
+				  struct mtk_cam_dev_node_desc *queue,
+				  struct v4l2_format *dest_fmt);
+void mtk_cam_dev_cal_mplane_pix_fmt(struct device *dev,
+				    struct v4l2_pix_format_mplane *dest_fmt,
+				    unsigned int node_id);
+#endif /*__MTK_CAM_CTX_H__*/
diff --git a/drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam-dev-ctx-core.c b/drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam-dev-ctx-core.c
new file mode 100644
index 0000000..c17b294
--- /dev/null
+++ b/drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam-dev-ctx-core.c
@@ -0,0 +1,302 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2018 MediaTek Inc.
+ * Author: Frederic Chen <frederic.chen@mediatek.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.
+ */
+
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <media/videobuf2-dma-contig.h>
+#include <linux/dma-mapping.h>
+#include <media/v4l2-event.h>
+
+#include "mtk_cam.h"
+#include "mtk_cam-ctx.h"
+#include "mtk_cam-dev.h"
+#include "mtk_cam-v4l2-util.h"
+#include "mtk_cam-smem.h"
+
+static __u32 get_pixel_byte_by_fmt(__u32 pix_fmt)
+{
+	switch (pix_fmt) {
+	case V4L2_PIX_FMT_MTISP_B8:
+	case V4L2_PIX_FMT_MTISP_F8:
+		return 8;
+	case V4L2_PIX_FMT_MTISP_B10:
+	case V4L2_PIX_FMT_MTISP_F10:
+		return 10;
+	case V4L2_PIX_FMT_MTISP_B12:
+	case V4L2_PIX_FMT_MTISP_F12:
+		return 12;
+	case V4L2_PIX_FMT_MTISP_B14:
+	case V4L2_PIX_FMT_MTISP_F14:
+		return 14;
+	case V4L2_PIX_FMT_MTISP_U8:
+	case V4L2_PIX_FMT_MTISP_U10:
+	case V4L2_PIX_FMT_MTISP_U12:
+	case V4L2_PIX_FMT_MTISP_U14:
+		return 16;
+	default:
+		return 0;
+	}
+}
+
+static __u32 align_main_stream_size(__u32 size, unsigned int pix_mode)
+{
+	switch (pix_mode) {
+	case default_pixel_mode:
+	case four_pixel_mode:
+		return ALIGN(size, 8);
+	case two_pixel_mode:
+		return ALIGN(size, 4);
+	case one_pixel_mode:
+		return ALIGN(size, 2);
+	default:
+		break;
+	}
+	return 0;
+}
+
+static unsigned int align_packetd_out_size(__u32 size,
+					   unsigned int pix_mode,
+					   __u32 fmt)
+{
+	switch (pix_mode) {
+	case default_pixel_mode:
+	case four_pixel_mode:
+		return ALIGN(size, 16);
+	case two_pixel_mode:
+		return ALIGN(size, 8);
+	case one_pixel_mode:
+		if (fmt == V4L2_PIX_FMT_MTISP_F10)
+			return ALIGN(size, 4);
+		else
+			return ALIGN(size, 8);
+	default:
+		return ALIGN(size, 16);
+	}
+	return 0;
+}
+
+static __u32 cal_main_stream_stride(struct device *dev,
+				    __u32 width,
+				    __u32 pix_fmt,
+				    __u32 pix_mode)
+{
+	__u32 stride;
+	__u32 pixel_byte = get_pixel_byte_by_fmt(pix_fmt);
+
+	width = ALIGN(width, 4);
+	stride = ALIGN(DIV_ROUND_UP(width * pixel_byte, 8), 2);
+	/* expand stride, instead of shrink width */
+	stride = align_main_stream_size(stride, pix_mode);
+
+	dev_dbg(dev,
+		"main width:%d, pix_mode:%d, stride:%d\n",
+		width, pix_mode, stride);
+	return stride;
+}
+
+static __u32 cal_packed_out_stride(struct device *dev,
+				   __u32 width,
+				   __u32 pix_fmt,
+				   __u32 pix_mode)
+{
+	__u32 stride;
+	__u32 pixel_byte = get_pixel_byte_by_fmt(pix_fmt);
+
+	width = ALIGN(width, 4);
+	stride = DIV_ROUND_UP(width * 3, 2);
+	stride = DIV_ROUND_UP(stride * pixel_byte, 8);
+	/* expand stride, instead of shrink width */
+	stride = align_packetd_out_size(stride, pix_mode, pix_fmt);
+
+	dev_dbg(dev,
+		"packed width:%d, pix_mode:%d, stride:%d\n",
+		width, pix_mode, stride);
+
+	return stride;
+}
+
+static __u32 cal_img_stride(struct device *dev,
+			    int node_id,
+			    __u32 width,
+			    __u32 pix_fmt)
+{
+	/* Currently, only support one_pixel_mode */
+	if (node_id == MTK_CAM_P1_MAIN_STREAM_OUT)
+		return cal_main_stream_stride(dev, width, pix_fmt,
+					      one_pixel_mode);
+	else if (node_id == MTK_CAM_P1_PACKED_BIN_OUT)
+		return cal_packed_out_stride(dev, width, pix_fmt,
+					      one_pixel_mode);
+
+	return 0;
+}
+
+struct v4l2_format *
+mtk_cam_dev_find_fmt(struct mtk_cam_dev_node_desc *desc, u32 format)
+{
+	unsigned int i;
+	struct v4l2_format *dev_fmt;
+
+	for (i = 0; i < desc->num_fmts; i++) {
+		dev_fmt = &desc->fmts[i];
+		if (dev_fmt->fmt.pix_mp.pixelformat == format)
+			return dev_fmt;
+	}
+
+	return NULL;
+}
+
+/* The helper to configure the device context */
+int mtk_cam_dev_core_queue_setup(struct mtk_cam_dev *cam_dev,
+				 struct mtk_cam_dev_queues_setting *setting)
+{
+	unsigned int i, node_idx;
+
+	node_idx = 0;
+
+	/* Setup the output queue */
+	for (i = 0; i < setting->total_output_nodes; i++)
+		cam_dev->mem2mem2_nodes[node_idx++].desc =
+			setting->output_node_descs[i];
+
+	/* Setup the capture queue */
+	for (i = 0; i < setting->total_capture_nodes; i++)
+		cam_dev->mem2mem2_nodes[node_idx++].desc =
+			setting->capture_node_descs[i];
+
+	cam_dev->dev_node_num = node_idx;
+
+	return 0;
+}
+
+int mtk_cam_dev_core_job_finish(struct mtk_cam_dev *cam_dev,
+				struct mtk_cam_dev_finish_param *fram_param)
+{
+	struct mtk_cam_dev_buffer *buf, *b0;
+
+	if (!cam_dev->streaming)
+		return 0;
+
+	dev_dbg(&cam_dev->pdev->dev,
+		"job recvied request fd(%d), frame_seq(%d) state(%d)\n",
+		fram_param->request_fd,
+		fram_param->frame_seq_no,
+		fram_param->state);
+
+	/*
+	 * Set the buffer's VB2 status so that the user can dequeue
+	 * the buffer.
+	 */
+	list_for_each_entry_safe(buf, b0, fram_param->list_buf, list) {
+		list_del(&buf->list);
+		buf->vbb.vb2_buf.timestamp = ktime_get_ns();
+		buf->vbb.sequence = fram_param->frame_seq_no;
+		vb2_buffer_done(&buf->vbb.vb2_buf, fram_param->state);
+	}
+
+	return 0;
+}
+
+int mtk_cam_dev_queue_event_dev_state(struct mtk_cam_dev *cam_dev,
+				      struct mtk_cam_dev_stat_event_data *stat)
+{
+	struct v4l2_event event;
+
+	memset(&event, 0, sizeof(event));
+	event.type = V4L2_EVENT_FRAME_SYNC;
+	event.u.frame_sync.frame_sequence = stat->frame_seq_no;
+	v4l2_event_queue(cam_dev->subdev.devnode, &event);
+
+	return 0;
+}
+
+/* Calcuate mplane pix format */
+void mtk_cam_dev_cal_mplane_pix_fmt(struct device *dev,
+				    struct v4l2_pix_format_mplane *dest_fmt,
+				    unsigned int node_id)
+{
+	unsigned int i;
+	__u32 bpl, sizeimage, imagsize;
+
+	imagsize = 0;
+	for (i = 0 ; i < dest_fmt->num_planes; ++i) {
+		bpl = cal_img_stride(dev,
+				     node_id,
+				     dest_fmt->width,
+				     dest_fmt->pixelformat);
+		sizeimage = bpl * dest_fmt->height;
+		imagsize += sizeimage;
+		dest_fmt->plane_fmt[i].bytesperline = bpl;
+		dest_fmt->plane_fmt[i].sizeimage = sizeimage;
+		memset(dest_fmt->plane_fmt[i].reserved,
+		       0, sizeof(dest_fmt->plane_fmt[i].reserved));
+		dev_dbg(dev, "plane:%d,bpl:%d,sizeimage:%u\n",
+			i,  bpl, dest_fmt->plane_fmt[i].sizeimage);
+	}
+	if (dest_fmt->num_planes == 1)
+		dest_fmt->plane_fmt[0].sizeimage = imagsize;
+}
+
+void mtk_cam_dev_fmt_set_img(struct device *dev,
+			     struct v4l2_pix_format_mplane *dest_fmt,
+			     struct v4l2_pix_format_mplane *src_fmt,
+			     unsigned int node_id)
+{
+	dest_fmt->width = src_fmt->width;
+	dest_fmt->height = src_fmt->height;
+	dest_fmt->pixelformat = src_fmt->pixelformat;
+	dest_fmt->field = src_fmt->field;
+	dest_fmt->colorspace = src_fmt->colorspace;
+	dest_fmt->num_planes = src_fmt->num_planes;
+	/* Use default */
+	dest_fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
+	dest_fmt->quantization = V4L2_QUANTIZATION_DEFAULT;
+	dest_fmt->xfer_func =
+		V4L2_MAP_XFER_FUNC_DEFAULT(dest_fmt->colorspace);
+	memset(dest_fmt->reserved, 0, sizeof(dest_fmt->reserved));
+
+	dev_dbg(dev, "%s: Dest Fmt:%c%c%c%c, w*h:%d*%d\n",
+		__func__,
+		(dest_fmt->pixelformat & 0xFF),
+		(dest_fmt->pixelformat >> 8) & 0xFF,
+		(dest_fmt->pixelformat >> 16) & 0xFF,
+		(dest_fmt->pixelformat >> 24) & 0xFF,
+		dest_fmt->width,
+		dest_fmt->height);
+
+	mtk_cam_dev_cal_mplane_pix_fmt(dev, dest_fmt, node_id);
+}
+
+/* Get the default format setting */
+void mtk_cam_dev_load_default_fmt(struct device *dev,
+				  struct mtk_cam_dev_node_desc *queue_desc,
+				  struct v4l2_format *dest)
+{
+	struct v4l2_format *default_fmt =
+		&queue_desc->fmts[queue_desc->default_fmt_idx];
+
+	dest->type = queue_desc->buf_type;
+
+	/* Configure default format based on node type */
+	if (queue_desc->image) {
+		mtk_cam_dev_fmt_set_img(dev,
+					&dest->fmt.pix_mp,
+					&default_fmt->fmt.pix_mp,
+					queue_desc->id);
+	} else {
+		dest->fmt.meta.dataformat = default_fmt->fmt.meta.dataformat;
+		dest->fmt.meta.buffersize = default_fmt->fmt.meta.buffersize;
+	}
+}
diff --git a/drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam-dev.c b/drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam-dev.c
new file mode 100644
index 0000000..35e77ee
--- /dev/null
+++ b/drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam-dev.c
@@ -0,0 +1,525 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2018 Mediatek Corporation.
+ * Copyright (c) 2017 Intel Corporation.
+ * Copyright (C) 2017 Google, Inc.
+ *
+ * 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.
+ *
+ * MTK_CAM-dev is highly based on Intel IPU3 ImgU driver.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/pm_runtime.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-event.h>
+#include <media/videobuf2-dma-contig.h>
+#include "mtk_cam.h"
+#include "mtk_cam-dev.h"
+#include "mtk_cam-smem.h"
+#include "mtk_cam-v4l2-util.h"
+
+static const struct v4l2_ioctl_ops mtk_cam_v4l2_vcap_ioctl_ops = {
+	.vidioc_querycap = mtk_cam_videoc_querycap,
+	.vidioc_enum_framesizes = mtk_cam_enum_framesizes,
+	.vidioc_enum_fmt_vid_cap_mplane = mtk_cam_videoc_enum_fmt,
+	.vidioc_g_fmt_vid_cap_mplane = mtk_cam_videoc_g_fmt,
+	.vidioc_s_fmt_vid_cap_mplane = mtk_cam_videoc_s_fmt,
+	.vidioc_try_fmt_vid_cap_mplane = mtk_cam_videoc_try_fmt,
+	.vidioc_enum_input = mtk_cam_vidioc_enum_input,
+	.vidioc_g_input = mtk_cam_vidioc_g_input,
+	.vidioc_s_input = mtk_cam_vidioc_s_input,
+	/* buffer queue management */
+	.vidioc_reqbufs = vb2_ioctl_reqbufs,
+	.vidioc_create_bufs = vb2_ioctl_create_bufs,
+	.vidioc_prepare_buf = vb2_ioctl_prepare_buf,
+	.vidioc_querybuf = vb2_ioctl_querybuf,
+	.vidioc_qbuf = vb2_ioctl_qbuf,
+	.vidioc_dqbuf = vb2_ioctl_dqbuf,
+	.vidioc_streamon = vb2_ioctl_streamon,
+	.vidioc_streamoff = vb2_ioctl_streamoff,
+	.vidioc_expbuf = vb2_ioctl_expbuf,
+	.vidioc_subscribe_event = mtk_cam_vidioc_subscribe_event,
+	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+static const struct v4l2_ioctl_ops mtk_cam_v4l2_vout_ioctl_ops = {
+	.vidioc_querycap = mtk_cam_videoc_querycap,
+	.vidioc_enum_framesizes = mtk_cam_enum_framesizes,
+	.vidioc_enum_fmt_vid_out_mplane = mtk_cam_videoc_enum_fmt,
+	.vidioc_g_fmt_vid_out_mplane = mtk_cam_videoc_g_fmt,
+	.vidioc_s_fmt_vid_out_mplane = mtk_cam_videoc_s_fmt,
+	.vidioc_try_fmt_vid_out_mplane = mtk_cam_videoc_try_fmt,
+	.vidioc_enum_input = mtk_cam_vidioc_enum_input,
+	.vidioc_g_input = mtk_cam_vidioc_g_input,
+	.vidioc_s_input = mtk_cam_vidioc_s_input,
+	/* buffer queue management */
+	.vidioc_reqbufs = vb2_ioctl_reqbufs,
+	.vidioc_create_bufs = vb2_ioctl_create_bufs,
+	.vidioc_prepare_buf = vb2_ioctl_prepare_buf,
+	.vidioc_querybuf = vb2_ioctl_querybuf,
+	.vidioc_qbuf = vb2_ioctl_qbuf,
+	.vidioc_dqbuf = vb2_ioctl_dqbuf,
+	.vidioc_streamon = vb2_ioctl_streamon,
+	.vidioc_streamoff = vb2_ioctl_streamoff,
+	.vidioc_expbuf = vb2_ioctl_expbuf,
+};
+
+static  const struct v4l2_ioctl_ops mtk_cam_v4l2_meta_cap_ioctl_ops = {
+	.vidioc_querycap = mtk_cam_videoc_querycap,
+	.vidioc_enum_fmt_meta_cap = mtk_cam_meta_enum_format,
+	.vidioc_g_fmt_meta_cap = mtk_cam_videoc_g_meta_fmt,
+	.vidioc_s_fmt_meta_cap = mtk_cam_videoc_g_meta_fmt,
+	.vidioc_try_fmt_meta_cap = mtk_cam_videoc_g_meta_fmt,
+	.vidioc_reqbufs = vb2_ioctl_reqbufs,
+	.vidioc_create_bufs = vb2_ioctl_create_bufs,
+	.vidioc_prepare_buf = vb2_ioctl_prepare_buf,
+	.vidioc_querybuf = vb2_ioctl_querybuf,
+	.vidioc_qbuf = vb2_ioctl_qbuf,
+	.vidioc_dqbuf = vb2_ioctl_dqbuf,
+	.vidioc_streamon = vb2_ioctl_streamon,
+	.vidioc_streamoff = vb2_ioctl_streamoff,
+	.vidioc_expbuf = vb2_ioctl_expbuf,
+};
+
+static  const struct v4l2_ioctl_ops mtk_cam_v4l2_meta_out_ioctl_ops = {
+	.vidioc_querycap = mtk_cam_videoc_querycap,
+	.vidioc_enum_fmt_meta_out = mtk_cam_meta_enum_format,
+	.vidioc_g_fmt_meta_out = mtk_cam_videoc_g_meta_fmt,
+	.vidioc_s_fmt_meta_out = mtk_cam_videoc_g_meta_fmt,
+	.vidioc_try_fmt_meta_out = mtk_cam_videoc_g_meta_fmt,
+	.vidioc_reqbufs = vb2_ioctl_reqbufs,
+	.vidioc_create_bufs = vb2_ioctl_create_bufs,
+	.vidioc_prepare_buf = vb2_ioctl_prepare_buf,
+	.vidioc_querybuf = vb2_ioctl_querybuf,
+	.vidioc_qbuf = vb2_ioctl_qbuf,
+	.vidioc_dqbuf = vb2_ioctl_dqbuf,
+	.vidioc_streamon = vb2_ioctl_streamon,
+	.vidioc_streamoff = vb2_ioctl_streamoff,
+	.vidioc_expbuf = vb2_ioctl_expbuf,
+};
+
+static struct v4l2_format meta_fmts[] = {
+	{
+		.fmt.meta = {
+			.dataformat = V4L2_META_FMT_MTISP_PARAMS,
+			.buffersize = 128 * PAGE_SIZE,
+		},
+	},
+	{
+		.fmt.meta = {
+			.dataformat = V4L2_META_FMT_MTISP_3A,
+			.buffersize = 300 * PAGE_SIZE,
+		},
+	},
+	{
+		.fmt.meta = {
+			.dataformat = V4L2_META_FMT_MTISP_AF,
+			.buffersize = 160 * PAGE_SIZE,
+		},
+	},
+	{
+		.fmt.meta = {
+			.dataformat = V4L2_META_FMT_MTISP_LCS,
+			.buffersize = 80 * PAGE_SIZE,
+		},
+	},
+	{
+		.fmt.meta = {
+			.dataformat = V4L2_META_FMT_MTISP_LMV,
+			.buffersize = 128 * PAGE_SIZE,
+		},
+	},
+};
+
+/* Need to update mtk_cam_dev_fmt_set_img for default format configuration */
+static struct v4l2_format stream_out_fmts[] = {
+	{
+		.fmt.pix_mp = {
+			.width = IMG_MAX_WIDTH,
+			.height = IMG_MAX_HEIGHT,
+			.pixelformat = V4L2_PIX_FMT_MTISP_B8,
+			.field = V4L2_FIELD_NONE,
+			.colorspace = V4L2_COLORSPACE_SRGB,
+			.num_planes = 1,
+		},
+	},
+	{
+		.fmt.pix_mp = {
+			.width = IMG_MAX_WIDTH,
+			.height = IMG_MAX_HEIGHT,
+			.pixelformat = V4L2_PIX_FMT_MTISP_B10,
+			.field = V4L2_FIELD_NONE,
+			.colorspace = V4L2_COLORSPACE_SRGB,
+			.num_planes = 1,
+		},
+	},
+	{
+		.fmt.pix_mp = {
+			.width = IMG_MAX_WIDTH,
+			.height = IMG_MAX_HEIGHT,
+			.pixelformat = V4L2_PIX_FMT_MTISP_B12,
+			.field = V4L2_FIELD_NONE,
+			.colorspace = V4L2_COLORSPACE_SRGB,
+			.num_planes = 1,
+		},
+	},
+	{
+		.fmt.pix_mp = {
+			.width = IMG_MAX_WIDTH,
+			.height = IMG_MAX_HEIGHT,
+			.pixelformat = V4L2_PIX_FMT_MTISP_B14,
+			.field = V4L2_FIELD_NONE,
+			.colorspace = V4L2_COLORSPACE_SRGB,
+			.num_planes = 1,
+		},
+	},
+};
+
+static struct v4l2_format bin_out_fmts[] = {
+	{
+		.fmt.pix_mp = {
+			.width = RRZ_MAX_WIDTH,
+			.height = RRZ_MAX_HEIGHT,
+			.pixelformat = V4L2_PIX_FMT_MTISP_F8,
+			.field = V4L2_FIELD_NONE,
+			.colorspace = V4L2_COLORSPACE_RAW,
+			.num_planes = 1,
+		},
+	},
+	{
+		.fmt.pix_mp = {
+			.width = RRZ_MAX_WIDTH,
+			.height = RRZ_MAX_HEIGHT,
+			.pixelformat = V4L2_PIX_FMT_MTISP_F10,
+			.field = V4L2_FIELD_NONE,
+			.colorspace = V4L2_COLORSPACE_RAW,
+			.num_planes = 1,
+		},
+	},
+	{
+		.fmt.pix_mp = {
+			.width = RRZ_MAX_WIDTH,
+			.height = RRZ_MAX_HEIGHT,
+			.pixelformat = V4L2_PIX_FMT_MTISP_F12,
+			.field = V4L2_FIELD_NONE,
+			.colorspace = V4L2_COLORSPACE_RAW,
+			.num_planes = 1,
+		},
+	},
+	{
+		.fmt.pix_mp = {
+			.width = RRZ_MAX_WIDTH,
+			.height = RRZ_MAX_HEIGHT,
+			.pixelformat = V4L2_PIX_FMT_MTISP_F14,
+			.field = V4L2_FIELD_NONE,
+			.colorspace = V4L2_COLORSPACE_RAW,
+			.num_planes = 1,
+		},
+	},
+};
+
+static struct mtk_cam_dev_node_desc
+	output_queues[MTK_CAM_P1_TOTAL_OUTPUT] = {
+	{
+		.id = MTK_CAM_P1_META_IN_0,
+		.name = "meta input",
+		.description = "ISP tuning parameters",
+		.cap = V4L2_CAP_META_OUTPUT,
+		.buf_type = V4L2_BUF_TYPE_META_OUTPUT,
+		.link_flags = 0,
+		.capture = false,
+		.image = false,
+		.smem_alloc = true,
+		.fmts = meta_fmts,
+		.num_fmts = ARRAY_SIZE(meta_fmts),
+		.default_fmt_idx = 0,
+		.max_buf_count = 10,
+		.ioctl_ops = &mtk_cam_v4l2_meta_out_ioctl_ops,
+	},
+};
+
+static struct mtk_cam_dev_node_desc
+	capture_queues[MTK_CAM_P1_TOTAL_CAPTURE] = {
+	{
+		.id = MTK_CAM_P1_MAIN_STREAM_OUT,
+		.name = "main stream",
+		.cap = V4L2_CAP_VIDEO_CAPTURE_MPLANE,
+		.buf_type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,
+		.link_flags = 0,
+		.capture = true,
+		.image = true,
+		.smem_alloc = false,
+		.dma_port = R_IMGO,
+		.fmts = stream_out_fmts,
+		.num_fmts = ARRAY_SIZE(stream_out_fmts),
+		.default_fmt_idx = 0,
+		.ioctl_ops = &mtk_cam_v4l2_vcap_ioctl_ops,
+	},
+	{
+		.id = MTK_CAM_P1_PACKED_BIN_OUT,
+		.name = "packed out",
+		.cap = V4L2_CAP_VIDEO_CAPTURE_MPLANE,
+		.buf_type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,
+		.link_flags = 0,
+		.capture = true,
+		.image = true,
+		.smem_alloc = false,
+		.dma_port = R_RRZO,
+		.fmts = bin_out_fmts,
+		.num_fmts = ARRAY_SIZE(bin_out_fmts),
+		.default_fmt_idx = 1,
+		.ioctl_ops = &mtk_cam_v4l2_vcap_ioctl_ops,
+	},
+	{
+		.id = MTK_CAM_P1_META_OUT_0,
+		.name = "partial meta 0",
+		.description = "AE/AWB histogram",
+		.cap = V4L2_CAP_META_CAPTURE,
+		.buf_type = V4L2_BUF_TYPE_META_CAPTURE,
+		.link_flags = 0,
+		.capture = true,
+		.image = false,
+		.smem_alloc = false,
+		.dma_port = R_AAO | R_FLKO | R_PSO,
+		.fmts = meta_fmts,
+		.num_fmts = ARRAY_SIZE(meta_fmts),
+		.default_fmt_idx = 1,
+		.max_buf_count = 5,
+		.ioctl_ops = &mtk_cam_v4l2_meta_cap_ioctl_ops,
+	},
+	{
+		.id = MTK_CAM_P1_META_OUT_1,
+		.name = "partial meta 1",
+		.description = "AF histogram",
+		.cap = V4L2_CAP_META_CAPTURE,
+		.buf_type = V4L2_BUF_TYPE_META_CAPTURE,
+		.link_flags = 0,
+		.capture = true,
+		.image = false,
+		.smem_alloc = false,
+		.dma_port = R_AFO,
+		.fmts = meta_fmts,
+		.num_fmts = ARRAY_SIZE(meta_fmts),
+		.default_fmt_idx = 2,
+		.max_buf_count = 5,
+		.ioctl_ops = &mtk_cam_v4l2_meta_cap_ioctl_ops,
+	},
+	{
+		.id = MTK_CAM_P1_META_OUT_2,
+		.name = "partial meta 2",
+		.description = "Local contrast enhanced statistics",
+		.cap = V4L2_CAP_META_CAPTURE,
+		.buf_type = V4L2_BUF_TYPE_META_CAPTURE,
+		.link_flags = MEDIA_LNK_FL_DYNAMIC,
+		.capture = true,
+		.image = false,
+		.smem_alloc = false,
+		.dma_port = R_LCSO,
+		.fmts = meta_fmts,
+		.num_fmts = ARRAY_SIZE(meta_fmts),
+		.default_fmt_idx = 3,
+		.max_buf_count = 10,
+		.ioctl_ops = &mtk_cam_v4l2_meta_cap_ioctl_ops,
+	},
+	{
+		.id = MTK_CAM_P1_META_OUT_3,
+		.name = "partial meta 3",
+		.description = "Local motion vector histogram",
+		.cap = V4L2_CAP_META_CAPTURE,
+		.buf_type = V4L2_BUF_TYPE_META_CAPTURE,
+		.link_flags = MEDIA_LNK_FL_DYNAMIC,
+		.capture = true,
+		.image = false,
+		.smem_alloc = false,
+		.dma_port = R_LMVO,
+		.fmts = meta_fmts,
+		.num_fmts = ARRAY_SIZE(meta_fmts),
+		.default_fmt_idx = 4,
+		.max_buf_count = 10,
+		.ioctl_ops = &mtk_cam_v4l2_meta_cap_ioctl_ops,
+	},
+};
+
+static struct mtk_cam_dev_queues_setting queues_setting = {
+	.output_node_descs = output_queues,
+	.total_output_nodes = MTK_CAM_P1_TOTAL_OUTPUT,
+	.capture_node_descs = capture_queues,
+	.total_capture_nodes = MTK_CAM_P1_TOTAL_CAPTURE,
+};
+
+static struct platform_device *
+mtk_cam_dev_of_find_smem_dev(struct platform_device *pdev)
+{
+	struct device_node *smem_dev_node;
+
+	smem_dev_node = of_parse_phandle(pdev->dev.of_node,
+					 "smem_device", 0);
+	if (!smem_dev_node) {
+		dev_err(&pdev->dev,
+			"failed to find isp smem device for (%s)\n",
+			pdev->name);
+		return NULL;
+	}
+
+	dev_dbg(&pdev->dev, "smem of node found, try to discovery device\n");
+	return of_find_device_by_node(smem_dev_node);
+}
+
+/* Initliaze a mtk_cam_dev representing a completed HW ISP device. */
+static int mtk_cam_dev_init(struct mtk_cam_dev *cam_dev,
+			    struct platform_device *pdev)
+{
+	int ret;
+
+	/* v4l2 sub-device registration */
+	dev_info(&cam_dev->pdev->dev, "mem2mem2.name: %s\n",
+		 MTK_CAM_DEV_P1_NAME);
+	ret = mtk_cam_mem2mem2_v4l2_register(cam_dev);
+	if (ret) {
+		dev_err(&cam_dev->pdev->dev,
+			"failed to create V4L2 devices (%d)\n", ret);
+		goto failed_mem2mem2;
+	}
+
+	ret = mtk_cam_v4l2_async_register(cam_dev);
+	if (ret) {
+		dev_err(&cam_dev->pdev->dev, "v4l2 async init failed\n");
+		goto failed_async;
+	}
+
+	return 0;
+
+failed_mem2mem2:
+failed_async:
+	return ret;
+}
+
+/* Get a free buffer from a video node */
+static struct mtk_cam_dev_buffer *
+mtk_cam_dev_get_pending_buf(struct mtk_cam_dev *cam_dev, int node)
+{
+	struct mtk_cam_dev_buffer *buf;
+	struct mtk_cam_video_device *vdev;
+
+	if (node > cam_dev->dev_node_num || node < 0) {
+		dev_err(&cam_dev->pdev->dev, "Invalid mtk_cam_dev node.\n");
+		return NULL;
+	}
+	vdev = &cam_dev->mem2mem2_nodes[node];
+
+	spin_lock(&cam_dev->mem2mem2_nodes[node].slock);
+	buf = list_first_entry_or_null(&vdev->pending_list,
+				       struct mtk_cam_dev_buffer,
+				       list);
+	if (!buf) {
+		spin_unlock(&cam_dev->mem2mem2_nodes[node].slock);
+		return NULL;
+	}
+	list_del(&buf->list);
+	spin_unlock(&cam_dev->mem2mem2_nodes[node].slock);
+
+	return buf;
+}
+
+int mtk_cam_dev_queue_buffers(struct mtk_cam_dev *cam_dev)
+{
+	unsigned int node;
+	const int mtk_cam_dev_node_num = cam_dev->dev_node_num;
+	struct device *dev = &cam_dev->pdev->dev;
+	struct mtk_cam_dev_start_param s_param;
+	struct mtk_cam_dev_buffer *buf;
+
+	memset(&s_param, 0, sizeof(struct mtk_cam_dev_start_param));
+
+	dev_dbg(dev, "%s\n", __func__);
+
+	if (!cam_dev->streaming) {
+		dev_dbg(dev, "%s: stream off, no enqueue\n", __func__);
+		return 0;
+	}
+
+	/* Check all enabled nodes to collect its buffer  */
+	for (node = 0; node < mtk_cam_dev_node_num; node++) {
+		if (!cam_dev->mem2mem2_nodes[node].enabled)
+			continue;
+
+		dev_dbg(dev, "Check node:%d, queue:%d\n",
+			node, cam_dev->mem2mem2_nodes[node].enabled);
+
+		buf = mtk_cam_dev_get_pending_buf(cam_dev, node);
+		if (!buf) {
+			dev_warn(dev, "No available buffer of enabled node %d\n",
+				 node);
+			continue;
+		}
+
+		buf->daddr =
+			vb2_dma_contig_plane_dma_addr(&buf->vbb.vb2_buf, 0);
+		if (cam_dev->mem2mem2_nodes[node].desc.smem_alloc) {
+			buf->scp_addr = mtk_cam_smem_iova_to_scp_addr
+				(&cam_dev->smem_pdev->dev,
+				buf->daddr);
+		} else {
+			buf->scp_addr = 0;
+		}
+
+		dev_dbg(dev,
+			"Buf: fd:%d idx:%d state:%d daddr:0x%pK scp_addr:0x%pK",
+			buf->vbb.request_fd,
+			buf->vbb.vb2_buf.index,
+			buf->vbb.vb2_buf.state,
+			buf->daddr,
+			buf->scp_addr);
+
+		s_param.buffers[node] = buf;
+		s_param.request_fd = buf->vbb.request_fd;
+	}
+
+	/* Trigger en-queued job to driver */
+	mtk_isp_enqueue(dev, &s_param);
+
+	return 0;
+}
+
+int mtk_cam_dev_core_init(struct platform_device *pdev,
+			  struct mtk_cam_dev *cam_dev)
+{
+	struct platform_device *smem_dev;
+
+	smem_dev = mtk_cam_dev_of_find_smem_dev(pdev);
+	if (!smem_dev) {
+		dev_err(&pdev->dev, "failed to find smem_dev\n");
+		return -EINVAL;
+	}
+
+	cam_dev->pdev = pdev;
+	cam_dev->smem_pdev = smem_dev;
+
+	mtk_cam_dev_core_queue_setup(cam_dev, &queues_setting);
+	mtk_cam_dev_init(cam_dev, pdev);
+
+	return 0;
+}
+
+int mtk_cam_dev_core_release(struct platform_device *pdev,
+			     struct mtk_cam_dev *cam_dev)
+{
+	mtk_cam_v4l2_async_unregister(cam_dev);
+	mtk_cam_v4l2_unregister(cam_dev);
+
+	return 0;
+}
diff --git a/drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam-dev.h b/drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam-dev.h
new file mode 100644
index 0000000..57c0261
--- /dev/null
+++ b/drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam-dev.h
@@ -0,0 +1,166 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2018 Mediatek Corporation.
+ * Copyright (c) 2017 Intel Corporation.
+ *
+ * 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.
+ *
+ * MTK_CAM-dev is highly based on Intel IPU3 ImgU driver.
+ *
+ */
+
+#ifndef __MTK_CAM_DEV_H__
+#define __MTK_CAM_DEV_H__
+
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+#include <linux/version.h>
+#include <media/v4l2-device.h>
+#include <media/videobuf2-v4l2.h>
+
+#include "mtk_cam-ctx.h"
+
+#define MTK_CAM_DEV_NODE_MAX			MTK_CAM_DEV_NODES
+#define MTK_CAM_DEV_P1_NAME			"MTK-ISP-P1-V4L2"
+
+#define MTK_CAM_MAX_SENSORS_NUM		2
+
+/* Sensor index */
+#define MTK_CAM_MAIN_SENSOR		0
+#define MTK_CAM_SUB_SENSOR		1
+
+/* Input video nodes */
+#define MTK_CAM_P1_META_IN_0		0
+#define MTK_CAM_P1_TOTAL_OUTPUT		1
+
+/* Output video nodes */
+#define MTK_CAM_P1_MAIN_STREAM_OUT	1
+#define MTK_CAM_P1_PACKED_BIN_OUT	2
+#define MTK_CAM_P1_META_OUT_0		3
+#define MTK_CAM_P1_META_OUT_1		4
+#define MTK_CAM_P1_META_OUT_2		5
+#define MTK_CAM_P1_META_OUT_3		6
+#define MTK_CAM_P1_TOTAL_CAPTURE	6
+
+struct mtk_cam_dev_buffer {
+	struct vb2_v4l2_buffer	vbb;
+	struct list_head	list;
+	/* Intenal part */
+	dma_addr_t		daddr;
+	dma_addr_t		scp_addr;
+};
+
+/*
+ * struct mtk_cam_video_device - Mediatek video device structure.
+ *
+ * @enabled: indicate stream on or off
+ * @id: id for mtk_cam_dev_node_desc or mem2mem2_nodes array
+ * @vdev_fmt: the V4L2 format of video device
+ * @vdev_apd: the media pad graph object of video device
+ * @vbq: a videobuf queue of video device
+ * @ctrl_handler: the control handler of video device
+ * @desc: the node attributes of video device
+ * @pending_list: list for pending buffers before enqueuing into driver
+ * @lock: serializes vb2 queue and video device operations.
+ * @slock: protect for pending_list.
+ *
+ */
+struct mtk_cam_video_device {
+	unsigned int enabled;
+	unsigned int id;
+	struct v4l2_format vdev_fmt;
+	struct video_device vdev;
+	struct media_pad vdev_pad;
+	struct vb2_queue vbq;
+	struct v4l2_ctrl_handler ctrl_handler;
+	struct mtk_cam_dev_node_desc desc;
+	struct list_head pending_list;
+	/* Used for vbq & vdev */
+	struct mutex lock;
+	/* protect for pending_list */
+	spinlock_t slock;
+};
+
+/*
+ * struct mtk_cam_dev - Mediatek camera device structure.
+ *
+ * @cio_enabled: indicate stream on or off
+ * @sensor: sensor sub-device
+ * @seninf: sensor_if sub-device
+ *
+ * Below is the graph topology for Camera IO connection.
+ * sensor 1 (main) --> sensor IF --> P1 sub-device
+ * sensor 2 (sub)  -->
+ *
+ */
+struct mtk_cam_dev {
+	struct platform_device *pdev;
+	struct platform_device *smem_pdev;
+	struct media_pipeline pipeline;
+	struct media_device media_dev;
+	struct media_pad *subdev_pads;
+	struct v4l2_subdev subdev;
+	struct v4l2_device v4l2_dev;
+	struct v4l2_ctrl_handler ctrl_handler;
+	struct v4l2_async_notifier notifier;
+	struct mtk_cam_video_device mem2mem2_nodes[MTK_CAM_DEV_NODE_MAX];
+	struct v4l2_subdev *sensor;
+	struct v4l2_subdev *sensor_if;
+	unsigned int cio_enabled;
+	unsigned int cio_pad_sink;
+	unsigned int dev_node_num;
+	unsigned int streaming;
+	int request_fd;
+	unsigned int request_count;
+};
+
+int mtk_cam_dev_core_init(struct platform_device *pdev,
+			  struct mtk_cam_dev *cam_dev);
+int mtk_cam_v4l2_register(struct device *dev,
+			  struct media_device *media_dev,
+			  struct v4l2_device *v4l2_dev,
+			  struct v4l2_ctrl_handler *ctrl_handler);
+int mtk_cam_v4l2_unregister(struct mtk_cam_dev *cam_dev);
+int mtk_cam_mem2mem2_v4l2_register(struct mtk_cam_dev *cam_dev);
+int mtk_cam_v4l2_async_register(struct mtk_cam_dev *cam_dev);
+void mtk_cam_v4l2_async_unregister(struct mtk_cam_dev *cam_dev);
+int mtk_cam_dev_queue_buffers(struct mtk_cam_dev *cam_dev);
+int mtk_cam_dev_core_release(struct platform_device *pdev,
+			     struct mtk_cam_dev *cam_dev);
+
+static inline struct mtk_cam_video_device *
+file_to_mtk_cam_node(struct file *__file)
+{
+	return container_of(video_devdata(__file),
+		struct mtk_cam_video_device, vdev);
+}
+
+static inline struct mtk_cam_dev *
+mtk_cam_subdev_to_dev(struct v4l2_subdev *__sd)
+{
+	return container_of(__sd,
+		struct mtk_cam_dev, subdev);
+}
+
+static inline struct mtk_cam_video_device *
+mtk_cam_vbq_to_vdev(struct vb2_queue *__vq)
+{
+	return container_of(__vq,
+		struct mtk_cam_video_device, vbq);
+}
+
+static inline struct mtk_cam_dev_buffer *
+mtk_cam_vb2_buf_to_dev_buf(struct vb2_buffer *__vb)
+{
+	return container_of(__vb,
+		struct mtk_cam_dev_buffer, vbb.vb2_buf);
+}
+
+#endif /* __MTK_CAM_DEV_H__ */
diff --git a/drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam-v4l2-util.c b/drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam-v4l2-util.c
new file mode 100644
index 0000000..66738ad
--- /dev/null
+++ b/drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam-v4l2-util.c
@@ -0,0 +1,1182 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2018 Mediatek Corporation.
+ * Copyright (c) 2017 Intel Corporation.
+ *
+ * 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.
+ *
+ * MTK_CAM-v4l2 is highly based on Intel IPU3 ImgU driver.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-ioctl.h>
+#include <media/videobuf2-dma-contig.h>
+#include <media/v4l2-subdev.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-fwnode.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/of_graph.h>
+#include <media/v4l2-common.h>
+#include <media/media-entity.h>
+#include <media/v4l2-async.h>
+
+#include "mtk_cam.h"
+#include "mtk_cam-ctrl.h"
+#include "mtk_cam-dev.h"
+#include "mtk_cam-v4l2-util.h"
+
+#define MTK_CAM_SENSOR_MAIN_PAD_SRC		0
+#define MTK_CAM_SENSOR_SUB_PAD_SRC		0
+#define MTK_CAM_SENSOR_IF_PAD_MAIN_SINK		0
+#define MTK_CAM_SENSOR_IF_PAD_SUB_SINK		1
+#define MTK_CAM_SENSOR_IF_PAD_SRC		4
+
+#define SUBDEV_CIO_NAME				"cam-io"
+#define SUBDEV_SENINF_NAME			"seninf"
+#define SUBDEV_SENSOR_NAME			"sensor"
+#define SUBDEV_SENSOR_MAIN_NAME			"sensor_main"
+#define SUBDEV_SENSOR_SUB_NAME			"sensor_sub"
+
+static int mtk_cam_subdev_open(struct v4l2_subdev *sd,
+			       struct v4l2_subdev_fh *fh)
+{
+	struct mtk_cam_dev *cam_dev = mtk_cam_subdev_to_dev(sd);
+
+	cam_dev->request_fd = -1;
+	cam_dev->request_count = 0;
+
+	return mtk_isp_open(&cam_dev->pdev->dev);
+}
+
+static int mtk_cam_subdev_close(struct v4l2_subdev *sd,
+				struct v4l2_subdev_fh *fh)
+{
+	struct mtk_cam_dev *cam_dev = mtk_cam_subdev_to_dev(sd);
+
+	return mtk_isp_release(&cam_dev->pdev->dev);
+}
+
+static int mtk_cam_v4l2_discover_sensor(struct mtk_cam_dev *cam_dev)
+{
+	struct media_graph graph;
+	struct media_entity *entity = &cam_dev->subdev.entity;
+	struct media_device *mdev = entity->graph_obj.mdev;
+	struct device *dev = &cam_dev->pdev->dev;
+	struct v4l2_subdev *sensor;
+	struct v4l2_subdev *sensor_if;
+	int ret;
+
+	mutex_lock(&mdev->graph_mutex);
+	ret = media_graph_walk_init(&graph, mdev);
+	if (ret) {
+		mutex_unlock(&mdev->graph_mutex);
+		return ret;
+	}
+
+	sensor = NULL;
+	sensor_if = NULL;
+
+	media_graph_walk_start(&graph, entity);
+	while ((entity = media_graph_walk_next(&graph))) {
+		dev_dbg(dev, "Graph traversal: entity: %s\n", entity->name);
+
+		if (!strcmp(entity->name, SUBDEV_SENINF_NAME)) {
+			sensor_if = media_entity_to_v4l2_subdev(entity);
+			dev_dbg(dev, "Sensor if entity found: %s\n",
+				entity->name);
+		}
+
+		if (!strncmp(entity->name, SUBDEV_SENSOR_NAME, 6)) {
+			sensor = media_entity_to_v4l2_subdev(entity);
+			dev_dbg(dev, "Sensor if entity found: %s\n",
+				entity->name);
+		}
+	}
+	mutex_unlock(&mdev->graph_mutex);
+	media_graph_walk_cleanup(&graph);
+
+	if (!sensor_if) {
+		dev_err(dev, "Sensor IF has not been connected\n");
+		return -EINVAL;
+	}
+	if (!sensor) {
+		dev_err(dev, "Sensor has not been not connected\n");
+		return -EINVAL;
+	}
+	cam_dev->sensor_if = sensor_if;
+	cam_dev->sensor = sensor;
+
+	return 0;
+}
+
+static int mtk_cam_cio_stream_on(struct mtk_cam_dev *cam_dev)
+{
+	struct device *dev = &cam_dev->pdev->dev;
+	int ret;
+
+	/* Align vb2_core_streamon design */
+	if (cam_dev->streaming) {
+		dev_warn(dev, "already streaming\n", dev);
+		return 0;
+	}
+
+	/*
+	 * Get sensor interace and sensor sub device.
+	 * If the call succeeds, sensor if and sensor are filled
+	 * in isp_dev->cio->sensor_if and isp_dev->cio->sensor.
+	 */
+	ret = mtk_cam_v4l2_discover_sensor(cam_dev);
+	if (ret) {
+		dev_err(dev, "no sensor/sensor if connected:%d\n", ret);
+		return -EPERM;
+	}
+
+	/* seninf must stream on first */
+	dev_dbg(dev, "streamed on sensor-if:%s\n",
+		cam_dev->sensor_if->entity.name);
+	ret = v4l2_subdev_call(cam_dev->sensor_if, video, s_stream, 1);
+	if (ret) {
+		dev_err(dev, "sensor-if:%s stream on failed:%d\n",
+			cam_dev->sensor_if->entity.name, ret);
+		return -EPERM;
+	}
+
+	dev_dbg(dev, "streamed on sensor:%s\n",
+		cam_dev->sensor->entity.name);
+	ret = v4l2_subdev_call(cam_dev->sensor, video, s_stream, 1);
+	if (ret) {
+		dev_err(dev, "sensor:%s stream on failed:%d\n",
+			cam_dev->sensor->entity.name, ret);
+		goto fail_sensor_on;
+	}
+
+	ret = mtk_isp_streamon(dev);
+	if (ret) {
+		dev_err(dev, "Pass 1 stream on failed:%d\n", ret);
+		goto fail_cam_on;
+	}
+	cam_dev->streaming = true;
+
+	dev_dbg(dev, "streamed on Pass 1\n");
+	mtk_cam_dev_queue_buffers(cam_dev);
+
+	return 0;
+
+fail_cam_on:
+	v4l2_subdev_call(cam_dev->sensor, video, s_stream, 0);
+fail_sensor_on:
+	v4l2_subdev_call(cam_dev->sensor_if, video, s_stream, 0);
+	return -EPERM;
+}
+
+static int mtk_cam_cio_stream_off(struct mtk_cam_dev *cam_dev)
+{
+	struct device *dev = &cam_dev->pdev->dev;
+	int ret;
+
+	if (!cam_dev->streaming) {
+		dev_warn(dev, "already stream off");
+		return 0;
+	}
+
+	dev_dbg(dev, "streamed off sensor:%s\n",
+		cam_dev->sensor->entity.name);
+	ret = v4l2_subdev_call(cam_dev->sensor, video, s_stream, 0);
+	if (ret) {
+		dev_err(dev, "sensor:%s stream off failed:%d\n",
+			cam_dev->sensor->entity.name, ret);
+		return -EPERM;
+	}
+
+	dev_dbg(dev, "streamed off sensor-if:%s\n",
+		cam_dev->sensor_if->entity.name);
+	ret = v4l2_subdev_call(cam_dev->sensor_if, video, s_stream, 0);
+	if (ret) {
+		dev_err(dev, "sensor_if:%s stream off failed:%d\n",
+			cam_dev->sensor_if->entity.name, ret);
+		goto fail_sensor_off;
+	}
+
+	mtk_isp_streamoff(dev);
+	cam_dev->streaming = false;
+	dev_dbg(dev, "streamed off Pass 1\n");
+
+	return 0;
+
+fail_sensor_off:
+	v4l2_subdev_call(cam_dev->sensor_if, video, s_stream, 1);
+	return -EPERM;
+}
+
+static int mtk_cam_subdev_s_stream(struct v4l2_subdev *sd,
+				   int enable)
+{
+	struct mtk_cam_dev *cam_dev = mtk_cam_subdev_to_dev(sd);
+
+	if (enable)
+		return mtk_cam_cio_stream_on(cam_dev);
+	else
+		return mtk_cam_cio_stream_off(cam_dev);
+}
+
+static int mtk_cam_subdev_subscribe_event(struct v4l2_subdev *subdev,
+					  struct v4l2_fh *fh,
+					  struct v4l2_event_subscription *sub)
+{
+	switch (sub->type) {
+	case V4L2_EVENT_FRAME_SYNC:
+		return v4l2_event_subscribe(fh, sub, 0, NULL);
+	default:
+		return -EINVAL;
+	}
+}
+
+static int mtk_cam_link_setup(struct media_entity *entity,
+			      const struct media_pad *local,
+	const struct media_pad *remote, u32 flags)
+{
+	struct mtk_cam_dev *cam_dev =
+		container_of(entity, struct mtk_cam_dev, subdev.entity);
+	u32 pad = local->index;
+
+	dev_dbg(&cam_dev->pdev->dev, "link setup: %d --> %d\n",
+		pad, remote->index);
+
+	if (pad == cam_dev->cio_pad_sink)
+		cam_dev->cio_enabled = !!(flags & MEDIA_LNK_FL_ENABLED);
+	else
+		cam_dev->mem2mem2_nodes[pad].enabled =
+			!!(flags & MEDIA_LNK_FL_ENABLED);
+
+	return 0;
+}
+
+static void mtk_cam_vb2_buf_queue(struct vb2_buffer *vb)
+{
+	struct mtk_cam_dev *mtk_cam_dev = vb2_get_drv_priv(vb->vb2_queue);
+	struct mtk_cam_video_device *node = mtk_cam_vbq_to_vdev(vb->vb2_queue);
+	struct device *dev = &mtk_cam_dev->pdev->dev;
+	struct mtk_cam_dev_buffer *buf;
+	struct vb2_v4l2_buffer *v4l2_buf;
+
+	dev_dbg(dev, "queue vb2_buf: Node(%s) queue id(%d)\n",
+		node->vdev.name,
+		node->id);
+
+	buf = mtk_cam_vb2_buf_to_dev_buf(vb);
+	v4l2_buf = to_vb2_v4l2_buffer(vb);
+
+	if (mtk_cam_dev->request_fd != v4l2_buf->request_fd) {
+		mtk_cam_dev->request_fd = v4l2_buf->request_fd;
+		mtk_cam_dev->request_count =
+			vb->req_obj.req->num_incomplete_objects;
+		dev_dbg(dev, "init  mtk_cam_dev_buf, fd(%d) count(%d)\n",
+			v4l2_buf->request_fd,
+			vb->req_obj.req->num_incomplete_objects);
+	}
+
+	/* Added the buffer into the tracking list */
+	spin_lock(&node->slock);
+	list_add_tail(&buf->list, &node->pending_list);
+	spin_unlock(&node->slock);
+
+	mtk_cam_dev->request_count--;
+
+	if (!mtk_cam_dev->request_count) {
+		mtk_cam_dev->request_fd = -1;
+		dev_dbg(dev, "%s: mtk_cam_dev_queue_buffers\n",
+			node->vdev.name);
+		mtk_cam_dev_queue_buffers(mtk_cam_dev);
+	}
+}
+
+static int mtk_cam_vb2_queue_setup(struct vb2_queue *vq,
+				   unsigned int *num_buffers,
+				   unsigned int *num_planes,
+				   unsigned int sizes[],
+				   struct device *alloc_devs[])
+{
+	struct mtk_cam_dev *cam_dev = vb2_get_drv_priv(vq);
+	struct mtk_cam_video_device *node = mtk_cam_vbq_to_vdev(vq);
+	struct device *dev = &cam_dev->pdev->dev;
+	unsigned int max_buffer_count = node->desc.max_buf_count;
+	const struct v4l2_format *fmt = &node->vdev_fmt;
+	unsigned int size;
+
+	/* Check the limitation of buffer size */
+	if (max_buffer_count > 0)
+		*num_buffers = clamp_val(*num_buffers, 1, max_buffer_count);
+	else
+		*num_buffers = clamp_val(*num_buffers, 1, VB2_MAX_FRAME);
+
+	if (node->desc.smem_alloc) {
+		alloc_devs[0] = &cam_dev->smem_pdev->dev;
+		dev_dbg(dev, "Select smem alloc_devs(0x%pK)\n", alloc_devs[0]);
+	} else {
+		alloc_devs[0] = &cam_dev->pdev->dev;
+		dev_dbg(dev, "Select default alloc_devs(0x%pK)\n",
+			alloc_devs[0]);
+	}
+
+	if (vq->type == V4L2_BUF_TYPE_META_OUTPUT ||
+	    vq->type == V4L2_BUF_TYPE_META_CAPTURE)
+		size = fmt->fmt.meta.buffersize;
+	else
+		size = fmt->fmt.pix_mp.plane_fmt[0].sizeimage;
+
+	/* Validate initialized num_planes & size[0] */
+	if (*num_planes) {
+		if (sizes[0] < size)
+			return -EINVAL;
+	} else {
+		*num_planes = 1;
+		sizes[0] = size;
+	}
+
+	/* Initialize buffer queue & locks */
+	INIT_LIST_HEAD(&node->pending_list);
+	mutex_init(&node->lock);
+	spin_lock_init(&node->slock);
+
+	return 0;
+}
+
+static bool
+mtk_cam_all_nodes_streaming(struct mtk_cam_dev *cam_dev,
+			    struct mtk_cam_video_device *except)
+{
+	unsigned int i;
+
+	for (i = 0; i < cam_dev->dev_node_num; i++) {
+		struct mtk_cam_video_device *node = &cam_dev->mem2mem2_nodes[i];
+
+		if (node == except)
+			continue;
+		if (node->enabled && !vb2_start_streaming_called(&node->vbq))
+			return false;
+	}
+
+	return true;
+}
+
+static void mtk_cam_return_all_buffers(struct mtk_cam_dev *cam_dev,
+				       struct mtk_cam_video_device *node,
+				       enum vb2_buffer_state state)
+{
+	struct mtk_cam_dev_buffer *b, *b0;
+	unsigned int i;
+
+	dev_dbg(&cam_dev->pdev->dev, "%s: node:%s", __func__, node->vdev.name);
+
+	/* Return all buffers */
+	spin_lock(&node->slock);
+	list_for_each_entry_safe(b, b0, &node->pending_list, list) {
+		list_del(&b->list);
+	}
+	spin_unlock(&node->slock);
+
+	for (i = 0; i < node->vbq.num_buffers; ++i)
+		if (node->vbq.bufs[i]->state == VB2_BUF_STATE_ACTIVE)
+			vb2_buffer_done(node->vbq.bufs[i], state);
+}
+
+static int mtk_cam_vb2_start_streaming(struct vb2_queue *vq,
+				       unsigned int count)
+{
+	struct mtk_cam_dev *cam_dev = vb2_get_drv_priv(vq);
+	struct mtk_cam_video_device *node = mtk_cam_vbq_to_vdev(vq);
+	int ret;
+
+	if (!node->enabled) {
+		dev_err(&cam_dev->pdev->dev, "Node:%d is not enable\n",
+			node->id);
+		ret = -EINVAL;
+		goto fail_return_bufs;
+	}
+
+	ret = media_pipeline_start(&node->vdev.entity, &cam_dev->pipeline);
+	if (ret < 0) {
+		dev_err(&cam_dev->pdev->dev, "Node:%d %s failed\n",
+			node->id, __func__);
+		goto fail_return_bufs;
+	}
+
+	if (!mtk_cam_all_nodes_streaming(cam_dev, node))
+		return 0;
+
+	/* Start streaming of the whole pipeline now */
+	ret = v4l2_subdev_call(&cam_dev->subdev, video, s_stream, 1);
+	if (ret < 0) {
+		dev_err(&cam_dev->pdev->dev, "Node:%d s_stream failed\n",
+			node->id);
+		goto fail_stop_pipeline;
+	}
+	return 0;
+
+fail_stop_pipeline:
+	media_pipeline_stop(&node->vdev.entity);
+fail_return_bufs:
+	mtk_cam_return_all_buffers(cam_dev, node, VB2_BUF_STATE_QUEUED);
+	return ret;
+}
+
+static void mtk_cam_vb2_stop_streaming(struct vb2_queue *vq)
+{
+	struct mtk_cam_dev *cam_dev = vb2_get_drv_priv(vq);
+	struct mtk_cam_video_device *node = mtk_cam_vbq_to_vdev(vq);
+
+	/* Was this the first node with streaming disabled? */
+	if (mtk_cam_all_nodes_streaming(cam_dev, node)) {
+		/* Yes, really stop streaming now */
+		if (v4l2_subdev_call(&cam_dev->subdev, video, s_stream, 0))
+			dev_err(&cam_dev->pdev->dev,
+				"failed to stop streaming\n");
+	}
+	mtk_cam_return_all_buffers(cam_dev, node, VB2_BUF_STATE_ERROR);
+	media_pipeline_stop(&node->vdev.entity);
+}
+
+int mtk_cam_videoc_querycap(struct file *file, void *fh,
+			    struct v4l2_capability *cap)
+{
+	struct mtk_cam_dev *cam_dev = video_drvdata(file);
+
+	strscpy(cap->driver, MTK_CAM_DEV_P1_NAME, sizeof(cap->driver));
+	strscpy(cap->card, MTK_CAM_DEV_P1_NAME, sizeof(cap->card));
+	snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s",
+		 dev_name(cam_dev->media_dev.dev));
+
+	return 0;
+}
+
+int mtk_cam_videoc_enum_fmt(struct file *file, void *fh,
+			    struct v4l2_fmtdesc *f)
+{
+	struct mtk_cam_video_device *node = file_to_mtk_cam_node(file);
+
+	if (f->index > node->desc.num_fmts || f->type != node->vbq.type)
+		return -EINVAL;
+
+	f->pixelformat = node->desc.fmts[f->index].fmt.pix_mp.pixelformat;
+	f->flags = 0;
+
+	return 0;
+}
+
+int mtk_cam_videoc_g_fmt(struct file *file, void *fh, struct v4l2_format *f)
+{
+	struct mtk_cam_video_device *node = file_to_mtk_cam_node(file);
+
+	if (f->type != node->vbq.type)
+		return -EINVAL;
+
+	f->fmt = node->vdev_fmt.fmt;
+
+	return 0;
+}
+
+int mtk_cam_videoc_try_fmt(struct file *file, void *fh,
+			   struct v4l2_format *in_fmt)
+{
+	struct mtk_cam_dev *cam_dev = video_drvdata(file);
+	struct mtk_cam_video_device *node = file_to_mtk_cam_node(file);
+	struct v4l2_format *dev_fmt;
+	__u32  width, height;
+
+	if (in_fmt->type != node->vbq.type)
+		return -EINVAL;
+
+	dev_dbg(&cam_dev->pdev->dev, "%s: fmt:%c%c%c%c, w*h:%u*%u\n",
+		__func__,
+		(in_fmt->fmt.pix_mp.pixelformat & 0xFF),
+		(in_fmt->fmt.pix_mp.pixelformat >> 8) & 0xFF,
+		(in_fmt->fmt.pix_mp.pixelformat >> 16) & 0xFF,
+		(in_fmt->fmt.pix_mp.pixelformat >> 24) & 0xFF,
+		in_fmt->fmt.pix_mp.width, in_fmt->fmt.pix_mp.height);
+
+	width = in_fmt->fmt.pix_mp.width;
+	height = in_fmt->fmt.pix_mp.height;
+
+	dev_fmt = mtk_cam_dev_find_fmt(&node->desc,
+				       in_fmt->fmt.pix_mp.pixelformat);
+	if (dev_fmt) {
+		mtk_cam_dev_fmt_set_img(&cam_dev->pdev->dev,
+					&in_fmt->fmt.pix_mp,
+					&dev_fmt->fmt.pix_mp,
+					node->id);
+	} else {
+		mtk_cam_dev_load_default_fmt(&cam_dev->pdev->dev,
+					     &node->desc,
+					     in_fmt);
+	}
+	in_fmt->fmt.pix_mp.width = clamp_t(u32,
+					   width,
+					   CAM_MIN_WIDTH,
+					   in_fmt->fmt.pix_mp.width);
+	in_fmt->fmt.pix_mp.height = clamp_t(u32,
+					    height,
+					    CAM_MIN_HEIGHT,
+					    in_fmt->fmt.pix_mp.height);
+	mtk_cam_dev_cal_mplane_pix_fmt(&cam_dev->pdev->dev,
+				       &in_fmt->fmt.pix_mp,
+				       node->id);
+
+	return 0;
+}
+
+int mtk_cam_videoc_s_fmt(struct file *file, void *fh, struct v4l2_format *f)
+{
+	struct mtk_cam_dev *cam_dev = video_drvdata(file);
+	struct mtk_cam_video_device *node = file_to_mtk_cam_node(file);
+
+	if (f->type != node->vbq.type)
+		return -EINVAL;
+
+	if (cam_dev->streaming)
+		return -EBUSY;
+
+	/* Get the valid format */
+	mtk_cam_videoc_try_fmt(file, fh, f);
+
+	/* Configure to video device */
+	mtk_cam_dev_fmt_set_img(&cam_dev->pdev->dev,
+				&node->vdev_fmt.fmt.pix_mp,
+				&f->fmt.pix_mp,
+				node->id);
+
+	return 0;
+}
+
+int mtk_cam_vidioc_enum_input(struct file *file, void *fh,
+			      struct v4l2_input *input)
+{
+	if (input->index > 0)
+		return -EINVAL;
+
+	strscpy(input->name, "camera", sizeof(input->name));
+	input->type = V4L2_INPUT_TYPE_CAMERA;
+
+	return 0;
+}
+
+int mtk_cam_vidioc_g_input(struct file *file, void *fh, unsigned int *input)
+{
+	*input = 0;
+
+	return 0;
+}
+
+int mtk_cam_vidioc_s_input(struct file *file, void *fh, unsigned int input)
+{
+	return input == 0 ? 0 : -EINVAL;
+}
+
+int mtk_cam_vidioc_subscribe_event(struct v4l2_fh *fh,
+				   const struct v4l2_event_subscription *sub)
+{
+	switch (sub->type) {
+	case V4L2_EVENT_CTRL:
+		return v4l2_ctrl_subscribe_event(fh, sub);
+	default:
+		return -EINVAL;
+	}
+}
+
+int mtk_cam_enum_framesizes(struct file *filp, void *priv,
+			    struct v4l2_frmsizeenum *sizes)
+{
+	struct mtk_cam_video_device *node = file_to_mtk_cam_node(filp);
+	struct v4l2_format *dev_fmt;
+
+	dev_fmt = mtk_cam_dev_find_fmt(&node->desc, sizes->pixel_format);
+	if (!dev_fmt || sizes->index)
+		return -EINVAL;
+
+	if (node->id == MTK_CAM_P1_MAIN_STREAM_OUT) {
+		sizes->type = V4L2_FRMSIZE_TYPE_CONTINUOUS;
+		sizes->stepwise.max_width = IMG_MAX_WIDTH;
+		sizes->stepwise.min_width = IMG_MIN_WIDTH;
+		sizes->stepwise.max_height = IMG_MAX_HEIGHT;
+		sizes->stepwise.min_height = IMG_MIN_HEIGHT;
+		sizes->stepwise.step_height = 1;
+		sizes->stepwise.step_width = 1;
+	} else if (node->id == MTK_CAM_P1_PACKED_BIN_OUT) {
+		sizes->type = V4L2_FRMSIZE_TYPE_CONTINUOUS;
+		sizes->stepwise.max_width = RRZ_MAX_WIDTH;
+		sizes->stepwise.min_width = RRZ_MIN_WIDTH;
+		sizes->stepwise.max_height = RRZ_MAX_HEIGHT;
+		sizes->stepwise.min_height = RRZ_MIN_HEIGHT;
+		sizes->stepwise.step_height = 1;
+		sizes->stepwise.step_width = 1;
+	}
+
+	return 0;
+}
+
+int mtk_cam_meta_enum_format(struct file *file, void *fh,
+			     struct v4l2_fmtdesc *f)
+{
+	struct mtk_cam_video_device *node = file_to_mtk_cam_node(file);
+
+	/* Each node is dedicated to only one meta format */
+	if (f->index > 0 || f->type != node->vbq.type)
+		return -EINVAL;
+
+	strscpy(f->description, node->desc.description,
+		sizeof(node->desc.description));
+	f->pixelformat = node->vdev_fmt.fmt.meta.dataformat;
+
+	return 0;
+}
+
+int mtk_cam_videoc_g_meta_fmt(struct file *file, void *fh,
+			      struct v4l2_format *f)
+{
+	struct mtk_cam_video_device *node = file_to_mtk_cam_node(file);
+
+	/* Each node is dedicated to only one meta format */
+	if (f->type != node->vbq.type)
+		return -EINVAL;
+
+	f->fmt = node->vdev_fmt.fmt;
+
+	return 0;
+}
+
+/* subdev internal operations */
+static const struct v4l2_subdev_internal_ops mtk_cam_subdev_internal_ops = {
+	.open = mtk_cam_subdev_open,
+	.close = mtk_cam_subdev_close,
+};
+
+static const struct v4l2_subdev_core_ops mtk_cam_subdev_core_ops = {
+	.subscribe_event = mtk_cam_subdev_subscribe_event,
+	.unsubscribe_event = v4l2_event_subdev_unsubscribe,
+};
+
+static const struct v4l2_subdev_video_ops mtk_cam_subdev_video_ops = {
+	.s_stream = mtk_cam_subdev_s_stream,
+};
+
+static const struct v4l2_subdev_ops mtk_cam_subdev_ops = {
+	.core = &mtk_cam_subdev_core_ops,
+	.video = &mtk_cam_subdev_video_ops,
+};
+
+static const struct media_entity_operations mtk_cam_media_ops = {
+	.link_setup = mtk_cam_link_setup,
+	.link_validate = v4l2_subdev_link_validate,
+};
+
+static void mtk_cam_vb2_buf_request_complete(struct vb2_buffer *vb)
+{
+	struct mtk_cam_dev *dev = vb2_get_drv_priv(vb->vb2_queue);
+
+	v4l2_ctrl_request_complete(vb->req_obj.req,
+				   dev->v4l2_dev.ctrl_handler);
+}
+
+static const struct vb2_ops mtk_cam_vb2_ops = {
+	.buf_queue = mtk_cam_vb2_buf_queue,
+	.queue_setup = mtk_cam_vb2_queue_setup,
+	.start_streaming = mtk_cam_vb2_start_streaming,
+	.stop_streaming = mtk_cam_vb2_stop_streaming,
+	.wait_prepare = vb2_ops_wait_prepare,
+	.wait_finish = vb2_ops_wait_finish,
+	.buf_request_complete = mtk_cam_vb2_buf_request_complete,
+};
+
+static const struct v4l2_file_operations mtk_cam_v4l2_fops = {
+	.unlocked_ioctl = video_ioctl2,
+	.open = v4l2_fh_open,
+	.release = vb2_fop_release,
+	.poll = vb2_fop_poll,
+	.mmap = vb2_fop_mmap,
+#ifdef CONFIG_COMPAT
+	.compat_ioctl32 = v4l2_compat_ioctl32,
+#endif
+};
+
+/*
+ * Config node's video properties
+ * according to the device context requirement
+ */
+static void mtk_cam_node_to_v4l2(struct mtk_cam_dev *cam_dev,
+				 unsigned int node,
+				 struct video_device *vdev,
+				 struct v4l2_format *f)
+{
+	struct mtk_cam_dev_node_desc *node_desc =
+		&cam_dev->mem2mem2_nodes[node].desc;
+
+	/* set cap/type/ioctl_ops of the video device */
+	vdev->device_caps = V4L2_CAP_STREAMING | node_desc->cap;
+	f->type = node_desc->buf_type;
+	vdev->ioctl_ops = node_desc->ioctl_ops;
+
+	mtk_cam_dev_load_default_fmt(&cam_dev->pdev->dev,
+				     node_desc,
+				     f);
+}
+
+static const struct media_device_ops mtk_cam_media_req_ops = {
+	.req_validate = vb2_request_validate,
+	.req_queue = vb2_request_queue,
+};
+
+static int mtk_cam_media_register(struct device *dev,
+				  struct media_device *media_dev)
+{
+	int ret;
+
+	media_dev->dev = dev;
+	strscpy(media_dev->model, MTK_CAM_DEV_P1_NAME,
+		sizeof(media_dev->model));
+	snprintf(media_dev->bus_info, sizeof(media_dev->bus_info),
+		 "platform:%s", dev_name(dev));
+	media_dev->hw_revision = 0;
+	media_device_init(media_dev);
+	media_dev->ops = &mtk_cam_media_req_ops;
+	dev_info(dev, "Register media device: %s, 0x%pK",
+		 MTK_CAM_DEV_P1_NAME, media_dev);
+
+	ret = media_device_register(media_dev);
+	if (ret) {
+		dev_err(dev, "failed to register media device (%d)\n", ret);
+		goto fail_v4l2_dev;
+	}
+	return 0;
+
+fail_v4l2_dev:
+	media_device_unregister(media_dev);
+	media_device_cleanup(media_dev);
+
+	return ret;
+}
+
+int mtk_cam_v4l2_register(struct device *dev,
+			  struct media_device *media_dev,
+			  struct v4l2_device *v4l2_dev,
+			  struct v4l2_ctrl_handler *ctrl_handler)
+{
+	int ret;
+
+	/* Set up v4l2 device */
+	v4l2_dev->ctrl_handler = ctrl_handler;
+	v4l2_dev->mdev = media_dev;
+	dev_info(dev, "Register v4l2 device: 0x%pK", v4l2_dev);
+	ret = v4l2_device_register(dev, v4l2_dev);
+	if (ret) {
+		dev_err(dev, "failed to register V4L2 device (%d)\n", ret);
+		goto fail_v4l2_dev;
+	}
+
+	return 0;
+
+fail_v4l2_dev:
+	media_device_unregister(media_dev);
+	media_device_cleanup(media_dev);
+
+	return ret;
+}
+
+int mtk_cam_mem2mem2_v4l2_register(struct mtk_cam_dev *cam_dev)
+{
+	struct device *dev = &cam_dev->pdev->dev;
+	unsigned int num_nodes = cam_dev->dev_node_num;
+	/* Total pad numbers is video devices + one seninf pad */
+	unsigned int num_subdev_pads = MTK_CAM_DEV_NODE_MAX + 1;
+	unsigned int i;
+	int ret;
+
+	ret = mtk_cam_media_register(dev,
+				     &cam_dev->media_dev);
+	if (ret) {
+		dev_err(dev, "failed to register media device:%d\n", ret);
+		goto fail_media_dev;
+	}
+
+	ret = mtk_cam_v4l2_register(dev,
+				    &cam_dev->media_dev,
+				    &cam_dev->v4l2_dev,
+				    NULL);
+	if (ret) {
+		dev_err(dev, "failed to register V4L2 device:%d\n", ret);
+		goto fail_v4l2_dev;
+	}
+
+	/* Initialize subdev media entity */
+	cam_dev->subdev_pads = kcalloc(num_subdev_pads,
+				       sizeof(*cam_dev->subdev_pads),
+				       GFP_KERNEL);
+	if (!cam_dev->subdev_pads) {
+		ret = -ENOMEM;
+		goto fail_subdev_pads;
+	}
+
+	ret = media_entity_pads_init(&cam_dev->subdev.entity,
+				     num_subdev_pads,
+				     cam_dev->subdev_pads);
+	if (ret) {
+		dev_err(dev, "failed initialize media pads:%d:\n", ret);
+		goto fail_media_entity;
+	}
+
+	/* Initialize all pads with MEDIA_PAD_FL_SOURCE */
+	for (i = 0; i < num_subdev_pads; i++)
+		cam_dev->subdev_pads[i].flags = MEDIA_PAD_FL_SOURCE;
+
+	/* Customize connection IO media info. */
+	cam_dev->cio_pad_sink = num_subdev_pads - 1;
+	cam_dev->subdev_pads[cam_dev->cio_pad_sink].flags =
+		MEDIA_PAD_FL_SINK;
+
+	/* Initialize subdev */
+	v4l2_subdev_init(&cam_dev->subdev, &mtk_cam_subdev_ops);
+	cam_dev->subdev.entity.function = MEDIA_ENT_F_PROC_VIDEO_STATISTICS;
+	cam_dev->subdev.entity.ops = &mtk_cam_media_ops;
+	cam_dev->subdev.flags = V4L2_SUBDEV_FL_HAS_DEVNODE |
+				V4L2_SUBDEV_FL_HAS_EVENTS;
+	snprintf(cam_dev->subdev.name, sizeof(cam_dev->subdev.name),
+		 "%s", MTK_CAM_DEV_P1_NAME);
+	v4l2_set_subdevdata(&cam_dev->subdev, cam_dev);
+	cam_dev->subdev.internal_ops = &mtk_cam_subdev_internal_ops;
+
+	dev_info(dev, "register subdev: %s\n", cam_dev->subdev.name);
+	ret = v4l2_device_register_subdev(&cam_dev->v4l2_dev, &cam_dev->subdev);
+	if (ret) {
+		dev_err(dev, "failed initialize subdev:%d\n", ret);
+		goto fail_subdev;
+	}
+
+	/* Create video nodes and links */
+	for (i = 0; i < num_nodes; i++) {
+		struct mtk_cam_video_device *node = &cam_dev->mem2mem2_nodes[i];
+		struct video_device *vdev = &node->vdev;
+		struct vb2_queue *vbq = &node->vbq;
+		u32 output = !cam_dev->mem2mem2_nodes[i].desc.capture;
+		u32 link_flags = cam_dev->mem2mem2_nodes[i].desc.link_flags;
+
+		cam_dev->subdev_pads[i].flags = output ?
+			MEDIA_PAD_FL_SINK : MEDIA_PAD_FL_SOURCE;
+
+		/* Initialize miscellaneous variables */
+		mutex_init(&node->lock);
+		spin_lock_init(&node->slock);
+		INIT_LIST_HEAD(&node->pending_list);
+
+		/* Initialize formats to default values */
+		mtk_cam_node_to_v4l2(cam_dev, i, vdev, &node->vdev_fmt);
+
+		/* Initialize media entities */
+		ret = media_entity_pads_init(&vdev->entity, 1, &node->vdev_pad);
+		if (ret) {
+			dev_err(dev, "failed initialize media pad:%d\n", ret);
+			goto fail_vdev_media_entity;
+		}
+		node->enabled = false;
+		node->id = i;
+		node->vdev_pad.flags = cam_dev->subdev_pads[i].flags;
+		vdev->entity.ops = NULL;
+		/* Initialize vbq */
+		vbq->type = node->vdev_fmt.type;
+		if (vbq->type == V4L2_BUF_TYPE_META_OUTPUT)
+			vbq->io_modes = VB2_MMAP;
+		else
+			vbq->io_modes = VB2_MMAP | VB2_DMABUF;
+		if (vbq->type == V4L2_BUF_TYPE_META_CAPTURE)
+			vdev->entity.function =
+				MEDIA_ENT_F_PROC_VIDEO_STATISTICS;
+		vbq->ops = &mtk_cam_vb2_ops;
+		vbq->mem_ops = &vb2_dma_contig_memops;
+		vbq->buf_struct_size = sizeof(struct mtk_cam_dev_buffer);
+		vbq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+		vbq->min_buffers_needed = 0;	/* Can streamon w/o buffers */
+		/* Put the process hub sub device in the vb2 private data */
+		vbq->drv_priv = cam_dev;
+		vbq->lock = &node->lock;
+		vbq->supports_requests = true;
+
+		ret = vb2_queue_init(vbq);
+		if (ret) {
+			dev_err(dev, "failed to init. vb2 queue:%d\n", ret);
+			goto fail_vdev;
+		}
+
+		/* Initialize vdev */
+		snprintf(vdev->name, sizeof(vdev->name), "%s %s",
+			 MTK_CAM_DEV_P1_NAME, node->desc.name);
+		vdev->release = video_device_release_empty;
+		vdev->fops = &mtk_cam_v4l2_fops;
+		vdev->lock = &node->lock;
+		vdev->v4l2_dev = &cam_dev->v4l2_dev;
+		vdev->queue = &node->vbq;
+		vdev->vfl_dir = output ? VFL_DIR_TX : VFL_DIR_RX;
+		/* Enable private control for image video devices */
+		if (node->desc.image) {
+			mtk_cam_ctrl_init(cam_dev, &node->ctrl_handler);
+			vdev->ctrl_handler = &node->ctrl_handler;
+		}
+		video_set_drvdata(vdev, cam_dev);
+		dev_info(dev, "register vdev:%d:%s\n", i, vdev->name);
+		ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
+		if (ret) {
+			dev_err(dev, "failed to register vde:%d\n", ret);
+			goto fail_vdev;
+		}
+
+		/* Create link between video node and the subdev pad */
+		if (output) {
+			ret = media_create_pad_link(&vdev->entity, 0,
+						    &cam_dev->subdev.entity,
+						    i, link_flags);
+		} else {
+			ret = media_create_pad_link(&cam_dev->subdev.entity,
+						    i, &vdev->entity, 0,
+						    link_flags);
+		}
+		if (ret)
+			goto fail_link;
+	}
+
+	return 0;
+
+	for (; i >= 0; i--) {
+fail_link:
+		video_unregister_device(&cam_dev->mem2mem2_nodes[i].vdev);
+fail_vdev:
+		media_entity_cleanup(&cam_dev->mem2mem2_nodes[i].vdev.entity);
+fail_vdev_media_entity:
+		mutex_destroy(&cam_dev->mem2mem2_nodes[i].lock);
+	}
+fail_subdev:
+	media_entity_cleanup(&cam_dev->subdev.entity);
+fail_media_entity:
+	kfree(cam_dev->subdev_pads);
+fail_subdev_pads:
+	v4l2_device_unregister(&cam_dev->v4l2_dev);
+fail_v4l2_dev:
+fail_media_dev:
+	dev_err(dev, "fail_v4l2_dev mdev: 0x%pK", &cam_dev->media_dev);
+	media_device_unregister(&cam_dev->media_dev);
+	media_device_cleanup(&cam_dev->media_dev);
+
+	return ret;
+}
+
+int mtk_cam_v4l2_unregister(struct mtk_cam_dev *cam_dev)
+{
+	unsigned int i;
+	struct mtk_cam_video_device *dev;
+
+	for (i = 0; i < cam_dev->dev_node_num; i++) {
+		dev = &cam_dev->mem2mem2_nodes[i];
+		video_unregister_device(&dev->vdev);
+		media_entity_cleanup(&dev->vdev.entity);
+		mutex_destroy(&dev->lock);
+		if (dev->desc.image)
+			v4l2_ctrl_handler_free(&dev->ctrl_handler);
+	}
+
+	v4l2_device_unregister_subdev(&cam_dev->subdev);
+	media_entity_cleanup(&cam_dev->subdev.entity);
+	kfree(cam_dev->subdev_pads);
+	v4l2_device_unregister(&cam_dev->v4l2_dev);
+	media_device_unregister(&cam_dev->media_dev);
+	media_device_cleanup(&cam_dev->media_dev);
+
+	return 0;
+}
+
+struct sensor_async_subdev {
+	struct v4l2_async_subdev asd;
+};
+
+static struct v4l2_subdev *get_subdev_by_name(struct mtk_cam_dev *cam_dev,
+					      char *name)
+{
+	struct device_node *node;
+	struct v4l2_subdev *sd;
+
+	list_for_each_entry(sd, &cam_dev->v4l2_dev.subdevs, list) {
+		if (!(sd->flags & V4L2_SUBDEV_FL_HAS_DEVNODE))
+			continue;
+		node = to_of_node(sd->fwnode);
+		if (node) {
+			if (!strcmp(node->name, name))
+				return sd;
+		}
+	}
+	return NULL;
+}
+
+static int mtk_cam_dev_complete(struct v4l2_async_notifier *notifier)
+{
+	struct mtk_cam_dev *cam_dev =
+		container_of(notifier, struct mtk_cam_dev, notifier);
+	struct device *dev = &cam_dev->pdev->dev;
+	struct v4l2_subdev *sd;
+	struct v4l2_subdev *src_sd, *sink_sd;
+	struct device_node *node;
+	int ret;
+
+	dev_info(dev, "Complete the v4l2 registration\n");
+
+	ret = v4l2_device_register_subdev_nodes(&cam_dev->v4l2_dev);
+	if (ret) {
+		dev_err(dev, "failed initialize subdev nodes:%d\n", ret);
+		return ret;
+	}
+
+	/* Links among sensors, sensor interface and cio */
+	list_for_each_entry(sd, &cam_dev->v4l2_dev.subdevs, list) {
+		if (!(sd->flags & V4L2_SUBDEV_FL_HAS_DEVNODE))
+			continue;
+		node = to_of_node(sd->fwnode);
+		if (node)
+			sd->entity.name = node->name;
+	}
+
+	src_sd = get_subdev_by_name(cam_dev, SUBDEV_SENSOR_MAIN_NAME);
+	sink_sd = get_subdev_by_name(cam_dev, SUBDEV_SENINF_NAME);
+	if (src_sd && sink_sd) {
+		dev_dbg(dev, "Link create:%s-->%s\n",
+			src_sd->entity.name, sink_sd->entity.name);
+		ret = media_create_pad_link(&src_sd->entity,
+					    MTK_CAM_SENSOR_MAIN_PAD_SRC,
+					    &sink_sd->entity,
+					    MTK_CAM_SENSOR_IF_PAD_MAIN_SINK,
+					    0);
+		if (ret)
+			dev_err(dev,
+				"fail to create pad link %s %s, ret:%d\n",
+				src_sd->entity.name, sink_sd->entity.name,
+				ret);
+	} else {
+		dev_err(dev, "not found: sensor_main(0x%pK), seninf(%pK)\n",
+			src_sd, sink_sd);
+	}
+
+	src_sd = get_subdev_by_name(cam_dev, SUBDEV_SENSOR_SUB_NAME);
+	if (src_sd && sink_sd) {
+		dev_dbg(dev, "Link create: %s --> %s\n",
+			src_sd->entity.name, sink_sd->entity.name);
+		ret = media_create_pad_link(&src_sd->entity,
+					    MTK_CAM_SENSOR_SUB_PAD_SRC,
+					    &sink_sd->entity,
+					    MTK_CAM_SENSOR_IF_PAD_SUB_SINK,
+					    0);
+		if (ret)
+			dev_err(dev,
+				"fail to create pad link %s %s, ret:%d:\n",
+				src_sd->entity.name, sink_sd->entity.name,
+				ret);
+	} else {
+		dev_warn(dev, "not found: sensor_sub(0x%pK), seninf(0x%pK)\n",
+			 src_sd, sink_sd);
+	}
+
+	ret = media_create_pad_link(&sink_sd->entity,
+				    MTK_CAM_SENSOR_IF_PAD_SRC,
+				    &cam_dev->subdev.entity,
+				    cam_dev->cio_pad_sink,
+				    0);
+	if (ret)
+		dev_err(dev, "fail to create pad link %s %s err:%d\n",
+			sink_sd->entity.name, cam_dev->subdev.entity.name, ret);
+
+	return ret;
+}
+
+static int mtk_cam_dev_notifier_bound(struct v4l2_async_notifier *notifier,
+				      struct v4l2_subdev *sd,
+				      struct v4l2_async_subdev *asd)
+{
+	struct mtk_cam_dev *cam_dev =
+		container_of(notifier, struct mtk_cam_dev, notifier);
+	struct device *dev = &cam_dev->pdev->dev;
+
+	dev_info(dev, "%s bound\n", sd->entity.name);
+	if (!strncmp(&sd->entity.name[9],
+		     SUBDEV_SENINF_NAME,
+		     strlen(SUBDEV_SENINF_NAME)))
+		mtk_cam_dev_complete(notifier);
+
+	return 0;
+}
+
+static void mtk_cam_dev_notifier_unbind(struct v4l2_async_notifier *notifier,
+					struct v4l2_subdev *sd,
+					struct v4l2_async_subdev *asd)
+{
+	struct mtk_cam_dev *cam_dev =
+		container_of(notifier, struct mtk_cam_dev, notifier);
+	struct device *dev = &cam_dev->pdev->dev;
+
+	dev_dbg(dev, "%s unbound\n", sd->entity.name);
+}
+
+static int mtk_cam_dev_notifier_complete(struct v4l2_async_notifier *notifier)
+{
+	return 0;
+}
+
+static const struct v4l2_async_notifier_operations mtk_cam_async_ops = {
+	.bound = mtk_cam_dev_notifier_bound,
+	.unbind = mtk_cam_dev_notifier_unbind,
+	.complete = mtk_cam_dev_notifier_complete,
+};
+
+static int mtk_cam_dev_fwnode_parse(struct device *dev,
+				    struct v4l2_fwnode_endpoint *vep,
+				    struct v4l2_async_subdev *asd)
+{
+	dev_dbg(dev, "%s: To be implemented\n", __func__);
+
+	return 0;
+}
+
+int mtk_cam_v4l2_async_register(struct mtk_cam_dev *cam_dev)
+{
+	int ret;
+
+	ret = v4l2_async_notifier_parse_fwnode_endpoints
+		(&cam_dev->pdev->dev, &cam_dev->notifier,
+		 sizeof(struct sensor_async_subdev),
+		 mtk_cam_dev_fwnode_parse);
+	if (ret < 0)
+		return ret;
+
+	if (!cam_dev->notifier.num_subdevs)
+		return -ENODEV;
+
+	cam_dev->notifier.ops = &mtk_cam_async_ops;
+	dev_info(&cam_dev->pdev->dev, "mtk_cam v4l2_async_notifier_register\n");
+	ret = v4l2_async_notifier_register(&cam_dev->v4l2_dev,
+					   &cam_dev->notifier);
+	if (ret) {
+		dev_err(&cam_dev->pdev->dev,
+			"failed to register async notifier : %d\n", ret);
+		v4l2_async_notifier_cleanup(&cam_dev->notifier);
+	}
+
+	return ret;
+}
+
+void mtk_cam_v4l2_async_unregister(struct mtk_cam_dev *cam_dev)
+{
+	v4l2_async_notifier_unregister(&cam_dev->notifier);
+	v4l2_async_notifier_cleanup(&cam_dev->notifier);
+}
diff --git a/drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam-v4l2-util.h b/drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam-v4l2-util.h
new file mode 100644
index 0000000..73b3691
--- /dev/null
+++ b/drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam-v4l2-util.h
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2018 MediaTek Inc.
+ * Author: Frederic Chen <frederic.chen@mediatek.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.
+ */
+
+#ifndef __MTK_CAM_DEV_V4L2_H__
+#define __MTK_CAM_DEV_V4L2_H__
+
+#include <media/v4l2-device.h>
+#include <media/videobuf2-v4l2.h>
+
+int mtk_cam_videoc_querycap(struct file *file, void *fh,
+			    struct v4l2_capability *cap);
+int mtk_cam_enum_framesizes(struct file *filp, void *priv,
+			    struct v4l2_frmsizeenum *sizes);
+int mtk_cam_videoc_enum_fmt(struct file *file, void *fh,
+			    struct v4l2_fmtdesc *f);
+int mtk_cam_videoc_g_fmt(struct file *file, void *fh, struct v4l2_format *f);
+int mtk_cam_videoc_s_fmt(struct file *file, void *fh, struct v4l2_format *f);
+int mtk_cam_videoc_try_fmt(struct file *file,
+			   void *fh, struct v4l2_format *in_fmt);
+int mtk_cam_vidioc_enum_input(struct file *file, void *fh,
+			      struct v4l2_input *input);
+int mtk_cam_vidioc_g_input(struct file *file, void *fh, unsigned int *input);
+int mtk_cam_vidioc_s_input(struct file *file, void *fh, unsigned int input);
+int mtk_cam_meta_enum_format(struct file *file, void *fh,
+			     struct v4l2_fmtdesc *f);
+int mtk_cam_videoc_g_meta_fmt(struct file *file, void *fh,
+			      struct v4l2_format *f);
+int mtk_cam_vidioc_subscribe_event(struct v4l2_fh *fh,
+				   const struct v4l2_event_subscription *sub);
+
+#endif /* __MTK_CAM_DEV_V4L2_H__ */
