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 <[email protected]> --- 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

