On Wed May 20 17:30:16 2026 +0530, Jai Luthra wrote:
> Cadence CSI-2 bridge IP supports capturing multiple virtual "streams"
> of data over the same physical interface using MIPI Virtual Channels.
> 
> While the hardware IP supports usecases where streams coming in the sink
> pad can be broadcasted to multiple source pads, the driver will need
> significant re-architecture to make that possible. The two users of this
> IP in mainline linux are TI Shim and StarFive JH7110 CAMSS, and both
> have only integrated the first source pad i.e stream0 of this IP. So for
> now keep it simple and only allow 1-to-1 mapping of streams from sink to
> source, without any broadcasting.
> 
> Signed-off-by: Jai Luthra <[email protected]>
> Reviewed-by: Changhuang Liang <[email protected]>
> Reviewed-by: Yemike Abhilash Chandra <[email protected]>
> Reviewed-by: Tomi Valkeinen <[email protected]>
> Co-developed-by: Rishikesh Donadkar <[email protected]>
> Signed-off-by: Rishikesh Donadkar <[email protected]>
> Signed-off-by: Sakari Ailus <[email protected]>

Patch committed.

Thanks,
Sakari Ailus

 drivers/media/platform/cadence/cdns-csi2rx.c | 241 +++++++++++++++++++++------
 1 file changed, 190 insertions(+), 51 deletions(-)

---

diff --git a/drivers/media/platform/cadence/cdns-csi2rx.c 
b/drivers/media/platform/cadence/cdns-csi2rx.c
index bba0e4c0a34d..8931e824c597 100644
--- a/drivers/media/platform/cadence/cdns-csi2rx.c
+++ b/drivers/media/platform/cadence/cdns-csi2rx.c
@@ -135,6 +135,7 @@ struct csi2rx_priv {
        struct phy                      *dphy;
 
        u8                              num_pixels[CSI2RX_STREAMS_MAX];
+       u32                             vc_select[CSI2RX_STREAMS_MAX];
        u8                              lanes[CSI2RX_LANES_MAX];
        u8                              num_lanes;
        u8                              max_lanes;
@@ -229,6 +230,21 @@ static const struct csi2rx_fmt *csi2rx_get_fmt_by_code(u32 
code)
        return NULL;
 }
 
+static int csi2rx_get_frame_desc_from_source(struct csi2rx_priv *csi2rx,
+                                            struct v4l2_mbus_frame_desc *fd)
+{
+       struct media_pad *remote_pad;
+
+       remote_pad = 
media_entity_remote_source_pad_unique(&csi2rx->subdev.entity);
+       if (IS_ERR(remote_pad)) {
+               dev_err(csi2rx->dev, "No remote pad found for sink\n");
+               return PTR_ERR(remote_pad);
+       }
+
+       return v4l2_subdev_call(csi2rx->source_subdev, pad, get_frame_desc,
+                               remote_pad->index, fd);
+}
+
 static inline
 struct csi2rx_priv *v4l2_subdev_to_csi2rx(struct v4l2_subdev *subdev)
 {
@@ -258,30 +274,46 @@ static void csi2rx_reset(struct csi2rx_priv *csi2rx)
 
 static int csi2rx_configure_ext_dphy(struct csi2rx_priv *csi2rx)
 {
-       struct media_pad *src_pad =
-               &csi2rx->source_subdev->entity.pads[csi2rx->source_pad];
        union phy_configure_opts opts = { };
        struct phy_configure_opts_mipi_dphy *cfg = &opts.mipi_dphy;
-       struct v4l2_subdev_state *state;
        struct v4l2_mbus_framefmt *framefmt;
+       struct v4l2_subdev_state *state;
        const struct csi2rx_fmt *fmt;
+       struct v4l2_subdev_route *route;
+       int source_pad = csi2rx->source_pad;
+       struct media_pad *pad = &csi2rx->source_subdev->entity.pads[source_pad];
        s64 link_freq;
        int ret;
+       u32 bpp;
 
        state = v4l2_subdev_get_locked_active_state(&csi2rx->subdev);
 
-       framefmt = v4l2_subdev_state_get_format(state, CSI2RX_PAD_SINK, 0);
-       if (!framefmt) {
-               dev_err(csi2rx->dev, "Did not find active sink format\n");
-               return -EINVAL;
-       }
+       /*
+        * For multi-stream transmitters there is no single pixel rate.
+        *
+        * In multistream usecase pass bpp as 0 so that v4l2_get_link_freq()
+        * returns an error if it falls back to V4L2_CID_PIXEL_RATE.
+        */
+       if (state->routing.num_routes > 1) {
+               bpp = 0;
+       } else {
+               route = &state->routing.routes[0];
+               framefmt = v4l2_subdev_state_get_format(state, CSI2RX_PAD_SINK,
+                                                       route->sink_stream);
+               if (!framefmt) {
+                       dev_err(csi2rx->dev, "Did not find active sink 
format\n");
+                       return -EINVAL;
+               }
 
-       fmt = csi2rx_get_fmt_by_code(framefmt->code);
+               fmt = csi2rx_get_fmt_by_code(framefmt->code);
+               bpp = fmt->bpp;
+       }
 
-       link_freq = v4l2_get_link_freq(src_pad,
-                                      fmt->bpp, 2 * csi2rx->num_lanes);
-       if (link_freq < 0)
+       link_freq = v4l2_get_link_freq(pad, bpp, 2 * csi2rx->num_lanes);
+       if (link_freq < 0) {
+               dev_err(csi2rx->dev, "Unable to calculate link frequency\n");
                return link_freq;
+       }
 
        ret = phy_mipi_dphy_get_default_config_for_hsclk(link_freq,
                                                         csi2rx->num_lanes, 
cfg);
@@ -379,11 +411,7 @@ static int csi2rx_start(struct csi2rx_priv *csi2rx)
                                          csi2rx->num_pixels[i]),
                       csi2rx->base + CSI2RX_STREAM_CFG_REG(i));
 
-               /*
-                * Enable one virtual channel. When multiple virtual channels
-                * are supported this will have to be changed.
-                */
-               writel(CSI2RX_STREAM_DATA_CFG_VC_SELECT(0),
+               writel(csi2rx->vc_select[i],
                       csi2rx->base + CSI2RX_STREAM_DATA_CFG_REG(i));
 
                writel(CSI2RX_STREAM_CTRL_START,
@@ -471,18 +499,57 @@ static int csi2rx_log_status(struct v4l2_subdev *sd)
        return 0;
 }
 
+static void csi2rx_update_vc_select(struct csi2rx_priv *csi2rx,
+                                   struct v4l2_subdev_state *state)
+{
+       struct v4l2_mbus_frame_desc fd = {0};
+       struct v4l2_subdev_route *route;
+       unsigned int i;
+       int ret;
+
+       ret = csi2rx_get_frame_desc_from_source(csi2rx, &fd);
+       if (ret || fd.type != V4L2_MBUS_FRAME_DESC_TYPE_CSI2) {
+               dev_dbg(csi2rx->dev,
+                       "Failed to get source frame desc, allowing only 
VC=0\n");
+               for (i = 0; i < CSI2RX_STREAMS_MAX; i++)
+                       csi2rx->vc_select[i] = 
CSI2RX_STREAM_DATA_CFG_VC_SELECT(0);
+               return;
+       }
+
+       /* If source provides per-stream VC info, use it to filter by VC */
+       memset(csi2rx->vc_select, 0, sizeof(csi2rx->vc_select));
+
+       for_each_active_route(&state->routing, route) {
+               u32 cdns_stream = route->source_pad - CSI2RX_PAD_SOURCE_STREAM0;
+
+               for (i = 0; i < fd.num_entries; i++) {
+                       if (fd.entry[i].stream != route->sink_stream)
+                               continue;
+
+                       csi2rx->vc_select[cdns_stream] |=
+                               
CSI2RX_STREAM_DATA_CFG_VC_SELECT(fd.entry[i].bus.csi2.vc);
+               }
+       }
+}
+
 static int csi2rx_enable_streams(struct v4l2_subdev *subdev,
                                 struct v4l2_subdev_state *state, u32 pad,
                                 u64 streams_mask)
 {
        struct csi2rx_priv *csi2rx = v4l2_subdev_to_csi2rx(subdev);
+       u64 sink_streams;
        int ret;
 
+       sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
+                                                      CSI2RX_PAD_SINK,
+                                                      &streams_mask);
+
        /*
         * If we're not the first users, there's no need to
         * enable the whole controller.
         */
        if (!csi2rx->count) {
+               csi2rx_update_vc_select(csi2rx, state);
                ret = csi2rx_start(csi2rx);
                if (ret)
                        return ret;
@@ -490,10 +557,11 @@ static int csi2rx_enable_streams(struct v4l2_subdev 
*subdev,
 
        /* Start streaming on the source */
        ret = v4l2_subdev_enable_streams(csi2rx->source_subdev, 
csi2rx->source_pad,
-                                        BIT_U64(0));
+                                        sink_streams);
        if (ret) {
                dev_err(csi2rx->dev,
-                       "Failed to start streams %d on subdev\n", 0);
+                       "Failed to start streams %#llx on subdev\n",
+                       sink_streams);
                if (!csi2rx->count)
                        csi2rx_stop(csi2rx);
                return ret;
@@ -508,9 +576,14 @@ static int csi2rx_disable_streams(struct v4l2_subdev 
*subdev,
                                  u64 streams_mask)
 {
        struct csi2rx_priv *csi2rx = v4l2_subdev_to_csi2rx(subdev);
+       u64 sink_streams;
+
+       sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
+                                                      CSI2RX_PAD_SINK,
+                                                      &streams_mask);
 
        if (v4l2_subdev_disable_streams(csi2rx->source_subdev,
-                                       csi2rx->source_pad, BIT_U64(0))) {
+                                                csi2rx->source_pad, 
sink_streams)) {
                dev_err(csi2rx->dev, "Couldn't disable our subdev\n");
        }
 
@@ -535,12 +608,53 @@ static int csi2rx_enum_mbus_code(struct v4l2_subdev 
*subdev,
        return 0;
 }
 
+static int _csi2rx_set_routing(struct v4l2_subdev *subdev,
+                              struct v4l2_subdev_state *state,
+                              struct v4l2_subdev_krouting *routing)
+{
+       static const struct v4l2_mbus_framefmt format = {
+               .width = 640,
+               .height = 480,
+               .code = MEDIA_BUS_FMT_UYVY8_1X16,
+               .field = V4L2_FIELD_NONE,
+               .colorspace = V4L2_COLORSPACE_SRGB,
+               .ycbcr_enc = V4L2_YCBCR_ENC_601,
+               .quantization = V4L2_QUANTIZATION_LIM_RANGE,
+               .xfer_func = V4L2_XFER_FUNC_SRGB,
+       };
+       int ret;
+
+       ret = v4l2_subdev_routing_validate(subdev, routing,
+                                          V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
+       if (ret)
+               return ret;
+
+       return v4l2_subdev_set_routing_with_fmt(subdev, state, routing, 
&format);
+}
+
+static int csi2rx_set_routing(struct v4l2_subdev *subdev,
+                             struct v4l2_subdev_state *state,
+                             enum v4l2_subdev_format_whence which,
+                             struct v4l2_subdev_krouting *routing)
+{
+       struct csi2rx_priv *csi2rx = v4l2_subdev_to_csi2rx(subdev);
+       int ret;
+
+       if (which == V4L2_SUBDEV_FORMAT_ACTIVE && csi2rx->count)
+               return -EBUSY;
+
+       ret = _csi2rx_set_routing(subdev, state, routing);
+       if (ret)
+               return ret;
+
+       return 0;
+}
+
 static int csi2rx_set_fmt(struct v4l2_subdev *subdev,
                          struct v4l2_subdev_state *state,
                          struct v4l2_subdev_format *format)
 {
        struct v4l2_mbus_framefmt *fmt;
-       unsigned int i;
 
        /* No transcoding, source and sink formats must match. */
        if (format->pad != CSI2RX_PAD_SINK)
@@ -552,14 +666,16 @@ static int csi2rx_set_fmt(struct v4l2_subdev *subdev,
        format->format.field = V4L2_FIELD_NONE;
 
        /* Set sink format */
-       fmt = v4l2_subdev_state_get_format(state, format->pad);
+       fmt = v4l2_subdev_state_get_format(state, format->pad, format->stream);
        *fmt = format->format;
 
-       /* Propagate to source formats */
-       for (i = CSI2RX_PAD_SOURCE_STREAM0; i < CSI2RX_PAD_MAX; i++) {
-               fmt = v4l2_subdev_state_get_format(state, i);
-               *fmt = format->format;
-       }
+       /* Propagate to source format */
+       fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad,
+                                                          format->stream);
+       if (!fmt)
+               return -EINVAL;
+
+       *fmt = format->format;
 
        return 0;
 }
@@ -567,21 +683,22 @@ static int csi2rx_set_fmt(struct v4l2_subdev *subdev,
 static int csi2rx_init_state(struct v4l2_subdev *subdev,
                             struct v4l2_subdev_state *state)
 {
-       struct v4l2_subdev_format format = {
-               .pad = CSI2RX_PAD_SINK,
-               .format = {
-                       .width = 640,
-                       .height = 480,
-                       .code = MEDIA_BUS_FMT_UYVY8_1X16,
-                       .field = V4L2_FIELD_NONE,
-                       .colorspace = V4L2_COLORSPACE_SRGB,
-                       .ycbcr_enc = V4L2_YCBCR_ENC_601,
-                       .quantization = V4L2_QUANTIZATION_LIM_RANGE,
-                       .xfer_func = V4L2_XFER_FUNC_SRGB,
+       struct v4l2_subdev_route routes[] = {
+               {
+                       .sink_pad = CSI2RX_PAD_SINK,
+                       .sink_stream = 0,
+                       .source_pad = CSI2RX_PAD_SOURCE_STREAM0,
+                       .source_stream = 0,
+                       .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
                },
        };
 
-       return csi2rx_set_fmt(subdev, state, &format);
+       struct v4l2_subdev_krouting routing = {
+               .num_routes = ARRAY_SIZE(routes),
+               .routes = routes,
+       };
+
+       return _csi2rx_set_routing(subdev, state, &routing);
 }
 
 int cdns_csi2rx_negotiate_ppc(struct v4l2_subdev *subdev, unsigned int pad,
@@ -589,35 +706,55 @@ int cdns_csi2rx_negotiate_ppc(struct v4l2_subdev *subdev, 
unsigned int pad,
 {
        struct csi2rx_priv *csi2rx = v4l2_subdev_to_csi2rx(subdev);
        const struct csi2rx_fmt *csi_fmt;
+       struct v4l2_subdev_route *route;
        struct v4l2_subdev_state *state;
        struct v4l2_mbus_framefmt *fmt;
+       int ret = 0;
 
        if (!ppc || pad < CSI2RX_PAD_SOURCE_STREAM0 || pad >= CSI2RX_PAD_MAX)
                return -EINVAL;
 
        state = v4l2_subdev_lock_and_get_active_state(subdev);
-       fmt = v4l2_subdev_state_get_format(state, pad);
-       csi_fmt = csi2rx_get_fmt_by_code(fmt->code);
+       /* Check all streams on requested pad */
+       for_each_active_route(&state->routing, route) {
+               if (route->source_pad != pad)
+                       continue;
+
+               fmt = v4l2_subdev_state_get_format(state, route->source_pad,
+                                                  route->source_stream);
+               if (!fmt) {
+                       ret = -EPIPE;
+                       *ppc = 1;
+                       break;
+               }
 
-       /* Reduce requested PPC if it is too high */
-       *ppc = min(*ppc, csi_fmt->max_pixels);
+               csi_fmt = csi2rx_get_fmt_by_code(fmt->code);
+               if (!csi_fmt) {
+                       ret = -EINVAL;
+                       *ppc = 1;
+                       break;
+               }
 
+               /* Reduce requested PPC if it is too high for this stream */
+               *ppc = min(*ppc, csi_fmt->max_pixels);
+       }
        v4l2_subdev_unlock_state(state);
 
        csi2rx->num_pixels[pad - CSI2RX_PAD_SOURCE_STREAM0] =
                CSI2RX_STREAM_CFG_NUM_PIXELS(*ppc);
 
-       return 0;
+       return ret;
 }
 EXPORT_SYMBOL_FOR_MODULES(cdns_csi2rx_negotiate_ppc, "j721e-csi2rx");
 
 static const struct v4l2_subdev_pad_ops csi2rx_pad_ops = {
-       .enum_mbus_code = csi2rx_enum_mbus_code,
-       .get_fmt        = v4l2_subdev_get_fmt,
-       .set_fmt        = csi2rx_set_fmt,
-       .enable_streams         = csi2rx_enable_streams,
-       .disable_streams        = csi2rx_disable_streams,
-       .get_frame_desc = v4l2_subdev_get_frame_desc_passthrough,
+       .enum_mbus_code         = csi2rx_enum_mbus_code,
+       .get_fmt                = v4l2_subdev_get_fmt,
+       .set_fmt                = csi2rx_set_fmt,
+       .get_frame_desc         = v4l2_subdev_get_frame_desc_passthrough,
+       .set_routing            = csi2rx_set_routing,
+       .enable_streams         = csi2rx_enable_streams,
+       .disable_streams        = csi2rx_disable_streams,
 };
 
 static const struct v4l2_subdev_core_ops csi2rx_core_ops = {
@@ -636,6 +773,7 @@ static const struct v4l2_subdev_internal_ops 
csi2rx_internal_ops = {
 static const struct media_entity_operations csi2rx_media_ops = {
        .link_validate = v4l2_subdev_link_validate,
        .get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1,
+       .has_pad_interdep = v4l2_subdev_has_pad_interdep,
 };
 
 static int csi2rx_async_bound(struct v4l2_async_notifier *notifier,
@@ -853,7 +991,8 @@ static int csi2rx_probe(struct platform_device *pdev)
        csi2rx->pads[CSI2RX_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
        for (i = CSI2RX_PAD_SOURCE_STREAM0; i < CSI2RX_PAD_MAX; i++)
                csi2rx->pads[i].flags = MEDIA_PAD_FL_SOURCE;
-       csi2rx->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+       csi2rx->subdev.flags = V4L2_SUBDEV_FL_HAS_DEVNODE |
+               V4L2_SUBDEV_FL_STREAMS;
        csi2rx->subdev.entity.ops = &csi2rx_media_ops;
 
        ret = media_entity_pads_init(&csi2rx->subdev.entity, CSI2RX_PAD_MAX,
_______________________________________________
linuxtv-commits mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to