new file mode 100644
@@ -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
new file mode 100644
@@ -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__*/
new file mode 100644
@@ -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;
+ }
+}
new file mode 100644
@@ -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;
+}
new file mode 100644
@@ -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__ */
new file mode 100644
@@ -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);
+}
new file mode 100644
@@ -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__ */
Implement standard V4L2 video driver that utilizes v4l2 and media frameworks. In this driver, supports one sub-device and six video devices. Moreover, it also connects with sensor & senif drivers with camera IO connection via media controller APIs. Signed-off-by: Jungo Lin <jungo.lin@mediatek.com> --- drivers/media/platform/mtk-isp/isp_50/cam/Makefile | 19 + .../platform/mtk-isp/isp_50/cam/mtk_cam-ctx.h | 116 ++ .../mtk-isp/isp_50/cam/mtk_cam-dev-ctx-core.c | 302 +++++ .../platform/mtk-isp/isp_50/cam/mtk_cam-dev.c | 525 +++++++++ .../platform/mtk-isp/isp_50/cam/mtk_cam-dev.h | 166 +++ .../mtk-isp/isp_50/cam/mtk_cam-v4l2-util.c | 1182 ++++++++++++++++++++ .../mtk-isp/isp_50/cam/mtk_cam-v4l2-util.h | 43 + 7 files changed, 2353 insertions(+) create mode 100644 drivers/media/platform/mtk-isp/isp_50/cam/Makefile create mode 100644 drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam-ctx.h create mode 100644 drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam-dev-ctx-core.c create mode 100644 drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam-dev.c create mode 100644 drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam-dev.h create mode 100644 drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam-v4l2-util.c create mode 100644 drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam-v4l2-util.h