[PATCH v2 07/14] media: qcom: camss: Add camss-isp-pipeline helper

Loic Poulain
2026-04-27T12:46:50Z

Add a declarative MC topology builder for CAMSS offline ISP drivers.
Drivers describe their entire media graph, entities (video devices,
subdevs, or base entities), their pads, and the links between them
in a static descriptor table. The builder validates the table,
allocates and registers all entities, and creates all MC pad links.

Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com>
---
drivers/media/platform/qcom/camss/Makefile | 3 +-
.../media/platform/qcom/camss/camss-isp-pipeline.c | 361 +++++++++++++++++++++
.../media/platform/qcom/camss/camss-isp-pipeline.h | 228 +++++++++++++
3 files changed, 591 insertions(+), 1 deletion(-)

diff --git a/drivers/media/platform/qcom/camss/Makefile b/drivers/media/platform/qcom/camss/Makefile
index f13c9f326cf81962bd165dc8dd2bb60207cd54a7..f3acb1b54b6c1455d72e2d947c860f0c337648de 100644
--- a/drivers/media/platform/qcom/camss/Makefile
+++ b/drivers/media/platform/qcom/camss/Makefile
@@ -31,7 +31,8 @@ qcom-camss-objs += \
obj-$(CONFIG_VIDEO_QCOM_CAMSS) += qcom-camss.o

qcom-camss-isp-objs := camss-isp-bufq.o \
- camss-isp-sched.o
+ camss-isp-sched.o \
+ camss-isp-pipeline.o

obj-$(CONFIG_VIDEO_QCOM_CAMSS_ISP) += qcom-camss-isp.o

diff --git a/drivers/media/platform/qcom/camss/camss-isp-pipeline.c b/drivers/media/platform/qcom/camss/camss-isp-pipeline.c
new file mode 100644
index 0000000000000000000000000000000000000000..8e44bedb0a41e3cf4fc7e3a138c1f48854f5efc8
--- /dev/null
+++ b/drivers/media/platform/qcom/camss/camss-isp-pipeline.c
@@ -0,0 +1,361 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * CAMSS ISP pipeline helper — declarative MC topology builder
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/slab.h>
+
+#include <media/media-device.h>
+#include <media/media-entity.h>
+#include <media/v4l2-dev.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-subdev.h>
+#include "camss-isp-pipeline.h"
+
+#if !IS_ENABLED(CONFIG_MEDIA_CONTROLLER)
+static inline int media_entity_pads_init(struct media_entity *e, u16 n,
+ struct media_pad *p) { return 0; }
+static inline void media_entity_remove_links(struct media_entity *e) {}
+static inline int media_create_pad_link(struct media_entity *src, u16 sp,
+ struct media_entity *sink, u16 dp,
+ u32 flags) { return 0; }
+#endif
+
+/* -------- Internal elpers -------- */
+
+static enum vfl_devnode_direction isp_caps_to_vfl_dir(u32 caps)
+{
+ if (caps & (V4L2_CAP_VIDEO_M2M | V4L2_CAP_VIDEO_M2M_MPLANE))
+ return VFL_DIR_M2M;
+ if (caps & (V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_VIDEO_OUTPUT_MPLANE |
+ V4L2_CAP_META_OUTPUT | V4L2_CAP_VBI_OUTPUT | V4L2_CAP_SDR_OUTPUT))
+ return VFL_DIR_TX;
+ return VFL_DIR_RX;
+}
+
+static unsigned int isp_count_pads(const struct camss_isp_pad_desc *pads)
+{
+ unsigned int n = 0;
+
+ if (!pads)
+ return 0;
+ while (pads[n].flags)
+ n++;
+ return n;
+}
+
+static struct media_entity *isp_pipeline_media_entity(struct camss_isp_pipeline *pipeline,
+ unsigned int idx)
+{
+ struct camss_isp_pipeline_entity *slot = &pipeline->entities[idx];
+
+ switch (slot->obj_type) {
+ case MEDIA_ENTITY_TYPE_VIDEO_DEVICE:
+ return &slot->vdev.entity;
+ case MEDIA_ENTITY_TYPE_V4L2_SUBDEV:
+ return &slot->subdev.entity;
+ default:
+ return &slot->entity;
+ }
+}
+
+/* -------- Validation -------- */
+
+static int isp_pipeline_validate(struct device *dev,
+ const struct camss_isp_entity_desc *descs,
+ unsigned int num_entities)
+{
+ unsigned int i, pi;
+
+ for (i = 0; i < num_entities; i++) {
+ const struct camss_isp_pad_desc *pads = descs[i].pads;
+ unsigned int num_pads = isp_count_pads(pads);
+
+ for (pi = 0; pi < num_pads; pi++) {
+ const struct camss_isp_pad_desc *pad = &pads[pi];
+ const struct camss_isp_pad_desc *peer_pad;
+ unsigned int peer_num_pads;
+ int peer_ent = pad->peer_entity;
+
+ if (peer_ent < 0)
+ continue;
+
+ if ((unsigned int)peer_ent >= num_entities) {
+ dev_err(dev, "entity[%u].p%u: peer_entity %d out of range\n",
+ i, pi, peer_ent);
+ return -EINVAL;
+ }
+
+ peer_num_pads = isp_count_pads(descs[peer_ent].pads);
+ if (pad->peer_pad >= peer_num_pads) {
+ dev_err(dev, "entity[%u].p%u: peer_pad %u out of range\n",
+ i, pi, pad->peer_pad);
+ return -EINVAL;
+ }
+
+ peer_pad = &descs[peer_ent].pads[pad->peer_pad];
+
+ /* Links are SOURCE->SINK; reject SOURCE->SOURCE or SINK->SINK */
+ if (((pad->flags & MEDIA_PAD_FL_SOURCE) &&
+ (peer_pad->flags & MEDIA_PAD_FL_SOURCE)) ||
+ ((pad->flags & MEDIA_PAD_FL_SINK) &&
+ (peer_pad->flags & MEDIA_PAD_FL_SINK))) {
+ dev_err(dev, "entity[%u].p%u -> entity[%d].p%u: invalid\n",
+ i, pi, peer_ent, pad->peer_pad);
+ return -EINVAL;
+ }
+
+ /* Verify back-reference consistency */
+ if (peer_pad->peer_entity >= 0 &&
+ ((unsigned int)peer_pad->peer_entity != i ||
+ peer_pad->peer_pad != pi)) {
+ dev_err(dev, "entity[%u].p%u <-> entity[%d].p%u: mismatch\n",
+ i, pi, peer_ent, pad->peer_pad);
+ return -EINVAL;
+ }
+ }
+ }
+
+ return 0;
+}
+
+/* -------- Allocation / Release -------- */
+
+struct camss_isp_pipeline *camss_isp_pipeline_alloc(unsigned int num_entities)
+{
+ struct camss_isp_pipeline *pipeline;
+
+ pipeline = kzalloc(struct_size(pipeline, entities, num_entities),
+ GFP_KERNEL);
+ if (!pipeline)
+ return ERR_PTR(-ENOMEM);
+
+ pipeline->num_entities = num_entities;
+ return pipeline;
+}
+EXPORT_SYMBOL_GPL(camss_isp_pipeline_alloc);
+
+void camss_isp_pipeline_free(struct camss_isp_pipeline *pipeline)
+{
+ kfree(pipeline);
+}
+EXPORT_SYMBOL_GPL(camss_isp_pipeline_free);
+
+/* -------- Registration -------- */
+
+void camss_isp_pipeline_unregister(struct camss_isp_pipeline *pipeline)
+{
+ int i;
+
+ /* Unregister entities in reverse order */
+ for (i = (int)pipeline->num_entities - 1; i >= 0; i--) {
+ struct camss_isp_pipeline_entity *slot = &pipeline->entities[i];
+
+ switch (slot->obj_type) {
+ case MEDIA_ENTITY_TYPE_VIDEO_DEVICE:
+ if (slot->vdev.name[0])
+ video_unregister_device(&slot->vdev);
+ break;
+ case MEDIA_ENTITY_TYPE_V4L2_SUBDEV:
+ if (slot->subdev.name[0])
+ v4l2_device_unregister_subdev(&slot->subdev);
+ break;
+ case MEDIA_ENTITY_TYPE_BASE:
+ if (slot->entity.name) {
+ media_entity_remove_links(&slot->entity);
+ media_device_unregister_entity(&slot->entity);
+ }
+ break;
+ }
+
+ kfree(slot->pads);
+ slot->pads = NULL;
+ }
+
+ pipeline->v4l2_dev = NULL;
+}
+EXPORT_SYMBOL_GPL(camss_isp_pipeline_unregister);
+
+static int isp_register_vdev(struct camss_isp_pipeline_entity *slot,
+ const struct camss_isp_entity_desc *desc,
+ struct v4l2_device *v4l2_dev)
+{
+ struct video_device *vdev = &slot->vdev;
+ int ret;
+
+ strscpy(vdev->name, desc->name, sizeof(vdev->name));
+ vdev->vfl_dir = isp_caps_to_vfl_dir(desc->vdev.caps);
+ vdev->v4l2_dev = v4l2_dev;
+ vdev->device_caps = desc->vdev.caps;
+ vdev->release = video_device_release_empty;
+ if (desc->vdev.fops)
+ vdev->fops = desc->vdev.fops;
+ if (desc->vdev.ioctl_ops)
+ vdev->ioctl_ops = desc->vdev.ioctl_ops;
+
+ vdev->entity.obj_type = MEDIA_ENTITY_TYPE_VIDEO_DEVICE;
+ vdev->entity.function = desc->function ? desc->function : MEDIA_ENT_F_IO_V4L;
+
+ ret = media_entity_pads_init(&vdev->entity, slot->num_pads, slot->pads);
+ if (ret)
+ return ret;
+
+ ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
+ if (ret)
+ return ret;
+
+ video_set_drvdata(vdev, desc->vdev.drvdata);
+
+ return 0;
+}
+
+static int isp_register_subdev(struct camss_isp_pipeline_entity *slot,
+ const struct camss_isp_entity_desc *desc,
+ struct v4l2_device *v4l2_dev)
+{
+ struct v4l2_subdev *sd = &slot->subdev;
+ int ret;
+
+ v4l2_subdev_init(sd, desc->subdev.ops);
+ strscpy(sd->name, desc->name, sizeof(sd->name));
+ sd->entity.function = desc->function ?
+ desc->function : MEDIA_ENT_F_V4L2_SUBDEV_UNKNOWN;
+
+ ret = media_entity_pads_init(&sd->entity, slot->num_pads, slot->pads);
+ if (ret)
+ return ret;
+
+ return v4l2_device_register_subdev(v4l2_dev, sd);
+}
+
+static int isp_register_base_entity(struct camss_isp_pipeline_entity *slot,
+ const struct camss_isp_entity_desc *desc,
+ struct v4l2_device *v4l2_dev)
+{
+ struct media_entity *entity = &slot->entity;
+ int ret;
+
+ entity->obj_type = MEDIA_ENTITY_TYPE_BASE;
+ entity->name = desc->name;
+ entity->function = desc->function;
+
+ ret = media_entity_pads_init(entity, slot->num_pads, slot->pads);
+ if (ret)
+ return ret;
+
+ return media_device_register_entity(v4l2_dev->mdev, entity);
+}
+
+static int isp_alloc_pads(struct camss_isp_pipeline_entity *slot,
+ const struct camss_isp_entity_desc *desc)
+{
+ unsigned int num_pads = isp_count_pads(desc->pads);
+ unsigned int i;
+
+ if (!num_pads)
+ goto done;
+
+ slot->pads = kcalloc(num_pads, sizeof(*slot->pads), GFP_KERNEL);
+ if (!slot->pads)
+ return -ENOMEM;
+
+ for (i = 0; i < num_pads; i++)
+ slot->pads[i].flags = desc->pads[i].flags;
+done:
+ slot->num_pads = num_pads;
+ return 0;
+}
+
+int camss_isp_pipeline_register(struct camss_isp_pipeline *pipeline,
+ struct v4l2_device *v4l2_dev,
+ const struct camss_isp_entity_desc *descs,
+ unsigned int num_entities)
+{
+ unsigned int i, pi;
+ int ret;
+
+ if (WARN_ON(num_entities != pipeline->num_entities))
+ return -EINVAL;
+
+ if (WARN_ON(!v4l2_dev || !v4l2_dev->mdev))
+ return -EINVAL;
+
+ ret = isp_pipeline_validate(v4l2_dev->dev, descs, num_entities);
+ if (ret)
+ return ret;
+
+ pipeline->v4l2_dev = v4l2_dev;
+
+ /* Register each entity */
+ for (i = 0; i < num_entities; i++) {
+ const struct camss_isp_entity_desc *desc = &descs[i];
+ struct camss_isp_pipeline_entity *slot = &pipeline->entities[i];
+
+ slot->obj_type = desc->obj_type;
+
+ ret = isp_alloc_pads(slot, desc);
+ if (ret)
+ goto err_unregister;
+
+ switch (desc->obj_type) {
+ case MEDIA_ENTITY_TYPE_VIDEO_DEVICE:
+ ret = isp_register_vdev(slot, desc, v4l2_dev);
+ break;
+ case MEDIA_ENTITY_TYPE_V4L2_SUBDEV:
+ ret = isp_register_subdev(slot, desc, v4l2_dev);
+ break;
+ case MEDIA_ENTITY_TYPE_BASE:
+ default:
+ ret = isp_register_base_entity(slot, desc, v4l2_dev);
+ break;
+ }
+ if (ret)
+ goto err_unregister;
+ }
+
+ /* Create links — only from SOURCE side to avoid duplicates */
+ for (i = 0; i < num_entities; i++) {
+ const struct camss_isp_entity_desc *desc = &descs[i];
+ unsigned int num_pads = isp_count_pads(desc->pads);
+
+ for (pi = 0; pi < num_pads; pi++) {
+ const struct camss_isp_pad_desc *pad = &desc->pads[pi];
+ struct media_entity *src_entity, *sink_entity;
+ unsigned int src_pad_idx, sink_pad_idx;
+ u32 lflags;
+
+ if (!(pad->flags & MEDIA_PAD_FL_SOURCE))
+ continue;
+ if (pad->peer_entity < 0)
+ continue;
+
+ src_entity = isp_pipeline_media_entity(pipeline, i);
+ sink_entity = isp_pipeline_media_entity(pipeline,
+ (unsigned int)pad->peer_entity);
+ src_pad_idx = pi;
+ sink_pad_idx = pad->peer_pad;
+
+ lflags = pad->link_flags ?
+ pad->link_flags :
+ (MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED);
+
+ ret = media_create_pad_link(src_entity, src_pad_idx,
+ sink_entity, sink_pad_idx,
+ lflags);
+ if (ret)
+ goto err_unregister;
+ }
+ }
+
+ return 0;
+
+err_unregister:
+ camss_isp_pipeline_unregister(pipeline);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(camss_isp_pipeline_register);
+
+MODULE_DESCRIPTION("CAMSS ISP pipeline topology builder");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/platform/qcom/camss/camss-isp-pipeline.h b/drivers/media/platform/qcom/camss/camss-isp-pipeline.h
new file mode 100644
index 0000000000000000000000000000000000000000..5dfa32dcafc0a944ca2c160fb5846a2c73214acc
--- /dev/null
+++ b/drivers/media/platform/qcom/camss/camss-isp-pipeline.h
@@ -0,0 +1,228 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * CAMSS ISP pipeline helper — declarative MC topology builder
+ *
+ * Drivers describe their entire media graph — entities (video devices,
+ * subdevs, or base entities), their pads, and the links between them —
+ * in a single static descriptor table. The builder validates the table,
+ * allocates and registers all entities, and creates all MC links.
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef _CAMSS_ISP_PIPELINE_H
+#define _CAMSS_ISP_PIPELINE_H
+
+#include <linux/mutex.h>
+#include <media/media-device.h>
+#include <media/media-entity.h>
+#include <media/v4l2-dev.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-subdev.h>
+
+/**
+ * struct camss_isp_pad_desc - descriptor for one pad and its optional link
+ *
+ * @flags: Pad flags: MEDIA_PAD_FL_SINK, MEDIA_PAD_FL_SOURCE,
+ * MEDIA_PAD_FL_MUST_CONNECT. A zero @flags value acts as
+ * the sentinel that terminates the pad list.
+ * @peer_entity: Index of the peer entity in the descriptor array, or -1
+ * if this pad has no link.
+ * @peer_pad: Pad index on the peer entity to link to.
+ * @link_flags: MC link flags (MEDIA_LNK_FL_*). Defaults to
+ * MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED when zero.
+ *
+ * Links are described from both sides (each endpoint references the other),
+ * but the builder only creates each link once — from the SOURCE side.
+ */
+struct camss_isp_pad_desc {
+ u32 flags;
+ int peer_entity;
+ unsigned int peer_pad;
+ u32 link_flags;
+};
+
+/**
+ * struct camss_isp_entity_desc - descriptor for one entity in the pipeline
+ *
+ * @name: Human-readable entity name (also used as video device name
+ * suffix when @obj_type is MEDIA_ENTITY_TYPE_VIDEO_DEVICE).
+ * @obj_type: MEDIA_ENTITY_TYPE_VIDEO_DEVICE, MEDIA_ENTITY_TYPE_V4L2_SUBDEV,
+ * or MEDIA_ENTITY_TYPE_BASE.
+ * @function: MEDIA_ENT_F_* function identifier.
+ * @pads: Sentinel-terminated (flags == 0) array of pad descriptors.
+ *
+ * Fields used only for MEDIA_ENTITY_TYPE_VIDEO_DEVICE:
+ * @vdev.caps: V4L2_CAP_* device capabilities.
+ * The video device direction (VFL_DIR_RX/TX/M2M) is derived
+ * automatically from @caps by the builder.
+ * @vdev.drvdata: Opaque pointer set via video_set_drvdata() after registration.
+ * @vdev.fops: File operations (may be NULL to use kernel defaults).
+ * @vdev.ioctl_ops: ioctl operations (may be NULL).
+ *
+ * Fields used only for MEDIA_ENTITY_TYPE_V4L2_SUBDEV:
+ * @subdev.ops: Subdev operations (may be NULL).
+ */
+struct camss_isp_entity_desc {
+ const char *name;
+ u32 obj_type;
+ u32 function;
+ const struct camss_isp_pad_desc *pads;
+
+ union {
+ /* MEDIA_ENTITY_TYPE_VIDEO_DEVICE */
+ struct {
+ u32 caps;
+ void *drvdata;
+ const struct v4l2_file_operations *fops;
+ const struct v4l2_ioctl_ops *ioctl_ops;
+ } vdev;
+ /* MEDIA_ENTITY_TYPE_V4L2_SUBDEV */
+ struct {
+ const struct v4l2_subdev_ops *ops;
+ } subdev;
+ };
+};
+
+/**
+ * struct camss_isp_pipeline_entity - one registered entity slot
+ *
+ * Internal to the pipeline; drivers access entities via the accessor helpers.
+ *
+ * @obj_type: mirrors the descriptor's @obj_type.
+ * @pads: allocated pad array for this entity.
+ * @num_pads: number of entries in @pads.
+ * @vdev: valid when @obj_type == MEDIA_ENTITY_TYPE_VIDEO_DEVICE.
+ * @subdev: valid when @obj_type == MEDIA_ENTITY_TYPE_V4L2_SUBDEV.
+ * @entity: valid when @obj_type == MEDIA_ENTITY_TYPE_BASE.
+ */
+struct camss_isp_pipeline_entity {
+ u32 obj_type;
+ struct media_pad *pads;
+ unsigned int num_pads;
+ union {
+ struct video_device vdev;
+ struct v4l2_subdev subdev;
+ struct media_entity entity;
+ };
+};
+
+/**
+ * struct camss_isp_pipeline - registered ISP pipeline topology
+ *
+ * Allocate with camss_isp_pipeline_alloc(), register with
+ * camss_isp_pipeline_register(), tear down with
+ * camss_isp_pipeline_unregister(), free with camss_isp_pipeline_free().
+ *
+ * @v4l2_dev: Pointer to the caller-provided V4L2 device.
+ * @drv_priv: Driver-private pointer; not touched by the framework.
+ * @num_entities: Number of entries in @entities.
+ * @entities: Per-entity state; flexible array.
+ */
+struct camss_isp_pipeline {
+ struct v4l2_device *v4l2_dev;
+ void *drv_priv;
+
+ unsigned int num_entities;
+ struct camss_isp_pipeline_entity entities[] __counted_by(num_entities);
+};
+
+/**
+ * camss_isp_pipeline_alloc() - allocate a pipeline for @num_entities entities
+ *
+ * Returns a pointer to the new pipeline or ERR_PTR on failure.
+ * Free with camss_isp_pipeline_free() if never registered, or call
+ * camss_isp_pipeline_unregister() followed by camss_isp_pipeline_free().
+ */
+struct camss_isp_pipeline *camss_isp_pipeline_alloc(unsigned int num_entities);
+
+/**
+ * camss_isp_pipeline_free() - free an unregistered pipeline
+ * @pipeline: pipeline to free (may be NULL)
+ */
+void camss_isp_pipeline_free(struct camss_isp_pipeline *pipeline);
+
+/**
+ * camss_isp_pipeline_register() - validate descriptors and register the graph
+ * @pipeline: pipeline (allocated with camss_isp_pipeline_alloc())
+ * @v4l2_dev: caller-owned and already-registered V4L2 device; its
+ * associated media_device (v4l2_dev->mdev) must also be
+ * initialised and registered before this call.
+ * @descs: array of @num_entities entity descriptors
+ * @num_entities: number of entities; must equal pipeline->num_entities
+ *
+ * Validates the descriptor table (link direction consistency, index bounds),
+ * then registers all entities into the provided v4l2_device / media_device
+ * and creates all MC pad links.
+ *
+ * Returns 0 on success or a negative error code.
+ */
+int camss_isp_pipeline_register(struct camss_isp_pipeline *pipeline,
+ struct v4l2_device *v4l2_dev,
+ const struct camss_isp_entity_desc *descs,
+ unsigned int num_entities);
+
+/**
+ * camss_isp_pipeline_unregister() - tear down a registered pipeline
+ * @pipeline: pipeline to unregister
+ */
+void camss_isp_pipeline_unregister(struct camss_isp_pipeline *pipeline);
+
+/**
+ * camss_isp_pipeline_get_vdev() - return the video_device for entity @idx
+ * @pipeline: registered pipeline
+ * @idx: entity index (must be MEDIA_ENTITY_TYPE_VIDEO_DEVICE)
+ *
+ * Returns NULL if @idx is out of range or the entity is not a video device.
+ */
+static inline struct video_device *
+camss_isp_pipeline_get_vdev(struct camss_isp_pipeline *pipeline,
+ unsigned int idx)
+{
+ if (WARN_ON(idx >= pipeline->num_entities))
+ return NULL;
+ if (WARN_ON(pipeline->entities[idx].obj_type !=
+ MEDIA_ENTITY_TYPE_VIDEO_DEVICE))
+ return NULL;
+ return &pipeline->entities[idx].vdev;
+}
+
+/**
+ * camss_isp_pipeline_get_subdev() - return the v4l2_subdev for entity @idx
+ * @pipeline: registered pipeline
+ * @idx: entity index (must be MEDIA_ENTITY_TYPE_V4L2_SUBDEV)
+ *
+ * Returns NULL if @idx is out of range or the entity is not a subdev.
+ */
+static inline struct v4l2_subdev *
+camss_isp_pipeline_get_subdev(struct camss_isp_pipeline *pipeline,
+ unsigned int idx)
+{
+ if (WARN_ON(idx >= pipeline->num_entities))
+ return NULL;
+ if (WARN_ON(pipeline->entities[idx].obj_type !=
+ MEDIA_ENTITY_TYPE_V4L2_SUBDEV))
+ return NULL;
+ return &pipeline->entities[idx].subdev;
+}
+
+/**
+ * camss_isp_pipeline_get_entity() - return the media_entity for entity @idx
+ * @pipeline: registered pipeline
+ * @idx: entity index (must be MEDIA_ENTITY_TYPE_BASE)
+ *
+ * Returns NULL if @idx is out of range or the entity is not a base entity.
+ */
+static inline struct media_entity *
+camss_isp_pipeline_get_entity(struct camss_isp_pipeline *pipeline,
+ unsigned int idx)
+{
+ if (WARN_ON(idx >= pipeline->num_entities))
+ return NULL;
+ if (WARN_ON(pipeline->entities[idx].obj_type !=
+ MEDIA_ENTITY_TYPE_BASE))
+ return NULL;
+ return &pipeline->entities[idx].entity;
+}
+
+#endif /* _CAMSS_ISP_PIPELINE_H */

--
2.34.1