[PATCH 6/8] media: qcom: camss: add streams API support for VFE
From: Gjorgji . Rosikopulos . gjorgji . rosikopulos
Date: Fri Sep 11 2026 - 02:30:37 EST
From: Gjorgji Rosikopulos <gjorgji.rosikopulos@xxxxxxxxxxxxxxxx>
Add the V4L2 subdev streams API to the VFE driver. VFE lines are
inherently single-consumer, so no per-stream refcounting is needed:
enable_streams reserves the output and enables the line, then
propagates a single-stream enable to the CSID; disable_streams mirrors
this in reverse.
On disable_streams, both the local VFE hardware disable and the
propagated disable to the remote CSID pad are attempted
unconditionally, and a failure on either side is logged rather than
skipping the other side's teardown or attempting a rollback. This
matches the best-effort error-handling convention used by other
streams-API drivers (e.g. dw-mipi-csi2rx, ds90ub960, cdns-csi2rx) for
this same situation, since there is no way to un-disable a peer that
already tore itself down. If the local disable also failed, its error
takes precedence in the value returned to the core.
This is opt-in per VFE instance via the new streams_enable resource
flag; no platform sets it yet, so VFE lines continue to use the
legacy non-streams subdev ops unchanged.
Signed-off-by: Gjorgji Rosikopulos <gjorgji.rosikopulos@xxxxxxxxxxxxxxxx>
---
drivers/media/platform/qcom/camss/camss-vfe.c | 119 +++++++++++++++++-
drivers/media/platform/qcom/camss/camss-vfe.h | 1 +
2 files changed, 118 insertions(+), 2 deletions(-)
diff --git a/drivers/media/platform/qcom/camss/camss-vfe.c b/drivers/media/platform/qcom/camss/camss-vfe.c
index 319d19158988..84da6600cf99 100644
--- a/drivers/media/platform/qcom/camss/camss-vfe.c
+++ b/drivers/media/platform/qcom/camss/camss-vfe.c
@@ -1993,6 +1993,104 @@ static const struct v4l2_subdev_internal_ops vfe_v4l2_internal_ops = {
.open = vfe_init_formats,
};
+/*
+ * vfe_pad_enable_streams - Enable one or more streams on the source pad
+ * @sd: VFE V4L2 subdevice
+ * @state: V4L2 subdevice state
+ * @pad: Pad number
+ * @streams_mask: Bitmask of streams to enable
+ *
+ * VFE lines are inherently single-consumer (vfe_link_setup() enforces one
+ * link per pad, and each line exposes only the implicit stream 0 of a
+ * non-streams subdev), so no local refcount is needed here.
+ *
+ * Return 0 on success or a negative error code otherwise
+ */
+static int vfe_pad_enable_streams(struct v4l2_subdev *sd, struct v4l2_subdev_state *state,
+ u32 pad, u64 streams_mask)
+{
+ struct vfe_line *line = v4l2_get_subdevdata(sd);
+ struct vfe_device *vfe = to_vfe(line);
+ struct media_pad *sink_pad = &line->pads[MSM_VFE_PAD_SINK];
+ struct media_pad *remote_pad = media_pad_remote_pad_first(sink_pad);
+ int ret;
+
+ line->output.state = VFE_OUTPUT_RESERVED;
+ ret = vfe->res->hw_ops->vfe_enable(line);
+ if (ret)
+ return ret;
+
+ if (remote_pad) {
+ ret = v4l2_subdev_enable_streams(media_entity_to_v4l2_subdev(remote_pad->entity),
+ remote_pad->index, BIT_ULL(0));
+ if (ret) {
+ vfe->res->hw_ops->vfe_disable(line);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * vfe_pad_disable_streams - Disable one or more streams on the source pad
+ * @sd: VFE V4L2 subdevice
+ * @state: V4L2 subdevice state
+ * @pad: Pad number
+ * @streams_mask: Bitmask of streams to disable
+ *
+ * Local and remote teardown are both attempted unconditionally on a
+ * best-effort basis, matching the convention used by other streams-API
+ * drivers (e.g. dw-mipi-csi2rx, ds90ub960, cdns-csi2rx): a failure on
+ * either side is logged and does not skip the other side's teardown, since
+ * there is no way to roll back a partially disabled pipeline. If the local
+ * disable also failed, its error takes precedence in the return value.
+ *
+ * Return 0 on success or a negative error code otherwise
+ */
+static int vfe_pad_disable_streams(struct v4l2_subdev *sd, struct v4l2_subdev_state *state,
+ u32 pad, u64 streams_mask)
+{
+ struct vfe_line *line = v4l2_get_subdevdata(sd);
+ struct vfe_device *vfe = to_vfe(line);
+ struct media_pad *sink_pad = &line->pads[MSM_VFE_PAD_SINK];
+ struct media_pad *remote_pad = media_pad_remote_pad_first(sink_pad);
+ int ret;
+
+ ret = vfe->res->hw_ops->vfe_disable(line);
+
+ if (remote_pad) {
+ struct v4l2_subdev *remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity);
+ int remote_ret;
+
+ remote_ret = v4l2_subdev_disable_streams(remote_sd, remote_pad->index, BIT_ULL(0));
+ if (remote_ret) {
+ dev_err(vfe->camss->dev,
+ "Failed to disable stream on remote pad: %d\n", remote_ret);
+ if (!ret)
+ ret = remote_ret;
+ }
+ }
+
+ return ret;
+}
+
+static const struct v4l2_subdev_pad_ops vfe_streams_pad_ops = {
+ .enum_mbus_code = vfe_enum_mbus_code,
+ .enum_frame_size = vfe_enum_frame_size,
+ .get_fmt = vfe_get_format,
+ .set_fmt = vfe_set_format,
+ .get_selection = vfe_get_selection,
+ .set_selection = vfe_set_selection,
+ .enable_streams = vfe_pad_enable_streams,
+ .disable_streams = vfe_pad_disable_streams,
+};
+
+static const struct v4l2_subdev_ops vfe_streams_v4l2_ops = {
+ .core = &vfe_core_ops,
+ .pad = &vfe_streams_pad_ops,
+};
+
static const struct media_entity_operations vfe_media_ops = {
.link_setup = vfe_link_setup,
.link_validate = v4l2_subdev_link_validate,
@@ -2070,7 +2168,8 @@ int msm_vfe_register_entities(struct vfe_device *vfe,
pads = vfe->line[i].pads;
video_out = &vfe->line[i].video_out;
- v4l2_subdev_init(sd, &vfe_v4l2_ops);
+ v4l2_subdev_init(sd, vfe->res->streams_enable ? &vfe_streams_v4l2_ops
+ : &vfe_v4l2_ops);
sd->internal_ops = &vfe_v4l2_internal_ops;
sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
if (i == VFE_LINE_PIX && vfe->res->is_lite == false)
@@ -2100,10 +2199,18 @@ int msm_vfe_register_entities(struct vfe_device *vfe,
goto error_init;
}
+ if (vfe->res->streams_enable) {
+ ret = v4l2_subdev_init_finalize(sd);
+ if (ret) {
+ dev_err(dev, "Failed to finalize subdev: %d\n", ret);
+ goto error_reg_subdev;
+ }
+ }
+
ret = v4l2_device_register_subdev(v4l2_dev, sd);
if (ret < 0) {
dev_err(dev, "Failed to register subdev: %d\n", ret);
- goto error_reg_subdev;
+ goto error_subdev_cleanup;
}
video_out->ops = &vfe->video_ops;
@@ -2147,6 +2254,10 @@ int msm_vfe_register_entities(struct vfe_device *vfe,
error_reg_video:
v4l2_device_unregister_subdev(sd);
+error_subdev_cleanup:
+ if (vfe->res->streams_enable)
+ v4l2_subdev_cleanup(sd);
+
error_reg_subdev:
media_entity_cleanup(&sd->entity);
@@ -2157,6 +2268,8 @@ int msm_vfe_register_entities(struct vfe_device *vfe,
msm_video_unregister(video_out);
v4l2_device_unregister_subdev(sd);
+ if (vfe->res->streams_enable)
+ v4l2_subdev_cleanup(sd);
media_entity_cleanup(&sd->entity);
}
@@ -2180,6 +2293,8 @@ void msm_vfe_unregister_entities(struct vfe_device *vfe)
msm_video_unregister(video_out);
v4l2_device_unregister_subdev(sd);
+ if (vfe->res->streams_enable)
+ v4l2_subdev_cleanup(sd);
media_entity_cleanup(&sd->entity);
}
}
diff --git a/drivers/media/platform/qcom/camss/camss-vfe.h b/drivers/media/platform/qcom/camss/camss-vfe.h
index ae9dad353a37..d1c54a516ba1 100644
--- a/drivers/media/platform/qcom/camss/camss-vfe.h
+++ b/drivers/media/platform/qcom/camss/camss-vfe.h
@@ -138,6 +138,7 @@ struct vfe_subdev_resources {
char *pd_name;
bool has_vbif;
char *vbif_name;
+ bool streams_enable;
const struct vfe_hw_ops *hw_ops;
const struct camss_formats *formats_rdi;
const struct camss_formats *formats_pix;
--
2.34.1