summaryrefslogtreecommitdiffstats
path: root/target/linux/bcm27xx/patches-6.1/950-1208-media-rp1-cfe-Dual-purpose-video-nodes.patch
diff options
context:
space:
mode:
Diffstat (limited to 'target/linux/bcm27xx/patches-6.1/950-1208-media-rp1-cfe-Dual-purpose-video-nodes.patch')
-rw-r--r--target/linux/bcm27xx/patches-6.1/950-1208-media-rp1-cfe-Dual-purpose-video-nodes.patch621
1 files changed, 621 insertions, 0 deletions
diff --git a/target/linux/bcm27xx/patches-6.1/950-1208-media-rp1-cfe-Dual-purpose-video-nodes.patch b/target/linux/bcm27xx/patches-6.1/950-1208-media-rp1-cfe-Dual-purpose-video-nodes.patch
new file mode 100644
index 0000000000..f68aa3975d
--- /dev/null
+++ b/target/linux/bcm27xx/patches-6.1/950-1208-media-rp1-cfe-Dual-purpose-video-nodes.patch
@@ -0,0 +1,621 @@
+From c54b8d2fc79c684deacc81a94f6baa1cb56c62be Mon Sep 17 00:00:00 2001
+From: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
+Date: Wed, 27 Sep 2023 17:18:09 +0300
+Subject: [PATCH] media: rp1: cfe: Dual purpose video nodes
+
+The RP1 CSI-2 DMA can capture both video and metadata just fine, but at
+the moment the video nodes are only set to support either video or
+metadata.
+
+Make the changes to support both video and metadata. This mostly means
+tracking both video format and metadata format separately for each video
+node, and using vb2_queue_change_type() to change the vb2 queue type
+when needed.
+
+Briefly, this means that the user can get/set both video and meta
+formats to a single video node. The vb2 queue buffer type will be
+changed when the user calls REQBUFS or CREATE_BUFS ioctls. This buffer
+type will be then used as the "mode" for the video node when the user
+starts the streaming, and based on that either the video or the meta
+format will be used.
+
+A bunch of macros are added (node_supports_xxx()), which tell if a node
+can support a particular mode, whereas the existing macros
+(is_xxx_node()) will tell if the node is currently in a particular mode.
+Note that the latter will only work correctly between the start of the
+streaming and the end of the streaming, and thus should be only used in
+those code paths.
+
+However, as the userspace (libcamera) does not support dual purpose
+video nodes, for the time being let's keep the second video node as
+V4L2_CAP_META_CAPTURE only to keep the userspace working.
+
+Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
+---
+ .../media/platform/raspberrypi/rp1_cfe/cfe.c | 271 ++++++++++++------
+ 1 file changed, 182 insertions(+), 89 deletions(-)
+
+--- a/drivers/media/platform/raspberrypi/rp1_cfe/cfe.c
++++ b/drivers/media/platform/raspberrypi/rp1_cfe/cfe.c
+@@ -116,7 +116,7 @@ const struct v4l2_mbus_framefmt cfe_defa
+ enum node_ids {
+ /* CSI2 HW output nodes first. */
+ CSI2_CH0,
+- CSI2_CH1_EMBEDDED,
++ CSI2_CH1,
+ CSI2_CH2,
+ CSI2_CH3,
+ /* FE only nodes from here on. */
+@@ -130,8 +130,7 @@ enum node_ids {
+ struct node_description {
+ unsigned int id;
+ const char *name;
+- enum v4l2_buf_type buf_type;
+- unsigned int cap;
++ unsigned int caps;
+ unsigned int pad_flags;
+ unsigned int link_pad;
+ };
+@@ -140,58 +139,55 @@ struct node_description {
+ static const struct node_description node_desc[NUM_NODES] = {
+ [CSI2_CH0] = {
+ .name = "csi2_ch0",
+- .buf_type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+- .cap = V4L2_CAP_VIDEO_CAPTURE,
++ .caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_META_CAPTURE,
+ .pad_flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT,
+ .link_pad = CSI2_NUM_CHANNELS + 0
+ },
+- /* This node is assigned for the embedded data channel! */
+- [CSI2_CH1_EMBEDDED] = {
++ /*
++ * TODO: This node should be named "csi2_ch1" and the caps should be set
++ * to both video and meta capture. However, to keep compatibility with
++ * the current libcamera, keep the name as "embedded" and support
++ * only meta capture.
++ */
++ [CSI2_CH1] = {
+ .name = "embedded",
+- .buf_type = V4L2_BUF_TYPE_META_CAPTURE,
+- .cap = V4L2_CAP_META_CAPTURE,
++ .caps = V4L2_CAP_META_CAPTURE,
+ .pad_flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT,
+ .link_pad = CSI2_NUM_CHANNELS + 1
+ },
+ [CSI2_CH2] = {
+ .name = "csi2_ch2",
+- .buf_type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+- .cap = V4L2_CAP_META_CAPTURE,
++ .caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_META_CAPTURE,
+ .pad_flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT,
+ .link_pad = CSI2_NUM_CHANNELS + 2
+ },
+ [CSI2_CH3] = {
+ .name = "csi2_ch3",
+- .buf_type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+- .cap = V4L2_CAP_META_CAPTURE,
++ .caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_META_CAPTURE,
+ .pad_flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT,
+ .link_pad = CSI2_NUM_CHANNELS + 3
+ },
+ [FE_OUT0] = {
+ .name = "fe_image0",
+- .buf_type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+- .cap = V4L2_CAP_VIDEO_CAPTURE,
++ .caps = V4L2_CAP_VIDEO_CAPTURE,
+ .pad_flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT,
+ .link_pad = FE_OUTPUT0_PAD
+ },
+ [FE_OUT1] = {
+ .name = "fe_image1",
+- .buf_type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+- .cap = V4L2_CAP_VIDEO_CAPTURE,
++ .caps = V4L2_CAP_VIDEO_CAPTURE,
+ .pad_flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT,
+ .link_pad = FE_OUTPUT1_PAD
+ },
+ [FE_STATS] = {
+ .name = "fe_stats",
+- .buf_type = V4L2_BUF_TYPE_META_CAPTURE,
+- .cap = V4L2_CAP_META_CAPTURE,
++ .caps = V4L2_CAP_META_CAPTURE,
+ .pad_flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT,
+ .link_pad = FE_STATS_PAD
+ },
+ [FE_CONFIG] = {
+ .name = "fe_config",
+- .buf_type = V4L2_BUF_TYPE_META_OUTPUT,
+- .cap = V4L2_CAP_META_OUTPUT,
++ .caps = V4L2_CAP_META_OUTPUT,
+ .pad_flags = MEDIA_PAD_FL_SOURCE | MEDIA_PAD_FL_MUST_CONNECT,
+ .link_pad = FE_CONFIG_PAD
+ },
+@@ -200,17 +196,29 @@ static const struct node_description nod
+ #define is_fe_node(node) (((node)->id) >= FE_OUT0)
+ #define is_csi2_node(node) (!is_fe_node(node))
+
++#define node_supports_image_output(node) \
++ (!!(node_desc[(node)->id].caps & V4L2_CAP_VIDEO_CAPTURE))
++#define node_supports_meta_output(node) \
++ (!!(node_desc[(node)->id].caps & V4L2_CAP_META_CAPTURE))
++#define node_supports_image_input(node) \
++ (!!(node_desc[(node)->id].caps & V4L2_CAP_VIDEO_OUTPUT))
++#define node_supports_meta_input(node) \
++ (!!(node_desc[(node)->id].caps & V4L2_CAP_META_OUTPUT))
++#define node_supports_image(node) \
++ (node_supports_image_output(node) || node_supports_image_input(node))
++#define node_supports_meta(node) \
++ (node_supports_meta_output(node) || node_supports_meta_input(node))
++
+ #define is_image_output_node(node) \
+- (node_desc[(node)->id].buf_type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
++ ((node)->buffer_queue.type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ #define is_image_input_node(node) \
+- (node_desc[(node)->id].buf_type == V4L2_BUF_TYPE_VIDEO_OUTPUT)
++ ((node)->buffer_queue.type == V4L2_BUF_TYPE_VIDEO_OUTPUT)
+ #define is_image_node(node) \
+ (is_image_output_node(node) || is_image_input_node(node))
+-
+ #define is_meta_output_node(node) \
+- (node_desc[(node)->id].buf_type == V4L2_BUF_TYPE_META_CAPTURE)
++ ((node)->buffer_queue.type == V4L2_BUF_TYPE_META_CAPTURE)
+ #define is_meta_input_node(node) \
+- (node_desc[(node)->id].buf_type == V4L2_BUF_TYPE_META_OUTPUT)
++ ((node)->buffer_queue.type == V4L2_BUF_TYPE_META_OUTPUT)
+ #define is_meta_node(node) \
+ (is_meta_output_node(node) || is_meta_input_node(node))
+
+@@ -250,7 +258,9 @@ struct cfe_node {
+ /* Pointer pointing to next v4l2_buffer */
+ struct cfe_buffer *next_frm;
+ /* Used to store current pixel format */
+- struct v4l2_format fmt;
++ struct v4l2_format vid_fmt;
++ /* Used to store current meta format */
++ struct v4l2_format meta_fmt;
+ /* Buffer queue used in video-buf */
+ struct vb2_queue buffer_queue;
+ /* Queue of filled frames */
+@@ -433,20 +443,21 @@ static int format_show(struct seq_file *
+ seq_printf(s, "\nNode %u (%s) state: 0x%lx\n", i,
+ node_desc[i].name, state);
+
+- if (is_image_node(node))
++ if (node_supports_image(node))
+ seq_printf(s, "format: " V4L2_FOURCC_CONV " 0x%x\n"
+ "resolution: %ux%u\nbpl: %u\nsize: %u\n",
+- V4L2_FOURCC_CONV_ARGS(node->fmt.fmt.pix.pixelformat),
+- node->fmt.fmt.pix.pixelformat,
+- node->fmt.fmt.pix.width,
+- node->fmt.fmt.pix.height,
+- node->fmt.fmt.pix.bytesperline,
+- node->fmt.fmt.pix.sizeimage);
+- else
++ V4L2_FOURCC_CONV_ARGS(node->vid_fmt.fmt.pix.pixelformat),
++ node->vid_fmt.fmt.pix.pixelformat,
++ node->vid_fmt.fmt.pix.width,
++ node->vid_fmt.fmt.pix.height,
++ node->vid_fmt.fmt.pix.bytesperline,
++ node->vid_fmt.fmt.pix.sizeimage);
++
++ if (node_supports_meta(node))
+ seq_printf(s, "format: " V4L2_FOURCC_CONV " 0x%x\nsize: %u\n",
+- V4L2_FOURCC_CONV_ARGS(node->fmt.fmt.meta.dataformat),
+- node->fmt.fmt.meta.dataformat,
+- node->fmt.fmt.meta.buffersize);
++ V4L2_FOURCC_CONV_ARGS(node->meta_fmt.fmt.meta.dataformat),
++ node->meta_fmt.fmt.meta.dataformat,
++ node->meta_fmt.fmt.meta.buffersize);
+ }
+
+ return 0;
+@@ -571,11 +582,11 @@ static void cfe_schedule_next_csi2_job(s
+ node_desc[node->id].name, &buf->vb.vb2_buf);
+
+ if (is_meta_node(node)) {
+- size = node->fmt.fmt.meta.buffersize;
++ size = node->meta_fmt.fmt.meta.buffersize;
+ stride = 0;
+ } else {
+- size = node->fmt.fmt.pix.sizeimage;
+- stride = node->fmt.fmt.pix.bytesperline;
++ size = node->vid_fmt.fmt.pix.sizeimage;
++ stride = node->vid_fmt.fmt.pix.bytesperline;
+ }
+
+ addr = vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
+@@ -867,10 +878,10 @@ static void cfe_start_channel(struct cfe
+ width = source_fmt->width;
+ height = source_fmt->height;
+
+- if (node->fmt.fmt.pix.pixelformat ==
++ if (node->vid_fmt.fmt.pix.pixelformat ==
+ fmt->remap[CFE_REMAP_16BIT])
+ mode = CSI2_MODE_REMAP;
+- else if (node->fmt.fmt.pix.pixelformat ==
++ else if (node->vid_fmt.fmt.pix.pixelformat ==
+ fmt->remap[CFE_REMAP_COMPRESSED]) {
+ mode = CSI2_MODE_COMPRESSED;
+ csi2_set_compression(&cfe->csi2, node->id,
+@@ -884,7 +895,7 @@ static void cfe_start_channel(struct cfe
+ /* Auto arm */
+ false,
+ /* Pack bytes */
+- node->id == CSI2_CH1_EMBEDDED ? true : false,
++ is_meta_node(node) ? true : false,
+ width, height);
+ }
+
+@@ -947,10 +958,11 @@ static int cfe_queue_setup(struct vb2_qu
+ {
+ struct cfe_node *node = vb2_get_drv_priv(vq);
+ struct cfe_device *cfe = node->cfe;
+- unsigned int size = is_image_node(node) ? node->fmt.fmt.pix.sizeimage :
+- node->fmt.fmt.meta.buffersize;
++ unsigned int size = is_image_node(node) ? node->vid_fmt.fmt.pix.sizeimage :
++ node->meta_fmt.fmt.meta.buffersize;
+
+- cfe_dbg("%s: [%s]\n", __func__, node_desc[node->id].name);
++ cfe_dbg("%s: [%s] type:%u\n", __func__, node_desc[node->id].name,
++ node->buffer_queue.type);
+
+ if (vq->num_buffers + *nbuffers < 3)
+ *nbuffers = 3 - vq->num_buffers;
+@@ -979,8 +991,8 @@ static int cfe_buffer_prepare(struct vb2
+ cfe_dbg_verbose("%s: [%s] buffer:%p\n", __func__,
+ node_desc[node->id].name, vb);
+
+- size = is_image_node(node) ? node->fmt.fmt.pix.sizeimage :
+- node->fmt.fmt.meta.buffersize;
++ size = is_image_node(node) ? node->vid_fmt.fmt.pix.sizeimage :
++ node->meta_fmt.fmt.meta.buffersize;
+ if (vb2_plane_size(vb, 0) < size) {
+ cfe_err("data will not fit into plane (%lu < %lu)\n",
+ vb2_plane_size(vb, 0), size);
+@@ -995,8 +1007,8 @@ static int cfe_buffer_prepare(struct vb2
+
+ memcpy(&b->config, addr, sizeof(struct pisp_fe_config));
+ return pisp_fe_validate_config(&cfe->fe, &b->config,
+- &cfe->node[FE_OUT0].fmt,
+- &cfe->node[FE_OUT1].fmt);
++ &cfe->node[FE_OUT0].vid_fmt,
++ &cfe->node[FE_OUT1].vid_fmt);
+ }
+
+ return 0;
+@@ -1256,7 +1268,7 @@ static int cfe_enum_fmt_vid_cap(struct f
+ struct cfe_device *cfe = node->cfe;
+ unsigned int i, j;
+
+- if (!is_image_output_node(node))
++ if (!node_supports_image_output(node))
+ return -EINVAL;
+
+ cfe_dbg("%s: [%s]\n", __func__, node_desc[node->id].name);
+@@ -1292,10 +1304,10 @@ static int cfe_g_fmt(struct file *file,
+
+ cfe_dbg("%s: [%s]\n", __func__, node_desc[node->id].name);
+
+- if (f->type != node->buffer_queue.type)
++ if (!node_supports_image(node))
+ return -EINVAL;
+
+- *f = node->fmt;
++ *f = node->vid_fmt;
+
+ return 0;
+ }
+@@ -1310,7 +1322,7 @@ static int try_fmt_vid_cap(struct cfe_no
+ f->fmt.pix.width, f->fmt.pix.height,
+ V4L2_FOURCC_CONV_ARGS(f->fmt.pix.pixelformat));
+
+- if (!is_image_output_node(node))
++ if (!node_supports_image_output(node))
+ return -EINVAL;
+
+ /*
+@@ -1351,11 +1363,11 @@ static int cfe_s_fmt_vid_cap(struct file
+ if (ret)
+ return ret;
+
+- node->fmt = *f;
++ node->vid_fmt = *f;
+
+ cfe_dbg("%s: Set %ux%u, V4L2 pix " V4L2_FOURCC_CONV "\n", __func__,
+- node->fmt.fmt.pix.width, node->fmt.fmt.pix.height,
+- V4L2_FOURCC_CONV_ARGS(node->fmt.fmt.pix.pixelformat));
++ node->vid_fmt.fmt.pix.width, node->vid_fmt.fmt.pix.height,
++ V4L2_FOURCC_CONV_ARGS(node->vid_fmt.fmt.pix.pixelformat));
+
+ return 0;
+ }
+@@ -1379,11 +1391,11 @@ static int cfe_enum_fmt_meta(struct file
+
+ cfe_dbg("%s: [%s]\n", __func__, node_desc[node->id].name);
+
+- if (!is_meta_node(node) || f->index != 0)
++ if (!node_supports_meta(node) || f->index != 0)
+ return -EINVAL;
+
+ switch (node->id) {
+- case CSI2_CH1_EMBEDDED:
++ case CSI2_CH0...CSI2_CH3:
+ f->pixelformat = V4L2_META_FMT_SENSOR_DATA;
+ return 0;
+ case FE_STATS:
+@@ -1399,8 +1411,11 @@ static int cfe_enum_fmt_meta(struct file
+
+ static int try_fmt_meta(struct cfe_node *node, struct v4l2_format *f)
+ {
++ if (!node_supports_meta(node))
++ return -EINVAL;
++
+ switch (node->id) {
+- case CSI2_CH1_EMBEDDED:
++ case CSI2_CH0...CSI2_CH3:
+ f->fmt.meta.dataformat = V4L2_META_FMT_SENSOR_DATA;
+ if (!f->fmt.meta.buffersize)
+ f->fmt.meta.buffersize = DEFAULT_EMBEDDED_SIZE;
+@@ -1422,6 +1437,21 @@ static int try_fmt_meta(struct cfe_node
+ return -EINVAL;
+ }
+
++static int cfe_g_fmt_meta(struct file *file, void *priv, struct v4l2_format *f)
++{
++ struct cfe_node *node = video_drvdata(file);
++ struct cfe_device *cfe = node->cfe;
++
++ cfe_dbg("%s: [%s]\n", __func__, node_desc[node->id].name);
++
++ if (!node_supports_meta(node))
++ return -EINVAL;
++
++ *f = node->meta_fmt;
++
++ return 0;
++}
++
+ static int cfe_s_fmt_meta(struct file *file, void *priv, struct v4l2_format *f)
+ {
+ struct cfe_node *node = video_drvdata(file);
+@@ -1434,17 +1464,17 @@ static int cfe_s_fmt_meta(struct file *f
+ if (vb2_is_busy(q))
+ return -EBUSY;
+
+- if (f->type != node->buffer_queue.type)
++ if (!node_supports_meta(node))
+ return -EINVAL;
+
+ ret = try_fmt_meta(node, f);
+ if (ret)
+ return ret;
+
+- node->fmt = *f;
++ node->meta_fmt = *f;
+
+ cfe_dbg("%s: Set " V4L2_FOURCC_CONV "\n", __func__,
+- V4L2_FOURCC_CONV_ARGS(node->fmt.fmt.meta.dataformat));
++ V4L2_FOURCC_CONV_ARGS(node->meta_fmt.fmt.meta.dataformat));
+
+ return 0;
+ }
+@@ -1491,6 +1521,52 @@ static int cfe_enum_framesizes(struct fi
+ return 0;
+ }
+
++static int cfe_vb2_ioctl_reqbufs(struct file *file, void *priv,
++ struct v4l2_requestbuffers *p)
++{
++ struct video_device *vdev = video_devdata(file);
++ struct cfe_node *node = video_get_drvdata(vdev);
++ struct cfe_device *cfe = node->cfe;
++ int ret;
++
++ cfe_dbg("%s: [%s] type:%u\n", __func__, node_desc[node->id].name,
++ p->type);
++
++ if (p->type != V4L2_BUF_TYPE_VIDEO_CAPTURE &&
++ p->type != V4L2_BUF_TYPE_META_CAPTURE &&
++ p->type != V4L2_BUF_TYPE_META_OUTPUT)
++ return -EINVAL;
++
++ ret = vb2_queue_change_type(vdev->queue, p->type);
++ if (ret)
++ return ret;
++
++ return vb2_ioctl_reqbufs(file, priv, p);
++}
++
++static int cfe_vb2_ioctl_create_bufs(struct file *file, void *priv,
++ struct v4l2_create_buffers *p)
++{
++ struct video_device *vdev = video_devdata(file);
++ struct cfe_node *node = video_get_drvdata(vdev);
++ struct cfe_device *cfe = node->cfe;
++ int ret;
++
++ cfe_dbg("%s: [%s] type:%u\n", __func__, node_desc[node->id].name,
++ p->format.type);
++
++ if (p->format.type != V4L2_BUF_TYPE_VIDEO_CAPTURE &&
++ p->format.type != V4L2_BUF_TYPE_META_CAPTURE &&
++ p->format.type != V4L2_BUF_TYPE_META_OUTPUT)
++ return -EINVAL;
++
++ ret = vb2_queue_change_type(vdev->queue, p->format.type);
++ if (ret)
++ return ret;
++
++ return vb2_ioctl_create_bufs(file, priv, p);
++}
++
+ static int cfe_subscribe_event(struct v4l2_fh *fh,
+ const struct v4l2_event_subscription *sub)
+ {
+@@ -1498,12 +1574,13 @@ static int cfe_subscribe_event(struct v4
+
+ switch (sub->type) {
+ case V4L2_EVENT_FRAME_SYNC:
+- if (!is_image_output_node(node))
++ if (!node_supports_image_output(node))
+ break;
+
+ return v4l2_event_subscribe(fh, sub, 2, NULL);
+ case V4L2_EVENT_SOURCE_CHANGE:
+- if (is_meta_input_node(node))
++ if (!node_supports_image_output(node) &&
++ !node_supports_meta_output(node))
+ break;
+
+ return v4l2_event_subscribe(fh, sub, 4, NULL);
+@@ -1520,19 +1597,19 @@ static const struct v4l2_ioctl_ops cfe_i
+ .vidioc_try_fmt_vid_cap = cfe_try_fmt_vid_cap,
+
+ .vidioc_enum_fmt_meta_cap = cfe_enum_fmt_meta,
+- .vidioc_g_fmt_meta_cap = cfe_g_fmt,
++ .vidioc_g_fmt_meta_cap = cfe_g_fmt_meta,
+ .vidioc_s_fmt_meta_cap = cfe_s_fmt_meta,
+ .vidioc_try_fmt_meta_cap = cfe_try_fmt_meta,
+
+ .vidioc_enum_fmt_meta_out = cfe_enum_fmt_meta,
+- .vidioc_g_fmt_meta_out = cfe_g_fmt,
++ .vidioc_g_fmt_meta_out = cfe_g_fmt_meta,
+ .vidioc_s_fmt_meta_out = cfe_s_fmt_meta,
+ .vidioc_try_fmt_meta_out = cfe_try_fmt_meta,
+
+ .vidioc_enum_framesizes = cfe_enum_framesizes,
+
+- .vidioc_reqbufs = vb2_ioctl_reqbufs,
+- .vidioc_create_bufs = vb2_ioctl_create_bufs,
++ .vidioc_reqbufs = cfe_vb2_ioctl_reqbufs,
++ .vidioc_create_bufs = cfe_vb2_ioctl_create_bufs,
+ .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
+ .vidioc_querybuf = vb2_ioctl_querybuf,
+ .vidioc_qbuf = vb2_ioctl_qbuf,
+@@ -1610,7 +1687,7 @@ static int cfe_video_link_validate(struc
+ }
+
+ if (is_image_output_node(node)) {
+- struct v4l2_pix_format *pix_fmt = &node->fmt.fmt.pix;
++ struct v4l2_pix_format *pix_fmt = &node->vid_fmt.fmt.pix;
+ const struct cfe_fmt *fmt = NULL;
+ unsigned int i;
+
+@@ -1636,8 +1713,8 @@ static int cfe_video_link_validate(struc
+ ret = -EINVAL;
+ goto out;
+ }
+- } else if (node->id == CSI2_CH1_EMBEDDED) {
+- struct v4l2_meta_format *meta_fmt = &node->fmt.fmt.meta;
++ } else if (is_csi2_node(node) && is_meta_output_node(node)) {
++ struct v4l2_meta_format *meta_fmt = &node->meta_fmt.fmt.meta;
+
+ if (source_fmt->width * source_fmt->height !=
+ meta_fmt->buffersize ||
+@@ -1698,15 +1775,17 @@ static int cfe_video_link_notify(struct
+
+ if (link->source->entity != csi2)
+ return 0;
+- if (link->sink->index != 0)
++ if (link->sink->entity != fe)
+ return 0;
+- if (link->source->index == node_desc[CSI2_CH1_EMBEDDED].link_pad)
++ if (link->sink->index != 0)
+ return 0;
+
+ cfe->fe_csi2_channel = -1;
+- if (link->sink->entity == fe && (link->flags & MEDIA_LNK_FL_ENABLED)) {
++ if (link->flags & MEDIA_LNK_FL_ENABLED) {
+ if (link->source->index == node_desc[CSI2_CH0].link_pad)
+ cfe->fe_csi2_channel = CSI2_CH0;
++ else if (link->source->index == node_desc[CSI2_CH1].link_pad)
++ cfe->fe_csi2_channel = CSI2_CH1;
+ else if (link->source->index == node_desc[CSI2_CH2].link_pad)
+ cfe->fe_csi2_channel = CSI2_CH2;
+ else if (link->source->index == node_desc[CSI2_CH3].link_pad)
+@@ -1763,30 +1842,42 @@ static int cfe_register_node(struct cfe_
+ node->cfe = cfe;
+ node->id = id;
+
+- if (is_image_node(node)) {
++ if (node_supports_image(node)) {
++ if (node_supports_image_output(node))
++ node->vid_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
++ else
++ node->vid_fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
++
+ fmt = find_format_by_code(cfe_default_format.code);
+ if (!fmt) {
+ cfe_err("Failed to find format code\n");
+ return -EINVAL;
+ }
+
+- node->fmt.fmt.pix.pixelformat = fmt->fourcc;
+- v4l2_fill_pix_format(&node->fmt.fmt.pix, &cfe_default_format);
++ node->vid_fmt.fmt.pix.pixelformat = fmt->fourcc;
++ v4l2_fill_pix_format(&node->vid_fmt.fmt.pix, &cfe_default_format);
+
+- ret = try_fmt_vid_cap(node, &node->fmt);
++ ret = try_fmt_vid_cap(node, &node->vid_fmt);
+ if (ret)
+ return ret;
+- } else {
+- ret = try_fmt_meta(node, &node->fmt);
++ }
++
++ if (node_supports_meta(node)) {
++ if (node_supports_meta_output(node))
++ node->meta_fmt.type = V4L2_BUF_TYPE_META_CAPTURE;
++ else
++ node->meta_fmt.type = V4L2_BUF_TYPE_META_OUTPUT;
++
++ ret = try_fmt_meta(node, &node->meta_fmt);
+ if (ret)
+ return ret;
+ }
+- node->fmt.type = node_desc[id].buf_type;
+
+ mutex_init(&node->lock);
+
+ q = &node->buffer_queue;
+- q->type = node_desc[id].buf_type;
++ q->type = node_supports_image(node) ? node->vid_fmt.type :
++ node->meta_fmt.type;
+ q->io_modes = VB2_MMAP | VB2_DMABUF;
+ q->drv_priv = node;
+ q->ops = &cfe_video_qops;
+@@ -1812,11 +1903,13 @@ static int cfe_register_node(struct cfe_
+ vdev->ioctl_ops = &cfe_ioctl_ops;
+ vdev->entity.ops = &cfe_media_entity_ops;
+ vdev->v4l2_dev = &cfe->v4l2_dev;
+- vdev->vfl_dir = (is_image_output_node(node) || is_meta_output_node(node))
+- ? VFL_DIR_RX : VFL_DIR_TX;
++ vdev->vfl_dir = (node_supports_image_output(node) ||
++ node_supports_meta_output(node)) ?
++ VFL_DIR_RX :
++ VFL_DIR_TX;
+ vdev->queue = q;
+ vdev->lock = &node->lock;
+- vdev->device_caps = node_desc[id].cap;
++ vdev->device_caps = node_desc[id].caps;
+ vdev->device_caps |= V4L2_CAP_STREAMING | V4L2_CAP_IO_MC;
+
+ /* Define the device names */
+@@ -1829,7 +1922,7 @@ static int cfe_register_node(struct cfe_
+ node->pad.flags = node_desc[id].pad_flags;
+ media_entity_pads_init(&vdev->entity, 1, &node->pad);
+
+- if (is_meta_node(node)) {
++ if (!node_supports_image(node)) {
+ v4l2_disable_ioctl(&node->video_dev,
+ VIDIOC_ENUM_FRAMEINTERVALS);
+ v4l2_disable_ioctl(&node->video_dev,
+@@ -1907,7 +2000,7 @@ static int cfe_link_node_pads(struct cfe
+ if (ret)
+ return ret;
+
+- if (node->id != CSI2_CH1_EMBEDDED) {
++ if (node_supports_image(node)) {
+ /* CSI2 channel # -> FE Input */
+ ret = media_create_pad_link(&cfe->csi2.sd.entity,
+ node_desc[i].link_pad,