public inbox for linux-media@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH v9 00/19] media: cadence,ti: CSI2RX Multistream Support
@ 2025-12-30  8:32 Rishikesh Donadkar
  2025-12-30  8:32 ` [PATCH v9 01/19] media: ti: j721e-csi2rx: Remove word size alignment on frame width Rishikesh Donadkar
                   ` (18 more replies)
  0 siblings, 19 replies; 54+ messages in thread
From: Rishikesh Donadkar @ 2025-12-30  8:32 UTC (permalink / raw)
  To: jai.luthra, laurent.pinchart, mripard
  Cc: r-donadkar, y-abhilashchandra, devarsht, s-jain1, vigneshr,
	mchehab, robh, krzk+dt, p.zabel, conor+dt, sakari.ailus,
	hverkuil-cisco, tomi.valkeinen, jai.luthra, changhuang.liang,
	jack.zhu, sjoerd, dan.carpenter, hverkuil+cisco, linux-kernel,
	linux-media, devicetree

This series adds multi-stream support and PM support for Cadence CSI2RX
and TI CSI2RX SHIM drivers.

PM patches are picked from:
https://lore.kernel.org/all/20250902-ti_csi_pm-v2-0-59a3be199940@ideasonboard.com/

PATCH 01 :    Remove word size alignment restriction on frame width
PATCH 02-09:  Support multiple DMA contexts/video nodes in TI CSI2RX
PATCH 10-11:  Use get_frame_desc to propagate virtual channel
              information across Cadence and TI CSI-RX subdevs
PATCH 12-13:  Use new multi-stream APIs across the drivers to support
              multiplexed cameras from sources like UB960 (FPDLink)
PATCH 14:     Optimize stream on by submitting all queued buffers to DMA
PATCH 15-16:  Change the drain architecture to support multi-stream,
              implement completion barriers for last drain
PATCH 17-19:  Runtime PM and System PM support for CSI-RX.

Testing for this series has been done on top of media tree with 4x IMX219
camera modules connected to TI's AM62A using V3 Link fusion mini board.

Runtime PM and System PM has been tested with IMX219 camera module
connect to TI's AM62.

Overlay and defconfig changes for the same can be found below:
https://github.com/RISHI27-dot/linux/commits/u/multistream_v9

v4l2-compliance results:
https://gist.github.com/Rishikesh-D/6bc8ab1f7dcb1b3bb4813473aacf3f4f

---
Changes in v9:

# New patches in v9:
[PATCH v9 08/18] media: staging: starfive: Move to .enable/disable_streams API
- Remove the s_stream API and use the enable/disable_streams API in stf
  drivers
[PATCH v9 16/19] media: ti: j721e-csi2rx: Return the partial frame as error
- Return the next frame after drain to usersapce as VB2_BUF_STATE_ERROR

# Changes:
[PATCH v9 01/18] media: ti: j721e-csi2rx: Remove word size alignment on frame width
- Ad R-by Tomi
[PATCH v9 06/18] media: ti: j721e-csi2rx: add a subdev for the core device
- Squash [PATCH v8 08/18] into this patch so that we dont have to use
  s_stream
- Use BIT_U64() instead of BIT()
[PATCH v9 07/18] media: cadence: csi2rx: Move to .enable/disable_streams API
- Use BIT_U64() instead of BIT()
[PATCH v9 11/19] media: ti: j721e-csi2rx: add support for processing virtual channels
- Update to commit message to s/hardcoded/VC0/
- Remove the mention of caching VC data from the commit message as that
  is done in 13/19
- Update the commit message to s/per stream/single stream/
[PATCH v9 12/19] media: cadence: csi2rx: add multistream support
- Remove the s_stream fallback
[PATCH v9 13/19] media: ti: j721e-csi2rx: add multistream support
- Move the code that is accessing the state from .start_streaming() call
  to .enabel_streams() call
- Remove the check for V4L2_SUBDEV_ROUTE_FL_ACTIVE
- Change the logic of for_each_active_route() usage to break the loop
  when the route is found
- Change the format variable to static const
- Update the commit message to s/4/32 for number of possible streams
[PATCH v9 17/19] media: cadence: csi2rx: Support runtime PM
- Shift to goto based error handing in the .enable_streams() function
[PATCH v9 18/19] media: ti: j721e-csi2rx: Support runtime suspend
- Make varialbe i and ret unsigned
- Move runtime_enable() call before notifier_register() in the .probe()
- In the .remove() function if the runtime PM state is active, set it to
  suspended and remove the pm_runtime_set_suspended() call at the end.
- Drop the call to init_dma() from init/cleanup_ctx()
- Remove pm_runtime_set_active() and pm_request_idle() from .probe()

Link to (v8):
https://lore.kernel.org/all/20251112115459.2479225-1-r-donadkar@ti.com/#t
---
Changes in v8:

# New patches in v8:
[PATCH v8 07/18] media: cadence: csi2rx: Move to .enable/disable_streams API
[PATCH v8 08/18] media: ti: j721e-csi2rx: Move to .enable/disable_streams API
- Remove the s_stream API and use the enable/disable_streams API right
  after adding the subdev for core device

Changes:
[PATCH v8 01/18] media: ti: j721e-csi2rx: Remove word size alignment on frame width
- Remove define for PSIL_WORD_SIZE_BYTES.
[PATCH v8 06/18] media: ti: j721e-csi2rx: add a subdev for the core device
- Use v4l2_subdev_state_get_format() instead of using the get_fmt from the 
  subdev pad ops in the ti_csi2rx_link_validate() function.
[PATCH v8 11/18] media: ti: j721e-csi2rx: add support for processing virtual channels
- Move routing support form this patch to PATCH 13/18.
[PATCH v8 12/18] media: cadence: csi2rx: add multistream support
- Move support for enable/disable_streams() API that was added in this patch to
  PATCH 07/18 earlier in this series.
- Remove the mutex from the driver
[PATCH v8 13/18] media: ti: j721e-csi2rx: add multistream support
- Move support for enable/disable_streams() API that was added in this patch to
  PATCH 08/18 earlier in this series.
- Remove csi->mutex protection inside enable/disable_streams() function calls to
  solve circular locking dependency warning.
- Mention that DT filtering is also supported in the commit message.

Link to (v7):
https://lore.kernel.org/all/20250911102832.1583440-1-r-donadkar@ti.com/#t

---
Changes in v7:

[PATCH v6 03/16] media: ti: j721e-csi2rx: separate out device and context
- Resolve bisect test fail because of undefined 'csi' variable
- Resolve bisect test fail because of 'vdev' not being in scope when used
[PATCH v6 07/16] media: ti: j721e-csi2rx: get number of contexts from device tree
- Resolve compiler warning regarding missing error code 'ret'
[PATCH v6 12/16] media: ti: j721e-csi2rx: Submit all available buffers
- Remove unused variable

Link to (v6):
https://lore.kernel.org/all/20250908134729.3940366-1-r-donadkar@ti.com/

---
Changes in v6:

# New patches in v6:

[PATCH v6 14/16] media: cadence: csi2rx: Support runtime PM
[PATCH v6 15/16] media: ti: j721e-csi2rx: Support runtime suspend
[PATCH v6 16/16] media: ti: j721e-csi2rx: Support system suspend
using pm_notifier

# Changes in patches from v5:
[PATCH v5 01/16] media: ti: j721e-csi2rx: Remove word size alignment
on frame width
- Remove restrictions on minimum frame width in ti_csi2rx_fill_fmt()
- Add Reviewed-by from Abhilash
[PATCH v5 02/16] dt-bindings: media: ti,j721e-csi2rx-shim: Support 32 dma chans
- No change
[PATCH v5 03/16] media: ti: j721e-csi2rx: separate out device and context
- No change
[PATCH v5 04/16] media: ti: j721e-csi2rx: prepare SHIM code for multiple contexts
- No change
[PATCH v5 05/16] media: ti: j721e-csi2rx: allocate DMA channel based on context index
- No change
[PATCH v5 06/16] media: ti: j721e-csi2rx: add a subdev for the core device
- No change
[PATCH v5 07/16] media: ti: j721e-csi2rx: get number of contexts from device tree
- Solve compilation warnings
[PATCH v5 08/16] media: cadence: csi2rx: add get_frame_desc wrapper
- No change
[PATCH v5 09/16] media: ti: j721e-csi2rx: add support for processing virtual channels
- Remove unnecessary call to v4l2_unlock_state()
[PATCH v5 10/16] media: cadence: csi2rx: add multistream support
- No change
[PATCH v5 11/16] media: ti: j721e-csi2rx: add multistream support
- No change
[PATCH v5 12/16] media: ti: j721e-csi2rx: Submit all available buffers
- No change
[PATCH v5 13/16] media: ti: j721e-csi2rx: Change the drain architecture for multistream
- Squash implementation of completion barriers for last drain cycle
- Make struct completion per ctx

Link to (v5):
https://lore.kernel.org/all/20250825142522.1826188-1-r-donadkar@ti.com/

---
Changes in v5:

# New patches in v5:

[PATCH v5 01/14] media: ti: j721e-csi2rx: Remove word size alignment
[PATCH v5 14/14] media: ti: j721e-csi2rx: Wait for the last drain

# Changes in patches from v4:

[PATCH v4 01/12] dt-bindings: media: ti,j721e-csi2rx-shim: Support 32 dma chans
- No change
[PATCH v4 02/12] media: ti: j721e-csi2rx: separate out device and context
- No change
[PATCH v4 03/12] media: ti: j721e-csi2rx: prepare SHIM code for multiple contexts
- No change
[PATCH v4 04/12] media: ti: j721e-csi2rx: allocate DMA channel based on context index
- No change
[PATCH v4 05/12] media: ti: j721e-csi2rx: add a subdev for the core device
- No change
[PATCH v4 06/12] media: ti: j721e-csi2rx: get number of contexts from device tree
- No change
[PATCH v4 07/12] media: cadence: csi2rx: add get_frame_desc wrapper
- No change
[PATCH v4 08/12] media: ti: j721e-csi2rx: add support for processing virtual channels
- No change
[PATCH v4 09/12] media: cadence: csi2rx: add multistream support
- No change
[PATCH v4 10/12] media: ti: j721e-csi2rx: add multistream support
- Serialize stream stop
- Remove the break statement to avoid early return in the loop, as
  reported by Sjoerd
[PATCH v4 11/12] media: ti: j721e-csi2rx: Submit all available buffers
- Delete the list node on DMA error to avoid kernel panic
[PATCH v4 12/12] media: ti: j721e-csi2rx: Change the drain architecture for multistream
- Mention about next frame after drain being bogus

Link to (v4):
  https://lore.kernel.org/all/20250514112527.1983068-1-r-donadkar@ti.com/

Changes in v4:

[PATCH 01/13] dt-bindings: media: ti,j721e-csi2rx-shim: Support 32 dma chans
  - No change
[PATCH 02/13] media: ti: j721e-csi2rx: separate out device and context
  - Add ctx identifier in the dev_err() message
  - No change
[PATCH 03/13] media: ti: j721e-csi2rx: prepare SHIM code for multiple contexts
  - Reduced the name string lenght from 32 chars to 5 chars
[PATCH 04/13] media: ti: j721e-csi2rx: allocate DMA channel based on context index
  - No change
[PATCH 05/13] media: ti: j721e-csi2rx: add a subdev for the core device
  - Add .enum_mbus_code callback
  - Replace statically allocated struct with a global static const struct
    v4l2_mbus_framefmt and used that in the _init_state() function
[PATCH 06/13] media: ti: j721e-csi2rx: get number of contexts from device tree
  - Fix the drain buffer being leaked
  - If the shows more number of ctx than the TI_CSI2RX_MAX_CTX, return an error
    instead of warning
[PATCH 07/13] media: cadence: csi2rx: add get_frame_desc wrapper
  - No change
[PATCH 08/13] media: ti: j721e-csi2rx: add support for processing virtual channels
  - Call ti_csi2rx_get_vc() only once on first stream start and cache the VC data in
    the driver, use the corresponding VC in all subsequent stream starts.
[PATCH 09/13] media: cadence: csi2rx: Use new enable stream APIs
[PATCH 10/13] media: cadence: csi2rx: Enable multi-stream support
  - Squash the above two patches into
    [PATCH v4 09/12] media: cadence: csi2rx: add multistream support
  - Use already obtained csi2rx->source_pad in enable_streams() and
    disable_streams() call
  - Update commit message with the reason for using a custom helper for s_stream
    instead of v4l2_subdev_s_stream_helper()
  - Use v4l2_get_link_freq() variant that takes pad of the source as its first
    argument instead of the one that takes v4l2_ctrl_handler
  - Call v4l2_get_link_freq() with bpp = 0 to prevent fallback to V4L2_CID_PIXEL_RATE
    in multi-stream case
  - Use lock guards to simplify error handling
  - Call csi2rx_update_vc_select() at first stream start before enabling the controller
[PATCH 11/13] media: ti: j721e-csi2rx: add multistream support
  - No change
[PATCH 12/13] media: ti: j721e-csi2rx: Submit all available buffers
  - No change
[PATCH 13/13] media: ti: j721e-csi2rx: Change the drain architecture for multistream
  - Fix checkpatch warning
  - Change commit message to give a better description of the patch

Link to (v3):
  https://lore.kernel.org/all/20250417065554.437541-1-r-donadkar@ti.com/

Changes in v3:

- Drop [PATCH v2 01/13] media: cadence: csi2rx: Support runtime PM from
  v2, support for runtime PM will be added in a separate series:
  https://lore.kernel.org/all/20250224-ti_csi_pm-v1-0-8f8c29ef646d@ideasonboard.com/
- Change the drain architecture to prevent FIFO overflow in multistream
  usecases.
- With the new drain architecture, we don't need the the driver to wait
  for userspace to start streaming on all "actively routed" video nodes
  before starting streaming on the source. So, revert back to the capture
  architecture where streams can be started and stopped independent
  to each other.

Link to (v2):
  https://lore.kernel.org/r/20240627-multistream-v2-0-6ae96c54c1c3@ti.com

Changes in v2:

- Change the multi-camera capture architecture to be similar to that of
  Tomi's RPi5 FE series, where the driver will wait for userspace to
  start streaming on all "actively routed" video nodes before starting
  streaming on the source. This simplifies things a lot from the HW
  perspective, which might run into deadlocks due to a shared FIFO
  between multiple DMA channels.

- Drop a few fixes that were posted separately and are already merged
- Fix dtschema warnings reported by Rob on [02/13]
- Fix warnings for uninitialized `used_vc` variable in cdns-csi2rx.c
- Return -EBUSY if someone updates routes for j721e-csi2rx subdev while
  streaming
- Only allow single-streams to be routed to the source pads (linked to
  video nodes) of the j721e-csi2rx device
- Squash the patches marked "SQUASH" in the v1 RFC series

Changhuang Liang (1):
  media: cadence: csi2rx: Support runtime PM

Jai Luthra (9):
  dt-bindings: media: ti,j721e-csi2rx-shim: Support 32 dma chans
  media: ti: j721e-csi2rx: separate out device and context
  media: ti: j721e-csi2rx: add a subdev for the core device
  media: ti: j721e-csi2rx: add support for processing virtual channels
  media: cadence: csi2rx: add multistream support
  media: ti: j721e-csi2rx: add multistream support
  media: ti: j721e-csi2rx: Submit all available buffers
  media: ti: j721e-csi2rx: Support runtime suspend
  media: ti: j721e-csi2rx: Support system suspend using pm_notifier

Pratyush Yadav (4):
  media: ti: j721e-csi2rx: prepare SHIM code for multiple contexts
  media: ti: j721e-csi2rx: allocate DMA channel based on context index
  media: ti: j721e-csi2rx: get number of contexts from device tree
  media: cadence: csi2rx: add get_frame_desc wrapper

Rishikesh Donadkar (5):
  media: ti: j721e-csi2rx: Remove word size alignment on frame width
  media: cadence: csi2rx: Move to .enable/disable_streams API
  media: staging: starfive: Move to enabel-disable streams in starfive
    drivers
  media: ti: j721e-csi2rx: Change the drain architecture for multistream
  media: ti: j721e-csi2rx: Return the partial frame as error

 .../bindings/media/ti,j721e-csi2rx-shim.yaml  |   39 +-
 drivers/media/platform/cadence/Kconfig        |    1 +
 drivers/media/platform/cadence/cdns-csi2rx.c  |  480 +++++--
 drivers/media/platform/ti/Kconfig             |    1 +
 .../platform/ti/j721e-csi2rx/j721e-csi2rx.c   | 1200 ++++++++++++-----
 .../staging/media/starfive/camss/stf-isp.c    |   43 +-
 .../staging/media/starfive/camss/stf-video.c  |    4 +-
 7 files changed, 1311 insertions(+), 457 deletions(-)

-- 
2.34.1


^ permalink raw reply	[flat|nested] 54+ messages in thread

* [PATCH v9 01/19] media: ti: j721e-csi2rx: Remove word size alignment on frame width
  2025-12-30  8:32 [PATCH v9 00/19] media: cadence,ti: CSI2RX Multistream Support Rishikesh Donadkar
@ 2025-12-30  8:32 ` Rishikesh Donadkar
  2025-12-30  8:32 ` [PATCH v9 02/19] dt-bindings: media: ti,j721e-csi2rx-shim: Support 32 dma chans Rishikesh Donadkar
                   ` (17 subsequent siblings)
  18 siblings, 0 replies; 54+ messages in thread
From: Rishikesh Donadkar @ 2025-12-30  8:32 UTC (permalink / raw)
  To: jai.luthra, laurent.pinchart, mripard
  Cc: r-donadkar, y-abhilashchandra, devarsht, s-jain1, vigneshr,
	mchehab, robh, krzk+dt, p.zabel, conor+dt, sakari.ailus,
	hverkuil-cisco, tomi.valkeinen, jai.luthra, changhuang.liang,
	jack.zhu, sjoerd, dan.carpenter, hverkuil+cisco, linux-kernel,
	linux-media, devicetree

j721e-csi2rx driver has a limitation of frame width being a multiple
word size. However, there is no such limitation imposed by the
hardware [1].

Remove this limitation from the driver.

Link: https://www.ti.com/lit/pdf/spruj16
Reviewed-by: Yemike Abhilash Chandra <y-abhilashchandra@ti.com>
Reviewed-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
---
 .../platform/ti/j721e-csi2rx/j721e-csi2rx.c   | 24 ++++---------------
 1 file changed, 4 insertions(+), 20 deletions(-)

diff --git a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
index b75aa363d1bf2..710d05a053539 100644
--- a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
+++ b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
@@ -43,7 +43,6 @@
 #define SHIM_PSI_CFG0_DST_TAG		GENMASK(31, 16)
 
 #define TI_CSI2RX_MAX_PIX_PER_CLK	4
-#define PSIL_WORD_SIZE_BYTES		16
 /*
  * There are no hard limits on the width or height. The DMA engine can handle
  * all sizes. The max width and height are arbitrary numbers for this driver.
@@ -250,19 +249,12 @@ static void ti_csi2rx_fill_fmt(const struct ti_csi2rx_fmt *csi_fmt,
 			       struct v4l2_format *v4l2_fmt)
 {
 	struct v4l2_pix_format *pix = &v4l2_fmt->fmt.pix;
-	unsigned int pixels_in_word;
-
-	pixels_in_word = PSIL_WORD_SIZE_BYTES * 8 / csi_fmt->bpp;
 
 	/* Clamp width and height to sensible maximums (16K x 16K) */
 	pix->width = clamp_t(unsigned int, pix->width,
-			     pixels_in_word,
-			     MAX_WIDTH_BYTES * 8 / csi_fmt->bpp);
+			     1, MAX_WIDTH_BYTES * 8 / csi_fmt->bpp);
 	pix->height = clamp_t(unsigned int, pix->height, 1, MAX_HEIGHT_LINES);
 
-	/* Width should be a multiple of transfer word-size */
-	pix->width = rounddown(pix->width, pixels_in_word);
-
 	v4l2_fmt->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
 	pix->pixelformat = csi_fmt->fourcc;
 	pix->bytesperline = pix->width * (csi_fmt->bpp / 8);
@@ -360,23 +352,15 @@ static int ti_csi2rx_enum_framesizes(struct file *file, void *fh,
 				     struct v4l2_frmsizeenum *fsize)
 {
 	const struct ti_csi2rx_fmt *fmt;
-	unsigned int pixels_in_word;
 
 	fmt = find_format_by_fourcc(fsize->pixel_format);
 	if (!fmt || fsize->index != 0)
 		return -EINVAL;
 
-	/*
-	 * Number of pixels in one PSI-L word. The transfer happens in multiples
-	 * of PSI-L word sizes.
-	 */
-	pixels_in_word = PSIL_WORD_SIZE_BYTES * 8 / fmt->bpp;
-
 	fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
-	fsize->stepwise.min_width = pixels_in_word;
-	fsize->stepwise.max_width = rounddown(MAX_WIDTH_BYTES * 8 / fmt->bpp,
-					      pixels_in_word);
-	fsize->stepwise.step_width = pixels_in_word;
+	fsize->stepwise.min_width = 1;
+	fsize->stepwise.max_width = MAX_WIDTH_BYTES * 8 / fmt->bpp;
+	fsize->stepwise.step_width = 1;
 	fsize->stepwise.min_height = 1;
 	fsize->stepwise.max_height = MAX_HEIGHT_LINES;
 	fsize->stepwise.step_height = 1;
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v9 02/19] dt-bindings: media: ti,j721e-csi2rx-shim: Support 32 dma chans
  2025-12-30  8:32 [PATCH v9 00/19] media: cadence,ti: CSI2RX Multistream Support Rishikesh Donadkar
  2025-12-30  8:32 ` [PATCH v9 01/19] media: ti: j721e-csi2rx: Remove word size alignment on frame width Rishikesh Donadkar
@ 2025-12-30  8:32 ` Rishikesh Donadkar
  2025-12-30  8:32 ` [PATCH v9 03/19] media: ti: j721e-csi2rx: separate out device and context Rishikesh Donadkar
                   ` (16 subsequent siblings)
  18 siblings, 0 replies; 54+ messages in thread
From: Rishikesh Donadkar @ 2025-12-30  8:32 UTC (permalink / raw)
  To: jai.luthra, laurent.pinchart, mripard
  Cc: r-donadkar, y-abhilashchandra, devarsht, s-jain1, vigneshr,
	mchehab, robh, krzk+dt, p.zabel, conor+dt, sakari.ailus,
	hverkuil-cisco, tomi.valkeinen, jai.luthra, changhuang.liang,
	jack.zhu, sjoerd, dan.carpenter, hverkuil+cisco, linux-kernel,
	linux-media, devicetree

From: Jai Luthra <j-luthra@ti.com>

The CSI2RX SHIM IP can support 32x DMA channels. These can be used to
split incoming "streams" of data on the CSI-RX port, distinguished by
MIPI Virtual Channel (or Data Type), into different locations in memory.

Actual number of DMA channels allocated to CSI-RX is dependent on the
usecase, and can be modified using the K3 Resource Partitioning tool [1].
So set the minimum channels as 1 and maximum as 32.

Link: https://software-dl.ti.com/processor-sdk-linux/esd/AM62X/10_00_07_04/exports/docs/linux/How_to_Guides/Host/K3_Resource_Partitioning_Tool.html [1]
Link: https://www.ti.com/lit/pdf/spruiv7
Signed-off-by: Jai Luthra <j-luthra@ti.com>
Reviewed-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
Reviewed-by: Yemike Abhilash Chandra <y-abhilashchandra@ti.com>
Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
---
 .../bindings/media/ti,j721e-csi2rx-shim.yaml  | 39 +++++++++++++++++--
 1 file changed, 36 insertions(+), 3 deletions(-)

diff --git a/Documentation/devicetree/bindings/media/ti,j721e-csi2rx-shim.yaml b/Documentation/devicetree/bindings/media/ti,j721e-csi2rx-shim.yaml
index b9f033f2f3ce4..bf62998b0445a 100644
--- a/Documentation/devicetree/bindings/media/ti,j721e-csi2rx-shim.yaml
+++ b/Documentation/devicetree/bindings/media/ti,j721e-csi2rx-shim.yaml
@@ -20,11 +20,44 @@ properties:
     const: ti,j721e-csi2rx-shim
 
   dmas:
-    maxItems: 1
+    minItems: 1
+    maxItems: 32
 
   dma-names:
+    minItems: 1
     items:
       - const: rx0
+      - const: rx1
+      - const: rx2
+      - const: rx3
+      - const: rx4
+      - const: rx5
+      - const: rx6
+      - const: rx7
+      - const: rx8
+      - const: rx9
+      - const: rx10
+      - const: rx11
+      - const: rx12
+      - const: rx13
+      - const: rx14
+      - const: rx15
+      - const: rx16
+      - const: rx17
+      - const: rx18
+      - const: rx19
+      - const: rx20
+      - const: rx21
+      - const: rx22
+      - const: rx23
+      - const: rx24
+      - const: rx25
+      - const: rx26
+      - const: rx27
+      - const: rx28
+      - const: rx29
+      - const: rx30
+      - const: rx31
 
   reg:
     maxItems: 1
@@ -62,8 +95,8 @@ examples:
 
     ti_csi2rx0: ticsi2rx@4500000 {
         compatible = "ti,j721e-csi2rx-shim";
-        dmas = <&main_udmap 0x4940>;
-        dma-names = "rx0";
+        dmas = <&main_udmap 0x4940>, <&main_udmap 0x4941>;
+        dma-names = "rx0", "rx1";
         reg = <0x4500000 0x1000>;
         power-domains = <&k3_pds 26 TI_SCI_PD_EXCLUSIVE>;
         #address-cells = <1>;
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v9 03/19] media: ti: j721e-csi2rx: separate out device and context
  2025-12-30  8:32 [PATCH v9 00/19] media: cadence,ti: CSI2RX Multistream Support Rishikesh Donadkar
  2025-12-30  8:32 ` [PATCH v9 01/19] media: ti: j721e-csi2rx: Remove word size alignment on frame width Rishikesh Donadkar
  2025-12-30  8:32 ` [PATCH v9 02/19] dt-bindings: media: ti,j721e-csi2rx-shim: Support 32 dma chans Rishikesh Donadkar
@ 2025-12-30  8:32 ` Rishikesh Donadkar
  2025-12-30  8:32 ` [PATCH v9 04/19] media: ti: j721e-csi2rx: prepare SHIM code for multiple contexts Rishikesh Donadkar
                   ` (15 subsequent siblings)
  18 siblings, 0 replies; 54+ messages in thread
From: Rishikesh Donadkar @ 2025-12-30  8:32 UTC (permalink / raw)
  To: jai.luthra, laurent.pinchart, mripard
  Cc: r-donadkar, y-abhilashchandra, devarsht, s-jain1, vigneshr,
	mchehab, robh, krzk+dt, p.zabel, conor+dt, sakari.ailus,
	hverkuil-cisco, tomi.valkeinen, jai.luthra, changhuang.liang,
	jack.zhu, sjoerd, dan.carpenter, hverkuil+cisco, linux-kernel,
	linux-media, devicetree

From: Jai Luthra <j-luthra@ti.com>

The TI CSI2RX wrapper has two parts: the main device and the DMA
contexts. The driver was originally written with single camera capture
in mind, so only one DMA context was needed. For the sake of simplicity,
the context specific stuff was not modeled different to the main device.

To enable multiplexed stream capture, the contexts need to be separated
out from the main device. Create a struct ti_csi2rx_ctx that holds the
DMA context specific things. Separate out functions handling the device
and context related functionality.

Reviewed-by: Yemike Abhilash Chandra <y-abhilashchandra@ti.com>
Reviewed-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
Co-developed-by: Pratyush Yadav <p.yadav@ti.com>
Signed-off-by: Pratyush Yadav <p.yadav@ti.com>
Signed-off-by: Jai Luthra <j-luthra@ti.com>
Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
---
 .../platform/ti/j721e-csi2rx/j721e-csi2rx.c   | 423 ++++++++++--------
 1 file changed, 234 insertions(+), 189 deletions(-)

diff --git a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
index 710d05a053539..23717a3b6c4cf 100644
--- a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
+++ b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
@@ -43,6 +43,8 @@
 #define SHIM_PSI_CFG0_DST_TAG		GENMASK(31, 16)
 
 #define TI_CSI2RX_MAX_PIX_PER_CLK	4
+#define TI_CSI2RX_NUM_CTX		1
+
 /*
  * There are no hard limits on the width or height. The DMA engine can handle
  * all sizes. The max width and height are arbitrary numbers for this driver.
@@ -69,7 +71,7 @@ struct ti_csi2rx_buffer {
 	/* Common v4l2 buffer. Must be first. */
 	struct vb2_v4l2_buffer		vb;
 	struct list_head		list;
-	struct ti_csi2rx_dev		*csi;
+	struct ti_csi2rx_ctx		*ctx;
 };
 
 enum ti_csi2rx_dma_state {
@@ -89,30 +91,38 @@ struct ti_csi2rx_dma {
 	 * Queue of buffers submitted to DMA engine.
 	 */
 	struct list_head		submitted;
-	/* Buffer to drain stale data from PSI-L endpoint */
-	struct {
-		void			*vaddr;
-		dma_addr_t		paddr;
-		size_t			len;
-	} drain;
+};
+
+struct ti_csi2rx_dev;
+
+struct ti_csi2rx_ctx {
+	struct ti_csi2rx_dev		*csi;
+	struct video_device		vdev;
+	struct vb2_queue		vidq;
+	struct mutex			mutex; /* To serialize ioctls. */
+	struct v4l2_format		v_fmt;
+	struct ti_csi2rx_dma		dma;
+	u32				sequence;
+	u32				idx;
 };
 
 struct ti_csi2rx_dev {
 	struct device			*dev;
 	void __iomem			*shim;
 	struct v4l2_device		v4l2_dev;
-	struct video_device		vdev;
 	struct media_device		mdev;
 	struct media_pipeline		pipe;
 	struct media_pad		pad;
 	struct v4l2_async_notifier	notifier;
 	struct v4l2_subdev		*source;
-	struct vb2_queue		vidq;
-	struct mutex			mutex; /* To serialize ioctls. */
-	struct v4l2_format		v_fmt;
-	struct ti_csi2rx_dma		dma;
-	u32				sequence;
+	struct ti_csi2rx_ctx		ctx[TI_CSI2RX_NUM_CTX];
 	u8				pix_per_clk;
+	/* Buffer to drain stale data from PSI-L endpoint */
+	struct {
+		void			*vaddr;
+		dma_addr_t		paddr;
+		size_t			len;
+	} drain;
 };
 
 static const struct ti_csi2rx_fmt ti_csi2rx_formats[] = {
@@ -218,7 +228,7 @@ static const struct ti_csi2rx_fmt ti_csi2rx_formats[] = {
 };
 
 /* Forward declaration needed by ti_csi2rx_dma_callback. */
-static int ti_csi2rx_start_dma(struct ti_csi2rx_dev *csi,
+static int ti_csi2rx_start_dma(struct ti_csi2rx_ctx *ctx,
 			       struct ti_csi2rx_buffer *buf);
 
 static const struct ti_csi2rx_fmt *find_format_by_fourcc(u32 pixelformat)
@@ -301,7 +311,7 @@ static int ti_csi2rx_enum_fmt_vid_cap(struct file *file, void *priv,
 static int ti_csi2rx_g_fmt_vid_cap(struct file *file, void *priv,
 				   struct v4l2_format *f)
 {
-	struct ti_csi2rx_dev *csi = video_drvdata(file);
+	struct ti_csi2rx_ctx *csi = video_drvdata(file);
 
 	*f = csi->v_fmt;
 
@@ -332,7 +342,7 @@ static int ti_csi2rx_try_fmt_vid_cap(struct file *file, void *priv,
 static int ti_csi2rx_s_fmt_vid_cap(struct file *file, void *priv,
 				   struct v4l2_format *f)
 {
-	struct ti_csi2rx_dev *csi = video_drvdata(file);
+	struct ti_csi2rx_ctx *csi = video_drvdata(file);
 	struct vb2_queue *q = &csi->vidq;
 	int ret;
 
@@ -410,26 +420,35 @@ static int csi_async_notifier_bound(struct v4l2_async_notifier *notifier,
 static int csi_async_notifier_complete(struct v4l2_async_notifier *notifier)
 {
 	struct ti_csi2rx_dev *csi = dev_get_drvdata(notifier->v4l2_dev->dev);
-	struct video_device *vdev = &csi->vdev;
-	int ret;
+	int ret, i;
 
-	ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
-	if (ret)
-		return ret;
-
-	ret = media_create_pad_link(&csi->source->entity, CSI2RX_BRIDGE_SOURCE_PAD,
-				    &vdev->entity, csi->pad.index,
-				    MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED);
+	for (i = 0; i < TI_CSI2RX_NUM_CTX; i++) {
+		struct ti_csi2rx_ctx *ctx = &csi->ctx[i];
+		struct video_device *vdev = &ctx->vdev;
 
-	if (ret) {
-		video_unregister_device(vdev);
-		return ret;
+		ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
+		if (ret)
+			goto unregister_dev;
 	}
 
+	ret = media_create_pad_link(&csi->source->entity,
+				    CSI2RX_BRIDGE_SOURCE_PAD,
+				    &csi->ctx[0].vdev.entity, csi->pad.index,
+				    MEDIA_LNK_FL_IMMUTABLE |
+				    MEDIA_LNK_FL_ENABLED);
+	if (ret)
+		goto unregister_dev;
+
 	ret = v4l2_device_register_subdev_nodes(&csi->v4l2_dev);
 	if (ret)
-		video_unregister_device(vdev);
+		goto unregister_dev;
 
+	return 0;
+
+unregister_dev:
+	i--;
+	for (; i >= 0; i--)
+		video_unregister_device(&csi->ctx[i].vdev);
 	return ret;
 }
 
@@ -474,13 +493,14 @@ static int ti_csi2rx_notifier_register(struct ti_csi2rx_dev *csi)
 }
 
 /* Request maximum possible pixels per clock from the bridge */
-static void ti_csi2rx_request_max_ppc(struct ti_csi2rx_dev *csi)
+static void ti_csi2rx_request_max_ppc(struct ti_csi2rx_ctx *ctx)
 {
+	struct ti_csi2rx_dev *csi = ctx->csi;
 	u8 ppc = TI_CSI2RX_MAX_PIX_PER_CLK;
 	struct media_pad *pad;
 	int ret;
 
-	pad = media_entity_remote_source_pad_unique(&csi->vdev.entity);
+	pad = media_entity_remote_source_pad_unique(&ctx->vdev.entity);
 	if (IS_ERR(pad))
 		return;
 
@@ -493,19 +513,20 @@ static void ti_csi2rx_request_max_ppc(struct ti_csi2rx_dev *csi)
 	}
 }
 
-static void ti_csi2rx_setup_shim(struct ti_csi2rx_dev *csi)
+static void ti_csi2rx_setup_shim(struct ti_csi2rx_ctx *ctx)
 {
+	struct ti_csi2rx_dev *csi = ctx->csi;
 	const struct ti_csi2rx_fmt *fmt;
 	unsigned int reg;
 
-	fmt = find_format_by_fourcc(csi->v_fmt.fmt.pix.pixelformat);
+	fmt = find_format_by_fourcc(ctx->v_fmt.fmt.pix.pixelformat);
 
 	/* De-assert the pixel interface reset. */
 	reg = SHIM_CNTL_PIX_RST;
 	writel(reg, csi->shim + SHIM_CNTL);
 
 	/* Negotiate pixel count from the source */
-	ti_csi2rx_request_max_ppc(csi);
+	ti_csi2rx_request_max_ppc(ctx);
 
 	reg = SHIM_DMACNTX_EN;
 	reg |= FIELD_PREP(SHIM_DMACNTX_FMT, fmt->csi_dt);
@@ -572,8 +593,9 @@ static void ti_csi2rx_drain_callback(void *param)
  * To prevent that stale data corrupting the subsequent transactions, it is
  * required to issue DMA requests to drain it out.
  */
-static int ti_csi2rx_drain_dma(struct ti_csi2rx_dev *csi)
+static int ti_csi2rx_drain_dma(struct ti_csi2rx_ctx *ctx)
 {
+	struct ti_csi2rx_dev *csi = ctx->csi;
 	struct dma_async_tx_descriptor *desc;
 	struct completion drain_complete;
 	dma_cookie_t cookie;
@@ -581,8 +603,8 @@ static int ti_csi2rx_drain_dma(struct ti_csi2rx_dev *csi)
 
 	init_completion(&drain_complete);
 
-	desc = dmaengine_prep_slave_single(csi->dma.chan, csi->dma.drain.paddr,
-					   csi->dma.drain.len, DMA_DEV_TO_MEM,
+	desc = dmaengine_prep_slave_single(ctx->dma.chan, csi->drain.paddr,
+					   csi->drain.len, DMA_DEV_TO_MEM,
 					   DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
 	if (!desc) {
 		ret = -EIO;
@@ -597,11 +619,11 @@ static int ti_csi2rx_drain_dma(struct ti_csi2rx_dev *csi)
 	if (ret)
 		goto out;
 
-	dma_async_issue_pending(csi->dma.chan);
+	dma_async_issue_pending(ctx->dma.chan);
 
 	if (!wait_for_completion_timeout(&drain_complete,
 					 msecs_to_jiffies(DRAIN_TIMEOUT_MS))) {
-		dmaengine_terminate_sync(csi->dma.chan);
+		dmaengine_terminate_sync(ctx->dma.chan);
 		dev_dbg(csi->dev, "DMA transfer timed out for drain buffer\n");
 		ret = -ETIMEDOUT;
 		goto out;
@@ -613,8 +635,9 @@ static int ti_csi2rx_drain_dma(struct ti_csi2rx_dev *csi)
 static void ti_csi2rx_dma_callback(void *param)
 {
 	struct ti_csi2rx_buffer *buf = param;
-	struct ti_csi2rx_dev *csi = buf->csi;
-	struct ti_csi2rx_dma *dma = &csi->dma;
+	struct ti_csi2rx_ctx *ctx = buf->ctx;
+	struct ti_csi2rx_dev *csi = ctx->csi;
+	struct ti_csi2rx_dma *dma = &ctx->dma;
 	unsigned long flags;
 
 	/*
@@ -622,7 +645,7 @@ static void ti_csi2rx_dma_callback(void *param)
 	 * hardware monitor registers.
 	 */
 	buf->vb.vb2_buf.timestamp = ktime_get_ns();
-	buf->vb.sequence = csi->sequence++;
+	buf->vb.sequence = ctx->sequence++;
 
 	spin_lock_irqsave(&dma->lock, flags);
 
@@ -634,7 +657,7 @@ static void ti_csi2rx_dma_callback(void *param)
 	while (!list_empty(&dma->queue)) {
 		buf = list_entry(dma->queue.next, struct ti_csi2rx_buffer, list);
 
-		if (ti_csi2rx_start_dma(csi, buf)) {
+		if (ti_csi2rx_start_dma(ctx, buf)) {
 			dev_err(csi->dev, "Failed to queue the next buffer for DMA\n");
 			list_del(&buf->list);
 			vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
@@ -649,17 +672,17 @@ static void ti_csi2rx_dma_callback(void *param)
 	spin_unlock_irqrestore(&dma->lock, flags);
 }
 
-static int ti_csi2rx_start_dma(struct ti_csi2rx_dev *csi,
+static int ti_csi2rx_start_dma(struct ti_csi2rx_ctx *ctx,
 			       struct ti_csi2rx_buffer *buf)
 {
 	unsigned long addr;
 	struct dma_async_tx_descriptor *desc;
-	size_t len = csi->v_fmt.fmt.pix.sizeimage;
+	size_t len = ctx->v_fmt.fmt.pix.sizeimage;
 	dma_cookie_t cookie;
 	int ret = 0;
 
 	addr = vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
-	desc = dmaengine_prep_slave_single(csi->dma.chan, addr, len,
+	desc = dmaengine_prep_slave_single(ctx->dma.chan, addr, len,
 					   DMA_DEV_TO_MEM,
 					   DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
 	if (!desc)
@@ -673,20 +696,20 @@ static int ti_csi2rx_start_dma(struct ti_csi2rx_dev *csi,
 	if (ret)
 		return ret;
 
-	dma_async_issue_pending(csi->dma.chan);
+	dma_async_issue_pending(ctx->dma.chan);
 
 	return 0;
 }
 
-static void ti_csi2rx_stop_dma(struct ti_csi2rx_dev *csi)
+static void ti_csi2rx_stop_dma(struct ti_csi2rx_ctx *ctx)
 {
-	struct ti_csi2rx_dma *dma = &csi->dma;
+	struct ti_csi2rx_dma *dma = &ctx->dma;
 	enum ti_csi2rx_dma_state state;
 	unsigned long flags;
 	int ret;
 
 	spin_lock_irqsave(&dma->lock, flags);
-	state = csi->dma.state;
+	state = ctx->dma.state;
 	dma->state = TI_CSI2RX_DMA_STOPPED;
 	spin_unlock_irqrestore(&dma->lock, flags);
 
@@ -697,30 +720,30 @@ static void ti_csi2rx_stop_dma(struct ti_csi2rx_dev *csi)
 		 * is stopped, as the module-level pixel reset cannot be
 		 * enforced before terminating DMA.
 		 */
-		ret = ti_csi2rx_drain_dma(csi);
+		ret = ti_csi2rx_drain_dma(ctx);
 		if (ret && ret != -ETIMEDOUT)
-			dev_warn(csi->dev,
+			dev_warn(ctx->csi->dev,
 				 "Failed to drain DMA. Next frame might be bogus\n");
 	}
 
-	ret = dmaengine_terminate_sync(csi->dma.chan);
+	ret = dmaengine_terminate_sync(ctx->dma.chan);
 	if (ret)
-		dev_err(csi->dev, "Failed to stop DMA: %d\n", ret);
+		dev_err(ctx->csi->dev, "Failed to stop DMA: %d\n", ret);
 }
 
-static void ti_csi2rx_cleanup_buffers(struct ti_csi2rx_dev *csi,
+static void ti_csi2rx_cleanup_buffers(struct ti_csi2rx_ctx *ctx,
 				      enum vb2_buffer_state state)
 {
-	struct ti_csi2rx_dma *dma = &csi->dma;
+	struct ti_csi2rx_dma *dma = &ctx->dma;
 	struct ti_csi2rx_buffer *buf, *tmp;
 	unsigned long flags;
 
 	spin_lock_irqsave(&dma->lock, flags);
-	list_for_each_entry_safe(buf, tmp, &csi->dma.queue, list) {
+	list_for_each_entry_safe(buf, tmp, &ctx->dma.queue, list) {
 		list_del(&buf->list);
 		vb2_buffer_done(&buf->vb.vb2_buf, state);
 	}
-	list_for_each_entry_safe(buf, tmp, &csi->dma.submitted, list) {
+	list_for_each_entry_safe(buf, tmp, &ctx->dma.submitted, list) {
 		list_del(&buf->list);
 		vb2_buffer_done(&buf->vb.vb2_buf, state);
 	}
@@ -731,8 +754,8 @@ static int ti_csi2rx_queue_setup(struct vb2_queue *q, unsigned int *nbuffers,
 				 unsigned int *nplanes, unsigned int sizes[],
 				 struct device *alloc_devs[])
 {
-	struct ti_csi2rx_dev *csi = vb2_get_drv_priv(q);
-	unsigned int size = csi->v_fmt.fmt.pix.sizeimage;
+	struct ti_csi2rx_ctx *ctx = vb2_get_drv_priv(q);
+	unsigned int size = ctx->v_fmt.fmt.pix.sizeimage;
 
 	if (*nplanes) {
 		if (sizes[0] < size)
@@ -748,11 +771,11 @@ static int ti_csi2rx_queue_setup(struct vb2_queue *q, unsigned int *nbuffers,
 
 static int ti_csi2rx_buffer_prepare(struct vb2_buffer *vb)
 {
-	struct ti_csi2rx_dev *csi = vb2_get_drv_priv(vb->vb2_queue);
-	unsigned long size = csi->v_fmt.fmt.pix.sizeimage;
+	struct ti_csi2rx_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
+	unsigned long size = ctx->v_fmt.fmt.pix.sizeimage;
 
 	if (vb2_plane_size(vb, 0) < size) {
-		dev_err(csi->dev, "Data will not fit into plane\n");
+		dev_err(ctx->csi->dev, "Data will not fit into plane\n");
 		return -EINVAL;
 	}
 
@@ -762,15 +785,15 @@ static int ti_csi2rx_buffer_prepare(struct vb2_buffer *vb)
 
 static void ti_csi2rx_buffer_queue(struct vb2_buffer *vb)
 {
-	struct ti_csi2rx_dev *csi = vb2_get_drv_priv(vb->vb2_queue);
+	struct ti_csi2rx_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
 	struct ti_csi2rx_buffer *buf;
-	struct ti_csi2rx_dma *dma = &csi->dma;
+	struct ti_csi2rx_dma *dma = &ctx->dma;
 	bool restart_dma = false;
 	unsigned long flags = 0;
 	int ret;
 
 	buf = container_of(vb, struct ti_csi2rx_buffer, vb.vb2_buf);
-	buf->csi = csi;
+	buf->ctx = ctx;
 
 	spin_lock_irqsave(&dma->lock, flags);
 	/*
@@ -799,18 +822,18 @@ static void ti_csi2rx_buffer_queue(struct vb2_buffer *vb)
 		 * the application and will only confuse it. Issue a DMA
 		 * transaction to drain that up.
 		 */
-		ret = ti_csi2rx_drain_dma(csi);
+		ret = ti_csi2rx_drain_dma(ctx);
 		if (ret && ret != -ETIMEDOUT)
-			dev_warn(csi->dev,
+			dev_warn(ctx->csi->dev,
 				 "Failed to drain DMA. Next frame might be bogus\n");
 
 		spin_lock_irqsave(&dma->lock, flags);
-		ret = ti_csi2rx_start_dma(csi, buf);
+		ret = ti_csi2rx_start_dma(ctx, buf);
 		if (ret) {
 			vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
 			dma->state = TI_CSI2RX_DMA_IDLE;
 			spin_unlock_irqrestore(&dma->lock, flags);
-			dev_err(csi->dev, "Failed to start DMA: %d\n", ret);
+			dev_err(ctx->csi->dev, "Failed to start DMA: %d\n", ret);
 		} else {
 			list_add_tail(&buf->list, &dma->submitted);
 			spin_unlock_irqrestore(&dma->lock, flags);
@@ -820,8 +843,9 @@ static void ti_csi2rx_buffer_queue(struct vb2_buffer *vb)
 
 static int ti_csi2rx_start_streaming(struct vb2_queue *vq, unsigned int count)
 {
-	struct ti_csi2rx_dev *csi = vb2_get_drv_priv(vq);
-	struct ti_csi2rx_dma *dma = &csi->dma;
+	struct ti_csi2rx_ctx *ctx = vb2_get_drv_priv(vq);
+	struct ti_csi2rx_dev *csi = ctx->csi;
+	struct ti_csi2rx_dma *dma = &ctx->dma;
 	struct ti_csi2rx_buffer *buf;
 	unsigned long flags;
 	int ret = 0;
@@ -833,18 +857,18 @@ static int ti_csi2rx_start_streaming(struct vb2_queue *vq, unsigned int count)
 	if (ret)
 		return ret;
 
-	ret = video_device_pipeline_start(&csi->vdev, &csi->pipe);
+	ret = video_device_pipeline_start(&ctx->vdev, &csi->pipe);
 	if (ret)
 		goto err;
 
-	ti_csi2rx_setup_shim(csi);
+	ti_csi2rx_setup_shim(ctx);
 
-	csi->sequence = 0;
+	ctx->sequence = 0;
 
 	spin_lock_irqsave(&dma->lock, flags);
 	buf = list_entry(dma->queue.next, struct ti_csi2rx_buffer, list);
 
-	ret = ti_csi2rx_start_dma(csi, buf);
+	ret = ti_csi2rx_start_dma(ctx, buf);
 	if (ret) {
 		dev_err(csi->dev, "Failed to start DMA: %d\n", ret);
 		spin_unlock_irqrestore(&dma->lock, flags);
@@ -862,22 +886,23 @@ static int ti_csi2rx_start_streaming(struct vb2_queue *vq, unsigned int count)
 	return 0;
 
 err_dma:
-	ti_csi2rx_stop_dma(csi);
+	ti_csi2rx_stop_dma(ctx);
 err_pipeline:
-	video_device_pipeline_stop(&csi->vdev);
+	video_device_pipeline_stop(&ctx->vdev);
 	writel(0, csi->shim + SHIM_CNTL);
 	writel(0, csi->shim + SHIM_DMACNTX);
 err:
-	ti_csi2rx_cleanup_buffers(csi, VB2_BUF_STATE_QUEUED);
+	ti_csi2rx_cleanup_buffers(ctx, VB2_BUF_STATE_QUEUED);
 	return ret;
 }
 
 static void ti_csi2rx_stop_streaming(struct vb2_queue *vq)
 {
-	struct ti_csi2rx_dev *csi = vb2_get_drv_priv(vq);
+	struct ti_csi2rx_ctx *ctx = vb2_get_drv_priv(vq);
+	struct ti_csi2rx_dev *csi = ctx->csi;
 	int ret;
 
-	video_device_pipeline_stop(&csi->vdev);
+	video_device_pipeline_stop(&ctx->vdev);
 
 	writel(0, csi->shim + SHIM_CNTL);
 	writel(0, csi->shim + SHIM_DMACNTX);
@@ -886,8 +911,8 @@ static void ti_csi2rx_stop_streaming(struct vb2_queue *vq)
 	if (ret)
 		dev_err(csi->dev, "Failed to stop subdev stream\n");
 
-	ti_csi2rx_stop_dma(csi);
-	ti_csi2rx_cleanup_buffers(csi, VB2_BUF_STATE_ERROR);
+	ti_csi2rx_stop_dma(ctx);
+	ti_csi2rx_cleanup_buffers(ctx, VB2_BUF_STATE_ERROR);
 }
 
 static const struct vb2_ops csi_vb2_qops = {
@@ -898,20 +923,43 @@ static const struct vb2_ops csi_vb2_qops = {
 	.stop_streaming = ti_csi2rx_stop_streaming,
 };
 
-static int ti_csi2rx_init_vb2q(struct ti_csi2rx_dev *csi)
+static void ti_csi2rx_cleanup_v4l2(struct ti_csi2rx_dev *csi)
 {
-	struct vb2_queue *q = &csi->vidq;
+	media_device_unregister(&csi->mdev);
+	v4l2_device_unregister(&csi->v4l2_dev);
+	media_device_cleanup(&csi->mdev);
+}
+
+static void ti_csi2rx_cleanup_notifier(struct ti_csi2rx_dev *csi)
+{
+	v4l2_async_nf_unregister(&csi->notifier);
+	v4l2_async_nf_cleanup(&csi->notifier);
+}
+
+static void ti_csi2rx_cleanup_ctx(struct ti_csi2rx_ctx *ctx)
+{
+	dma_release_channel(ctx->dma.chan);
+	vb2_queue_release(&ctx->vidq);
+
+	video_unregister_device(&ctx->vdev);
+
+	mutex_destroy(&ctx->mutex);
+}
+
+static int ti_csi2rx_init_vb2q(struct ti_csi2rx_ctx *ctx)
+{
+	struct vb2_queue *q = &ctx->vidq;
 	int ret;
 
 	q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
 	q->io_modes = VB2_MMAP | VB2_DMABUF;
-	q->drv_priv = csi;
+	q->drv_priv = ctx;
 	q->buf_struct_size = sizeof(struct ti_csi2rx_buffer);
 	q->ops = &csi_vb2_qops;
 	q->mem_ops = &vb2_dma_contig_memops;
 	q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
-	q->dev = dmaengine_get_dma_device(csi->dma.chan);
-	q->lock = &csi->mutex;
+	q->dev = dmaengine_get_dma_device(ctx->dma.chan);
+	q->lock = &ctx->mutex;
 	q->min_queued_buffers = 1;
 	q->allow_cache_hints = 1;
 
@@ -919,7 +967,7 @@ static int ti_csi2rx_init_vb2q(struct ti_csi2rx_dev *csi)
 	if (ret)
 		return ret;
 
-	csi->vdev.queue = q;
+	ctx->vdev.queue = q;
 
 	return 0;
 }
@@ -928,8 +976,9 @@ static int ti_csi2rx_link_validate(struct media_link *link)
 {
 	struct media_entity *entity = link->sink->entity;
 	struct video_device *vdev = media_entity_to_video_device(entity);
-	struct ti_csi2rx_dev *csi = container_of(vdev, struct ti_csi2rx_dev, vdev);
-	struct v4l2_pix_format *csi_fmt = &csi->v_fmt.fmt.pix;
+	struct ti_csi2rx_ctx *ctx = container_of(vdev, struct ti_csi2rx_ctx, vdev);
+	struct ti_csi2rx_dev *csi = ctx->csi;
+	struct v4l2_pix_format *csi_fmt = &ctx->v_fmt.fmt.pix;
 	struct v4l2_subdev_format source_fmt = {
 		.which	= V4L2_SUBDEV_FORMAT_ACTIVE,
 		.pad	= link->source->index,
@@ -982,47 +1031,63 @@ static const struct media_entity_operations ti_csi2rx_video_entity_ops = {
 	.link_validate = ti_csi2rx_link_validate,
 };
 
-static int ti_csi2rx_init_dma(struct ti_csi2rx_dev *csi)
+static int ti_csi2rx_init_dma(struct ti_csi2rx_ctx *ctx)
 {
 	struct dma_slave_config cfg = {
 		.src_addr_width = DMA_SLAVE_BUSWIDTH_16_BYTES,
 	};
 	int ret;
 
-	INIT_LIST_HEAD(&csi->dma.queue);
-	INIT_LIST_HEAD(&csi->dma.submitted);
-	spin_lock_init(&csi->dma.lock);
-
-	csi->dma.state = TI_CSI2RX_DMA_STOPPED;
+	ctx->dma.chan = dma_request_chan(ctx->csi->dev, "rx0");
+	if (IS_ERR(ctx->dma.chan))
+		return PTR_ERR(ctx->dma.chan);
 
-	csi->dma.chan = dma_request_chan(csi->dev, "rx0");
-	if (IS_ERR(csi->dma.chan))
-		return PTR_ERR(csi->dma.chan);
-
-	ret = dmaengine_slave_config(csi->dma.chan, &cfg);
+	ret = dmaengine_slave_config(ctx->dma.chan, &cfg);
 	if (ret) {
-		dma_release_channel(csi->dma.chan);
+		dma_release_channel(ctx->dma.chan);
 		return ret;
 	}
 
-	csi->dma.drain.len = DRAIN_BUFFER_SIZE;
-	csi->dma.drain.vaddr = dma_alloc_coherent(csi->dev, csi->dma.drain.len,
-						  &csi->dma.drain.paddr,
-						  GFP_KERNEL);
-	if (!csi->dma.drain.vaddr)
-		return -ENOMEM;
-
 	return 0;
 }
 
 static int ti_csi2rx_v4l2_init(struct ti_csi2rx_dev *csi)
 {
 	struct media_device *mdev = &csi->mdev;
-	struct video_device *vdev = &csi->vdev;
+	int ret;
+
+	mdev->dev = csi->dev;
+	mdev->hw_revision = 1;
+	strscpy(mdev->model, "TI-CSI2RX", sizeof(mdev->model));
+
+	media_device_init(mdev);
+
+	csi->v4l2_dev.mdev = mdev;
+
+	ret = v4l2_device_register(csi->dev, &csi->v4l2_dev);
+	if (ret)
+		return ret;
+
+	ret = media_device_register(mdev);
+	if (ret) {
+		v4l2_device_unregister(&csi->v4l2_dev);
+		media_device_cleanup(mdev);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int ti_csi2rx_init_ctx(struct ti_csi2rx_ctx *ctx)
+{
+	struct ti_csi2rx_dev *csi = ctx->csi;
+	struct video_device *vdev = &ctx->vdev;
 	const struct ti_csi2rx_fmt *fmt;
-	struct v4l2_pix_format *pix_fmt = &csi->v_fmt.fmt.pix;
+	struct v4l2_pix_format *pix_fmt = &ctx->v_fmt.fmt.pix;
 	int ret;
 
+	mutex_init(&ctx->mutex);
+
 	fmt = find_format_by_fourcc(V4L2_PIX_FMT_UYVY);
 	if (!fmt)
 		return -EINVAL;
@@ -1031,19 +1096,20 @@ static int ti_csi2rx_v4l2_init(struct ti_csi2rx_dev *csi)
 	pix_fmt->height = 480;
 	pix_fmt->field = V4L2_FIELD_NONE;
 	pix_fmt->colorspace = V4L2_COLORSPACE_SRGB;
-	pix_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
-	pix_fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE;
-	pix_fmt->xfer_func = V4L2_XFER_FUNC_SRGB;
-
-	ti_csi2rx_fill_fmt(fmt, &csi->v_fmt);
+	pix_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601,
+	pix_fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE,
+	pix_fmt->xfer_func = V4L2_XFER_FUNC_SRGB,
 
-	mdev->dev = csi->dev;
-	mdev->hw_revision = 1;
-	strscpy(mdev->model, "TI-CSI2RX", sizeof(mdev->model));
+	ti_csi2rx_fill_fmt(fmt, &ctx->v_fmt);
 
-	media_device_init(mdev);
+	csi->pad.flags = MEDIA_PAD_FL_SINK;
+	vdev->entity.ops = &ti_csi2rx_video_entity_ops;
+	ret = media_entity_pads_init(&ctx->vdev.entity, 1, &csi->pad);
+	if (ret)
+		return ret;
 
-	strscpy(vdev->name, TI_CSI2RX_MODULE_NAME, sizeof(vdev->name));
+	snprintf(vdev->name, sizeof(vdev->name), "%s context %u",
+		 dev_name(csi->dev), ctx->idx);
 	vdev->v4l2_dev = &csi->v4l2_dev;
 	vdev->vfl_dir = VFL_DIR_RX;
 	vdev->fops = &csi_fops;
@@ -1051,61 +1117,33 @@ static int ti_csi2rx_v4l2_init(struct ti_csi2rx_dev *csi)
 	vdev->release = video_device_release_empty;
 	vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING |
 			    V4L2_CAP_IO_MC;
-	vdev->lock = &csi->mutex;
-	video_set_drvdata(vdev, csi);
-
-	csi->pad.flags = MEDIA_PAD_FL_SINK;
-	vdev->entity.ops = &ti_csi2rx_video_entity_ops;
-	ret = media_entity_pads_init(&csi->vdev.entity, 1, &csi->pad);
-	if (ret)
-		return ret;
+	vdev->lock = &ctx->mutex;
+	video_set_drvdata(vdev, ctx);
 
-	csi->v4l2_dev.mdev = mdev;
+	INIT_LIST_HEAD(&ctx->dma.queue);
+	INIT_LIST_HEAD(&ctx->dma.submitted);
+	spin_lock_init(&ctx->dma.lock);
+	ctx->dma.state = TI_CSI2RX_DMA_STOPPED;
 
-	ret = v4l2_device_register(csi->dev, &csi->v4l2_dev);
+	ret = ti_csi2rx_init_dma(ctx);
 	if (ret)
 		return ret;
 
-	ret = media_device_register(mdev);
-	if (ret) {
-		v4l2_device_unregister(&csi->v4l2_dev);
-		media_device_cleanup(mdev);
-		return ret;
-	}
+	ret = ti_csi2rx_init_vb2q(ctx);
+	if (ret)
+		goto cleanup_dma;
 
 	return 0;
-}
-
-static void ti_csi2rx_cleanup_dma(struct ti_csi2rx_dev *csi)
-{
-	dma_free_coherent(csi->dev, csi->dma.drain.len,
-			  csi->dma.drain.vaddr, csi->dma.drain.paddr);
-	csi->dma.drain.vaddr = NULL;
-	dma_release_channel(csi->dma.chan);
-}
-
-static void ti_csi2rx_cleanup_v4l2(struct ti_csi2rx_dev *csi)
-{
-	media_device_unregister(&csi->mdev);
-	v4l2_device_unregister(&csi->v4l2_dev);
-	media_device_cleanup(&csi->mdev);
-}
 
-static void ti_csi2rx_cleanup_subdev(struct ti_csi2rx_dev *csi)
-{
-	v4l2_async_nf_unregister(&csi->notifier);
-	v4l2_async_nf_cleanup(&csi->notifier);
-}
-
-static void ti_csi2rx_cleanup_vb2q(struct ti_csi2rx_dev *csi)
-{
-	vb2_queue_release(&csi->vidq);
+cleanup_dma:
+	dma_release_channel(ctx->dma.chan);
+	return ret;
 }
 
 static int ti_csi2rx_probe(struct platform_device *pdev)
 {
 	struct ti_csi2rx_dev *csi;
-	int ret;
+	int ret, i;
 
 	csi = devm_kzalloc(&pdev->dev, sizeof(*csi), GFP_KERNEL);
 	if (!csi)
@@ -1114,62 +1152,69 @@ static int ti_csi2rx_probe(struct platform_device *pdev)
 	csi->dev = &pdev->dev;
 	platform_set_drvdata(pdev, csi);
 
-	mutex_init(&csi->mutex);
 	csi->shim = devm_platform_ioremap_resource(pdev, 0);
 	if (IS_ERR(csi->shim)) {
 		ret = PTR_ERR(csi->shim);
-		goto err_mutex;
+		return ret;
 	}
 
-	ret = ti_csi2rx_init_dma(csi);
-	if (ret)
-		goto err_mutex;
+	csi->drain.len = DRAIN_BUFFER_SIZE;
+	csi->drain.vaddr = dma_alloc_coherent(csi->dev, csi->drain.len,
+					      &csi->drain.paddr,
+					      GFP_KERNEL);
+	if (!csi->drain.vaddr)
+		return -ENOMEM;
 
 	ret = ti_csi2rx_v4l2_init(csi);
-	if (ret)
-		goto err_dma;
-
-	ret = ti_csi2rx_init_vb2q(csi);
 	if (ret)
 		goto err_v4l2;
 
+	for (i = 0; i < TI_CSI2RX_NUM_CTX; i++) {
+		csi->ctx[i].idx = i;
+		csi->ctx[i].csi = csi;
+		ret = ti_csi2rx_init_ctx(&csi->ctx[i]);
+		if (ret)
+			goto err_ctx;
+	}
+
 	ret = ti_csi2rx_notifier_register(csi);
 	if (ret)
-		goto err_vb2q;
+		goto err_ctx;
 
 	ret = devm_of_platform_populate(csi->dev);
 	if (ret) {
 		dev_err(csi->dev, "Failed to create children: %d\n", ret);
-		goto err_subdev;
+		goto err_notifier;
 	}
 
 	return 0;
 
-err_subdev:
-	ti_csi2rx_cleanup_subdev(csi);
-err_vb2q:
-	ti_csi2rx_cleanup_vb2q(csi);
-err_v4l2:
+err_notifier:
+	ti_csi2rx_cleanup_notifier(csi);
+err_ctx:
+	i--;
+	for (; i >= 0; i--)
+		ti_csi2rx_cleanup_ctx(&csi->ctx[i]);
 	ti_csi2rx_cleanup_v4l2(csi);
-err_dma:
-	ti_csi2rx_cleanup_dma(csi);
-err_mutex:
-	mutex_destroy(&csi->mutex);
+err_v4l2:
+	dma_free_coherent(csi->dev, csi->drain.len, csi->drain.vaddr,
+			  csi->drain.paddr);
 	return ret;
 }
 
 static void ti_csi2rx_remove(struct platform_device *pdev)
 {
 	struct ti_csi2rx_dev *csi = platform_get_drvdata(pdev);
+	unsigned int i;
 
-	video_unregister_device(&csi->vdev);
+	for (i = 0; i < TI_CSI2RX_NUM_CTX; i++)
+		ti_csi2rx_cleanup_ctx(&csi->ctx[i]);
 
-	ti_csi2rx_cleanup_vb2q(csi);
-	ti_csi2rx_cleanup_subdev(csi);
+	ti_csi2rx_cleanup_notifier(csi);
 	ti_csi2rx_cleanup_v4l2(csi);
-	ti_csi2rx_cleanup_dma(csi);
 
-	mutex_destroy(&csi->mutex);
+	dma_free_coherent(csi->dev, csi->drain.len, csi->drain.vaddr,
+			  csi->drain.paddr);
 }
 
 static const struct of_device_id ti_csi2rx_of_match[] = {
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v9 04/19] media: ti: j721e-csi2rx: prepare SHIM code for multiple contexts
  2025-12-30  8:32 [PATCH v9 00/19] media: cadence,ti: CSI2RX Multistream Support Rishikesh Donadkar
                   ` (2 preceding siblings ...)
  2025-12-30  8:32 ` [PATCH v9 03/19] media: ti: j721e-csi2rx: separate out device and context Rishikesh Donadkar
@ 2025-12-30  8:32 ` Rishikesh Donadkar
  2025-12-30  8:32 ` [PATCH v9 05/19] media: ti: j721e-csi2rx: allocate DMA channel based on context index Rishikesh Donadkar
                   ` (14 subsequent siblings)
  18 siblings, 0 replies; 54+ messages in thread
From: Rishikesh Donadkar @ 2025-12-30  8:32 UTC (permalink / raw)
  To: jai.luthra, laurent.pinchart, mripard
  Cc: r-donadkar, y-abhilashchandra, devarsht, s-jain1, vigneshr,
	mchehab, robh, krzk+dt, p.zabel, conor+dt, sakari.ailus,
	hverkuil-cisco, tomi.valkeinen, jai.luthra, changhuang.liang,
	jack.zhu, sjoerd, dan.carpenter, hverkuil+cisco, linux-kernel,
	linux-media, devicetree

From: Pratyush Yadav <p.yadav@ti.com>

Currently the SHIM code to configure the context only touches the first
context. Add support for writing to the context's registers based on the
context index.

Signed-off-by: Pratyush Yadav <p.yadav@ti.com>
Signed-off-by: Jai Luthra <j-luthra@ti.com>
Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Yemike Abhilash Chandra <y-abhilashchandra@ti.com>
Reviewed-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
---
 .../media/platform/ti/j721e-csi2rx/j721e-csi2rx.c  | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)

diff --git a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
index 23717a3b6c4cf..fdbe72df5c5f8 100644
--- a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
+++ b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
@@ -27,7 +27,7 @@
 #define SHIM_CNTL			0x10
 #define SHIM_CNTL_PIX_RST		BIT(0)
 
-#define SHIM_DMACNTX			0x20
+#define SHIM_DMACNTX(i)			(0x20 + ((i) * 0x20))
 #define SHIM_DMACNTX_EN			BIT(31)
 #define SHIM_DMACNTX_YUV422		GENMASK(27, 26)
 #define SHIM_DMACNTX_DUAL_PCK_CFG	BIT(24)
@@ -38,7 +38,7 @@
 #define SHIM_DMACNTX_SIZE_16		1
 #define SHIM_DMACNTX_SIZE_32		2
 
-#define SHIM_PSI_CFG0			0x24
+#define SHIM_PSI_CFG0(i)		(0x24 + ((i) * 0x20))
 #define SHIM_PSI_CFG0_SRC_TAG		GENMASK(15, 0)
 #define SHIM_PSI_CFG0_DST_TAG		GENMASK(31, 16)
 
@@ -568,11 +568,13 @@ static void ti_csi2rx_setup_shim(struct ti_csi2rx_ctx *ctx)
 		break;
 	}
 
-	writel(reg, csi->shim + SHIM_DMACNTX);
+	reg |= FIELD_PREP(SHIM_DMACNTX_SIZE, fmt->size);
+
+	writel(reg, csi->shim + SHIM_DMACNTX(ctx->idx));
 
 	reg = FIELD_PREP(SHIM_PSI_CFG0_SRC_TAG, 0) |
 	      FIELD_PREP(SHIM_PSI_CFG0_DST_TAG, 0);
-	writel(reg, csi->shim + SHIM_PSI_CFG0);
+	writel(reg, csi->shim + SHIM_PSI_CFG0(ctx->idx));
 }
 
 static void ti_csi2rx_drain_callback(void *param)
@@ -890,7 +892,7 @@ static int ti_csi2rx_start_streaming(struct vb2_queue *vq, unsigned int count)
 err_pipeline:
 	video_device_pipeline_stop(&ctx->vdev);
 	writel(0, csi->shim + SHIM_CNTL);
-	writel(0, csi->shim + SHIM_DMACNTX);
+	writel(0, csi->shim + SHIM_DMACNTX(ctx->idx));
 err:
 	ti_csi2rx_cleanup_buffers(ctx, VB2_BUF_STATE_QUEUED);
 	return ret;
@@ -905,7 +907,7 @@ static void ti_csi2rx_stop_streaming(struct vb2_queue *vq)
 	video_device_pipeline_stop(&ctx->vdev);
 
 	writel(0, csi->shim + SHIM_CNTL);
-	writel(0, csi->shim + SHIM_DMACNTX);
+	writel(0, csi->shim + SHIM_DMACNTX(ctx->idx));
 
 	ret = v4l2_subdev_call(csi->source, video, s_stream, 0);
 	if (ret)
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v9 05/19] media: ti: j721e-csi2rx: allocate DMA channel based on context index
  2025-12-30  8:32 [PATCH v9 00/19] media: cadence,ti: CSI2RX Multistream Support Rishikesh Donadkar
                   ` (3 preceding siblings ...)
  2025-12-30  8:32 ` [PATCH v9 04/19] media: ti: j721e-csi2rx: prepare SHIM code for multiple contexts Rishikesh Donadkar
@ 2025-12-30  8:32 ` Rishikesh Donadkar
  2025-12-30  8:32 ` [PATCH v9 06/19] media: ti: j721e-csi2rx: add a subdev for the core device Rishikesh Donadkar
                   ` (13 subsequent siblings)
  18 siblings, 0 replies; 54+ messages in thread
From: Rishikesh Donadkar @ 2025-12-30  8:32 UTC (permalink / raw)
  To: jai.luthra, laurent.pinchart, mripard
  Cc: r-donadkar, y-abhilashchandra, devarsht, s-jain1, vigneshr,
	mchehab, robh, krzk+dt, p.zabel, conor+dt, sakari.ailus,
	hverkuil-cisco, tomi.valkeinen, jai.luthra, changhuang.liang,
	jack.zhu, sjoerd, dan.carpenter, hverkuil+cisco, linux-kernel,
	linux-media, devicetree

From: Pratyush Yadav <p.yadav@ti.com>

With multiple contexts, there needs to be a different DMA channel for
each context. Earlier, the DMA channel name was hard coded to "rx0" for
the sake of simplicity. Generate the DMA channel name based on its index
and get the channel corresponding to the context.

Signed-off-by: Pratyush Yadav <p.yadav@ti.com>
Signed-off-by: Jai Luthra <j-luthra@ti.com>
Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Yemike Abhilash Chandra <y-abhilashchandra@ti.com>
Reviewed-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
---
 drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
index fdbe72df5c5f8..f66d68edcd57a 100644
--- a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
+++ b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
@@ -1038,9 +1038,11 @@ static int ti_csi2rx_init_dma(struct ti_csi2rx_ctx *ctx)
 	struct dma_slave_config cfg = {
 		.src_addr_width = DMA_SLAVE_BUSWIDTH_16_BYTES,
 	};
+	char name[5];
 	int ret;
 
-	ctx->dma.chan = dma_request_chan(ctx->csi->dev, "rx0");
+	snprintf(name, sizeof(name), "rx%u", ctx->idx);
+	ctx->dma.chan = dma_request_chan(ctx->csi->dev, name);
 	if (IS_ERR(ctx->dma.chan))
 		return PTR_ERR(ctx->dma.chan);
 
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v9 06/19] media: ti: j721e-csi2rx: add a subdev for the core device
  2025-12-30  8:32 [PATCH v9 00/19] media: cadence,ti: CSI2RX Multistream Support Rishikesh Donadkar
                   ` (4 preceding siblings ...)
  2025-12-30  8:32 ` [PATCH v9 05/19] media: ti: j721e-csi2rx: allocate DMA channel based on context index Rishikesh Donadkar
@ 2025-12-30  8:32 ` Rishikesh Donadkar
  2026-01-14 15:21   ` Tomi Valkeinen
  2025-12-30  8:32 ` [PATCH v9 07/19] media: cadence: csi2rx: Move to .enable/disable_streams API Rishikesh Donadkar
                   ` (12 subsequent siblings)
  18 siblings, 1 reply; 54+ messages in thread
From: Rishikesh Donadkar @ 2025-12-30  8:32 UTC (permalink / raw)
  To: jai.luthra, laurent.pinchart, mripard
  Cc: r-donadkar, y-abhilashchandra, devarsht, s-jain1, vigneshr,
	mchehab, robh, krzk+dt, p.zabel, conor+dt, sakari.ailus,
	hverkuil-cisco, tomi.valkeinen, jai.luthra, changhuang.liang,
	jack.zhu, sjoerd, dan.carpenter, hverkuil+cisco, linux-kernel,
	linux-media, devicetree

From: Jai Luthra <j-luthra@ti.com>

With single stream capture, it was simpler to use the video device as
the media entity representing the main TI CSI2RX device. Now with multi
stream capture coming into the picture, the model has shifted to each
video device having a link to the main device's subdev. The routing
would then be set on this subdev.

Add this subdev, link each context to this subdev's entity and link the
subdev's entity to the source. Also add an array of media pads. It will
have one sink pad and source pads equal to the number of contexts.

Support the new enable_stream()/disable_stream() APIs in the subdev
instead of s_stream() hook.

Reviewed-by: Yemike Abhilash Chandra <y-abhilashchandra@ti.com>
Co-developed-by: Pratyush Yadav <p.yadav@ti.com>
Signed-off-by: Pratyush Yadav <p.yadav@ti.com>
Signed-off-by: Jai Luthra <j-luthra@ti.com>
Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
---
 .../platform/ti/j721e-csi2rx/j721e-csi2rx.c   | 292 +++++++++++++++---
 1 file changed, 248 insertions(+), 44 deletions(-)

diff --git a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
index f66d68edcd57a..8f49ea2638585 100644
--- a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
+++ b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
@@ -54,6 +54,11 @@
 #define MAX_WIDTH_BYTES			SZ_16K
 #define MAX_HEIGHT_LINES		SZ_16K
 
+#define TI_CSI2RX_PAD_SINK		0
+#define TI_CSI2RX_PAD_FIRST_SOURCE	1
+#define TI_CSI2RX_NUM_SOURCE_PADS	1
+#define TI_CSI2RX_NUM_PADS		(1 + TI_CSI2RX_NUM_SOURCE_PADS)
+
 #define DRAIN_TIMEOUT_MS		50
 #define DRAIN_BUFFER_SIZE		SZ_32K
 
@@ -102,6 +107,7 @@ struct ti_csi2rx_ctx {
 	struct mutex			mutex; /* To serialize ioctls. */
 	struct v4l2_format		v_fmt;
 	struct ti_csi2rx_dma		dma;
+	struct media_pad		pad;
 	u32				sequence;
 	u32				idx;
 };
@@ -109,12 +115,15 @@ struct ti_csi2rx_ctx {
 struct ti_csi2rx_dev {
 	struct device			*dev;
 	void __iomem			*shim;
+	struct mutex			mutex; /* To serialize ioctls. */
+	unsigned int			enable_count;
 	struct v4l2_device		v4l2_dev;
 	struct media_device		mdev;
 	struct media_pipeline		pipe;
-	struct media_pad		pad;
+	struct media_pad		pads[TI_CSI2RX_NUM_PADS];
 	struct v4l2_async_notifier	notifier;
 	struct v4l2_subdev		*source;
+	struct v4l2_subdev		subdev;
 	struct ti_csi2rx_ctx		ctx[TI_CSI2RX_NUM_CTX];
 	u8				pix_per_clk;
 	/* Buffer to drain stale data from PSI-L endpoint */
@@ -125,6 +134,22 @@ struct ti_csi2rx_dev {
 	} drain;
 };
 
+static inline struct ti_csi2rx_dev *to_csi2rx_dev(struct v4l2_subdev *sd)
+{
+	return container_of(sd, struct ti_csi2rx_dev, subdev);
+}
+
+static const struct v4l2_mbus_framefmt ti_csi2rx_default_fmt = {
+	.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,
+};
+
 static const struct ti_csi2rx_fmt ti_csi2rx_formats[] = {
 	{
 		.fourcc			= V4L2_PIX_FMT_YUYV,
@@ -422,6 +447,18 @@ static int csi_async_notifier_complete(struct v4l2_async_notifier *notifier)
 	struct ti_csi2rx_dev *csi = dev_get_drvdata(notifier->v4l2_dev->dev);
 	int ret, i;
 
+	/* Create link from source to subdev */
+	ret = media_create_pad_link(&csi->source->entity,
+				    CSI2RX_BRIDGE_SOURCE_PAD,
+				    &csi->subdev.entity,
+				    TI_CSI2RX_PAD_SINK,
+				    MEDIA_LNK_FL_IMMUTABLE |
+				    MEDIA_LNK_FL_ENABLED);
+
+	if (ret)
+		return ret;
+
+	/* Create and link video nodes for all DMA contexts */
 	for (i = 0; i < TI_CSI2RX_NUM_CTX; i++) {
 		struct ti_csi2rx_ctx *ctx = &csi->ctx[i];
 		struct video_device *vdev = &ctx->vdev;
@@ -429,15 +466,17 @@ static int csi_async_notifier_complete(struct v4l2_async_notifier *notifier)
 		ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
 		if (ret)
 			goto unregister_dev;
-	}
 
-	ret = media_create_pad_link(&csi->source->entity,
-				    CSI2RX_BRIDGE_SOURCE_PAD,
-				    &csi->ctx[0].vdev.entity, csi->pad.index,
-				    MEDIA_LNK_FL_IMMUTABLE |
-				    MEDIA_LNK_FL_ENABLED);
-	if (ret)
-		goto unregister_dev;
+		ret = media_create_pad_link(&csi->subdev.entity,
+					    TI_CSI2RX_PAD_FIRST_SOURCE + ctx->idx,
+					    &vdev->entity, 0,
+					    MEDIA_LNK_FL_IMMUTABLE |
+					    MEDIA_LNK_FL_ENABLED);
+		if (ret) {
+			video_unregister_device(vdev);
+			goto unregister_dev;
+		}
+	}
 
 	ret = v4l2_device_register_subdev_nodes(&csi->v4l2_dev);
 	if (ret)
@@ -447,8 +486,10 @@ static int csi_async_notifier_complete(struct v4l2_async_notifier *notifier)
 
 unregister_dev:
 	i--;
-	for (; i >= 0; i--)
+	for (; i >= 0; i--) {
+		media_entity_remove_links(&csi->ctx[i].vdev.entity);
 		video_unregister_device(&csi->ctx[i].vdev);
+	}
 	return ret;
 }
 
@@ -493,14 +534,13 @@ static int ti_csi2rx_notifier_register(struct ti_csi2rx_dev *csi)
 }
 
 /* Request maximum possible pixels per clock from the bridge */
-static void ti_csi2rx_request_max_ppc(struct ti_csi2rx_ctx *ctx)
+static void ti_csi2rx_request_max_ppc(struct ti_csi2rx_dev *csi)
 {
-	struct ti_csi2rx_dev *csi = ctx->csi;
 	u8 ppc = TI_CSI2RX_MAX_PIX_PER_CLK;
 	struct media_pad *pad;
 	int ret;
 
-	pad = media_entity_remote_source_pad_unique(&ctx->vdev.entity);
+	pad = media_entity_remote_source_pad_unique(&csi->subdev.entity);
 	if (IS_ERR(pad))
 		return;
 
@@ -526,7 +566,7 @@ static void ti_csi2rx_setup_shim(struct ti_csi2rx_ctx *ctx)
 	writel(reg, csi->shim + SHIM_CNTL);
 
 	/* Negotiate pixel count from the source */
-	ti_csi2rx_request_max_ppc(ctx);
+	ti_csi2rx_request_max_ppc(csi);
 
 	reg = SHIM_DMACNTX_EN;
 	reg |= FIELD_PREP(SHIM_DMACNTX_FMT, fmt->csi_dt);
@@ -881,7 +921,9 @@ static int ti_csi2rx_start_streaming(struct vb2_queue *vq, unsigned int count)
 	dma->state = TI_CSI2RX_DMA_ACTIVE;
 	spin_unlock_irqrestore(&dma->lock, flags);
 
-	ret = v4l2_subdev_call(csi->source, video, s_stream, 1);
+	ret = v4l2_subdev_enable_streams(&csi->subdev,
+					 TI_CSI2RX_PAD_FIRST_SOURCE,
+					 BIT_U64(0));
 	if (ret)
 		goto err_dma;
 
@@ -909,7 +951,9 @@ static void ti_csi2rx_stop_streaming(struct vb2_queue *vq)
 	writel(0, csi->shim + SHIM_CNTL);
 	writel(0, csi->shim + SHIM_DMACNTX(ctx->idx));
 
-	ret = v4l2_subdev_call(csi->source, video, s_stream, 0);
+	ret = v4l2_subdev_disable_streams(&csi->subdev,
+					  TI_CSI2RX_PAD_FIRST_SOURCE,
+					  BIT_U64(0));
 	if (ret)
 		dev_err(csi->dev, "Failed to stop subdev stream\n");
 
@@ -925,8 +969,121 @@ static const struct vb2_ops csi_vb2_qops = {
 	.stop_streaming = ti_csi2rx_stop_streaming,
 };
 
+static int ti_csi2rx_enum_mbus_code(struct v4l2_subdev *subdev,
+				    struct v4l2_subdev_state *state,
+				    struct v4l2_subdev_mbus_code_enum *code_enum)
+{
+	if (code_enum->index >= ARRAY_SIZE(ti_csi2rx_formats))
+		return -EINVAL;
+
+	code_enum->code = ti_csi2rx_formats[code_enum->index].code;
+
+	return 0;
+}
+
+static int ti_csi2rx_sd_set_fmt(struct v4l2_subdev *sd,
+				struct v4l2_subdev_state *state,
+				struct v4l2_subdev_format *format)
+{
+	struct v4l2_mbus_framefmt *fmt;
+
+	/* No transcoding, don't allow setting source fmt */
+	if (format->pad > TI_CSI2RX_PAD_SINK)
+		return v4l2_subdev_get_fmt(sd, state, format);
+
+	if (!find_format_by_code(format->format.code))
+		format->format.code = ti_csi2rx_formats[0].code;
+
+	format->format.field = V4L2_FIELD_NONE;
+
+	fmt = v4l2_subdev_state_get_format(state, format->pad, format->stream);
+	*fmt = format->format;
+
+	fmt = v4l2_subdev_state_get_format(state, TI_CSI2RX_PAD_FIRST_SOURCE,
+					   format->stream);
+	*fmt = format->format;
+
+	return 0;
+}
+
+static int ti_csi2rx_sd_init_state(struct v4l2_subdev *sd,
+				   struct v4l2_subdev_state *state)
+{
+	struct v4l2_mbus_framefmt *fmt;
+
+	fmt = v4l2_subdev_state_get_format(state, TI_CSI2RX_PAD_SINK);
+	*fmt = ti_csi2rx_default_fmt;
+
+	fmt = v4l2_subdev_state_get_format(state, TI_CSI2RX_PAD_FIRST_SOURCE);
+	*fmt = ti_csi2rx_default_fmt;
+
+	return 0;
+}
+
+static int ti_csi2rx_sd_enable_streams(struct v4l2_subdev *sd,
+				       struct v4l2_subdev_state *state,
+				       u32 pad, u64 streams_mask)
+{
+	struct ti_csi2rx_dev *csi = to_csi2rx_dev(sd);
+	struct media_pad *remote_pad;
+	int ret = 0;
+
+	remote_pad = media_entity_remote_source_pad_unique(&csi->subdev.entity);
+	if (!remote_pad)
+		return -ENODEV;
+
+	ret = v4l2_subdev_enable_streams(csi->source, remote_pad->index,
+					 BIT_U64(0));
+	if (ret)
+		return ret;
+
+	csi->enable_count++;
+
+	return 0;
+}
+
+static int ti_csi2rx_sd_disable_streams(struct v4l2_subdev *sd,
+					struct v4l2_subdev_state *state,
+					u32 pad, u64 streams_mask)
+{
+	struct ti_csi2rx_dev *csi = to_csi2rx_dev(sd);
+	struct media_pad *remote_pad;
+	int ret = 0;
+
+	remote_pad = media_entity_remote_source_pad_unique(&csi->subdev.entity);
+	if (!remote_pad)
+		return -ENODEV;
+
+	if (csi->enable_count == 0)
+		return -EINVAL;
+
+	ret = v4l2_subdev_disable_streams(csi->source, remote_pad->index,
+					  BIT_U64(0));
+	if (!ret)
+		--csi->enable_count;
+
+	return 0;
+}
+
+static const struct v4l2_subdev_pad_ops ti_csi2rx_subdev_pad_ops = {
+	.enum_mbus_code	= ti_csi2rx_enum_mbus_code,
+	.get_fmt = v4l2_subdev_get_fmt,
+	.set_fmt = ti_csi2rx_sd_set_fmt,
+	.enable_streams = ti_csi2rx_sd_enable_streams,
+	.disable_streams = ti_csi2rx_sd_disable_streams,
+};
+
+static const struct v4l2_subdev_ops ti_csi2rx_subdev_ops = {
+	.pad = &ti_csi2rx_subdev_pad_ops,
+};
+
+static const struct v4l2_subdev_internal_ops ti_csi2rx_internal_ops = {
+	.init_state = ti_csi2rx_sd_init_state,
+};
+
 static void ti_csi2rx_cleanup_v4l2(struct ti_csi2rx_dev *csi)
 {
+	v4l2_subdev_cleanup(&csi->subdev);
 	media_device_unregister(&csi->mdev);
 	v4l2_device_unregister(&csi->v4l2_dev);
 	media_device_cleanup(&csi->mdev);
@@ -981,48 +1138,52 @@ static int ti_csi2rx_link_validate(struct media_link *link)
 	struct ti_csi2rx_ctx *ctx = container_of(vdev, struct ti_csi2rx_ctx, vdev);
 	struct ti_csi2rx_dev *csi = ctx->csi;
 	struct v4l2_pix_format *csi_fmt = &ctx->v_fmt.fmt.pix;
-	struct v4l2_subdev_format source_fmt = {
-		.which	= V4L2_SUBDEV_FORMAT_ACTIVE,
-		.pad	= link->source->index,
-	};
+	struct v4l2_mbus_framefmt *format;
+	struct v4l2_subdev_state *state;
 	const struct ti_csi2rx_fmt *ti_fmt;
-	int ret;
 
-	ret = v4l2_subdev_call_state_active(csi->source, pad,
-					    get_fmt, &source_fmt);
-	if (ret)
-		return ret;
+	state = v4l2_subdev_lock_and_get_active_state(&csi->subdev);
+	format = v4l2_subdev_state_get_format(state, link->source->index, 0);
+	v4l2_subdev_unlock_state(state);
 
-	if (source_fmt.format.width != csi_fmt->width) {
+	if (!format) {
+		dev_dbg(csi->dev,
+			"Skipping validation as no format present on \"%s\":%u:0\n",
+			link->source->entity->name, link->source->index);
+		return 0;
+	}
+
+	if (format->width != csi_fmt->width) {
 		dev_dbg(csi->dev, "Width does not match (source %u, sink %u)\n",
-			source_fmt.format.width, csi_fmt->width);
+			format->width, csi_fmt->width);
 		return -EPIPE;
 	}
 
-	if (source_fmt.format.height != csi_fmt->height) {
+	if (format->height != csi_fmt->height) {
 		dev_dbg(csi->dev, "Height does not match (source %u, sink %u)\n",
-			source_fmt.format.height, csi_fmt->height);
+			format->height, csi_fmt->height);
 		return -EPIPE;
 	}
 
-	if (source_fmt.format.field != csi_fmt->field &&
+	if (format->field != csi_fmt->field &&
 	    csi_fmt->field != V4L2_FIELD_NONE) {
 		dev_dbg(csi->dev, "Field does not match (source %u, sink %u)\n",
-			source_fmt.format.field, csi_fmt->field);
+			format->field, csi_fmt->field);
 		return -EPIPE;
 	}
 
-	ti_fmt = find_format_by_code(source_fmt.format.code);
+	ti_fmt = find_format_by_code(format->code);
 	if (!ti_fmt) {
 		dev_dbg(csi->dev, "Media bus format 0x%x not supported\n",
-			source_fmt.format.code);
+			format->code);
 		return -EPIPE;
 	}
 
 	if (ti_fmt->fourcc != csi_fmt->pixelformat) {
 		dev_dbg(csi->dev,
-			"Cannot transform source fmt 0x%x to sink fmt 0x%x\n",
-			ti_fmt->fourcc, csi_fmt->pixelformat);
+			"Cannot transform \"%s\":%u format %p4cc to %p4cc\n",
+			link->source->entity->name, link->source->index,
+			&ti_fmt->fourcc, &csi_fmt->pixelformat);
 		return -EPIPE;
 	}
 
@@ -1033,6 +1194,10 @@ static const struct media_entity_operations ti_csi2rx_video_entity_ops = {
 	.link_validate = ti_csi2rx_link_validate,
 };
 
+static const struct media_entity_operations ti_csi2rx_subdev_entity_ops = {
+	.link_validate = v4l2_subdev_link_validate,
+};
+
 static int ti_csi2rx_init_dma(struct ti_csi2rx_ctx *ctx)
 {
 	struct dma_slave_config cfg = {
@@ -1058,6 +1223,7 @@ static int ti_csi2rx_init_dma(struct ti_csi2rx_ctx *ctx)
 static int ti_csi2rx_v4l2_init(struct ti_csi2rx_dev *csi)
 {
 	struct media_device *mdev = &csi->mdev;
+	struct v4l2_subdev *sd = &csi->subdev;
 	int ret;
 
 	mdev->dev = csi->dev;
@@ -1070,16 +1236,51 @@ static int ti_csi2rx_v4l2_init(struct ti_csi2rx_dev *csi)
 
 	ret = v4l2_device_register(csi->dev, &csi->v4l2_dev);
 	if (ret)
-		return ret;
+		goto cleanup_media;
 
 	ret = media_device_register(mdev);
-	if (ret) {
-		v4l2_device_unregister(&csi->v4l2_dev);
-		media_device_cleanup(mdev);
-		return ret;
-	}
+	if (ret)
+		goto unregister_v4l2;
+
+	v4l2_subdev_init(sd, &ti_csi2rx_subdev_ops);
+	sd->internal_ops = &ti_csi2rx_internal_ops;
+	sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
+	sd->flags = V4L2_SUBDEV_FL_HAS_DEVNODE;
+	strscpy(sd->name, dev_name(csi->dev), sizeof(sd->name));
+	sd->dev = csi->dev;
+	sd->entity.ops = &ti_csi2rx_subdev_entity_ops;
+
+	csi->pads[TI_CSI2RX_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+
+	for (unsigned int i = TI_CSI2RX_PAD_FIRST_SOURCE;
+	     i < TI_CSI2RX_NUM_PADS; i++)
+		csi->pads[i].flags = MEDIA_PAD_FL_SOURCE;
+
+	ret = media_entity_pads_init(&sd->entity, ARRAY_SIZE(csi->pads),
+				     csi->pads);
+	if (ret)
+		goto unregister_media;
+
+	ret = v4l2_subdev_init_finalize(sd);
+	if (ret)
+		goto unregister_media;
+
+	ret = v4l2_device_register_subdev(&csi->v4l2_dev, sd);
+	if (ret)
+		goto cleanup_subdev;
 
 	return 0;
+
+cleanup_subdev:
+	v4l2_subdev_cleanup(sd);
+unregister_media:
+	media_device_unregister(mdev);
+unregister_v4l2:
+	v4l2_device_unregister(&csi->v4l2_dev);
+cleanup_media:
+	media_device_cleanup(mdev);
+
+	return ret;
 }
 
 static int ti_csi2rx_init_ctx(struct ti_csi2rx_ctx *ctx)
@@ -1106,9 +1307,9 @@ static int ti_csi2rx_init_ctx(struct ti_csi2rx_ctx *ctx)
 
 	ti_csi2rx_fill_fmt(fmt, &ctx->v_fmt);
 
-	csi->pad.flags = MEDIA_PAD_FL_SINK;
+	ctx->pad.flags = MEDIA_PAD_FL_SINK;
 	vdev->entity.ops = &ti_csi2rx_video_entity_ops;
-	ret = media_entity_pads_init(&ctx->vdev.entity, 1, &csi->pad);
+	ret = media_entity_pads_init(&ctx->vdev.entity, 1, &ctx->pad);
 	if (ret)
 		return ret;
 
@@ -1169,6 +1370,8 @@ static int ti_csi2rx_probe(struct platform_device *pdev)
 	if (!csi->drain.vaddr)
 		return -ENOMEM;
 
+	mutex_init(&csi->mutex);
+
 	ret = ti_csi2rx_v4l2_init(csi);
 	if (ret)
 		goto err_v4l2;
@@ -1201,6 +1404,7 @@ static int ti_csi2rx_probe(struct platform_device *pdev)
 		ti_csi2rx_cleanup_ctx(&csi->ctx[i]);
 	ti_csi2rx_cleanup_v4l2(csi);
 err_v4l2:
+	mutex_destroy(&csi->mutex);
 	dma_free_coherent(csi->dev, csi->drain.len, csi->drain.vaddr,
 			  csi->drain.paddr);
 	return ret;
@@ -1216,7 +1420,7 @@ static void ti_csi2rx_remove(struct platform_device *pdev)
 
 	ti_csi2rx_cleanup_notifier(csi);
 	ti_csi2rx_cleanup_v4l2(csi);
-
+	mutex_destroy(&csi->mutex);
 	dma_free_coherent(csi->dev, csi->drain.len, csi->drain.vaddr,
 			  csi->drain.paddr);
 }
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v9 07/19] media: cadence: csi2rx: Move to .enable/disable_streams API
  2025-12-30  8:32 [PATCH v9 00/19] media: cadence,ti: CSI2RX Multistream Support Rishikesh Donadkar
                   ` (5 preceding siblings ...)
  2025-12-30  8:32 ` [PATCH v9 06/19] media: ti: j721e-csi2rx: add a subdev for the core device Rishikesh Donadkar
@ 2025-12-30  8:32 ` Rishikesh Donadkar
  2026-01-14 15:25   ` Tomi Valkeinen
  2025-12-30  8:32 ` [PATCH v9 08/19] media: staging: starfive: Move to enabel-disable streams in starfive drivers Rishikesh Donadkar
                   ` (11 subsequent siblings)
  18 siblings, 1 reply; 54+ messages in thread
From: Rishikesh Donadkar @ 2025-12-30  8:32 UTC (permalink / raw)
  To: jai.luthra, laurent.pinchart, mripard
  Cc: r-donadkar, y-abhilashchandra, devarsht, s-jain1, vigneshr,
	mchehab, robh, krzk+dt, p.zabel, conor+dt, sakari.ailus,
	hverkuil-cisco, tomi.valkeinen, jai.luthra, changhuang.liang,
	jack.zhu, sjoerd, dan.carpenter, hverkuil+cisco, linux-kernel,
	linux-media, devicetree

The enable_streams() API in v4l2 supports passing a bitmask to enable
each pad/stream combination individually on any media subdev. Use this
API instead of  s_stream() API.

Implement the enable_stream and disable_stream hooks in place of the
stream-unaware s_stream hook.

Remove the lock that was used to serialize stream starts/stops which
is not required anymore since the v4l2-core serializes the
enable/disable_streams() calls for the subdev.

Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
---
 drivers/media/platform/cadence/cdns-csi2rx.c | 108 +++++++++----------
 1 file changed, 54 insertions(+), 54 deletions(-)

diff --git a/drivers/media/platform/cadence/cdns-csi2rx.c b/drivers/media/platform/cadence/cdns-csi2rx.c
index 8c19f125da3e5..8e7af2da62262 100644
--- a/drivers/media/platform/cadence/cdns-csi2rx.c
+++ b/drivers/media/platform/cadence/cdns-csi2rx.c
@@ -125,12 +125,6 @@ struct csi2rx_priv {
 	unsigned int			count;
 	int				error_irq;
 
-	/*
-	 * Used to prevent race conditions between multiple,
-	 * concurrent calls to start and stop.
-	 */
-	struct mutex			lock;
-
 	void __iomem			*base;
 	struct clk			*sys_clk;
 	struct clk			*p_clk;
@@ -268,20 +262,21 @@ static int csi2rx_configure_ext_dphy(struct csi2rx_priv *csi2rx)
 		&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_format sd_fmt = {
-		.which	= V4L2_SUBDEV_FORMAT_ACTIVE,
-		.pad	= CSI2RX_PAD_SINK,
-	};
+	struct v4l2_subdev_state *state;
+	struct v4l2_mbus_framefmt *framefmt;
 	const struct csi2rx_fmt *fmt;
 	s64 link_freq;
 	int ret;
 
-	ret = v4l2_subdev_call_state_active(&csi2rx->subdev, pad, get_fmt,
-					    &sd_fmt);
-	if (ret < 0)
-		return ret;
+	state = v4l2_subdev_get_locked_active_state(&csi2rx->subdev);
 
-	fmt = csi2rx_get_fmt_by_code(sd_fmt.format.code);
+	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;
+	}
+
+	fmt = csi2rx_get_fmt_by_code(framefmt->code);
 
 	link_freq = v4l2_get_link_freq(src_pad,
 				       fmt->bpp, 2 * csi2rx->num_lanes);
@@ -401,16 +396,10 @@ static int csi2rx_start(struct csi2rx_priv *csi2rx)
 
 	reset_control_deassert(csi2rx->sys_rst);
 
-	ret = v4l2_subdev_call(csi2rx->source_subdev, video, s_stream, true);
-	if (ret)
-		goto err_disable_sysclk;
-
 	clk_disable_unprepare(csi2rx->p_clk);
 
 	return 0;
 
-err_disable_sysclk:
-	clk_disable_unprepare(csi2rx->sys_clk);
 err_disable_pixclk:
 	for (; i > 0; i--) {
 		reset_control_assert(csi2rx->pixel_rst[i - 1]);
@@ -459,9 +448,6 @@ static void csi2rx_stop(struct csi2rx_priv *csi2rx)
 	reset_control_assert(csi2rx->p_rst);
 	clk_disable_unprepare(csi2rx->p_clk);
 
-	if (v4l2_subdev_call(csi2rx->source_subdev, video, s_stream, false))
-		dev_warn(csi2rx->dev, "Couldn't disable our subdev\n");
-
 	if (csi2rx->dphy) {
 		writel(0, csi2rx->base + CSI2RX_DPHY_LANE_CTRL_REG);
 
@@ -485,38 +471,56 @@ static int csi2rx_log_status(struct v4l2_subdev *sd)
 	return 0;
 }
 
-static int csi2rx_s_stream(struct v4l2_subdev *subdev, int enable)
+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);
-	int ret = 0;
-
-	mutex_lock(&csi2rx->lock);
-
-	if (enable) {
-		/*
-		 * If we're not the first users, there's no need to
-		 * enable the whole controller.
-		 */
-		if (!csi2rx->count) {
-			ret = csi2rx_start(csi2rx);
-			if (ret)
-				goto out;
-		}
+	int ret;
 
-		csi2rx->count++;
-	} else {
-		csi2rx->count--;
+	/*
+	 * If we're not the first users, there's no need to
+	 * enable the whole controller.
+	 */
+	if (!csi2rx->count) {
+		ret = csi2rx_start(csi2rx);
+		if (ret)
+			return ret;
+	}
 
-		/*
-		 * Let the last user turn off the lights.
-		 */
+	/* Start streaming on the source */
+	ret = v4l2_subdev_enable_streams(csi2rx->source_subdev, csi2rx->source_pad,
+					 BIT_U64(0));
+	if (ret) {
+		dev_err(csi2rx->dev,
+			"Failed to start streams %d on subdev\n", 0);
 		if (!csi2rx->count)
 			csi2rx_stop(csi2rx);
+		return ret;
 	}
 
-out:
-	mutex_unlock(&csi2rx->lock);
-	return ret;
+	csi2rx->count++;
+	return 0;
+}
+
+static int csi2rx_disable_streams(struct v4l2_subdev *subdev,
+				  struct v4l2_subdev_state *state, u32 pad,
+				  u64 streams_mask)
+{
+	struct csi2rx_priv *csi2rx = v4l2_subdev_to_csi2rx(subdev);
+
+	if (v4l2_subdev_disable_streams(csi2rx->source_subdev,
+					csi2rx->source_pad, BIT_U64(0))) {
+		dev_err(csi2rx->dev, "Couldn't disable our subdev\n");
+	}
+
+	csi2rx->count--;
+
+	/* Let the last user turn off the lights. */
+	if (!csi2rx->count)
+		csi2rx_stop(csi2rx);
+
+	return 0;
 }
 
 static int csi2rx_enum_mbus_code(struct v4l2_subdev *subdev,
@@ -611,10 +615,8 @@ 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,
-};
-
-static const struct v4l2_subdev_video_ops csi2rx_video_ops = {
-	.s_stream	= csi2rx_s_stream,
+	.enable_streams         = csi2rx_enable_streams,
+	.disable_streams        = csi2rx_disable_streams,
 };
 
 static const struct v4l2_subdev_core_ops csi2rx_core_ops = {
@@ -623,7 +625,6 @@ static const struct v4l2_subdev_core_ops csi2rx_core_ops = {
 
 static const struct v4l2_subdev_ops csi2rx_subdev_ops = {
 	.core		= &csi2rx_core_ops,
-	.video		= &csi2rx_video_ops,
 	.pad		= &csi2rx_pad_ops,
 };
 
@@ -829,7 +830,6 @@ static int csi2rx_probe(struct platform_device *pdev)
 		return -ENOMEM;
 	platform_set_drvdata(pdev, csi2rx);
 	csi2rx->dev = &pdev->dev;
-	mutex_init(&csi2rx->lock);
 
 	ret = csi2rx_get_resources(csi2rx, pdev);
 	if (ret)
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v9 08/19] media: staging: starfive: Move to enabel-disable streams in starfive drivers
  2025-12-30  8:32 [PATCH v9 00/19] media: cadence,ti: CSI2RX Multistream Support Rishikesh Donadkar
                   ` (6 preceding siblings ...)
  2025-12-30  8:32 ` [PATCH v9 07/19] media: cadence: csi2rx: Move to .enable/disable_streams API Rishikesh Donadkar
@ 2025-12-30  8:32 ` Rishikesh Donadkar
  2026-01-14 12:51   ` Jai Luthra
  2025-12-30  8:32 ` [PATCH v9 09/19] media: ti: j721e-csi2rx: get number of contexts from device tree Rishikesh Donadkar
                   ` (10 subsequent siblings)
  18 siblings, 1 reply; 54+ messages in thread
From: Rishikesh Donadkar @ 2025-12-30  8:32 UTC (permalink / raw)
  To: jai.luthra, laurent.pinchart, mripard
  Cc: r-donadkar, y-abhilashchandra, devarsht, s-jain1, vigneshr,
	mchehab, robh, krzk+dt, p.zabel, conor+dt, sakari.ailus,
	hverkuil-cisco, tomi.valkeinen, jai.luthra, changhuang.liang,
	jack.zhu, sjoerd, dan.carpenter, hverkuil+cisco, linux-kernel,
	linux-media, devicetree

The enable_streams() API in v4l2 supports passing a bitmask to enable
each pad/stream combination individually on any media subdev. Use this
API instead of  s_stream() API in the starfive drivers

Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
---
 .../staging/media/starfive/camss/stf-isp.c    | 43 ++++++++++++-------
 .../staging/media/starfive/camss/stf-video.c  |  4 +-
 2 files changed, 30 insertions(+), 17 deletions(-)

diff --git a/drivers/staging/media/starfive/camss/stf-isp.c b/drivers/staging/media/starfive/camss/stf-isp.c
index df7a903fbb1b0..4930ffb0e07a6 100644
--- a/drivers/staging/media/starfive/camss/stf-isp.c
+++ b/drivers/staging/media/starfive/camss/stf-isp.c
@@ -55,27 +55,43 @@ int stf_isp_init(struct stfcamss *stfcamss)
 	return 0;
 }
 
-static int isp_set_stream(struct v4l2_subdev *sd, int enable)
+static int isp_sd_enable_stream(struct v4l2_subdev *sd,
+				struct v4l2_subdev_state *state,
+				u32 pad, u64 streams_mask)
 {
 	struct stf_isp_dev *isp_dev = v4l2_get_subdevdata(sd);
 	struct v4l2_subdev_state *sd_state;
 	struct v4l2_mbus_framefmt *fmt;
 	struct v4l2_rect *crop;
+	int ret;
 
-	sd_state = v4l2_subdev_lock_and_get_active_state(sd);
+	sd_state = v4l2_subdev_get_locked_active_state(sd);
 	fmt = v4l2_subdev_state_get_format(sd_state, STF_ISP_PAD_SINK);
 	crop = v4l2_subdev_state_get_crop(sd_state, STF_ISP_PAD_SRC);
 
-	if (enable) {
-		stf_isp_reset(isp_dev);
-		stf_isp_init_cfg(isp_dev);
-		stf_isp_settings(isp_dev, crop, fmt->code);
-		stf_isp_stream_set(isp_dev);
-	}
+	stf_isp_reset(isp_dev);
+	stf_isp_init_cfg(isp_dev);
+	stf_isp_settings(isp_dev, crop, fmt->code);
+	stf_isp_stream_set(isp_dev);
+
+	ret = v4l2_subdev_enable_streams(isp_dev->source_subdev, 1, BIT(0));
+	if (ret)
+		return ret;
+
+	return 0;
+}
 
-	v4l2_subdev_call(isp_dev->source_subdev, video, s_stream, enable);
+static int isp_sd_disable_stream(struct v4l2_subdev *sd,
+				 struct v4l2_subdev_state *state,
+				 u32 pad, u64 streams_mask)
+{
+	struct stf_isp_dev *isp_dev = v4l2_get_subdevdata(sd);
+	int ret;
+
+	ret = v4l2_subdev_disable_streams(isp_dev->source_subdev, 1, BIT(0));
+	if (ret)
+		return ret;
 
-	v4l2_subdev_unlock_state(sd_state);
 	return 0;
 }
 
@@ -300,20 +316,17 @@ static int isp_init_formats(struct v4l2_subdev *sd,
 	return isp_set_format(sd, sd_state, &format);
 }
 
-static const struct v4l2_subdev_video_ops isp_video_ops = {
-	.s_stream = isp_set_stream,
-};
-
 static const struct v4l2_subdev_pad_ops isp_pad_ops = {
 	.enum_mbus_code = isp_enum_mbus_code,
 	.get_fmt = v4l2_subdev_get_fmt,
 	.set_fmt = isp_set_format,
 	.get_selection = isp_get_selection,
 	.set_selection = isp_set_selection,
+	.enable_streams = isp_sd_enable_stream,
+	.disable_streams = isp_sd_disable_stream,
 };
 
 static const struct v4l2_subdev_ops isp_v4l2_ops = {
-	.video = &isp_video_ops,
 	.pad = &isp_pad_ops,
 };
 
diff --git a/drivers/staging/media/starfive/camss/stf-video.c b/drivers/staging/media/starfive/camss/stf-video.c
index a0420eb6a0aa0..2db29bf8bdef8 100644
--- a/drivers/staging/media/starfive/camss/stf-video.c
+++ b/drivers/staging/media/starfive/camss/stf-video.c
@@ -287,7 +287,7 @@ static int video_start_streaming(struct vb2_queue *q, unsigned int count)
 
 	video->ops->start_streaming(video);
 
-	ret = v4l2_subdev_call(video->source_subdev, video, s_stream, true);
+	ret = v4l2_subdev_enable_streams(video->source_subdev, 1, BIT(0));
 	if (ret) {
 		dev_err(video->stfcamss->dev, "stream on failed\n");
 		goto err_pm_put;
@@ -311,7 +311,7 @@ static void video_stop_streaming(struct vb2_queue *q)
 
 	video->ops->stop_streaming(video);
 
-	v4l2_subdev_call(video->source_subdev, video, s_stream, false);
+	v4l2_subdev_disable_streams(video->source_subdev, 1, BIT(0));
 
 	pm_runtime_put(video->stfcamss->dev);
 
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v9 09/19] media: ti: j721e-csi2rx: get number of contexts from device tree
  2025-12-30  8:32 [PATCH v9 00/19] media: cadence,ti: CSI2RX Multistream Support Rishikesh Donadkar
                   ` (7 preceding siblings ...)
  2025-12-30  8:32 ` [PATCH v9 08/19] media: staging: starfive: Move to enabel-disable streams in starfive drivers Rishikesh Donadkar
@ 2025-12-30  8:32 ` Rishikesh Donadkar
  2025-12-30  8:32 ` [PATCH v9 10/19] media: cadence: csi2rx: add get_frame_desc wrapper Rishikesh Donadkar
                   ` (9 subsequent siblings)
  18 siblings, 0 replies; 54+ messages in thread
From: Rishikesh Donadkar @ 2025-12-30  8:32 UTC (permalink / raw)
  To: jai.luthra, laurent.pinchart, mripard
  Cc: r-donadkar, y-abhilashchandra, devarsht, s-jain1, vigneshr,
	mchehab, robh, krzk+dt, p.zabel, conor+dt, sakari.ailus,
	hverkuil-cisco, tomi.valkeinen, jai.luthra, changhuang.liang,
	jack.zhu, sjoerd, dan.carpenter, hverkuil+cisco, linux-kernel,
	linux-media, devicetree

From: Pratyush Yadav <p.yadav@ti.com>

Different platforms that use this driver might have different number of
DMA channels allocated for CSI. So only as many DMA contexts can be used
as the number of DMA channels available. Get the number of channels
provided via device tree and only configure that many contexts, and
hence only that many pads.

Reviewed-by: Yemike Abhilash Chandra <y-abhilashchandra@ti.com>
Reviewed-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
Signed-off-by: Pratyush Yadav <p.yadav@ti.com>
Co-developed-by: Jai Luthra <j-luthra@ti.com>
Signed-off-by: Jai Luthra <j-luthra@ti.com>
Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
---
 .../platform/ti/j721e-csi2rx/j721e-csi2rx.c   | 43 ++++++++++++++-----
 1 file changed, 32 insertions(+), 11 deletions(-)

diff --git a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
index 8f49ea2638585..f54ad67ff3f9d 100644
--- a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
+++ b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
@@ -43,7 +43,7 @@
 #define SHIM_PSI_CFG0_DST_TAG		GENMASK(31, 16)
 
 #define TI_CSI2RX_MAX_PIX_PER_CLK	4
-#define TI_CSI2RX_NUM_CTX		1
+#define TI_CSI2RX_MAX_CTX		32
 
 /*
  * There are no hard limits on the width or height. The DMA engine can handle
@@ -56,8 +56,8 @@
 
 #define TI_CSI2RX_PAD_SINK		0
 #define TI_CSI2RX_PAD_FIRST_SOURCE	1
-#define TI_CSI2RX_NUM_SOURCE_PADS	1
-#define TI_CSI2RX_NUM_PADS		(1 + TI_CSI2RX_NUM_SOURCE_PADS)
+#define TI_CSI2RX_MAX_SOURCE_PADS	TI_CSI2RX_MAX_CTX
+#define TI_CSI2RX_MAX_PADS		(1 + TI_CSI2RX_MAX_SOURCE_PADS)
 
 #define DRAIN_TIMEOUT_MS		50
 #define DRAIN_BUFFER_SIZE		SZ_32K
@@ -117,14 +117,15 @@ struct ti_csi2rx_dev {
 	void __iomem			*shim;
 	struct mutex			mutex; /* To serialize ioctls. */
 	unsigned int			enable_count;
+	unsigned int			num_ctx;
 	struct v4l2_device		v4l2_dev;
 	struct media_device		mdev;
 	struct media_pipeline		pipe;
-	struct media_pad		pads[TI_CSI2RX_NUM_PADS];
+	struct media_pad		pads[TI_CSI2RX_MAX_PADS];
 	struct v4l2_async_notifier	notifier;
 	struct v4l2_subdev		*source;
 	struct v4l2_subdev		subdev;
-	struct ti_csi2rx_ctx		ctx[TI_CSI2RX_NUM_CTX];
+	struct ti_csi2rx_ctx		ctx[TI_CSI2RX_MAX_CTX];
 	u8				pix_per_clk;
 	/* Buffer to drain stale data from PSI-L endpoint */
 	struct {
@@ -459,7 +460,7 @@ static int csi_async_notifier_complete(struct v4l2_async_notifier *notifier)
 		return ret;
 
 	/* Create and link video nodes for all DMA contexts */
-	for (i = 0; i < TI_CSI2RX_NUM_CTX; i++) {
+	for (i = 0; i < csi->num_ctx; i++) {
 		struct ti_csi2rx_ctx *ctx = &csi->ctx[i];
 		struct video_device *vdev = &ctx->vdev;
 
@@ -1253,10 +1254,11 @@ static int ti_csi2rx_v4l2_init(struct ti_csi2rx_dev *csi)
 	csi->pads[TI_CSI2RX_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
 
 	for (unsigned int i = TI_CSI2RX_PAD_FIRST_SOURCE;
-	     i < TI_CSI2RX_NUM_PADS; i++)
+	     i < TI_CSI2RX_PAD_FIRST_SOURCE + csi->num_ctx; i++)
 		csi->pads[i].flags = MEDIA_PAD_FL_SOURCE;
 
-	ret = media_entity_pads_init(&sd->entity, ARRAY_SIZE(csi->pads),
+	ret = media_entity_pads_init(&sd->entity,
+				     TI_CSI2RX_PAD_FIRST_SOURCE + csi->num_ctx,
 				     csi->pads);
 	if (ret)
 		goto unregister_media;
@@ -1347,8 +1349,9 @@ static int ti_csi2rx_init_ctx(struct ti_csi2rx_ctx *ctx)
 
 static int ti_csi2rx_probe(struct platform_device *pdev)
 {
+	struct device_node *np = pdev->dev.of_node;
 	struct ti_csi2rx_dev *csi;
-	int ret, i;
+	int ret = 0, i, count;
 
 	csi = devm_kzalloc(&pdev->dev, sizeof(*csi), GFP_KERNEL);
 	if (!csi)
@@ -1370,13 +1373,30 @@ static int ti_csi2rx_probe(struct platform_device *pdev)
 	if (!csi->drain.vaddr)
 		return -ENOMEM;
 
+	/* Only use as many contexts as the number of DMA channels allocated. */
+	count = of_property_count_strings(np, "dma-names");
+	if (count < 0) {
+		dev_err(csi->dev, "Failed to get DMA channel count: %d\n", count);
+		ret = count;
+		goto err_dma_chan;
+	}
+
+	csi->num_ctx = count;
+	if (csi->num_ctx > TI_CSI2RX_MAX_CTX) {
+		dev_err(csi->dev,
+			"%u DMA channels passed. Maximum is %u.\n",
+			csi->num_ctx, TI_CSI2RX_MAX_CTX);
+		ret = -EINVAL;
+		goto err_dma_chan;
+	}
+
 	mutex_init(&csi->mutex);
 
 	ret = ti_csi2rx_v4l2_init(csi);
 	if (ret)
 		goto err_v4l2;
 
-	for (i = 0; i < TI_CSI2RX_NUM_CTX; i++) {
+	for (i = 0; i < csi->num_ctx; i++) {
 		csi->ctx[i].idx = i;
 		csi->ctx[i].csi = csi;
 		ret = ti_csi2rx_init_ctx(&csi->ctx[i]);
@@ -1405,6 +1425,7 @@ static int ti_csi2rx_probe(struct platform_device *pdev)
 	ti_csi2rx_cleanup_v4l2(csi);
 err_v4l2:
 	mutex_destroy(&csi->mutex);
+err_dma_chan:
 	dma_free_coherent(csi->dev, csi->drain.len, csi->drain.vaddr,
 			  csi->drain.paddr);
 	return ret;
@@ -1415,7 +1436,7 @@ static void ti_csi2rx_remove(struct platform_device *pdev)
 	struct ti_csi2rx_dev *csi = platform_get_drvdata(pdev);
 	unsigned int i;
 
-	for (i = 0; i < TI_CSI2RX_NUM_CTX; i++)
+	for (i = 0; i < csi->num_ctx; i++)
 		ti_csi2rx_cleanup_ctx(&csi->ctx[i]);
 
 	ti_csi2rx_cleanup_notifier(csi);
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v9 10/19] media: cadence: csi2rx: add get_frame_desc wrapper
  2025-12-30  8:32 [PATCH v9 00/19] media: cadence,ti: CSI2RX Multistream Support Rishikesh Donadkar
                   ` (8 preceding siblings ...)
  2025-12-30  8:32 ` [PATCH v9 09/19] media: ti: j721e-csi2rx: get number of contexts from device tree Rishikesh Donadkar
@ 2025-12-30  8:32 ` Rishikesh Donadkar
  2025-12-30  8:32 ` [PATCH v9 11/19] media: ti: j721e-csi2rx: add support for processing virtual channels Rishikesh Donadkar
                   ` (8 subsequent siblings)
  18 siblings, 0 replies; 54+ messages in thread
From: Rishikesh Donadkar @ 2025-12-30  8:32 UTC (permalink / raw)
  To: jai.luthra, laurent.pinchart, mripard
  Cc: r-donadkar, y-abhilashchandra, devarsht, s-jain1, vigneshr,
	mchehab, robh, krzk+dt, p.zabel, conor+dt, sakari.ailus,
	hverkuil-cisco, tomi.valkeinen, jai.luthra, changhuang.liang,
	jack.zhu, sjoerd, dan.carpenter, hverkuil+cisco, linux-kernel,
	linux-media, devicetree

From: Pratyush Yadav <p.yadav@ti.com>

J721E wrapper CSI2RX driver needs to get the frame descriptor from the
source to find out info about virtual channel. This driver itself does
not touch the routing or virtual channels in any way. So simply pass the
descriptor through from the source.

Signed-off-by: Pratyush Yadav <p.yadav@ti.com>
Signed-off-by: Jai Luthra <j-luthra@ti.com>
Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
Reviewed-by: Changhuang Liang <changhuang.liang@starfivetech.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Yemike Abhilash Chandra <y-abhilashchandra@ti.com>
Reviewed-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
---
 drivers/media/platform/cadence/cdns-csi2rx.c | 24 ++++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/drivers/media/platform/cadence/cdns-csi2rx.c b/drivers/media/platform/cadence/cdns-csi2rx.c
index 8e7af2da62262..65c6acb02f85b 100644
--- a/drivers/media/platform/cadence/cdns-csi2rx.c
+++ b/drivers/media/platform/cadence/cdns-csi2rx.c
@@ -229,6 +229,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 (!remote_pad) {
+		dev_err(csi2rx->dev, "No remote pad found for sink\n");
+		return -ENODEV;
+	}
+
+	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)
 {
@@ -611,12 +626,21 @@ int cdns_csi2rx_negotiate_ppc(struct v4l2_subdev *subdev, unsigned int pad,
 }
 EXPORT_SYMBOL_FOR_MODULES(cdns_csi2rx_negotiate_ppc, "j721e-csi2rx");
 
+static int csi2rx_get_frame_desc(struct v4l2_subdev *subdev, unsigned int pad,
+				 struct v4l2_mbus_frame_desc *fd)
+{
+	struct csi2rx_priv *csi2rx = v4l2_subdev_to_csi2rx(subdev);
+
+	return csi2rx_get_frame_desc_from_source(csi2rx, fd);
+}
+
 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	= csi2rx_get_frame_desc,
 };
 
 static const struct v4l2_subdev_core_ops csi2rx_core_ops = {
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v9 11/19] media: ti: j721e-csi2rx: add support for processing virtual channels
  2025-12-30  8:32 [PATCH v9 00/19] media: cadence,ti: CSI2RX Multistream Support Rishikesh Donadkar
                   ` (9 preceding siblings ...)
  2025-12-30  8:32 ` [PATCH v9 10/19] media: cadence: csi2rx: add get_frame_desc wrapper Rishikesh Donadkar
@ 2025-12-30  8:32 ` Rishikesh Donadkar
  2026-01-14 15:31   ` Tomi Valkeinen
  2025-12-30  8:32 ` [PATCH v9 12/19] media: cadence: csi2rx: add multistream support Rishikesh Donadkar
                   ` (7 subsequent siblings)
  18 siblings, 1 reply; 54+ messages in thread
From: Rishikesh Donadkar @ 2025-12-30  8:32 UTC (permalink / raw)
  To: jai.luthra, laurent.pinchart, mripard
  Cc: r-donadkar, y-abhilashchandra, devarsht, s-jain1, vigneshr,
	mchehab, robh, krzk+dt, p.zabel, conor+dt, sakari.ailus,
	hverkuil-cisco, tomi.valkeinen, jai.luthra, changhuang.liang,
	jack.zhu, sjoerd, dan.carpenter, hverkuil+cisco, linux-kernel,
	linux-media, devicetree

From: Jai Luthra <j-luthra@ti.com>

Use get_frame_desc() to get the frame desc from the connected source,
and use the provided virtual channel instead of VC 0.

get_frame_desc() works for single stream case, but as we don't
support multiple streams yet, we will just always use stream 0.
If the source doesn't support get_frame_desc(), fall back to
the previous method of always capturing virtual channel 0.

Reviewed-by: Yemike Abhilash Chandra <y-abhilashchandra@ti.com>
Co-developed-by: Pratyush Yadav <p.yadav@ti.com>
Signed-off-by: Pratyush Yadav <p.yadav@ti.com>
Signed-off-by: Jai Luthra <j-luthra@ti.com>
Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
---
 .../platform/ti/j721e-csi2rx/j721e-csi2rx.c   | 45 ++++++++++++++++++-
 1 file changed, 44 insertions(+), 1 deletion(-)

diff --git a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
index f54ad67ff3f9d..6f9f34aa26f1b 100644
--- a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
+++ b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
@@ -32,6 +32,7 @@
 #define SHIM_DMACNTX_YUV422		GENMASK(27, 26)
 #define SHIM_DMACNTX_DUAL_PCK_CFG	BIT(24)
 #define SHIM_DMACNTX_SIZE		GENMASK(21, 20)
+#define SHIM_DMACNTX_VC			GENMASK(9, 6)
 #define SHIM_DMACNTX_FMT		GENMASK(5, 0)
 #define SHIM_DMACNTX_YUV422_MODE_11	3
 #define SHIM_DMACNTX_SIZE_8		0
@@ -110,6 +111,9 @@ struct ti_csi2rx_ctx {
 	struct media_pad		pad;
 	u32				sequence;
 	u32				idx;
+	u32				vc;
+	u32				dt;
+	u32				stream;
 };
 
 struct ti_csi2rx_dev {
@@ -570,7 +574,7 @@ static void ti_csi2rx_setup_shim(struct ti_csi2rx_ctx *ctx)
 	ti_csi2rx_request_max_ppc(csi);
 
 	reg = SHIM_DMACNTX_EN;
-	reg |= FIELD_PREP(SHIM_DMACNTX_FMT, fmt->csi_dt);
+	reg |= FIELD_PREP(SHIM_DMACNTX_FMT, ctx->dt);
 
 	/*
 	 * The hardware assumes incoming YUV422 8-bit data on MIPI CSI2 bus
@@ -610,6 +614,7 @@ static void ti_csi2rx_setup_shim(struct ti_csi2rx_ctx *ctx)
 	}
 
 	reg |= FIELD_PREP(SHIM_DMACNTX_SIZE, fmt->size);
+	reg |= FIELD_PREP(SHIM_DMACNTX_VC, ctx->vc);
 
 	writel(reg, csi->shim + SHIM_DMACNTX(ctx->idx));
 
@@ -884,12 +889,41 @@ static void ti_csi2rx_buffer_queue(struct vb2_buffer *vb)
 	}
 }
 
+static int ti_csi2rx_get_vc_and_dt(struct ti_csi2rx_ctx *ctx)
+{
+	struct ti_csi2rx_dev *csi = ctx->csi;
+	struct v4l2_mbus_frame_desc fd;
+	struct media_pad *pad;
+	int ret, i;
+
+	pad = media_entity_remote_pad_unique(&csi->subdev.entity, MEDIA_PAD_FL_SOURCE);
+	if (!pad)
+		return -ENODEV;
+
+	ret = v4l2_subdev_call(csi->source, pad, get_frame_desc, pad->index, &fd);
+	if (ret)
+		return ret;
+
+	if (fd.type != V4L2_MBUS_FRAME_DESC_TYPE_CSI2)
+		return -EINVAL;
+
+	for (i = 0; i < fd.num_entries; i++) {
+		if (ctx->stream == fd.entry[i].stream) {
+			ctx->vc = fd.entry[i].bus.csi2.vc;
+			ctx->dt = fd.entry[i].bus.csi2.dt;
+		}
+	}
+
+	return 0;
+}
+
 static int ti_csi2rx_start_streaming(struct vb2_queue *vq, unsigned int count)
 {
 	struct ti_csi2rx_ctx *ctx = vb2_get_drv_priv(vq);
 	struct ti_csi2rx_dev *csi = ctx->csi;
 	struct ti_csi2rx_dma *dma = &ctx->dma;
 	struct ti_csi2rx_buffer *buf;
+	const struct ti_csi2rx_fmt *fmt;
 	unsigned long flags;
 	int ret = 0;
 
@@ -904,6 +938,15 @@ static int ti_csi2rx_start_streaming(struct vb2_queue *vq, unsigned int count)
 	if (ret)
 		goto err;
 
+	ret = ti_csi2rx_get_vc_and_dt(ctx);
+	if (ret == -ENOIOCTLCMD) {
+		ctx->vc = 0;
+		fmt = find_format_by_fourcc(ctx->v_fmt.fmt.pix.pixelformat);
+		ctx->dt = fmt->csi_dt;
+	} else if (ret < 0) {
+		goto err;
+	}
+
 	ti_csi2rx_setup_shim(ctx);
 
 	ctx->sequence = 0;
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v9 12/19] media: cadence: csi2rx: add multistream support
  2025-12-30  8:32 [PATCH v9 00/19] media: cadence,ti: CSI2RX Multistream Support Rishikesh Donadkar
                   ` (10 preceding siblings ...)
  2025-12-30  8:32 ` [PATCH v9 11/19] media: ti: j721e-csi2rx: add support for processing virtual channels Rishikesh Donadkar
@ 2025-12-30  8:32 ` Rishikesh Donadkar
  2026-01-15 12:01   ` Tomi Valkeinen
  2025-12-30  8:32 ` [PATCH v9 13/19] media: ti: j721e-csi2rx: " Rishikesh Donadkar
                   ` (6 subsequent siblings)
  18 siblings, 1 reply; 54+ messages in thread
From: Rishikesh Donadkar @ 2025-12-30  8:32 UTC (permalink / raw)
  To: jai.luthra, laurent.pinchart, mripard
  Cc: r-donadkar, y-abhilashchandra, devarsht, s-jain1, vigneshr,
	mchehab, robh, krzk+dt, p.zabel, conor+dt, sakari.ailus,
	hverkuil-cisco, tomi.valkeinen, jai.luthra, changhuang.liang,
	jack.zhu, sjoerd, dan.carpenter, hverkuil+cisco, linux-kernel,
	linux-media, devicetree

From: Jai Luthra <j-luthra@ti.com>

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 <j-luthra@ti.com>
Reviewed-by: Changhuang Liang <changhuang.liang@starfivetech.com>
Reviewed-by: Yemike Abhilash Chandra <y-abhilashchandra@ti.com>
Co-developed-by: Rishikesh Donadkar <r-donadkar@ti.com>
Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
---
 drivers/media/platform/cadence/cdns-csi2rx.c | 248 +++++++++++++++----
 1 file changed, 201 insertions(+), 47 deletions(-)

diff --git a/drivers/media/platform/cadence/cdns-csi2rx.c b/drivers/media/platform/cadence/cdns-csi2rx.c
index 65c6acb02f85b..5c16a2e509136 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;
@@ -273,30 +274,43 @@ 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;
+	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 {
+		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;
+		}
 
-	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);
@@ -394,11 +408,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,
@@ -486,18 +496,59 @@ 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;
+
+	/* Capture VC=0 by default */
+	for (i = 0; i < CSI2RX_STREAMS_MAX; i++)
+		csi2rx->vc_select[i] = CSI2RX_STREAM_DATA_CFG_VC_SELECT(0);
+
+	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");
+		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;
@@ -505,10 +556,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;
@@ -523,9 +575,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");
 	}
 
@@ -550,12 +607,56 @@ 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;
+
+	if (routing->num_routes > V4L2_FRAME_DESC_ENTRY_MAX)
+		return -EINVAL;
+
+	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)
@@ -567,14 +668,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;
 }
@@ -582,21 +685,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,
@@ -630,17 +734,66 @@ static int csi2rx_get_frame_desc(struct v4l2_subdev *subdev, unsigned int pad,
 				 struct v4l2_mbus_frame_desc *fd)
 {
 	struct csi2rx_priv *csi2rx = v4l2_subdev_to_csi2rx(subdev);
+	struct v4l2_mbus_frame_desc source_fd = {0};
+	struct v4l2_subdev_route *route;
+	struct v4l2_subdev_state *state;
+	int ret;
 
-	return csi2rx_get_frame_desc_from_source(csi2rx, fd);
+	ret = csi2rx_get_frame_desc_from_source(csi2rx, &source_fd);
+	if (ret)
+		return ret;
+
+	fd->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2;
+
+	state = v4l2_subdev_lock_and_get_active_state(subdev);
+
+	for_each_active_route(&state->routing, route) {
+		struct v4l2_mbus_frame_desc_entry *source_entry = NULL;
+		unsigned int i;
+
+		if (route->source_pad != pad)
+			continue;
+
+		for (i = 0; i < source_fd.num_entries; i++) {
+			if (source_fd.entry[i].stream == route->sink_stream) {
+				source_entry = &source_fd.entry[i];
+				break;
+			}
+		}
+
+		if (!source_entry) {
+			dev_err(csi2rx->dev,
+				"Failed to find stream from source frame desc\n");
+			ret = -EPIPE;
+			goto err_missing_stream;
+		}
+
+		fd->entry[fd->num_entries].stream = route->source_stream;
+		fd->entry[fd->num_entries].flags = source_entry->flags;
+		fd->entry[fd->num_entries].length = source_entry->length;
+		fd->entry[fd->num_entries].pixelcode = source_entry->pixelcode;
+		fd->entry[fd->num_entries].bus.csi2.vc =
+			source_entry->bus.csi2.vc;
+		fd->entry[fd->num_entries].bus.csi2.dt =
+			source_entry->bus.csi2.dt;
+
+		fd->num_entries++;
+	}
+
+err_missing_stream:
+	v4l2_subdev_unlock_state(state);
+
+	return ret;
 }
 
 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	= csi2rx_get_frame_desc,
+	.enum_mbus_code		= csi2rx_enum_mbus_code,
+	.get_fmt		= v4l2_subdev_get_fmt,
+	.set_fmt		= csi2rx_set_fmt,
+	.get_frame_desc		= csi2rx_get_frame_desc,
+	.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 = {
@@ -876,7 +1029,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,
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v9 13/19] media: ti: j721e-csi2rx: add multistream support
  2025-12-30  8:32 [PATCH v9 00/19] media: cadence,ti: CSI2RX Multistream Support Rishikesh Donadkar
                   ` (11 preceding siblings ...)
  2025-12-30  8:32 ` [PATCH v9 12/19] media: cadence: csi2rx: add multistream support Rishikesh Donadkar
@ 2025-12-30  8:32 ` Rishikesh Donadkar
  2026-01-15 12:27   ` Tomi Valkeinen
  2026-01-15 12:28   ` Tomi Valkeinen
  2025-12-30  8:32 ` [PATCH v9 14/19] media: ti: j721e-csi2rx: Submit all available buffers Rishikesh Donadkar
                   ` (5 subsequent siblings)
  18 siblings, 2 replies; 54+ messages in thread
From: Rishikesh Donadkar @ 2025-12-30  8:32 UTC (permalink / raw)
  To: jai.luthra, laurent.pinchart, mripard
  Cc: r-donadkar, y-abhilashchandra, devarsht, s-jain1, vigneshr,
	mchehab, robh, krzk+dt, p.zabel, conor+dt, sakari.ailus,
	hverkuil-cisco, tomi.valkeinen, jai.luthra, changhuang.liang,
	jack.zhu, sjoerd, dan.carpenter, hverkuil+cisco, linux-kernel,
	linux-media, devicetree

From: Jai Luthra <j-luthra@ti.com>

Each CSI2 stream can be multiplexed into 4 independent streams, each
identified by its virtual channel number and data type. The incoming
data from these streams can be filtered on the basis of either the
virtual channel or the data type.

To capture this multiplexed stream, the application needs to tell
the driver how it wants to route the data. It needs to specify
which context should process which stream. This is done via the
new routing APIs.

Add ioctls to accept routing information from the application and save
that in the driver. This can be used when starting streaming on a
context to determine which route and consequently which virtual channel
it should process.

De-assert the pixel interface reset on first start_streaming() and assert
it on the last stop_streaming().

Reviewed-by: Yemike Abhilash Chandra <y-abhilashchandra@ti.com>
Co-developed-by: Pratyush Yadav <p.yadav@ti.com>
Signed-off-by: Pratyush Yadav <p.yadav@ti.com>
Signed-off-by: Jai Luthra <j-luthra@ti.com>
Co-developed-by: Rishikesh Donadkar <r-donadkar@ti.com>
Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
---
 .../platform/ti/j721e-csi2rx/j721e-csi2rx.c   | 259 +++++++++++++-----
 1 file changed, 189 insertions(+), 70 deletions(-)

diff --git a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
index 6f9f34aa26f1b..4a063364f893e 100644
--- a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
+++ b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
@@ -137,6 +137,7 @@ struct ti_csi2rx_dev {
 		dma_addr_t		paddr;
 		size_t			len;
 	} drain;
+	bool                            vc_cached;
 };
 
 static inline struct ti_csi2rx_dev *to_csi2rx_dev(struct v4l2_subdev *sd)
@@ -144,17 +145,6 @@ static inline struct ti_csi2rx_dev *to_csi2rx_dev(struct v4l2_subdev *sd)
 	return container_of(sd, struct ti_csi2rx_dev, subdev);
 }
 
-static const struct v4l2_mbus_framefmt ti_csi2rx_default_fmt = {
-	.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,
-};
-
 static const struct ti_csi2rx_fmt ti_csi2rx_formats[] = {
 	{
 		.fourcc			= V4L2_PIX_FMT_YUYV,
@@ -567,8 +557,10 @@ static void ti_csi2rx_setup_shim(struct ti_csi2rx_ctx *ctx)
 	fmt = find_format_by_fourcc(ctx->v_fmt.fmt.pix.pixelformat);
 
 	/* De-assert the pixel interface reset. */
-	reg = SHIM_CNTL_PIX_RST;
-	writel(reg, csi->shim + SHIM_CNTL);
+	if (!csi->enable_count) {
+		reg = SHIM_CNTL_PIX_RST;
+		writel(reg, csi->shim + SHIM_CNTL);
+	}
 
 	/* Negotiate pixel count from the source */
 	ti_csi2rx_request_max_ppc(csi);
@@ -889,29 +881,69 @@ static void ti_csi2rx_buffer_queue(struct vb2_buffer *vb)
 	}
 }
 
+static int ti_csi2rx_get_stream(struct ti_csi2rx_ctx *ctx)
+{
+	struct ti_csi2rx_dev *csi = ctx->csi;
+	struct media_pad *pad;
+	struct v4l2_subdev_state *state;
+	struct v4l2_subdev_route *r;
+
+	/* Get the source pad connected to this ctx */
+	pad = media_entity_remote_source_pad_unique(ctx->pad.entity);
+	if (!pad) {
+		dev_err(csi->dev, "No pad connected to ctx %d\n", ctx->idx);
+		return -ENODEV;
+	}
+
+	state = v4l2_subdev_get_locked_active_state(&csi->subdev);
+
+	for_each_active_route(&state->routing, r) {
+		if (r->source_pad == pad->index) {
+			ctx->stream = r->sink_stream;
+			return 0;
+		}
+	}
+
+	/* No route found for this ctx */
+	return -ENODEV;
+}
+
 static int ti_csi2rx_get_vc_and_dt(struct ti_csi2rx_ctx *ctx)
 {
 	struct ti_csi2rx_dev *csi = ctx->csi;
+	struct ti_csi2rx_ctx *curr_ctx;
 	struct v4l2_mbus_frame_desc fd;
-	struct media_pad *pad;
-	int ret, i;
+	struct media_pad *source_pad;
+	int ret;
+	unsigned int i, j;
 
-	pad = media_entity_remote_pad_unique(&csi->subdev.entity, MEDIA_PAD_FL_SOURCE);
-	if (!pad)
+	/* Get the frame desc form source */
+	source_pad = media_entity_remote_pad_unique(&csi->subdev.entity, MEDIA_PAD_FL_SOURCE);
+	if (!source_pad)
 		return -ENODEV;
 
-	ret = v4l2_subdev_call(csi->source, pad, get_frame_desc, pad->index, &fd);
+	ret = v4l2_subdev_call(csi->source, pad, get_frame_desc, source_pad->index, &fd);
 	if (ret)
 		return ret;
 
 	if (fd.type != V4L2_MBUS_FRAME_DESC_TYPE_CSI2)
 		return -EINVAL;
 
-	for (i = 0; i < fd.num_entries; i++) {
-		if (ctx->stream == fd.entry[i].stream) {
-			ctx->vc = fd.entry[i].bus.csi2.vc;
-			ctx->dt = fd.entry[i].bus.csi2.dt;
-		}
+	for (i = 0; i < csi->num_ctx; i++) {
+		curr_ctx = &csi->ctx[i];
+
+		/* Capture VC 0 by default */
+		curr_ctx->vc = 0;
+
+		ret = ti_csi2rx_get_stream(curr_ctx);
+		if (ret)
+			continue;
+
+		for (j = 0; j < fd.num_entries; j++)
+			if (curr_ctx->stream == fd.entry[j].stream) {
+				curr_ctx->vc = fd.entry[j].bus.csi2.vc;
+				curr_ctx->dt = fd.entry[j].bus.csi2.dt;
+			}
 	}
 
 	return 0;
@@ -922,8 +954,6 @@ static int ti_csi2rx_start_streaming(struct vb2_queue *vq, unsigned int count)
 	struct ti_csi2rx_ctx *ctx = vb2_get_drv_priv(vq);
 	struct ti_csi2rx_dev *csi = ctx->csi;
 	struct ti_csi2rx_dma *dma = &ctx->dma;
-	struct ti_csi2rx_buffer *buf;
-	const struct ti_csi2rx_fmt *fmt;
 	unsigned long flags;
 	int ret = 0;
 
@@ -938,35 +968,9 @@ static int ti_csi2rx_start_streaming(struct vb2_queue *vq, unsigned int count)
 	if (ret)
 		goto err;
 
-	ret = ti_csi2rx_get_vc_and_dt(ctx);
-	if (ret == -ENOIOCTLCMD) {
-		ctx->vc = 0;
-		fmt = find_format_by_fourcc(ctx->v_fmt.fmt.pix.pixelformat);
-		ctx->dt = fmt->csi_dt;
-	} else if (ret < 0) {
-		goto err;
-	}
-
-	ti_csi2rx_setup_shim(ctx);
-
-	ctx->sequence = 0;
-
-	spin_lock_irqsave(&dma->lock, flags);
-	buf = list_entry(dma->queue.next, struct ti_csi2rx_buffer, list);
-
-	ret = ti_csi2rx_start_dma(ctx, buf);
-	if (ret) {
-		dev_err(csi->dev, "Failed to start DMA: %d\n", ret);
-		spin_unlock_irqrestore(&dma->lock, flags);
-		goto err_pipeline;
-	}
-
-	list_move_tail(&buf->list, &dma->submitted);
-	dma->state = TI_CSI2RX_DMA_ACTIVE;
-	spin_unlock_irqrestore(&dma->lock, flags);
-
+	/* Start stream 0, we don't allow multiple streams on the source pad */
 	ret = v4l2_subdev_enable_streams(&csi->subdev,
-					 TI_CSI2RX_PAD_FIRST_SOURCE,
+					 TI_CSI2RX_PAD_FIRST_SOURCE + ctx->idx,
 					 BIT_U64(0));
 	if (ret)
 		goto err_dma;
@@ -975,7 +979,6 @@ static int ti_csi2rx_start_streaming(struct vb2_queue *vq, unsigned int count)
 
 err_dma:
 	ti_csi2rx_stop_dma(ctx);
-err_pipeline:
 	video_device_pipeline_stop(&ctx->vdev);
 	writel(0, csi->shim + SHIM_CNTL);
 	writel(0, csi->shim + SHIM_DMACNTX(ctx->idx));
@@ -990,17 +993,26 @@ static void ti_csi2rx_stop_streaming(struct vb2_queue *vq)
 	struct ti_csi2rx_dev *csi = ctx->csi;
 	int ret;
 
-	video_device_pipeline_stop(&ctx->vdev);
+	mutex_lock(&csi->mutex);
 
-	writel(0, csi->shim + SHIM_CNTL);
 	writel(0, csi->shim + SHIM_DMACNTX(ctx->idx));
 
+	/* assert pixel reset to prevent stale data */
+	if (csi->enable_count == 1) {
+		writel(0, csi->shim + SHIM_CNTL);
+		csi->vc_cached = false;
+	}
+
+	video_device_pipeline_stop(&ctx->vdev);
+
 	ret = v4l2_subdev_disable_streams(&csi->subdev,
-					  TI_CSI2RX_PAD_FIRST_SOURCE,
+					  TI_CSI2RX_PAD_FIRST_SOURCE + ctx->idx,
 					  BIT_U64(0));
 	if (ret)
 		dev_err(csi->dev, "Failed to stop subdev stream\n");
 
+	mutex_unlock(&csi->mutex);
+
 	ti_csi2rx_stop_dma(ctx);
 	ti_csi2rx_cleanup_buffers(ctx, VB2_BUF_STATE_ERROR);
 }
@@ -1043,25 +1055,84 @@ static int ti_csi2rx_sd_set_fmt(struct v4l2_subdev *sd,
 	fmt = v4l2_subdev_state_get_format(state, format->pad, format->stream);
 	*fmt = format->format;
 
-	fmt = v4l2_subdev_state_get_format(state, TI_CSI2RX_PAD_FIRST_SOURCE,
-					   format->stream);
+	fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad,
+							   format->stream);
+	if (!fmt)
+		return -EINVAL;
+
 	*fmt = format->format;
 
 	return 0;
 }
 
-static int ti_csi2rx_sd_init_state(struct v4l2_subdev *sd,
-				   struct v4l2_subdev_state *state)
+static int _ti_csi2rx_sd_set_routing(struct v4l2_subdev *sd,
+				     struct v4l2_subdev_state *state,
+				     struct v4l2_subdev_krouting *routing)
 {
-	struct v4l2_mbus_framefmt *fmt;
+	int ret;
+
+	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,
+	};
 
-	fmt = v4l2_subdev_state_get_format(state, TI_CSI2RX_PAD_SINK);
-	*fmt = ti_csi2rx_default_fmt;
+	ret = v4l2_subdev_routing_validate(sd, routing,
+					   V4L2_SUBDEV_ROUTING_ONLY_1_TO_1 |
+					   V4L2_SUBDEV_ROUTING_NO_SOURCE_MULTIPLEXING);
 
-	fmt = v4l2_subdev_state_get_format(state, TI_CSI2RX_PAD_FIRST_SOURCE);
-	*fmt = ti_csi2rx_default_fmt;
+	if (ret)
+		return ret;
 
-	return 0;
+	/* Only stream ID 0 allowed on source pads */
+	for (unsigned int i = 0; i < routing->num_routes; ++i) {
+		const struct v4l2_subdev_route *route = &routing->routes[i];
+
+		if (route->source_stream != 0)
+			return -EINVAL;
+	}
+
+	ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
+
+	return ret;
+}
+
+static int ti_csi2rx_sd_set_routing(struct v4l2_subdev *sd,
+				    struct v4l2_subdev_state *state,
+				    enum v4l2_subdev_format_whence which,
+				    struct v4l2_subdev_krouting *routing)
+{
+	struct ti_csi2rx_dev *csi = to_csi2rx_dev(sd);
+
+	if (csi->enable_count > 0)
+		return -EBUSY;
+
+	return _ti_csi2rx_sd_set_routing(sd, state, routing);
+}
+
+static int ti_csi2rx_sd_init_state(struct v4l2_subdev *sd,
+				   struct v4l2_subdev_state *state)
+{
+	struct v4l2_subdev_route routes[] = { {
+		.sink_pad = 0,
+		.sink_stream = 0,
+		.source_pad = TI_CSI2RX_PAD_FIRST_SOURCE,
+		.source_stream = 0,
+		.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
+	} };
+
+	struct v4l2_subdev_krouting routing = {
+		.num_routes = 1,
+		.routes = routes,
+	};
+
+	/* Initialize routing to single route to the fist source pad */
+	return _ti_csi2rx_sd_set_routing(sd, state, &routing);
 }
 
 static int ti_csi2rx_sd_enable_streams(struct v4l2_subdev *sd,
@@ -1069,15 +1140,58 @@ static int ti_csi2rx_sd_enable_streams(struct v4l2_subdev *sd,
 				       u32 pad, u64 streams_mask)
 {
 	struct ti_csi2rx_dev *csi = to_csi2rx_dev(sd);
+	struct ti_csi2rx_ctx *ctx = &csi->ctx[pad - TI_CSI2RX_PAD_FIRST_SOURCE];
+	struct ti_csi2rx_dma *dma = &ctx->dma;
 	struct media_pad *remote_pad;
+	struct ti_csi2rx_buffer *buf;
+	const struct ti_csi2rx_fmt *fmt;
+	unsigned long flags;
+	u64 sink_streams;
 	int ret = 0;
 
+	ret = ti_csi2rx_get_stream(ctx);
+	if (ret)
+		return ret;
+
+	/* Get the VC and DT for all enabled ctx on first stream start */
+	if (!csi->vc_cached) {
+		ret = ti_csi2rx_get_vc_and_dt(ctx);
+		if (ret == -ENOIOCTLCMD) {
+			ctx->vc = 0;
+			fmt = find_format_by_fourcc(ctx->v_fmt.fmt.pix.pixelformat);
+			ctx->dt = fmt->csi_dt;
+		} else if (ret < 0) {
+			return ret;
+		}
+		csi->vc_cached = true;
+	}
+
+	ti_csi2rx_setup_shim(ctx);
+	ctx->sequence = 0;
+
+	spin_lock_irqsave(&dma->lock, flags);
+	buf = list_entry(dma->queue.next, struct ti_csi2rx_buffer, list);
+
+	ret = ti_csi2rx_start_dma(ctx, buf);
+	if (ret) {
+		dev_err(csi->dev, "Failed to start DMA: %d\n", ret);
+		spin_unlock_irqrestore(&dma->lock, flags);
+		return ret;
+	}
+
+	list_move_tail(&buf->list, &dma->submitted);
+	dma->state = TI_CSI2RX_DMA_ACTIVE;
+	spin_unlock_irqrestore(&dma->lock, flags);
+
 	remote_pad = media_entity_remote_source_pad_unique(&csi->subdev.entity);
 	if (!remote_pad)
 		return -ENODEV;
+	sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
+						       TI_CSI2RX_PAD_SINK,
+						       &streams_mask);
 
 	ret = v4l2_subdev_enable_streams(csi->source, remote_pad->index,
-					 BIT_U64(0));
+					 sink_streams);
 	if (ret)
 		return ret;
 
@@ -1092,17 +1206,21 @@ static int ti_csi2rx_sd_disable_streams(struct v4l2_subdev *sd,
 {
 	struct ti_csi2rx_dev *csi = to_csi2rx_dev(sd);
 	struct media_pad *remote_pad;
+	u64 sink_streams;
 	int ret = 0;
 
 	remote_pad = media_entity_remote_source_pad_unique(&csi->subdev.entity);
 	if (!remote_pad)
 		return -ENODEV;
+	sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
+						       TI_CSI2RX_PAD_SINK,
+						       &streams_mask);
 
 	if (csi->enable_count == 0)
 		return -EINVAL;
 
 	ret = v4l2_subdev_disable_streams(csi->source, remote_pad->index,
-					  BIT_U64(0));
+					  sink_streams);
 	if (!ret)
 		--csi->enable_count;
 
@@ -1111,6 +1229,7 @@ static int ti_csi2rx_sd_disable_streams(struct v4l2_subdev *sd,
 
 static const struct v4l2_subdev_pad_ops ti_csi2rx_subdev_pad_ops = {
 	.enum_mbus_code	= ti_csi2rx_enum_mbus_code,
+	.set_routing = ti_csi2rx_sd_set_routing,
 	.get_fmt = v4l2_subdev_get_fmt,
 	.set_fmt = ti_csi2rx_sd_set_fmt,
 	.enable_streams = ti_csi2rx_sd_enable_streams,
@@ -1289,7 +1408,7 @@ static int ti_csi2rx_v4l2_init(struct ti_csi2rx_dev *csi)
 	v4l2_subdev_init(sd, &ti_csi2rx_subdev_ops);
 	sd->internal_ops = &ti_csi2rx_internal_ops;
 	sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
-	sd->flags = V4L2_SUBDEV_FL_HAS_DEVNODE;
+	sd->flags = V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS;
 	strscpy(sd->name, dev_name(csi->dev), sizeof(sd->name));
 	sd->dev = csi->dev;
 	sd->entity.ops = &ti_csi2rx_subdev_entity_ops;
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v9 14/19] media: ti: j721e-csi2rx: Submit all available buffers
  2025-12-30  8:32 [PATCH v9 00/19] media: cadence,ti: CSI2RX Multistream Support Rishikesh Donadkar
                   ` (12 preceding siblings ...)
  2025-12-30  8:32 ` [PATCH v9 13/19] media: ti: j721e-csi2rx: " Rishikesh Donadkar
@ 2025-12-30  8:32 ` Rishikesh Donadkar
  2025-12-30  8:32 ` [PATCH v9 15/19] media: ti: j721e-csi2rx: Change the drain architecture for multistream Rishikesh Donadkar
                   ` (4 subsequent siblings)
  18 siblings, 0 replies; 54+ messages in thread
From: Rishikesh Donadkar @ 2025-12-30  8:32 UTC (permalink / raw)
  To: jai.luthra, laurent.pinchart, mripard
  Cc: r-donadkar, y-abhilashchandra, devarsht, s-jain1, vigneshr,
	mchehab, robh, krzk+dt, p.zabel, conor+dt, sakari.ailus,
	hverkuil-cisco, tomi.valkeinen, jai.luthra, changhuang.liang,
	jack.zhu, sjoerd, dan.carpenter, hverkuil+cisco, linux-kernel,
	linux-media, devicetree

From: Jai Luthra <j-luthra@ti.com>

We already make sure to submit all available buffers to DMA in each DMA
completion callback.

Move that logic in a separate function, and use it during stream start
as well, as most application queue all their buffers before stream on.

Signed-off-by: Jai Luthra <j-luthra@ti.com>
Reviewed-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
Reviewed-by: Yemike Abhilash Chandra <y-abhilashchandra@ti.com>
Co-developed-by: Rishikesh Donadkar <r-donadkar@ti.com>
Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
---
 .../platform/ti/j721e-csi2rx/j721e-csi2rx.c   | 42 +++++++++++--------
 1 file changed, 24 insertions(+), 18 deletions(-)

diff --git a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
index 4a063364f893e..fa6152464d4b6 100644
--- a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
+++ b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
@@ -672,11 +672,32 @@ static int ti_csi2rx_drain_dma(struct ti_csi2rx_ctx *ctx)
 	return ret;
 }
 
+static int ti_csi2rx_dma_submit_pending(struct ti_csi2rx_ctx *ctx)
+{
+	struct ti_csi2rx_dma *dma = &ctx->dma;
+	struct ti_csi2rx_buffer *buf;
+	int ret = 0;
+
+	/* If there are more buffers to process then start their transfer. */
+	while (!list_empty(&dma->queue)) {
+		buf = list_entry(dma->queue.next, struct ti_csi2rx_buffer, list);
+		ret = ti_csi2rx_start_dma(ctx, buf);
+		if (ret) {
+			dev_err(ctx->csi->dev,
+				"Failed to queue the next buffer for DMA\n");
+			vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+			list_del(&buf->list);
+		} else {
+			list_move_tail(&buf->list, &dma->submitted);
+		}
+	}
+	return ret;
+}
+
 static void ti_csi2rx_dma_callback(void *param)
 {
 	struct ti_csi2rx_buffer *buf = param;
 	struct ti_csi2rx_ctx *ctx = buf->ctx;
-	struct ti_csi2rx_dev *csi = ctx->csi;
 	struct ti_csi2rx_dma *dma = &ctx->dma;
 	unsigned long flags;
 
@@ -693,18 +714,7 @@ static void ti_csi2rx_dma_callback(void *param)
 	vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
 	list_del(&buf->list);
 
-	/* If there are more buffers to process then start their transfer. */
-	while (!list_empty(&dma->queue)) {
-		buf = list_entry(dma->queue.next, struct ti_csi2rx_buffer, list);
-
-		if (ti_csi2rx_start_dma(ctx, buf)) {
-			dev_err(csi->dev, "Failed to queue the next buffer for DMA\n");
-			list_del(&buf->list);
-			vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
-		} else {
-			list_move_tail(&buf->list, &dma->submitted);
-		}
-	}
+	ti_csi2rx_dma_submit_pending(ctx);
 
 	if (list_empty(&dma->submitted))
 		dma->state = TI_CSI2RX_DMA_IDLE;
@@ -1143,7 +1153,6 @@ static int ti_csi2rx_sd_enable_streams(struct v4l2_subdev *sd,
 	struct ti_csi2rx_ctx *ctx = &csi->ctx[pad - TI_CSI2RX_PAD_FIRST_SOURCE];
 	struct ti_csi2rx_dma *dma = &ctx->dma;
 	struct media_pad *remote_pad;
-	struct ti_csi2rx_buffer *buf;
 	const struct ti_csi2rx_fmt *fmt;
 	unsigned long flags;
 	u64 sink_streams;
@@ -1170,16 +1179,13 @@ static int ti_csi2rx_sd_enable_streams(struct v4l2_subdev *sd,
 	ctx->sequence = 0;
 
 	spin_lock_irqsave(&dma->lock, flags);
-	buf = list_entry(dma->queue.next, struct ti_csi2rx_buffer, list);
 
-	ret = ti_csi2rx_start_dma(ctx, buf);
+	ret = ti_csi2rx_dma_submit_pending(ctx);
 	if (ret) {
-		dev_err(csi->dev, "Failed to start DMA: %d\n", ret);
 		spin_unlock_irqrestore(&dma->lock, flags);
 		return ret;
 	}
 
-	list_move_tail(&buf->list, &dma->submitted);
 	dma->state = TI_CSI2RX_DMA_ACTIVE;
 	spin_unlock_irqrestore(&dma->lock, flags);
 
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v9 15/19] media: ti: j721e-csi2rx: Change the drain architecture for multistream
  2025-12-30  8:32 [PATCH v9 00/19] media: cadence,ti: CSI2RX Multistream Support Rishikesh Donadkar
                   ` (13 preceding siblings ...)
  2025-12-30  8:32 ` [PATCH v9 14/19] media: ti: j721e-csi2rx: Submit all available buffers Rishikesh Donadkar
@ 2025-12-30  8:32 ` Rishikesh Donadkar
  2026-01-15 12:37   ` Tomi Valkeinen
  2025-12-30  8:32 ` [PATCH v9 16/19] media: ti: j721e-csi2rx: Return the partial frame as error Rishikesh Donadkar
                   ` (3 subsequent siblings)
  18 siblings, 1 reply; 54+ messages in thread
From: Rishikesh Donadkar @ 2025-12-30  8:32 UTC (permalink / raw)
  To: jai.luthra, laurent.pinchart, mripard
  Cc: r-donadkar, y-abhilashchandra, devarsht, s-jain1, vigneshr,
	mchehab, robh, krzk+dt, p.zabel, conor+dt, sakari.ailus,
	hverkuil-cisco, tomi.valkeinen, jai.luthra, changhuang.liang,
	jack.zhu, sjoerd, dan.carpenter, hverkuil+cisco, linux-kernel,
	linux-media, devicetree

On buffer starvation the DMA is marked IDLE, and the stale data in the
internal FIFOs gets drained only on the next VIDIOC_QBUF call from the
userspace. This approach works fine for a single stream case.

But in multistream scenarios, buffer starvation for one stream i.e. one
virtual channel, can block the shared HW FIFO of the CSI2RX IP. This can
stall the pipeline for all other virtual channels, even if buffers are
available for them.

This patch introduces a new architecture, that continuously drains data
from the shared HW FIFO into a small (32KiB) buffer if no buffers are made
available to the driver from the userspace. This ensures independence
between different streams, where a slower downstream element for one
camera does not block streaming for other cameras.

Additionally, after a drain is done for a VC, the next frame will be a
partial frame, as a portion of its data will have already been drained
before a valid buffer is queued by user space to the driver.

Use wait for completion barrier to make sure the shared hardware FIFO
is cleared of the data at the end of stream after the source has stopped
sending data.

Reviewed-by: Jai Luthra <jai.luthra@ideasonboard.com>
Reviewed-by: Yemike Abhilash Chandra <y-abhilashchandra@ti.com>
Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
---
 .../platform/ti/j721e-csi2rx/j721e-csi2rx.c   | 112 ++++++++----------
 1 file changed, 50 insertions(+), 62 deletions(-)

diff --git a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
index fa6152464d4b6..e713293696eb1 100644
--- a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
+++ b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
@@ -82,7 +82,6 @@ struct ti_csi2rx_buffer {
 
 enum ti_csi2rx_dma_state {
 	TI_CSI2RX_DMA_STOPPED,	/* Streaming not started yet. */
-	TI_CSI2RX_DMA_IDLE,	/* Streaming but no pending DMA operation. */
 	TI_CSI2RX_DMA_ACTIVE,	/* Streaming and pending DMA operation. */
 };
 
@@ -109,6 +108,7 @@ struct ti_csi2rx_ctx {
 	struct v4l2_format		v_fmt;
 	struct ti_csi2rx_dma		dma;
 	struct media_pad		pad;
+	struct completion		drain_complete;
 	u32				sequence;
 	u32				idx;
 	u32				vc;
@@ -251,6 +251,10 @@ static const struct ti_csi2rx_fmt ti_csi2rx_formats[] = {
 static int ti_csi2rx_start_dma(struct ti_csi2rx_ctx *ctx,
 			       struct ti_csi2rx_buffer *buf);
 
+/* Forward declarations needed by ti_csi2rx_drain_callback. */
+static int ti_csi2rx_drain_dma(struct ti_csi2rx_ctx *ctx);
+static int ti_csi2rx_dma_submit_pending(struct ti_csi2rx_ctx *ctx);
+
 static const struct ti_csi2rx_fmt *find_format_by_fourcc(u32 pixelformat)
 {
 	unsigned int i;
@@ -617,9 +621,32 @@ static void ti_csi2rx_setup_shim(struct ti_csi2rx_ctx *ctx)
 
 static void ti_csi2rx_drain_callback(void *param)
 {
-	struct completion *drain_complete = param;
+	struct ti_csi2rx_ctx *ctx = param;
+	struct ti_csi2rx_dma *dma = &ctx->dma;
+	unsigned long flags;
+
+	spin_lock_irqsave(&dma->lock, flags);
+
+	if (dma->state == TI_CSI2RX_DMA_STOPPED) {
+		complete(&ctx->drain_complete);
+		spin_unlock_irqrestore(&dma->lock, flags);
+		return;
+	}
 
-	complete(drain_complete);
+	/*
+	 * If dma->queue is empty, it indicates that no buffer has been
+	 * provided by user space. In this case, initiate a transactions
+	 * to drain the DMA. Since one drain of size DRAIN_BUFFER_SIZE
+	 * will be done here, the subsequent frame will be a
+	 * partial frame, with a size of frame_size - DRAIN_BUFFER_SIZE
+	 */
+	if (list_empty(&dma->queue)) {
+		if (ti_csi2rx_drain_dma(ctx))
+			dev_warn(ctx->csi->dev, "DMA drain failed\n");
+	} else {
+		ti_csi2rx_dma_submit_pending(ctx);
+	}
+	spin_unlock_irqrestore(&dma->lock, flags);
 }
 
 /*
@@ -637,12 +664,9 @@ static int ti_csi2rx_drain_dma(struct ti_csi2rx_ctx *ctx)
 {
 	struct ti_csi2rx_dev *csi = ctx->csi;
 	struct dma_async_tx_descriptor *desc;
-	struct completion drain_complete;
 	dma_cookie_t cookie;
 	int ret;
 
-	init_completion(&drain_complete);
-
 	desc = dmaengine_prep_slave_single(ctx->dma.chan, csi->drain.paddr,
 					   csi->drain.len, DMA_DEV_TO_MEM,
 					   DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
@@ -652,7 +676,7 @@ static int ti_csi2rx_drain_dma(struct ti_csi2rx_ctx *ctx)
 	}
 
 	desc->callback = ti_csi2rx_drain_callback;
-	desc->callback_param = &drain_complete;
+	desc->callback_param = ctx;
 
 	cookie = dmaengine_submit(desc);
 	ret = dma_submit_error(cookie);
@@ -661,13 +685,6 @@ static int ti_csi2rx_drain_dma(struct ti_csi2rx_ctx *ctx)
 
 	dma_async_issue_pending(ctx->dma.chan);
 
-	if (!wait_for_completion_timeout(&drain_complete,
-					 msecs_to_jiffies(DRAIN_TIMEOUT_MS))) {
-		dmaengine_terminate_sync(ctx->dma.chan);
-		dev_dbg(csi->dev, "DMA transfer timed out for drain buffer\n");
-		ret = -ETIMEDOUT;
-		goto out;
-	}
 out:
 	return ret;
 }
@@ -716,9 +733,11 @@ static void ti_csi2rx_dma_callback(void *param)
 
 	ti_csi2rx_dma_submit_pending(ctx);
 
-	if (list_empty(&dma->submitted))
-		dma->state = TI_CSI2RX_DMA_IDLE;
-
+	if (list_empty(&dma->submitted)) {
+		if (ti_csi2rx_drain_dma(ctx))
+			dev_warn(ctx->csi->dev,
+				 "DMA drain failed on one of the transactions\n");
+	}
 	spin_unlock_irqrestore(&dma->lock, flags);
 }
 
@@ -754,6 +773,7 @@ static int ti_csi2rx_start_dma(struct ti_csi2rx_ctx *ctx,
 static void ti_csi2rx_stop_dma(struct ti_csi2rx_ctx *ctx)
 {
 	struct ti_csi2rx_dma *dma = &ctx->dma;
+	struct ti_csi2rx_dev *csi = ctx->csi;
 	enum ti_csi2rx_dma_state state;
 	unsigned long flags;
 	int ret;
@@ -763,6 +783,8 @@ static void ti_csi2rx_stop_dma(struct ti_csi2rx_ctx *ctx)
 	dma->state = TI_CSI2RX_DMA_STOPPED;
 	spin_unlock_irqrestore(&dma->lock, flags);
 
+	init_completion(&ctx->drain_complete);
+
 	if (state != TI_CSI2RX_DMA_STOPPED) {
 		/*
 		 * Normal DMA termination does not clean up pending data on
@@ -771,11 +793,20 @@ static void ti_csi2rx_stop_dma(struct ti_csi2rx_ctx *ctx)
 		 * enforced before terminating DMA.
 		 */
 		ret = ti_csi2rx_drain_dma(ctx);
-		if (ret && ret != -ETIMEDOUT)
+		if (ret)
 			dev_warn(ctx->csi->dev,
 				 "Failed to drain DMA. Next frame might be bogus\n");
 	}
 
+	/* We wait for the drain to complete so that the stream stops
+	 * cleanly, making sure the shared hardware FIFO is cleared of
+	 * data from the current stream. No more data will be coming from
+	 * the source after this.
+	 */
+	if (!wait_for_completion_timeout(&ctx->drain_complete,
+					 msecs_to_jiffies(DRAIN_TIMEOUT_MS)))
+		dev_dbg(csi->dev, "DMA transfer timed out for drain buffer\n");
+
 	ret = dmaengine_terminate_sync(ctx->dma.chan);
 	if (ret)
 		dev_err(ctx->csi->dev, "Failed to stop DMA: %d\n", ret);
@@ -838,57 +869,14 @@ static void ti_csi2rx_buffer_queue(struct vb2_buffer *vb)
 	struct ti_csi2rx_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
 	struct ti_csi2rx_buffer *buf;
 	struct ti_csi2rx_dma *dma = &ctx->dma;
-	bool restart_dma = false;
 	unsigned long flags = 0;
-	int ret;
 
 	buf = container_of(vb, struct ti_csi2rx_buffer, vb.vb2_buf);
 	buf->ctx = ctx;
 
 	spin_lock_irqsave(&dma->lock, flags);
-	/*
-	 * Usually the DMA callback takes care of queueing the pending buffers.
-	 * But if DMA has stalled due to lack of buffers, restart it now.
-	 */
-	if (dma->state == TI_CSI2RX_DMA_IDLE) {
-		/*
-		 * Do not restart DMA with the lock held because
-		 * ti_csi2rx_drain_dma() might block for completion.
-		 * There won't be a race on queueing DMA anyway since the
-		 * callback is not being fired.
-		 */
-		restart_dma = true;
-		dma->state = TI_CSI2RX_DMA_ACTIVE;
-	} else {
-		list_add_tail(&buf->list, &dma->queue);
-	}
+	list_add_tail(&buf->list, &dma->queue);
 	spin_unlock_irqrestore(&dma->lock, flags);
-
-	if (restart_dma) {
-		/*
-		 * Once frames start dropping, some data gets stuck in the DMA
-		 * pipeline somewhere. So the first DMA transfer after frame
-		 * drops gives a partial frame. This is obviously not useful to
-		 * the application and will only confuse it. Issue a DMA
-		 * transaction to drain that up.
-		 */
-		ret = ti_csi2rx_drain_dma(ctx);
-		if (ret && ret != -ETIMEDOUT)
-			dev_warn(ctx->csi->dev,
-				 "Failed to drain DMA. Next frame might be bogus\n");
-
-		spin_lock_irqsave(&dma->lock, flags);
-		ret = ti_csi2rx_start_dma(ctx, buf);
-		if (ret) {
-			vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
-			dma->state = TI_CSI2RX_DMA_IDLE;
-			spin_unlock_irqrestore(&dma->lock, flags);
-			dev_err(ctx->csi->dev, "Failed to start DMA: %d\n", ret);
-		} else {
-			list_add_tail(&buf->list, &dma->submitted);
-			spin_unlock_irqrestore(&dma->lock, flags);
-		}
-	}
 }
 
 static int ti_csi2rx_get_stream(struct ti_csi2rx_ctx *ctx)
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v9 16/19] media: ti: j721e-csi2rx: Return the partial frame as error
  2025-12-30  8:32 [PATCH v9 00/19] media: cadence,ti: CSI2RX Multistream Support Rishikesh Donadkar
                   ` (14 preceding siblings ...)
  2025-12-30  8:32 ` [PATCH v9 15/19] media: ti: j721e-csi2rx: Change the drain architecture for multistream Rishikesh Donadkar
@ 2025-12-30  8:32 ` Rishikesh Donadkar
  2026-01-06 11:15   ` Jai Luthra
  2026-01-15 12:39   ` Tomi Valkeinen
  2025-12-30  8:32 ` [PATCH v9 17/19] media: cadence: csi2rx: Support runtime PM Rishikesh Donadkar
                   ` (2 subsequent siblings)
  18 siblings, 2 replies; 54+ messages in thread
From: Rishikesh Donadkar @ 2025-12-30  8:32 UTC (permalink / raw)
  To: jai.luthra, laurent.pinchart, mripard
  Cc: r-donadkar, y-abhilashchandra, devarsht, s-jain1, vigneshr,
	mchehab, robh, krzk+dt, p.zabel, conor+dt, sakari.ailus,
	hverkuil-cisco, tomi.valkeinen, jai.luthra, changhuang.liang,
	jack.zhu, sjoerd, dan.carpenter, hverkuil+cisco, linux-kernel,
	linux-media, devicetree

After draining, when a buffer is queued to the driver, ti will fill out
the buffer with a partial frame as some part of the frame is drained.
Return the partial frame with VB2_BUF_STATE_ERROR.

Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
---
 drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
index e713293696eb1..3922bd67e78da 100644
--- a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
+++ b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
@@ -83,6 +83,7 @@ struct ti_csi2rx_buffer {
 enum ti_csi2rx_dma_state {
 	TI_CSI2RX_DMA_STOPPED,	/* Streaming not started yet. */
 	TI_CSI2RX_DMA_ACTIVE,	/* Streaming and pending DMA operation. */
+	TI_CSI2RX_DMA_DRAINING, /* Dumping all the data in drain buffer */
 };
 
 struct ti_csi2rx_dma {
@@ -728,12 +729,20 @@ static void ti_csi2rx_dma_callback(void *param)
 	spin_lock_irqsave(&dma->lock, flags);
 
 	WARN_ON(!list_is_first(&buf->list, &dma->submitted));
-	vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
+
+	if (dma->state == TI_CSI2RX_DMA_DRAINING) {
+		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+		dma->state = TI_CSI2RX_DMA_ACTIVE;
+	} else {
+		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
+	}
+
 	list_del(&buf->list);
 
 	ti_csi2rx_dma_submit_pending(ctx);
 
 	if (list_empty(&dma->submitted)) {
+		dma->state = TI_CSI2RX_DMA_DRAINING;
 		if (ti_csi2rx_drain_dma(ctx))
 			dev_warn(ctx->csi->dev,
 				 "DMA drain failed on one of the transactions\n");
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v9 17/19] media: cadence: csi2rx: Support runtime PM
  2025-12-30  8:32 [PATCH v9 00/19] media: cadence,ti: CSI2RX Multistream Support Rishikesh Donadkar
                   ` (15 preceding siblings ...)
  2025-12-30  8:32 ` [PATCH v9 16/19] media: ti: j721e-csi2rx: Return the partial frame as error Rishikesh Donadkar
@ 2025-12-30  8:32 ` Rishikesh Donadkar
  2026-01-14 17:04   ` Tomi Valkeinen
  2025-12-30  8:32 ` [PATCH v9 18/19] media: ti: j721e-csi2rx: Support runtime suspend Rishikesh Donadkar
  2025-12-30  8:32 ` [PATCH v9 19/19] media: ti: j721e-csi2rx: Support system suspend using pm_notifier Rishikesh Donadkar
  18 siblings, 1 reply; 54+ messages in thread
From: Rishikesh Donadkar @ 2025-12-30  8:32 UTC (permalink / raw)
  To: jai.luthra, laurent.pinchart, mripard
  Cc: r-donadkar, y-abhilashchandra, devarsht, s-jain1, vigneshr,
	mchehab, robh, krzk+dt, p.zabel, conor+dt, sakari.ailus,
	hverkuil-cisco, tomi.valkeinen, jai.luthra, changhuang.liang,
	jack.zhu, sjoerd, dan.carpenter, hverkuil+cisco, linux-kernel,
	linux-media, devicetree

From: Changhuang Liang <changhuang.liang@starfivetech.com>

Use runtime power management hooks to save power when CSI-RX is not in
use. Also, shift to goto based error handling in
csi2rx_enable_streams() function

Signed-off-by: Changhuang Liang <changhuang.liang@starfivetech.com>
Tested-by: Rishikesh Donadkar <r-donadkar@ti.com>
Reviewed-by: Rishikesh Donadkar <r-donadkar@ti.com>
Signed-off-by: Jai Luthra <jai.luthra@ideasonboard.com>
Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
---
 drivers/media/platform/cadence/Kconfig       |   1 +
 drivers/media/platform/cadence/cdns-csi2rx.c | 136 ++++++++++++-------
 2 files changed, 88 insertions(+), 49 deletions(-)

diff --git a/drivers/media/platform/cadence/Kconfig b/drivers/media/platform/cadence/Kconfig
index 1aa608c00dbce..ea85ef82760e6 100644
--- a/drivers/media/platform/cadence/Kconfig
+++ b/drivers/media/platform/cadence/Kconfig
@@ -5,6 +5,7 @@ comment "Cadence media platform drivers"
 config VIDEO_CADENCE_CSI2RX
 	tristate "Cadence MIPI-CSI2 RX Controller"
 	depends on VIDEO_DEV
+	depends on PM
 	select MEDIA_CONTROLLER
 	select VIDEO_V4L2_SUBDEV_API
 	select V4L2_FWNODE
diff --git a/drivers/media/platform/cadence/cdns-csi2rx.c b/drivers/media/platform/cadence/cdns-csi2rx.c
index 5c16a2e509136..40c947c813248 100644
--- a/drivers/media/platform/cadence/cdns-csi2rx.c
+++ b/drivers/media/platform/cadence/cdns-csi2rx.c
@@ -337,11 +337,6 @@ static int csi2rx_start(struct csi2rx_priv *csi2rx)
 	u32 reg;
 	int ret;
 
-	ret = clk_prepare_enable(csi2rx->p_clk);
-	if (ret)
-		return ret;
-
-	reset_control_deassert(csi2rx->p_rst);
 	csi2rx_reset(csi2rx);
 
 	if (csi2rx->error_irq >= 0)
@@ -382,7 +377,7 @@ static int csi2rx_start(struct csi2rx_priv *csi2rx)
 		if (ret) {
 			dev_err(csi2rx->dev,
 				"Failed to configure external DPHY: %d\n", ret);
-			goto err_disable_pclk;
+			return ret;
 		}
 	}
 
@@ -397,12 +392,6 @@ static int csi2rx_start(struct csi2rx_priv *csi2rx)
 	 * hence the reference counting.
 	 */
 	for (i = 0; i < csi2rx->max_streams; i++) {
-		ret = clk_prepare_enable(csi2rx->pixel_clk[i]);
-		if (ret)
-			goto err_disable_pixclk;
-
-		reset_control_deassert(csi2rx->pixel_rst[i]);
-
 		writel(CSI2RX_STREAM_CFG_FIFO_MODE_LARGE_BUF |
 			       FIELD_PREP(CSI2RX_STREAM_CFG_NUM_PIXELS_MASK,
 					  csi2rx->num_pixels[i]),
@@ -415,30 +404,8 @@ static int csi2rx_start(struct csi2rx_priv *csi2rx)
 		       csi2rx->base + CSI2RX_STREAM_CTRL_REG(i));
 	}
 
-	ret = clk_prepare_enable(csi2rx->sys_clk);
-	if (ret)
-		goto err_disable_pixclk;
-
-	reset_control_deassert(csi2rx->sys_rst);
-
-	clk_disable_unprepare(csi2rx->p_clk);
 
 	return 0;
-
-err_disable_pixclk:
-	for (; i > 0; i--) {
-		reset_control_assert(csi2rx->pixel_rst[i - 1]);
-		clk_disable_unprepare(csi2rx->pixel_clk[i - 1]);
-	}
-
-	if (csi2rx->dphy) {
-		writel(0, csi2rx->base + CSI2RX_DPHY_LANE_CTRL_REG);
-		phy_power_off(csi2rx->dphy);
-	}
-err_disable_pclk:
-	clk_disable_unprepare(csi2rx->p_clk);
-
-	return ret;
 }
 
 static void csi2rx_stop(struct csi2rx_priv *csi2rx)
@@ -447,10 +414,6 @@ static void csi2rx_stop(struct csi2rx_priv *csi2rx)
 	u32 val;
 	int ret;
 
-	clk_prepare_enable(csi2rx->p_clk);
-	reset_control_assert(csi2rx->sys_rst);
-	clk_disable_unprepare(csi2rx->sys_clk);
-
 	writel(0, csi2rx->base + CSI2RX_ERROR_IRQS_MASK_REG);
 
 	for (i = 0; i < csi2rx->max_streams; i++) {
@@ -465,14 +428,8 @@ static void csi2rx_stop(struct csi2rx_priv *csi2rx)
 		if (ret)
 			dev_warn(csi2rx->dev,
 				 "Failed to stop streaming on pad%u\n", i);
-
-		reset_control_assert(csi2rx->pixel_rst[i]);
-		clk_disable_unprepare(csi2rx->pixel_clk[i]);
 	}
 
-	reset_control_assert(csi2rx->p_rst);
-	clk_disable_unprepare(csi2rx->p_clk);
-
 	if (csi2rx->dphy) {
 		writel(0, csi2rx->base + CSI2RX_DPHY_LANE_CTRL_REG);
 
@@ -548,10 +505,15 @@ static int csi2rx_enable_streams(struct v4l2_subdev *subdev,
 	 * enable the whole controller.
 	 */
 	if (!csi2rx->count) {
+		ret = pm_runtime_resume_and_get(csi2rx->dev);
+		if (ret < 0)
+			goto err;
+
 		csi2rx_update_vc_select(csi2rx, state);
+
 		ret = csi2rx_start(csi2rx);
 		if (ret)
-			return ret;
+			goto err_put_pm;
 	}
 
 	/* Start streaming on the source */
@@ -561,13 +523,20 @@ static int csi2rx_enable_streams(struct v4l2_subdev *subdev,
 		dev_err(csi2rx->dev,
 			"Failed to start streams %#llx on subdev\n",
 			sink_streams);
-		if (!csi2rx->count)
-			csi2rx_stop(csi2rx);
-		return ret;
+		goto err_stop_csi;
 	}
 
 	csi2rx->count++;
 	return 0;
+
+err_stop_csi:
+	if (!csi2rx->count)
+		csi2rx_stop(csi2rx);
+err_put_pm:
+	if (!csi2rx->count)
+		pm_runtime_put(csi2rx->dev);
+err:
+	return ret;
 }
 
 static int csi2rx_disable_streams(struct v4l2_subdev *subdev,
@@ -589,8 +558,10 @@ static int csi2rx_disable_streams(struct v4l2_subdev *subdev,
 	csi2rx->count--;
 
 	/* Let the last user turn off the lights. */
-	if (!csi2rx->count)
+	if (!csi2rx->count) {
 		csi2rx_stop(csi2rx);
+		pm_runtime_put(csi2rx->dev);
+	}
 
 	return 0;
 }
@@ -1057,6 +1028,7 @@ static int csi2rx_probe(struct platform_device *pdev)
 	if (ret)
 		goto err_cleanup;
 
+	pm_runtime_enable(csi2rx->dev);
 	ret = v4l2_async_register_subdev(&csi2rx->subdev);
 	if (ret < 0)
 		goto err_free_state;
@@ -1071,6 +1043,7 @@ static int csi2rx_probe(struct platform_device *pdev)
 
 err_free_state:
 	v4l2_subdev_cleanup(&csi2rx->subdev);
+	pm_runtime_disable(csi2rx->dev);
 err_cleanup:
 	v4l2_async_nf_unregister(&csi2rx->notifier);
 	v4l2_async_nf_cleanup(&csi2rx->notifier);
@@ -1089,9 +1062,73 @@ static void csi2rx_remove(struct platform_device *pdev)
 	v4l2_async_unregister_subdev(&csi2rx->subdev);
 	v4l2_subdev_cleanup(&csi2rx->subdev);
 	media_entity_cleanup(&csi2rx->subdev.entity);
+	pm_runtime_disable(csi2rx->dev);
 	kfree(csi2rx);
 }
 
+static int csi2rx_runtime_suspend(struct device *dev)
+{
+	struct csi2rx_priv *csi2rx = dev_get_drvdata(dev);
+	unsigned int i;
+
+	reset_control_assert(csi2rx->sys_rst);
+	clk_disable_unprepare(csi2rx->sys_clk);
+
+	for (i = 0; i < csi2rx->max_streams; i++) {
+		reset_control_assert(csi2rx->pixel_rst[i]);
+		clk_disable_unprepare(csi2rx->pixel_clk[i]);
+	}
+
+	reset_control_assert(csi2rx->p_rst);
+	clk_disable_unprepare(csi2rx->p_clk);
+
+	return 0;
+}
+
+static int csi2rx_runtime_resume(struct device *dev)
+{
+	struct csi2rx_priv *csi2rx = dev_get_drvdata(dev);
+	unsigned int i;
+	int ret;
+
+	ret = clk_prepare_enable(csi2rx->p_clk);
+	if (ret)
+		return ret;
+
+	reset_control_deassert(csi2rx->p_rst);
+
+	for (i = 0; i < csi2rx->max_streams; i++) {
+		ret = clk_prepare_enable(csi2rx->pixel_clk[i]);
+		if (ret)
+			goto err_disable_pixclk;
+
+		reset_control_deassert(csi2rx->pixel_rst[i]);
+	}
+
+	ret = clk_prepare_enable(csi2rx->sys_clk);
+	if (ret)
+		goto err_disable_pixclk;
+
+	reset_control_deassert(csi2rx->sys_rst);
+
+	return 0;
+
+err_disable_pixclk:
+	for (; i > 0; i--) {
+		reset_control_assert(csi2rx->pixel_rst[i - 1]);
+		clk_disable_unprepare(csi2rx->pixel_clk[i - 1]);
+	}
+
+	reset_control_assert(csi2rx->p_rst);
+	clk_disable_unprepare(csi2rx->p_clk);
+
+	return ret;
+}
+
+static const struct dev_pm_ops csi2rx_pm_ops = {
+	RUNTIME_PM_OPS(csi2rx_runtime_suspend, csi2rx_runtime_resume, NULL)
+};
+
 static const struct of_device_id csi2rx_of_table[] = {
 	{ .compatible = "starfive,jh7110-csi2rx" },
 	{ .compatible = "cdns,csi2rx" },
@@ -1106,6 +1143,7 @@ static struct platform_driver csi2rx_driver = {
 	.driver	= {
 		.name		= "cdns-csi2rx",
 		.of_match_table	= csi2rx_of_table,
+		.pm		= &csi2rx_pm_ops,
 	},
 };
 module_platform_driver(csi2rx_driver);
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v9 18/19] media: ti: j721e-csi2rx: Support runtime suspend
  2025-12-30  8:32 [PATCH v9 00/19] media: cadence,ti: CSI2RX Multistream Support Rishikesh Donadkar
                   ` (16 preceding siblings ...)
  2025-12-30  8:32 ` [PATCH v9 17/19] media: cadence: csi2rx: Support runtime PM Rishikesh Donadkar
@ 2025-12-30  8:32 ` Rishikesh Donadkar
  2026-01-15 12:46   ` Tomi Valkeinen
  2025-12-30  8:32 ` [PATCH v9 19/19] media: ti: j721e-csi2rx: Support system suspend using pm_notifier Rishikesh Donadkar
  18 siblings, 1 reply; 54+ messages in thread
From: Rishikesh Donadkar @ 2025-12-30  8:32 UTC (permalink / raw)
  To: jai.luthra, laurent.pinchart, mripard
  Cc: r-donadkar, y-abhilashchandra, devarsht, s-jain1, vigneshr,
	mchehab, robh, krzk+dt, p.zabel, conor+dt, sakari.ailus,
	hverkuil-cisco, tomi.valkeinen, jai.luthra, changhuang.liang,
	jack.zhu, sjoerd, dan.carpenter, hverkuil+cisco, linux-kernel,
	linux-media, devicetree

From: Jai Luthra <jai.luthra@ideasonboard.com>

Add support for runtime power-management to enable powering off the
shared power domain between Cadence CSI2RX and TI CSI2RX wrapper when
the device(s) are not in use.

When powering off the IP, the PSI-L endpoint loses the paired DMA
channels. Thus we have to release the DMA channels at runtime suspend
and request them again at resume.

Tested-by: Rishikesh Donadkar <r-donadkar@ti.com>
Reviewed-by: Rishikesh Donadkar <r-donadkar@ti.com>
Signed-off-by: Jai Luthra <jai.luthra@ideasonboard.com>
Co-developed-by: Rishikesh Donadkar <r-donadkar@ti.com>
Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
---
 drivers/media/platform/ti/Kconfig             |  1 +
 .../platform/ti/j721e-csi2rx/j721e-csi2rx.c   | 59 +++++++++++++++----
 2 files changed, 50 insertions(+), 10 deletions(-)

diff --git a/drivers/media/platform/ti/Kconfig b/drivers/media/platform/ti/Kconfig
index 3bc4aa35887e6..a808063e24779 100644
--- a/drivers/media/platform/ti/Kconfig
+++ b/drivers/media/platform/ti/Kconfig
@@ -70,6 +70,7 @@ config VIDEO_TI_J721E_CSI2RX
 	depends on VIDEO_CADENCE_CSI2RX
 	depends on PHY_CADENCE_DPHY_RX || COMPILE_TEST
 	depends on ARCH_K3 || COMPILE_TEST
+	depends on PM
 	select VIDEOBUF2_DMA_CONTIG
 	select V4L2_FWNODE
 	help
diff --git a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
index 3922bd67e78da..72da58738e16e 100644
--- a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
+++ b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
@@ -13,6 +13,7 @@
 #include <linux/module.h>
 #include <linux/of_platform.h>
 #include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
 #include <linux/property.h>
 
 #include <media/cadence/cdns-csi2rx.h>
@@ -964,12 +965,16 @@ static int ti_csi2rx_start_streaming(struct vb2_queue *vq, unsigned int count)
 	unsigned long flags;
 	int ret = 0;
 
+	ret = pm_runtime_resume_and_get(csi->dev);
+	if (ret)
+		return ret;
+
 	spin_lock_irqsave(&dma->lock, flags);
 	if (list_empty(&dma->queue))
 		ret = -EIO;
 	spin_unlock_irqrestore(&dma->lock, flags);
 	if (ret)
-		return ret;
+		goto err;
 
 	ret = video_device_pipeline_start(&ctx->vdev, &csi->pipe);
 	if (ret)
@@ -991,6 +996,8 @@ static int ti_csi2rx_start_streaming(struct vb2_queue *vq, unsigned int count)
 	writel(0, csi->shim + SHIM_DMACNTX(ctx->idx));
 err:
 	ti_csi2rx_cleanup_buffers(ctx, VB2_BUF_STATE_QUEUED);
+	pm_runtime_put(csi->dev);
+
 	return ret;
 }
 
@@ -1022,6 +1029,7 @@ static void ti_csi2rx_stop_streaming(struct vb2_queue *vq)
 
 	ti_csi2rx_stop_dma(ctx);
 	ti_csi2rx_cleanup_buffers(ctx, VB2_BUF_STATE_ERROR);
+	pm_runtime_put(csi->dev);
 }
 
 static const struct vb2_ops csi_vb2_qops = {
@@ -1263,7 +1271,6 @@ static void ti_csi2rx_cleanup_notifier(struct ti_csi2rx_dev *csi)
 
 static void ti_csi2rx_cleanup_ctx(struct ti_csi2rx_ctx *ctx)
 {
-	dma_release_channel(ctx->dma.chan);
 	vb2_queue_release(&ctx->vidq);
 
 	video_unregister_device(&ctx->vdev);
@@ -1283,7 +1290,7 @@ static int ti_csi2rx_init_vb2q(struct ti_csi2rx_ctx *ctx)
 	q->ops = &csi_vb2_qops;
 	q->mem_ops = &vb2_dma_contig_memops;
 	q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
-	q->dev = dmaengine_get_dma_device(ctx->dma.chan);
+	q->dev = ctx->csi->dev;
 	q->lock = &ctx->mutex;
 	q->min_queued_buffers = 1;
 	q->allow_cache_hints = 1;
@@ -1497,21 +1504,46 @@ static int ti_csi2rx_init_ctx(struct ti_csi2rx_ctx *ctx)
 	spin_lock_init(&ctx->dma.lock);
 	ctx->dma.state = TI_CSI2RX_DMA_STOPPED;
 
-	ret = ti_csi2rx_init_dma(ctx);
+	ret = ti_csi2rx_init_vb2q(ctx);
 	if (ret)
 		return ret;
 
-	ret = ti_csi2rx_init_vb2q(ctx);
-	if (ret)
-		goto cleanup_dma;
+	return 0;
+}
+
+static int ti_csi2rx_runtime_suspend(struct device *dev)
+{
+	struct ti_csi2rx_dev *csi = dev_get_drvdata(dev);
+	int i;
+
+	if (csi->enable_count != 0)
+		return -EBUSY;
+
+	for (i = 0; i < csi->num_ctx; i++)
+		dma_release_channel(csi->ctx[i].dma.chan);
 
 	return 0;
+}
 
-cleanup_dma:
-	dma_release_channel(ctx->dma.chan);
-	return ret;
+static int ti_csi2rx_runtime_resume(struct device *dev)
+{
+	struct ti_csi2rx_dev *csi = dev_get_drvdata(dev);
+	unsigned int ret, i;
+
+	for (i = 0; i < csi->num_ctx; i++) {
+		ret = ti_csi2rx_init_dma(&csi->ctx[i]);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
 }
 
+static const struct dev_pm_ops ti_csi2rx_pm_ops = {
+	RUNTIME_PM_OPS(ti_csi2rx_runtime_suspend, ti_csi2rx_runtime_resume,
+		       NULL)
+};
+
 static int ti_csi2rx_probe(struct platform_device *pdev)
 {
 	struct device_node *np = pdev->dev.of_node;
@@ -1569,6 +1601,8 @@ static int ti_csi2rx_probe(struct platform_device *pdev)
 			goto err_ctx;
 	}
 
+	pm_runtime_enable(csi->dev);
+
 	ret = ti_csi2rx_notifier_register(csi);
 	if (ret)
 		goto err_ctx;
@@ -1601,6 +1635,9 @@ static void ti_csi2rx_remove(struct platform_device *pdev)
 	struct ti_csi2rx_dev *csi = platform_get_drvdata(pdev);
 	unsigned int i;
 
+	if (!pm_runtime_status_suspended(&pdev->dev))
+		pm_runtime_set_suspended(&pdev->dev);
+
 	for (i = 0; i < csi->num_ctx; i++)
 		ti_csi2rx_cleanup_ctx(&csi->ctx[i]);
 
@@ -1609,6 +1646,7 @@ static void ti_csi2rx_remove(struct platform_device *pdev)
 	mutex_destroy(&csi->mutex);
 	dma_free_coherent(csi->dev, csi->drain.len, csi->drain.vaddr,
 			  csi->drain.paddr);
+	pm_runtime_disable(&pdev->dev);
 }
 
 static const struct of_device_id ti_csi2rx_of_match[] = {
@@ -1623,6 +1661,7 @@ static struct platform_driver ti_csi2rx_pdrv = {
 	.driver = {
 		.name = TI_CSI2RX_MODULE_NAME,
 		.of_match_table = ti_csi2rx_of_match,
+		.pm		= &ti_csi2rx_pm_ops,
 	},
 };
 
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 54+ messages in thread

* [PATCH v9 19/19] media: ti: j721e-csi2rx: Support system suspend using pm_notifier
  2025-12-30  8:32 [PATCH v9 00/19] media: cadence,ti: CSI2RX Multistream Support Rishikesh Donadkar
                   ` (17 preceding siblings ...)
  2025-12-30  8:32 ` [PATCH v9 18/19] media: ti: j721e-csi2rx: Support runtime suspend Rishikesh Donadkar
@ 2025-12-30  8:32 ` Rishikesh Donadkar
  2026-01-15 12:50   ` Tomi Valkeinen
  18 siblings, 1 reply; 54+ messages in thread
From: Rishikesh Donadkar @ 2025-12-30  8:32 UTC (permalink / raw)
  To: jai.luthra, laurent.pinchart, mripard
  Cc: r-donadkar, y-abhilashchandra, devarsht, s-jain1, vigneshr,
	mchehab, robh, krzk+dt, p.zabel, conor+dt, sakari.ailus,
	hverkuil-cisco, tomi.valkeinen, jai.luthra, changhuang.liang,
	jack.zhu, sjoerd, dan.carpenter, hverkuil+cisco, linux-kernel,
	linux-media, devicetree

From: Jai Luthra <jai.luthra@ideasonboard.com>

As this device is the "orchestrator" for the rest of the media
pipeline, we need to stop all on-going streams before system suspend and
enable them back when the system wakes up from sleep.

Using .suspend/.resume callbacks does not work, as the order of those
callbacks amongst various devices in the camera pipeline like the sensor,
FPD serdes, CSI bridge etc. is impossible to enforce, even with
device links. For example, the Cadence CSI bridge is a child device of
this device, thus we cannot create a device link with the CSI bridge as
a provider and this device as consumer. This can lead to situations
where all the dependencies for the bridge have not yet resumed when we
request the subdev to start streaming again through the .resume callback
defined in this device.

Instead here we register a notifier callback with the PM framework
which is triggered when the system is fully functional. At this point we
can cleanly stop or start the streams, because we know all other devices
and their dependencies are functional. A downside of this approach is
that the userspace is also alive (not frozen yet, or just thawed), so
the suspend notifier might complete before the userspace has completed
all ioctls, like QBUF/DQBUF/STREAMON/STREAMOFF.

Tested-by: Rishikesh Donadkar <r-donadkar@ti.com>
Reviewed-by: Rishikesh Donadkar <r-donadkar@ti.com>
Signed-off-by: Jai Luthra <jai.luthra@ideasonboard.com>
Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
---
 .../platform/ti/j721e-csi2rx/j721e-csi2rx.c   | 128 ++++++++++++++++++
 1 file changed, 128 insertions(+)

diff --git a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
index 72da58738e16e..f8e55aa402e0b 100644
--- a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
+++ b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
@@ -132,6 +132,7 @@ struct ti_csi2rx_dev {
 	struct v4l2_subdev		*source;
 	struct v4l2_subdev		subdev;
 	struct ti_csi2rx_ctx		ctx[TI_CSI2RX_MAX_CTX];
+	struct notifier_block		pm_notifier;
 	u8				pix_per_clk;
 	/* Buffer to drain stale data from PSI-L endpoint */
 	struct {
@@ -1539,6 +1540,124 @@ static int ti_csi2rx_runtime_resume(struct device *dev)
 	return 0;
 }
 
+static int ti_csi2rx_suspend(struct device *dev)
+{
+	struct ti_csi2rx_dev *csi = dev_get_drvdata(dev);
+	enum ti_csi2rx_dma_state state;
+	struct ti_csi2rx_ctx *ctx;
+	struct ti_csi2rx_dma *dma;
+	unsigned long flags = 0;
+	int i, ret = 0;
+
+	/* If device was not in use we can simply suspend */
+	if (pm_runtime_status_suspended(dev))
+		return 0;
+
+	/*
+	 * If device is running, assert the pixel reset to cleanly stop any
+	 * on-going streams before we suspend.
+	 */
+	writel(0, csi->shim + SHIM_CNTL);
+
+	for (i = 0; i < csi->num_ctx; i++) {
+		ctx = &csi->ctx[i];
+		dma = &ctx->dma;
+
+		spin_lock_irqsave(&dma->lock, flags);
+		state = dma->state;
+		spin_unlock_irqrestore(&dma->lock, flags);
+
+		if (state != TI_CSI2RX_DMA_STOPPED) {
+			/* Disable source */
+			ret = v4l2_subdev_disable_streams(&csi->subdev,
+							  TI_CSI2RX_PAD_FIRST_SOURCE + ctx->idx,
+							  BIT(0));
+			if (ret)
+				dev_err(csi->dev, "Failed to stop subdev stream\n");
+		}
+
+		/* Stop any on-going streams */
+		writel(0, csi->shim + SHIM_DMACNTX(ctx->idx));
+
+		/* Drain DMA */
+		ti_csi2rx_drain_dma(ctx);
+
+		/* Terminate DMA */
+		ret = dmaengine_terminate_sync(ctx->dma.chan);
+		if (ret)
+			dev_err(csi->dev, "Failed to stop DMA\n");
+	}
+
+	return ret;
+}
+
+static int ti_csi2rx_resume(struct device *dev)
+{
+	struct ti_csi2rx_dev *csi = dev_get_drvdata(dev);
+	struct ti_csi2rx_ctx *ctx;
+	struct ti_csi2rx_dma *dma;
+	struct ti_csi2rx_buffer *buf;
+	unsigned long flags = 0;
+	unsigned int reg;
+	int i, ret = 0;
+
+	/* If device was not in use, we can simply wakeup */
+	if (pm_runtime_status_suspended(dev))
+		return 0;
+
+	/* If device was in use before, restore all the running streams */
+	reg = SHIM_CNTL_PIX_RST;
+	writel(reg, csi->shim + SHIM_CNTL);
+
+	for (i = 0; i < csi->num_ctx; i++) {
+		ctx = &csi->ctx[i];
+		dma = &ctx->dma;
+		spin_lock_irqsave(&dma->lock, flags);
+		if (dma->state != TI_CSI2RX_DMA_STOPPED) {
+			/* Re-submit all previously submitted buffers to DMA */
+			list_for_each_entry(buf, &ctx->dma.submitted, list) {
+				ti_csi2rx_start_dma(ctx, buf);
+			}
+			spin_unlock_irqrestore(&dma->lock, flags);
+
+			/* Restore stream config */
+			ti_csi2rx_setup_shim(ctx);
+
+			ret = v4l2_subdev_enable_streams(&csi->subdev,
+							 TI_CSI2RX_PAD_FIRST_SOURCE + ctx->idx,
+							 BIT(0));
+			if (ret)
+				dev_err(ctx->csi->dev, "Failed to start subdev\n");
+		} else {
+			spin_unlock_irqrestore(&dma->lock, flags);
+		}
+	}
+
+	return ret;
+}
+
+static int ti_csi2rx_pm_notifier(struct notifier_block *nb,
+				 unsigned long action, void *data)
+{
+	struct ti_csi2rx_dev *csi =
+		container_of(nb, struct ti_csi2rx_dev, pm_notifier);
+
+	switch (action) {
+	case PM_HIBERNATION_PREPARE:
+	case PM_SUSPEND_PREPARE:
+	case PM_RESTORE_PREPARE:
+		ti_csi2rx_suspend(csi->dev);
+		break;
+	case PM_POST_SUSPEND:
+	case PM_POST_HIBERNATION:
+	case PM_POST_RESTORE:
+		ti_csi2rx_resume(csi->dev);
+		break;
+	}
+
+	return NOTIFY_DONE;
+}
+
 static const struct dev_pm_ops ti_csi2rx_pm_ops = {
 	RUNTIME_PM_OPS(ti_csi2rx_runtime_suspend, ti_csi2rx_runtime_resume,
 		       NULL)
@@ -1613,6 +1732,13 @@ static int ti_csi2rx_probe(struct platform_device *pdev)
 		goto err_notifier;
 	}
 
+	csi->pm_notifier.notifier_call = ti_csi2rx_pm_notifier;
+	ret = register_pm_notifier(&csi->pm_notifier);
+	if (ret) {
+		dev_err(csi->dev, "Failed to create PM notifier: %d\n", ret);
+		goto err_notifier;
+	}
+
 	return 0;
 
 err_notifier:
@@ -1642,6 +1768,8 @@ static void ti_csi2rx_remove(struct platform_device *pdev)
 		ti_csi2rx_cleanup_ctx(&csi->ctx[i]);
 
 	ti_csi2rx_cleanup_notifier(csi);
+	unregister_pm_notifier(&csi->pm_notifier);
+
 	ti_csi2rx_cleanup_v4l2(csi);
 	mutex_destroy(&csi->mutex);
 	dma_free_coherent(csi->dev, csi->drain.len, csi->drain.vaddr,
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 54+ messages in thread

* Re: [PATCH v9 16/19] media: ti: j721e-csi2rx: Return the partial frame as error
  2025-12-30  8:32 ` [PATCH v9 16/19] media: ti: j721e-csi2rx: Return the partial frame as error Rishikesh Donadkar
@ 2026-01-06 11:15   ` Jai Luthra
  2026-01-08  5:37     ` Rishikesh Donadkar
  2026-01-15 12:39   ` Tomi Valkeinen
  1 sibling, 1 reply; 54+ messages in thread
From: Jai Luthra @ 2026-01-06 11:15 UTC (permalink / raw)
  To: Rishikesh Donadkar, jai.luthra, laurent.pinchart, mripard
  Cc: r-donadkar, y-abhilashchandra, devarsht, s-jain1, vigneshr,
	mchehab, robh, krzk+dt, p.zabel, conor+dt, sakari.ailus,
	hverkuil-cisco, tomi.valkeinen, changhuang.liang, jack.zhu,
	sjoerd, dan.carpenter, hverkuil+cisco, linux-kernel, linux-media,
	devicetree

Hi Rishikesh,

Quoting Rishikesh Donadkar (2025-12-30 14:02:17)
> After draining, when a buffer is queued to the driver, ti will fill out
> the buffer with a partial frame as some part of the frame is drained.
> Return the partial frame with VB2_BUF_STATE_ERROR.

This should be squashed with the previous patch which changes the drain
architecture and leads to the partial frames. So for the combined patch:

Reviewed-by: Jai Luthra <jai.luthra@ideasonboard.com>

> 
> Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
> ---
>  drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c | 11 ++++++++++-
>  1 file changed, 10 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
> index e713293696eb1..3922bd67e78da 100644
> --- a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
> +++ b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
> @@ -83,6 +83,7 @@ struct ti_csi2rx_buffer {
>  enum ti_csi2rx_dma_state {
>         TI_CSI2RX_DMA_STOPPED,  /* Streaming not started yet. */
>         TI_CSI2RX_DMA_ACTIVE,   /* Streaming and pending DMA operation. */
> +       TI_CSI2RX_DMA_DRAINING, /* Dumping all the data in drain buffer */
>  };
>  
>  struct ti_csi2rx_dma {
> @@ -728,12 +729,20 @@ static void ti_csi2rx_dma_callback(void *param)
>         spin_lock_irqsave(&dma->lock, flags);
>  
>         WARN_ON(!list_is_first(&buf->list, &dma->submitted));
> -       vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
> +
> +       if (dma->state == TI_CSI2RX_DMA_DRAINING) {
> +               vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
> +               dma->state = TI_CSI2RX_DMA_ACTIVE;
> +       } else {
> +               vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
> +       }
> +
>         list_del(&buf->list);
>  
>         ti_csi2rx_dma_submit_pending(ctx);
>  
>         if (list_empty(&dma->submitted)) {
> +               dma->state = TI_CSI2RX_DMA_DRAINING;
>                 if (ti_csi2rx_drain_dma(ctx))
>                         dev_warn(ctx->csi->dev,
>                                  "DMA drain failed on one of the transactions\n");
> -- 
> 2.34.1
> 
>

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v9 16/19] media: ti: j721e-csi2rx: Return the partial frame as error
  2026-01-06 11:15   ` Jai Luthra
@ 2026-01-08  5:37     ` Rishikesh Donadkar
  0 siblings, 0 replies; 54+ messages in thread
From: Rishikesh Donadkar @ 2026-01-08  5:37 UTC (permalink / raw)
  To: Jai Luthra, jai.luthra, laurent.pinchart, mripard
  Cc: y-abhilashchandra, devarsht, s-jain1, vigneshr, mchehab, robh,
	krzk+dt, p.zabel, conor+dt, sakari.ailus, hverkuil-cisco,
	tomi.valkeinen, changhuang.liang, jack.zhu, sjoerd, dan.carpenter,
	hverkuil+cisco, linux-kernel, linux-media, devicetree


On 06/01/26 16:45, Jai Luthra wrote:
> Hi Rishikesh,

Hi Jai,

Thanks !

>
> Quoting Rishikesh Donadkar (2025-12-30 14:02:17)
>> After draining, when a buffer is queued to the driver, ti will fill out
>> the buffer with a partial frame as some part of the frame is drained.
>> Return the partial frame with VB2_BUF_STATE_ERROR.
> This should be squashed with the previous patch which changes the drain
> architecture and leads to the partial frames. So for the combined patch:

Will do.


Regards,

Rishikesh

>
> Reviewed-by: Jai Luthra <jai.luthra@ideasonboard.com>
>
>> Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
>> ---
>>   drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c | 11 ++++++++++-
>>   1 file changed, 10 insertions(+), 1 deletion(-)
>>
>> diff --git a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
>> index e713293696eb1..3922bd67e78da 100644
>> --- a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
>> +++ b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
>> @@ -83,6 +83,7 @@ struct ti_csi2rx_buffer {
>>   enum ti_csi2rx_dma_state {
>>          TI_CSI2RX_DMA_STOPPED,  /* Streaming not started yet. */
>>          TI_CSI2RX_DMA_ACTIVE,   /* Streaming and pending DMA operation. */
>> +       TI_CSI2RX_DMA_DRAINING, /* Dumping all the data in drain buffer */
>>   };
>>   
>>   struct ti_csi2rx_dma {
>> @@ -728,12 +729,20 @@ static void ti_csi2rx_dma_callback(void *param)
>>          spin_lock_irqsave(&dma->lock, flags);
>>   
>>          WARN_ON(!list_is_first(&buf->list, &dma->submitted));
>> -       vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
>> +
>> +       if (dma->state == TI_CSI2RX_DMA_DRAINING) {
>> +               vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
>> +               dma->state = TI_CSI2RX_DMA_ACTIVE;
>> +       } else {
>> +               vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
>> +       }
>> +
>>          list_del(&buf->list);
>>   
>>          ti_csi2rx_dma_submit_pending(ctx);
>>   
>>          if (list_empty(&dma->submitted)) {
>> +               dma->state = TI_CSI2RX_DMA_DRAINING;
>>                  if (ti_csi2rx_drain_dma(ctx))
>>                          dev_warn(ctx->csi->dev,
>>                                   "DMA drain failed on one of the transactions\n");
>> -- 
>> 2.34.1
>>
>>

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v9 08/19] media: staging: starfive: Move to enabel-disable streams in starfive drivers
  2025-12-30  8:32 ` [PATCH v9 08/19] media: staging: starfive: Move to enabel-disable streams in starfive drivers Rishikesh Donadkar
@ 2026-01-14 12:51   ` Jai Luthra
  2026-01-14 13:05     ` Laurent Pinchart
  0 siblings, 1 reply; 54+ messages in thread
From: Jai Luthra @ 2026-01-14 12:51 UTC (permalink / raw)
  To: Rishikesh Donadkar, jai.luthra, laurent.pinchart, mripard
  Cc: r-donadkar, y-abhilashchandra, devarsht, s-jain1, vigneshr,
	mchehab, robh, krzk+dt, p.zabel, conor+dt, sakari.ailus,
	hverkuil-cisco, tomi.valkeinen, changhuang.liang, jack.zhu,
	sjoerd, dan.carpenter, hverkuil+cisco, linux-kernel, linux-media,
	devicetree

Hi Rishikesh,

Thanks for the patch.

> Subject: [PATCH v9 08/19] media: staging: starfive: Move to enabel-disable streams in starfive drivers

s/enabel/enable

Quoting Rishikesh Donadkar (2025-12-30 14:02:09)
> The enable_streams() API in v4l2 supports passing a bitmask to enable
> each pad/stream combination individually on any media subdev. Use this
> API instead of  s_stream() API in the starfive drivers

nit: I think the description can be explicit that this driver does not
support "multiple streams" (at least right now), but just switching to the
new API while ignoring the passed streams mask.

> 
> Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
> ---
>  .../staging/media/starfive/camss/stf-isp.c    | 43 ++++++++++++-------
>  .../staging/media/starfive/camss/stf-video.c  |  4 +-
>  2 files changed, 30 insertions(+), 17 deletions(-)
> 
> diff --git a/drivers/staging/media/starfive/camss/stf-isp.c b/drivers/staging/media/starfive/camss/stf-isp.c
> index df7a903fbb1b0..4930ffb0e07a6 100644
> --- a/drivers/staging/media/starfive/camss/stf-isp.c
> +++ b/drivers/staging/media/starfive/camss/stf-isp.c
> @@ -55,27 +55,43 @@ int stf_isp_init(struct stfcamss *stfcamss)
>         return 0;
>  }
>  
> -static int isp_set_stream(struct v4l2_subdev *sd, int enable)
> +static int isp_sd_enable_stream(struct v4l2_subdev *sd,
> +                               struct v4l2_subdev_state *state,
> +                               u32 pad, u64 streams_mask)
>  {
>         struct stf_isp_dev *isp_dev = v4l2_get_subdevdata(sd);
>         struct v4l2_subdev_state *sd_state;
>         struct v4l2_mbus_framefmt *fmt;
>         struct v4l2_rect *crop;
> +       int ret;
>  
> -       sd_state = v4l2_subdev_lock_and_get_active_state(sd);
> +       sd_state = v4l2_subdev_get_locked_active_state(sd);
>         fmt = v4l2_subdev_state_get_format(sd_state, STF_ISP_PAD_SINK);
>         crop = v4l2_subdev_state_get_crop(sd_state, STF_ISP_PAD_SRC);
>  
> -       if (enable) {
> -               stf_isp_reset(isp_dev);
> -               stf_isp_init_cfg(isp_dev);
> -               stf_isp_settings(isp_dev, crop, fmt->code);
> -               stf_isp_stream_set(isp_dev);
> -       }
> +       stf_isp_reset(isp_dev);
> +       stf_isp_init_cfg(isp_dev);
> +       stf_isp_settings(isp_dev, crop, fmt->code);
> +       stf_isp_stream_set(isp_dev);
> +
> +       ret = v4l2_subdev_enable_streams(isp_dev->source_subdev, 1, BIT(0));

Given you have a streams_mask argument in this function now, it might be
cleaner to use it here (and let stf-video populate it with BIT(0)).

> +       if (ret)
> +               return ret;
> +
> +       return 0;
> +}
>  
> -       v4l2_subdev_call(isp_dev->source_subdev, video, s_stream, enable);
> +static int isp_sd_disable_stream(struct v4l2_subdev *sd,
> +                                struct v4l2_subdev_state *state,
> +                                u32 pad, u64 streams_mask)
> +{
> +       struct stf_isp_dev *isp_dev = v4l2_get_subdevdata(sd);
> +       int ret;
> +
> +       ret = v4l2_subdev_disable_streams(isp_dev->source_subdev, 1, BIT(0));

Same here.

> +       if (ret)
> +               return ret;
>  
> -       v4l2_subdev_unlock_state(sd_state);
>         return 0;
>  }
>  
> @@ -300,20 +316,17 @@ static int isp_init_formats(struct v4l2_subdev *sd,
>         return isp_set_format(sd, sd_state, &format);
>  }
>  
> -static const struct v4l2_subdev_video_ops isp_video_ops = {
> -       .s_stream = isp_set_stream,
> -};
> -
>  static const struct v4l2_subdev_pad_ops isp_pad_ops = {
>         .enum_mbus_code = isp_enum_mbus_code,
>         .get_fmt = v4l2_subdev_get_fmt,
>         .set_fmt = isp_set_format,
>         .get_selection = isp_get_selection,
>         .set_selection = isp_set_selection,
> +       .enable_streams = isp_sd_enable_stream,
> +       .disable_streams = isp_sd_disable_stream,
>  };
>  
>  static const struct v4l2_subdev_ops isp_v4l2_ops = {
> -       .video = &isp_video_ops,
>         .pad = &isp_pad_ops,
>  };
>  
> diff --git a/drivers/staging/media/starfive/camss/stf-video.c b/drivers/staging/media/starfive/camss/stf-video.c
> index a0420eb6a0aa0..2db29bf8bdef8 100644
> --- a/drivers/staging/media/starfive/camss/stf-video.c
> +++ b/drivers/staging/media/starfive/camss/stf-video.c
> @@ -287,7 +287,7 @@ static int video_start_streaming(struct vb2_queue *q, unsigned int count)
>  
>         video->ops->start_streaming(video);
>  
> -       ret = v4l2_subdev_call(video->source_subdev, video, s_stream, true);
> +       ret = v4l2_subdev_enable_streams(video->source_subdev, 1, BIT(0));

Now that I think of it, it was not necessary to implement enable / disable
API for the ISP subdev driver given v4l2_subdev_*_streams falls back on
s_stream. But it's anyway good to move drivers, so I guess it's alright.

>         if (ret) {
>                 dev_err(video->stfcamss->dev, "stream on failed\n");
>                 goto err_pm_put;
> @@ -311,7 +311,7 @@ static void video_stop_streaming(struct vb2_queue *q)
>  
>         video->ops->stop_streaming(video);
>  
> -       v4l2_subdev_call(video->source_subdev, video, s_stream, false);
> +       v4l2_subdev_disable_streams(video->source_subdev, 1, BIT(0));
>  
>         pm_runtime_put(video->stfcamss->dev);
>  
> -- 
> 2.34.1
> 
> 

Thanks,
    Jai

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v9 08/19] media: staging: starfive: Move to enabel-disable streams in starfive drivers
  2026-01-14 12:51   ` Jai Luthra
@ 2026-01-14 13:05     ` Laurent Pinchart
  0 siblings, 0 replies; 54+ messages in thread
From: Laurent Pinchart @ 2026-01-14 13:05 UTC (permalink / raw)
  To: Jai Luthra
  Cc: Rishikesh Donadkar, jai.luthra, mripard, y-abhilashchandra,
	devarsht, s-jain1, vigneshr, mchehab, robh, krzk+dt, p.zabel,
	conor+dt, sakari.ailus, hverkuil-cisco, tomi.valkeinen,
	changhuang.liang, jack.zhu, sjoerd, dan.carpenter, hverkuil+cisco,
	linux-kernel, linux-media, devicetree

On Wed, Jan 14, 2026 at 06:21:21PM +0530, Jai Luthra wrote:
> Hi Rishikesh,
> 
> Thanks for the patch.

We should actually drop the driver. Starfive has confirmed they don't
plan to develop it further, so it shouldn't stay in staging.

> > Subject: [PATCH v9 08/19] media: staging: starfive: Move to enabel-disable streams in starfive drivers
> 
> s/enabel/enable
> 
> Quoting Rishikesh Donadkar (2025-12-30 14:02:09)
> > The enable_streams() API in v4l2 supports passing a bitmask to enable
> > each pad/stream combination individually on any media subdev. Use this
> > API instead of  s_stream() API in the starfive drivers
> 
> nit: I think the description can be explicit that this driver does not
> support "multiple streams" (at least right now), but just switching to the
> new API while ignoring the passed streams mask.
> 
> > 
> > Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
> > ---
> >  .../staging/media/starfive/camss/stf-isp.c    | 43 ++++++++++++-------
> >  .../staging/media/starfive/camss/stf-video.c  |  4 +-
> >  2 files changed, 30 insertions(+), 17 deletions(-)
> > 
> > diff --git a/drivers/staging/media/starfive/camss/stf-isp.c b/drivers/staging/media/starfive/camss/stf-isp.c
> > index df7a903fbb1b0..4930ffb0e07a6 100644
> > --- a/drivers/staging/media/starfive/camss/stf-isp.c
> > +++ b/drivers/staging/media/starfive/camss/stf-isp.c
> > @@ -55,27 +55,43 @@ int stf_isp_init(struct stfcamss *stfcamss)
> >         return 0;
> >  }
> >  
> > -static int isp_set_stream(struct v4l2_subdev *sd, int enable)
> > +static int isp_sd_enable_stream(struct v4l2_subdev *sd,
> > +                               struct v4l2_subdev_state *state,
> > +                               u32 pad, u64 streams_mask)
> >  {
> >         struct stf_isp_dev *isp_dev = v4l2_get_subdevdata(sd);
> >         struct v4l2_subdev_state *sd_state;
> >         struct v4l2_mbus_framefmt *fmt;
> >         struct v4l2_rect *crop;
> > +       int ret;
> >  
> > -       sd_state = v4l2_subdev_lock_and_get_active_state(sd);
> > +       sd_state = v4l2_subdev_get_locked_active_state(sd);
> >         fmt = v4l2_subdev_state_get_format(sd_state, STF_ISP_PAD_SINK);
> >         crop = v4l2_subdev_state_get_crop(sd_state, STF_ISP_PAD_SRC);
> >  
> > -       if (enable) {
> > -               stf_isp_reset(isp_dev);
> > -               stf_isp_init_cfg(isp_dev);
> > -               stf_isp_settings(isp_dev, crop, fmt->code);
> > -               stf_isp_stream_set(isp_dev);
> > -       }
> > +       stf_isp_reset(isp_dev);
> > +       stf_isp_init_cfg(isp_dev);
> > +       stf_isp_settings(isp_dev, crop, fmt->code);
> > +       stf_isp_stream_set(isp_dev);
> > +
> > +       ret = v4l2_subdev_enable_streams(isp_dev->source_subdev, 1, BIT(0));
> 
> Given you have a streams_mask argument in this function now, it might be
> cleaner to use it here (and let stf-video populate it with BIT(0)).
> 
> > +       if (ret)
> > +               return ret;
> > +
> > +       return 0;
> > +}
> >  
> > -       v4l2_subdev_call(isp_dev->source_subdev, video, s_stream, enable);
> > +static int isp_sd_disable_stream(struct v4l2_subdev *sd,
> > +                                struct v4l2_subdev_state *state,
> > +                                u32 pad, u64 streams_mask)
> > +{
> > +       struct stf_isp_dev *isp_dev = v4l2_get_subdevdata(sd);
> > +       int ret;
> > +
> > +       ret = v4l2_subdev_disable_streams(isp_dev->source_subdev, 1, BIT(0));
> 
> Same here.
> 
> > +       if (ret)
> > +               return ret;
> >  
> > -       v4l2_subdev_unlock_state(sd_state);
> >         return 0;
> >  }
> >  
> > @@ -300,20 +316,17 @@ static int isp_init_formats(struct v4l2_subdev *sd,
> >         return isp_set_format(sd, sd_state, &format);
> >  }
> >  
> > -static const struct v4l2_subdev_video_ops isp_video_ops = {
> > -       .s_stream = isp_set_stream,
> > -};
> > -
> >  static const struct v4l2_subdev_pad_ops isp_pad_ops = {
> >         .enum_mbus_code = isp_enum_mbus_code,
> >         .get_fmt = v4l2_subdev_get_fmt,
> >         .set_fmt = isp_set_format,
> >         .get_selection = isp_get_selection,
> >         .set_selection = isp_set_selection,
> > +       .enable_streams = isp_sd_enable_stream,
> > +       .disable_streams = isp_sd_disable_stream,
> >  };
> >  
> >  static const struct v4l2_subdev_ops isp_v4l2_ops = {
> > -       .video = &isp_video_ops,
> >         .pad = &isp_pad_ops,
> >  };
> >  
> > diff --git a/drivers/staging/media/starfive/camss/stf-video.c b/drivers/staging/media/starfive/camss/stf-video.c
> > index a0420eb6a0aa0..2db29bf8bdef8 100644
> > --- a/drivers/staging/media/starfive/camss/stf-video.c
> > +++ b/drivers/staging/media/starfive/camss/stf-video.c
> > @@ -287,7 +287,7 @@ static int video_start_streaming(struct vb2_queue *q, unsigned int count)
> >  
> >         video->ops->start_streaming(video);
> >  
> > -       ret = v4l2_subdev_call(video->source_subdev, video, s_stream, true);
> > +       ret = v4l2_subdev_enable_streams(video->source_subdev, 1, BIT(0));
> 
> Now that I think of it, it was not necessary to implement enable / disable
> API for the ISP subdev driver given v4l2_subdev_*_streams falls back on
> s_stream. But it's anyway good to move drivers, so I guess it's alright.
> 
> >         if (ret) {
> >                 dev_err(video->stfcamss->dev, "stream on failed\n");
> >                 goto err_pm_put;
> > @@ -311,7 +311,7 @@ static void video_stop_streaming(struct vb2_queue *q)
> >  
> >         video->ops->stop_streaming(video);
> >  
> > -       v4l2_subdev_call(video->source_subdev, video, s_stream, false);
> > +       v4l2_subdev_disable_streams(video->source_subdev, 1, BIT(0));
> >  
> >         pm_runtime_put(video->stfcamss->dev);
> >  

-- 
Regards,

Laurent Pinchart

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v9 06/19] media: ti: j721e-csi2rx: add a subdev for the core device
  2025-12-30  8:32 ` [PATCH v9 06/19] media: ti: j721e-csi2rx: add a subdev for the core device Rishikesh Donadkar
@ 2026-01-14 15:21   ` Tomi Valkeinen
  2026-01-15  6:36     ` Jai Luthra
  2026-01-21  9:10     ` Rishikesh Donadkar
  0 siblings, 2 replies; 54+ messages in thread
From: Tomi Valkeinen @ 2026-01-14 15:21 UTC (permalink / raw)
  To: Rishikesh Donadkar
  Cc: y-abhilashchandra, devarsht, s-jain1, vigneshr, mchehab, robh,
	krzk+dt, p.zabel, conor+dt, sakari.ailus, hverkuil-cisco,
	jai.luthra, changhuang.liang, jack.zhu, sjoerd, dan.carpenter,
	hverkuil+cisco, linux-kernel, linux-media, devicetree, jai.luthra,
	laurent.pinchart, mripard

Hi,

On 30/12/2025 10:32, Rishikesh Donadkar wrote:
> From: Jai Luthra <j-luthra@ti.com>
> 
> With single stream capture, it was simpler to use the video device as
> the media entity representing the main TI CSI2RX device. Now with multi
> stream capture coming into the picture, the model has shifted to each
> video device having a link to the main device's subdev. The routing
> would then be set on this subdev.
> 
> Add this subdev, link each context to this subdev's entity and link the
> subdev's entity to the source. Also add an array of media pads. It will
> have one sink pad and source pads equal to the number of contexts.
> 
> Support the new enable_stream()/disable_stream() APIs in the subdev
> instead of s_stream() hook.
> 
> Reviewed-by: Yemike Abhilash Chandra <y-abhilashchandra@ti.com>
> Co-developed-by: Pratyush Yadav <p.yadav@ti.com>
> Signed-off-by: Pratyush Yadav <p.yadav@ti.com>
> Signed-off-by: Jai Luthra <j-luthra@ti.com>
> Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
> ---
>  .../platform/ti/j721e-csi2rx/j721e-csi2rx.c   | 292 +++++++++++++++---
>  1 file changed, 248 insertions(+), 44 deletions(-)
> 
> diff --git a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
> index f66d68edcd57a..8f49ea2638585 100644
> --- a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
> +++ b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
> @@ -54,6 +54,11 @@
>  #define MAX_WIDTH_BYTES			SZ_16K
>  #define MAX_HEIGHT_LINES		SZ_16K
>  
> +#define TI_CSI2RX_PAD_SINK		0
> +#define TI_CSI2RX_PAD_FIRST_SOURCE	1
> +#define TI_CSI2RX_NUM_SOURCE_PADS	1
> +#define TI_CSI2RX_NUM_PADS		(1 + TI_CSI2RX_NUM_SOURCE_PADS)
> +
>  #define DRAIN_TIMEOUT_MS		50
>  #define DRAIN_BUFFER_SIZE		SZ_32K
>  
> @@ -102,6 +107,7 @@ struct ti_csi2rx_ctx {
>  	struct mutex			mutex; /* To serialize ioctls. */
>  	struct v4l2_format		v_fmt;
>  	struct ti_csi2rx_dma		dma;
> +	struct media_pad		pad;
>  	u32				sequence;
>  	u32				idx;
>  };
> @@ -109,12 +115,15 @@ struct ti_csi2rx_ctx {
>  struct ti_csi2rx_dev {
>  	struct device			*dev;
>  	void __iomem			*shim;
> +	struct mutex			mutex; /* To serialize ioctls. */

The mutex is not used (in this patch at least).

> +	unsigned int			enable_count;
>  	struct v4l2_device		v4l2_dev;
>  	struct media_device		mdev;
>  	struct media_pipeline		pipe;
> -	struct media_pad		pad;
> +	struct media_pad		pads[TI_CSI2RX_NUM_PADS];
>  	struct v4l2_async_notifier	notifier;
>  	struct v4l2_subdev		*source;
> +	struct v4l2_subdev		subdev;
>  	struct ti_csi2rx_ctx		ctx[TI_CSI2RX_NUM_CTX];
>  	u8				pix_per_clk;
>  	/* Buffer to drain stale data from PSI-L endpoint */
> @@ -125,6 +134,22 @@ struct ti_csi2rx_dev {
>  	} drain;
>  };
>  
> +static inline struct ti_csi2rx_dev *to_csi2rx_dev(struct v4l2_subdev *sd)
> +{
> +	return container_of(sd, struct ti_csi2rx_dev, subdev);
> +}
> +
> +static const struct v4l2_mbus_framefmt ti_csi2rx_default_fmt = {
> +	.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,
> +};
> +
>  static const struct ti_csi2rx_fmt ti_csi2rx_formats[] = {
>  	{
>  		.fourcc			= V4L2_PIX_FMT_YUYV,
> @@ -422,6 +447,18 @@ static int csi_async_notifier_complete(struct v4l2_async_notifier *notifier)
>  	struct ti_csi2rx_dev *csi = dev_get_drvdata(notifier->v4l2_dev->dev);
>  	int ret, i;
>  
> +	/* Create link from source to subdev */
> +	ret = media_create_pad_link(&csi->source->entity,
> +				    CSI2RX_BRIDGE_SOURCE_PAD,
> +				    &csi->subdev.entity,
> +				    TI_CSI2RX_PAD_SINK,
> +				    MEDIA_LNK_FL_IMMUTABLE |
> +				    MEDIA_LNK_FL_ENABLED);
> +
> +	if (ret)
> +		return ret;
> +
> +	/* Create and link video nodes for all DMA contexts */
>  	for (i = 0; i < TI_CSI2RX_NUM_CTX; i++) {
>  		struct ti_csi2rx_ctx *ctx = &csi->ctx[i];
>  		struct video_device *vdev = &ctx->vdev;
> @@ -429,15 +466,17 @@ static int csi_async_notifier_complete(struct v4l2_async_notifier *notifier)
>  		ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
>  		if (ret)
>  			goto unregister_dev;
> -	}
>  
> -	ret = media_create_pad_link(&csi->source->entity,
> -				    CSI2RX_BRIDGE_SOURCE_PAD,
> -				    &csi->ctx[0].vdev.entity, csi->pad.index,
> -				    MEDIA_LNK_FL_IMMUTABLE |
> -				    MEDIA_LNK_FL_ENABLED);
> -	if (ret)
> -		goto unregister_dev;
> +		ret = media_create_pad_link(&csi->subdev.entity,
> +					    TI_CSI2RX_PAD_FIRST_SOURCE + ctx->idx,
> +					    &vdev->entity, 0,
> +					    MEDIA_LNK_FL_IMMUTABLE |
> +					    MEDIA_LNK_FL_ENABLED);
> +		if (ret) {
> +			video_unregister_device(vdev);
> +			goto unregister_dev;
> +		}
> +	}
>  
>  	ret = v4l2_device_register_subdev_nodes(&csi->v4l2_dev);
>  	if (ret)
> @@ -447,8 +486,10 @@ static int csi_async_notifier_complete(struct v4l2_async_notifier *notifier)
>  
>  unregister_dev:
>  	i--;
> -	for (; i >= 0; i--)
> +	for (; i >= 0; i--) {
> +		media_entity_remove_links(&csi->ctx[i].vdev.entity);
>  		video_unregister_device(&csi->ctx[i].vdev);
> +	}
>  	return ret;
>  }
>  
> @@ -493,14 +534,13 @@ static int ti_csi2rx_notifier_register(struct ti_csi2rx_dev *csi)
>  }
>  
>  /* Request maximum possible pixels per clock from the bridge */
> -static void ti_csi2rx_request_max_ppc(struct ti_csi2rx_ctx *ctx)
> +static void ti_csi2rx_request_max_ppc(struct ti_csi2rx_dev *csi)
>  {
> -	struct ti_csi2rx_dev *csi = ctx->csi;
>  	u8 ppc = TI_CSI2RX_MAX_PIX_PER_CLK;
>  	struct media_pad *pad;
>  	int ret;
>  
> -	pad = media_entity_remote_source_pad_unique(&ctx->vdev.entity);
> +	pad = media_entity_remote_source_pad_unique(&csi->subdev.entity);
>  	if (IS_ERR(pad))
>  		return;
>  
> @@ -526,7 +566,7 @@ static void ti_csi2rx_setup_shim(struct ti_csi2rx_ctx *ctx)
>  	writel(reg, csi->shim + SHIM_CNTL);
>  
>  	/* Negotiate pixel count from the source */
> -	ti_csi2rx_request_max_ppc(ctx);
> +	ti_csi2rx_request_max_ppc(csi);
>  
>  	reg = SHIM_DMACNTX_EN;
>  	reg |= FIELD_PREP(SHIM_DMACNTX_FMT, fmt->csi_dt);
> @@ -881,7 +921,9 @@ static int ti_csi2rx_start_streaming(struct vb2_queue *vq, unsigned int count)
>  	dma->state = TI_CSI2RX_DMA_ACTIVE;
>  	spin_unlock_irqrestore(&dma->lock, flags);
>  
> -	ret = v4l2_subdev_call(csi->source, video, s_stream, 1);
> +	ret = v4l2_subdev_enable_streams(&csi->subdev,
> +					 TI_CSI2RX_PAD_FIRST_SOURCE,
> +					 BIT_U64(0));
>  	if (ret)
>  		goto err_dma;
>  
> @@ -909,7 +951,9 @@ static void ti_csi2rx_stop_streaming(struct vb2_queue *vq)
>  	writel(0, csi->shim + SHIM_CNTL);
>  	writel(0, csi->shim + SHIM_DMACNTX(ctx->idx));
>  
> -	ret = v4l2_subdev_call(csi->source, video, s_stream, 0);
> +	ret = v4l2_subdev_disable_streams(&csi->subdev,
> +					  TI_CSI2RX_PAD_FIRST_SOURCE,
> +					  BIT_U64(0));
>  	if (ret)
>  		dev_err(csi->dev, "Failed to stop subdev stream\n");
>  
> @@ -925,8 +969,121 @@ static const struct vb2_ops csi_vb2_qops = {
>  	.stop_streaming = ti_csi2rx_stop_streaming,
>  };
>  
> +static int ti_csi2rx_enum_mbus_code(struct v4l2_subdev *subdev,
> +				    struct v4l2_subdev_state *state,
> +				    struct v4l2_subdev_mbus_code_enum *code_enum)
> +{
> +	if (code_enum->index >= ARRAY_SIZE(ti_csi2rx_formats))
> +		return -EINVAL;
> +
> +	code_enum->code = ti_csi2rx_formats[code_enum->index].code;
> +
> +	return 0;
> +}
> +
> +static int ti_csi2rx_sd_set_fmt(struct v4l2_subdev *sd,
> +				struct v4l2_subdev_state *state,
> +				struct v4l2_subdev_format *format)
> +{
> +	struct v4l2_mbus_framefmt *fmt;
> +
> +	/* No transcoding, don't allow setting source fmt */
> +	if (format->pad > TI_CSI2RX_PAD_SINK)
> +		return v4l2_subdev_get_fmt(sd, state, format);
> +
> +	if (!find_format_by_code(format->format.code))
> +		format->format.code = ti_csi2rx_formats[0].code;
> +
> +	format->format.field = V4L2_FIELD_NONE;
> +
> +	fmt = v4l2_subdev_state_get_format(state, format->pad, format->stream);
> +	*fmt = format->format;
> +
> +	fmt = v4l2_subdev_state_get_format(state, TI_CSI2RX_PAD_FIRST_SOURCE,
> +					   format->stream);
> +	*fmt = format->format;
> +
> +	return 0;
> +}
> +
> +static int ti_csi2rx_sd_init_state(struct v4l2_subdev *sd,
> +				   struct v4l2_subdev_state *state)
> +{
> +	struct v4l2_mbus_framefmt *fmt;
> +
> +	fmt = v4l2_subdev_state_get_format(state, TI_CSI2RX_PAD_SINK);
> +	*fmt = ti_csi2rx_default_fmt;
> +
> +	fmt = v4l2_subdev_state_get_format(state, TI_CSI2RX_PAD_FIRST_SOURCE);
> +	*fmt = ti_csi2rx_default_fmt;
> +
> +	return 0;
> +}
> +
> +static int ti_csi2rx_sd_enable_streams(struct v4l2_subdev *sd,
> +				       struct v4l2_subdev_state *state,
> +				       u32 pad, u64 streams_mask)
> +{
> +	struct ti_csi2rx_dev *csi = to_csi2rx_dev(sd);
> +	struct media_pad *remote_pad;
> +	int ret = 0;
> +
> +	remote_pad = media_entity_remote_source_pad_unique(&csi->subdev.entity);
> +	if (!remote_pad)
> +		return -ENODEV;
> +
> +	ret = v4l2_subdev_enable_streams(csi->source, remote_pad->index,
> +					 BIT_U64(0));
> +	if (ret)
> +		return ret;
> +
> +	csi->enable_count++;
> +
> +	return 0;
> +}
> +
> +static int ti_csi2rx_sd_disable_streams(struct v4l2_subdev *sd,
> +					struct v4l2_subdev_state *state,
> +					u32 pad, u64 streams_mask)
> +{
> +	struct ti_csi2rx_dev *csi = to_csi2rx_dev(sd);
> +	struct media_pad *remote_pad;
> +	int ret = 0;
> +
> +	remote_pad = media_entity_remote_source_pad_unique(&csi->subdev.entity);
> +	if (!remote_pad)
> +		return -ENODEV;
> +
> +	if (csi->enable_count == 0)
> +		return -EINVAL;
> +
> +	ret = v4l2_subdev_disable_streams(csi->source, remote_pad->index,
> +					  BIT_U64(0));
> +	if (!ret)
> +		--csi->enable_count;
> +
> +	return 0;
> +}
> +
> +static const struct v4l2_subdev_pad_ops ti_csi2rx_subdev_pad_ops = {
> +	.enum_mbus_code	= ti_csi2rx_enum_mbus_code,
> +	.get_fmt = v4l2_subdev_get_fmt,
> +	.set_fmt = ti_csi2rx_sd_set_fmt,
> +	.enable_streams = ti_csi2rx_sd_enable_streams,
> +	.disable_streams = ti_csi2rx_sd_disable_streams,
> +};
> +
> +static const struct v4l2_subdev_ops ti_csi2rx_subdev_ops = {
> +	.pad = &ti_csi2rx_subdev_pad_ops,
> +};
> +
> +static const struct v4l2_subdev_internal_ops ti_csi2rx_internal_ops = {
> +	.init_state = ti_csi2rx_sd_init_state,
> +};
> +
>  static void ti_csi2rx_cleanup_v4l2(struct ti_csi2rx_dev *csi)
>  {
> +	v4l2_subdev_cleanup(&csi->subdev);
>  	media_device_unregister(&csi->mdev);
>  	v4l2_device_unregister(&csi->v4l2_dev);
>  	media_device_cleanup(&csi->mdev);
> @@ -981,48 +1138,52 @@ static int ti_csi2rx_link_validate(struct media_link *link)
>  	struct ti_csi2rx_ctx *ctx = container_of(vdev, struct ti_csi2rx_ctx, vdev);
>  	struct ti_csi2rx_dev *csi = ctx->csi;
>  	struct v4l2_pix_format *csi_fmt = &ctx->v_fmt.fmt.pix;
> -	struct v4l2_subdev_format source_fmt = {
> -		.which	= V4L2_SUBDEV_FORMAT_ACTIVE,
> -		.pad	= link->source->index,
> -	};
> +	struct v4l2_mbus_framefmt *format;
> +	struct v4l2_subdev_state *state;
>  	const struct ti_csi2rx_fmt *ti_fmt;
> -	int ret;
>  
> -	ret = v4l2_subdev_call_state_active(csi->source, pad,
> -					    get_fmt, &source_fmt);
> -	if (ret)
> -		return ret;
> +	state = v4l2_subdev_lock_and_get_active_state(&csi->subdev);
> +	format = v4l2_subdev_state_get_format(state, link->source->index, 0);
> +	v4l2_subdev_unlock_state(state);
>  
> -	if (source_fmt.format.width != csi_fmt->width) {
> +	if (!format) {
> +		dev_dbg(csi->dev,
> +			"Skipping validation as no format present on \"%s\":%u:0\n",
> +			link->source->entity->name, link->source->index);
> +		return 0;

Isn't this an error?

 Tomi


> +	}
> +
> +	if (format->width != csi_fmt->width) {
>  		dev_dbg(csi->dev, "Width does not match (source %u, sink %u)\n",
> -			source_fmt.format.width, csi_fmt->width);
> +			format->width, csi_fmt->width);
>  		return -EPIPE;
>  	}
>  
> -	if (source_fmt.format.height != csi_fmt->height) {
> +	if (format->height != csi_fmt->height) {
>  		dev_dbg(csi->dev, "Height does not match (source %u, sink %u)\n",
> -			source_fmt.format.height, csi_fmt->height);
> +			format->height, csi_fmt->height);
>  		return -EPIPE;
>  	}
>  
> -	if (source_fmt.format.field != csi_fmt->field &&
> +	if (format->field != csi_fmt->field &&
>  	    csi_fmt->field != V4L2_FIELD_NONE) {
>  		dev_dbg(csi->dev, "Field does not match (source %u, sink %u)\n",
> -			source_fmt.format.field, csi_fmt->field);
> +			format->field, csi_fmt->field);
>  		return -EPIPE;
>  	}
>  
> -	ti_fmt = find_format_by_code(source_fmt.format.code);
> +	ti_fmt = find_format_by_code(format->code);
>  	if (!ti_fmt) {
>  		dev_dbg(csi->dev, "Media bus format 0x%x not supported\n",
> -			source_fmt.format.code);
> +			format->code);
>  		return -EPIPE;
>  	}
>  
>  	if (ti_fmt->fourcc != csi_fmt->pixelformat) {
>  		dev_dbg(csi->dev,
> -			"Cannot transform source fmt 0x%x to sink fmt 0x%x\n",
> -			ti_fmt->fourcc, csi_fmt->pixelformat);
> +			"Cannot transform \"%s\":%u format %p4cc to %p4cc\n",
> +			link->source->entity->name, link->source->index,
> +			&ti_fmt->fourcc, &csi_fmt->pixelformat);
>  		return -EPIPE;
>  	}
>  
> @@ -1033,6 +1194,10 @@ static const struct media_entity_operations ti_csi2rx_video_entity_ops = {
>  	.link_validate = ti_csi2rx_link_validate,
>  };
>  
> +static const struct media_entity_operations ti_csi2rx_subdev_entity_ops = {
> +	.link_validate = v4l2_subdev_link_validate,
> +};
> +
>  static int ti_csi2rx_init_dma(struct ti_csi2rx_ctx *ctx)
>  {
>  	struct dma_slave_config cfg = {
> @@ -1058,6 +1223,7 @@ static int ti_csi2rx_init_dma(struct ti_csi2rx_ctx *ctx)
>  static int ti_csi2rx_v4l2_init(struct ti_csi2rx_dev *csi)
>  {
>  	struct media_device *mdev = &csi->mdev;
> +	struct v4l2_subdev *sd = &csi->subdev;
>  	int ret;
>  
>  	mdev->dev = csi->dev;
> @@ -1070,16 +1236,51 @@ static int ti_csi2rx_v4l2_init(struct ti_csi2rx_dev *csi)
>  
>  	ret = v4l2_device_register(csi->dev, &csi->v4l2_dev);
>  	if (ret)
> -		return ret;
> +		goto cleanup_media;
>  
>  	ret = media_device_register(mdev);
> -	if (ret) {
> -		v4l2_device_unregister(&csi->v4l2_dev);
> -		media_device_cleanup(mdev);
> -		return ret;
> -	}
> +	if (ret)
> +		goto unregister_v4l2;
> +
> +	v4l2_subdev_init(sd, &ti_csi2rx_subdev_ops);
> +	sd->internal_ops = &ti_csi2rx_internal_ops;
> +	sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
> +	sd->flags = V4L2_SUBDEV_FL_HAS_DEVNODE;
> +	strscpy(sd->name, dev_name(csi->dev), sizeof(sd->name));
> +	sd->dev = csi->dev;
> +	sd->entity.ops = &ti_csi2rx_subdev_entity_ops;
> +
> +	csi->pads[TI_CSI2RX_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
> +
> +	for (unsigned int i = TI_CSI2RX_PAD_FIRST_SOURCE;
> +	     i < TI_CSI2RX_NUM_PADS; i++)
> +		csi->pads[i].flags = MEDIA_PAD_FL_SOURCE;
> +
> +	ret = media_entity_pads_init(&sd->entity, ARRAY_SIZE(csi->pads),
> +				     csi->pads);
> +	if (ret)
> +		goto unregister_media;
> +
> +	ret = v4l2_subdev_init_finalize(sd);
> +	if (ret)
> +		goto unregister_media;
> +
> +	ret = v4l2_device_register_subdev(&csi->v4l2_dev, sd);
> +	if (ret)
> +		goto cleanup_subdev;
>  
>  	return 0;
> +
> +cleanup_subdev:
> +	v4l2_subdev_cleanup(sd);
> +unregister_media:
> +	media_device_unregister(mdev);
> +unregister_v4l2:
> +	v4l2_device_unregister(&csi->v4l2_dev);
> +cleanup_media:
> +	media_device_cleanup(mdev);
> +
> +	return ret;
>  }
>  
>  static int ti_csi2rx_init_ctx(struct ti_csi2rx_ctx *ctx)
> @@ -1106,9 +1307,9 @@ static int ti_csi2rx_init_ctx(struct ti_csi2rx_ctx *ctx)
>  
>  	ti_csi2rx_fill_fmt(fmt, &ctx->v_fmt);
>  
> -	csi->pad.flags = MEDIA_PAD_FL_SINK;
> +	ctx->pad.flags = MEDIA_PAD_FL_SINK;
>  	vdev->entity.ops = &ti_csi2rx_video_entity_ops;
> -	ret = media_entity_pads_init(&ctx->vdev.entity, 1, &csi->pad);
> +	ret = media_entity_pads_init(&ctx->vdev.entity, 1, &ctx->pad);
>  	if (ret)
>  		return ret;
>  
> @@ -1169,6 +1370,8 @@ static int ti_csi2rx_probe(struct platform_device *pdev)
>  	if (!csi->drain.vaddr)
>  		return -ENOMEM;
>  
> +	mutex_init(&csi->mutex);
> +
>  	ret = ti_csi2rx_v4l2_init(csi);
>  	if (ret)
>  		goto err_v4l2;
> @@ -1201,6 +1404,7 @@ static int ti_csi2rx_probe(struct platform_device *pdev)
>  		ti_csi2rx_cleanup_ctx(&csi->ctx[i]);
>  	ti_csi2rx_cleanup_v4l2(csi);
>  err_v4l2:
> +	mutex_destroy(&csi->mutex);
>  	dma_free_coherent(csi->dev, csi->drain.len, csi->drain.vaddr,
>  			  csi->drain.paddr);
>  	return ret;
> @@ -1216,7 +1420,7 @@ static void ti_csi2rx_remove(struct platform_device *pdev)
>  
>  	ti_csi2rx_cleanup_notifier(csi);
>  	ti_csi2rx_cleanup_v4l2(csi);
> -
> +	mutex_destroy(&csi->mutex);
>  	dma_free_coherent(csi->dev, csi->drain.len, csi->drain.vaddr,
>  			  csi->drain.paddr);
>  }


^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v9 07/19] media: cadence: csi2rx: Move to .enable/disable_streams API
  2025-12-30  8:32 ` [PATCH v9 07/19] media: cadence: csi2rx: Move to .enable/disable_streams API Rishikesh Donadkar
@ 2026-01-14 15:25   ` Tomi Valkeinen
  0 siblings, 0 replies; 54+ messages in thread
From: Tomi Valkeinen @ 2026-01-14 15:25 UTC (permalink / raw)
  To: Rishikesh Donadkar
  Cc: y-abhilashchandra, devarsht, s-jain1, vigneshr, mchehab, robh,
	krzk+dt, p.zabel, conor+dt, sakari.ailus, hverkuil-cisco,
	jai.luthra, changhuang.liang, jack.zhu, sjoerd, dan.carpenter,
	hverkuil+cisco, linux-kernel, linux-media, devicetree, jai.luthra,
	laurent.pinchart, mripard

Hi,

On 30/12/2025 10:32, Rishikesh Donadkar wrote:
> The enable_streams() API in v4l2 supports passing a bitmask to enable
> each pad/stream combination individually on any media subdev. Use this
> API instead of  s_stream() API.
> 
> Implement the enable_stream and disable_stream hooks in place of the
> stream-unaware s_stream hook.
> 
> Remove the lock that was used to serialize stream starts/stops which
> is not required anymore since the v4l2-core serializes the
> enable/disable_streams() calls for the subdev.
> 
> Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
> ---
>  drivers/media/platform/cadence/cdns-csi2rx.c | 108 +++++++++----------
>  1 file changed, 54 insertions(+), 54 deletions(-)
> 

Reviewed-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>

 Tomi

> diff --git a/drivers/media/platform/cadence/cdns-csi2rx.c b/drivers/media/platform/cadence/cdns-csi2rx.c
> index 8c19f125da3e5..8e7af2da62262 100644
> --- a/drivers/media/platform/cadence/cdns-csi2rx.c
> +++ b/drivers/media/platform/cadence/cdns-csi2rx.c
> @@ -125,12 +125,6 @@ struct csi2rx_priv {
>  	unsigned int			count;
>  	int				error_irq;
>  
> -	/*
> -	 * Used to prevent race conditions between multiple,
> -	 * concurrent calls to start and stop.
> -	 */
> -	struct mutex			lock;
> -
>  	void __iomem			*base;
>  	struct clk			*sys_clk;
>  	struct clk			*p_clk;
> @@ -268,20 +262,21 @@ static int csi2rx_configure_ext_dphy(struct csi2rx_priv *csi2rx)
>  		&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_format sd_fmt = {
> -		.which	= V4L2_SUBDEV_FORMAT_ACTIVE,
> -		.pad	= CSI2RX_PAD_SINK,
> -	};
> +	struct v4l2_subdev_state *state;
> +	struct v4l2_mbus_framefmt *framefmt;
>  	const struct csi2rx_fmt *fmt;
>  	s64 link_freq;
>  	int ret;
>  
> -	ret = v4l2_subdev_call_state_active(&csi2rx->subdev, pad, get_fmt,
> -					    &sd_fmt);
> -	if (ret < 0)
> -		return ret;
> +	state = v4l2_subdev_get_locked_active_state(&csi2rx->subdev);
>  
> -	fmt = csi2rx_get_fmt_by_code(sd_fmt.format.code);
> +	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;
> +	}
> +
> +	fmt = csi2rx_get_fmt_by_code(framefmt->code);
>  
>  	link_freq = v4l2_get_link_freq(src_pad,
>  				       fmt->bpp, 2 * csi2rx->num_lanes);
> @@ -401,16 +396,10 @@ static int csi2rx_start(struct csi2rx_priv *csi2rx)
>  
>  	reset_control_deassert(csi2rx->sys_rst);
>  
> -	ret = v4l2_subdev_call(csi2rx->source_subdev, video, s_stream, true);
> -	if (ret)
> -		goto err_disable_sysclk;
> -
>  	clk_disable_unprepare(csi2rx->p_clk);
>  
>  	return 0;
>  
> -err_disable_sysclk:
> -	clk_disable_unprepare(csi2rx->sys_clk);
>  err_disable_pixclk:
>  	for (; i > 0; i--) {
>  		reset_control_assert(csi2rx->pixel_rst[i - 1]);
> @@ -459,9 +448,6 @@ static void csi2rx_stop(struct csi2rx_priv *csi2rx)
>  	reset_control_assert(csi2rx->p_rst);
>  	clk_disable_unprepare(csi2rx->p_clk);
>  
> -	if (v4l2_subdev_call(csi2rx->source_subdev, video, s_stream, false))
> -		dev_warn(csi2rx->dev, "Couldn't disable our subdev\n");
> -
>  	if (csi2rx->dphy) {
>  		writel(0, csi2rx->base + CSI2RX_DPHY_LANE_CTRL_REG);
>  
> @@ -485,38 +471,56 @@ static int csi2rx_log_status(struct v4l2_subdev *sd)
>  	return 0;
>  }
>  
> -static int csi2rx_s_stream(struct v4l2_subdev *subdev, int enable)
> +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);
> -	int ret = 0;
> -
> -	mutex_lock(&csi2rx->lock);
> -
> -	if (enable) {
> -		/*
> -		 * If we're not the first users, there's no need to
> -		 * enable the whole controller.
> -		 */
> -		if (!csi2rx->count) {
> -			ret = csi2rx_start(csi2rx);
> -			if (ret)
> -				goto out;
> -		}
> +	int ret;
>  
> -		csi2rx->count++;
> -	} else {
> -		csi2rx->count--;
> +	/*
> +	 * If we're not the first users, there's no need to
> +	 * enable the whole controller.
> +	 */
> +	if (!csi2rx->count) {
> +		ret = csi2rx_start(csi2rx);
> +		if (ret)
> +			return ret;
> +	}
>  
> -		/*
> -		 * Let the last user turn off the lights.
> -		 */
> +	/* Start streaming on the source */
> +	ret = v4l2_subdev_enable_streams(csi2rx->source_subdev, csi2rx->source_pad,
> +					 BIT_U64(0));
> +	if (ret) {
> +		dev_err(csi2rx->dev,
> +			"Failed to start streams %d on subdev\n", 0);
>  		if (!csi2rx->count)
>  			csi2rx_stop(csi2rx);
> +		return ret;
>  	}
>  
> -out:
> -	mutex_unlock(&csi2rx->lock);
> -	return ret;
> +	csi2rx->count++;
> +	return 0;
> +}
> +
> +static int csi2rx_disable_streams(struct v4l2_subdev *subdev,
> +				  struct v4l2_subdev_state *state, u32 pad,
> +				  u64 streams_mask)
> +{
> +	struct csi2rx_priv *csi2rx = v4l2_subdev_to_csi2rx(subdev);
> +
> +	if (v4l2_subdev_disable_streams(csi2rx->source_subdev,
> +					csi2rx->source_pad, BIT_U64(0))) {
> +		dev_err(csi2rx->dev, "Couldn't disable our subdev\n");
> +	}
> +
> +	csi2rx->count--;
> +
> +	/* Let the last user turn off the lights. */
> +	if (!csi2rx->count)
> +		csi2rx_stop(csi2rx);
> +
> +	return 0;
>  }
>  
>  static int csi2rx_enum_mbus_code(struct v4l2_subdev *subdev,
> @@ -611,10 +615,8 @@ 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,
> -};
> -
> -static const struct v4l2_subdev_video_ops csi2rx_video_ops = {
> -	.s_stream	= csi2rx_s_stream,
> +	.enable_streams         = csi2rx_enable_streams,
> +	.disable_streams        = csi2rx_disable_streams,
>  };
>  
>  static const struct v4l2_subdev_core_ops csi2rx_core_ops = {
> @@ -623,7 +625,6 @@ static const struct v4l2_subdev_core_ops csi2rx_core_ops = {
>  
>  static const struct v4l2_subdev_ops csi2rx_subdev_ops = {
>  	.core		= &csi2rx_core_ops,
> -	.video		= &csi2rx_video_ops,
>  	.pad		= &csi2rx_pad_ops,
>  };
>  
> @@ -829,7 +830,6 @@ static int csi2rx_probe(struct platform_device *pdev)
>  		return -ENOMEM;
>  	platform_set_drvdata(pdev, csi2rx);
>  	csi2rx->dev = &pdev->dev;
> -	mutex_init(&csi2rx->lock);
>  
>  	ret = csi2rx_get_resources(csi2rx, pdev);
>  	if (ret)


^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v9 11/19] media: ti: j721e-csi2rx: add support for processing virtual channels
  2025-12-30  8:32 ` [PATCH v9 11/19] media: ti: j721e-csi2rx: add support for processing virtual channels Rishikesh Donadkar
@ 2026-01-14 15:31   ` Tomi Valkeinen
  2026-01-16 10:28     ` Rishikesh Donadkar
  0 siblings, 1 reply; 54+ messages in thread
From: Tomi Valkeinen @ 2026-01-14 15:31 UTC (permalink / raw)
  To: Rishikesh Donadkar
  Cc: y-abhilashchandra, devarsht, s-jain1, vigneshr, mchehab, robh,
	krzk+dt, p.zabel, conor+dt, sakari.ailus, hverkuil-cisco,
	jai.luthra, changhuang.liang, jack.zhu, sjoerd, dan.carpenter,
	hverkuil+cisco, linux-kernel, linux-media, devicetree, jai.luthra,
	laurent.pinchart, mripard

Hi,

On 30/12/2025 10:32, Rishikesh Donadkar wrote:
> From: Jai Luthra <j-luthra@ti.com>
> 
> Use get_frame_desc() to get the frame desc from the connected source,
> and use the provided virtual channel instead of VC 0.
> 
> get_frame_desc() works for single stream case, but as we don't

Is that supposed to say "get_frame_desc works for multi-stream use case"?

> support multiple streams yet, we will just always use stream 0.
> If the source doesn't support get_frame_desc(), fall back to
> the previous method of always capturing virtual channel 0.
> 
> Reviewed-by: Yemike Abhilash Chandra <y-abhilashchandra@ti.com>
> Co-developed-by: Pratyush Yadav <p.yadav@ti.com>
> Signed-off-by: Pratyush Yadav <p.yadav@ti.com>
> Signed-off-by: Jai Luthra <j-luthra@ti.com>
> Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
> ---
>  .../platform/ti/j721e-csi2rx/j721e-csi2rx.c   | 45 ++++++++++++++++++-
>  1 file changed, 44 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
> index f54ad67ff3f9d..6f9f34aa26f1b 100644
> --- a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
> +++ b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
> @@ -32,6 +32,7 @@
>  #define SHIM_DMACNTX_YUV422		GENMASK(27, 26)
>  #define SHIM_DMACNTX_DUAL_PCK_CFG	BIT(24)
>  #define SHIM_DMACNTX_SIZE		GENMASK(21, 20)
> +#define SHIM_DMACNTX_VC			GENMASK(9, 6)
>  #define SHIM_DMACNTX_FMT		GENMASK(5, 0)
>  #define SHIM_DMACNTX_YUV422_MODE_11	3
>  #define SHIM_DMACNTX_SIZE_8		0
> @@ -110,6 +111,9 @@ struct ti_csi2rx_ctx {
>  	struct media_pad		pad;
>  	u32				sequence;
>  	u32				idx;
> +	u32				vc;
> +	u32				dt;
> +	u32				stream;
>  };
>  
>  struct ti_csi2rx_dev {
> @@ -570,7 +574,7 @@ static void ti_csi2rx_setup_shim(struct ti_csi2rx_ctx *ctx)
>  	ti_csi2rx_request_max_ppc(csi);
>  
>  	reg = SHIM_DMACNTX_EN;
> -	reg |= FIELD_PREP(SHIM_DMACNTX_FMT, fmt->csi_dt);
> +	reg |= FIELD_PREP(SHIM_DMACNTX_FMT, ctx->dt);
>  
>  	/*
>  	 * The hardware assumes incoming YUV422 8-bit data on MIPI CSI2 bus
> @@ -610,6 +614,7 @@ static void ti_csi2rx_setup_shim(struct ti_csi2rx_ctx *ctx)
>  	}
>  
>  	reg |= FIELD_PREP(SHIM_DMACNTX_SIZE, fmt->size);
> +	reg |= FIELD_PREP(SHIM_DMACNTX_VC, ctx->vc);
>  
>  	writel(reg, csi->shim + SHIM_DMACNTX(ctx->idx));
>  
> @@ -884,12 +889,41 @@ static void ti_csi2rx_buffer_queue(struct vb2_buffer *vb)
>  	}
>  }
>  
> +static int ti_csi2rx_get_vc_and_dt(struct ti_csi2rx_ctx *ctx)
> +{
> +	struct ti_csi2rx_dev *csi = ctx->csi;
> +	struct v4l2_mbus_frame_desc fd;
> +	struct media_pad *pad;
> +	int ret, i;
> +
> +	pad = media_entity_remote_pad_unique(&csi->subdev.entity, MEDIA_PAD_FL_SOURCE);
> +	if (!pad)
> +		return -ENODEV;
> +
> +	ret = v4l2_subdev_call(csi->source, pad, get_frame_desc, pad->index, &fd);
> +	if (ret)
> +		return ret;
> +
> +	if (fd.type != V4L2_MBUS_FRAME_DESC_TYPE_CSI2)
> +		return -EINVAL;
> +
> +	for (i = 0; i < fd.num_entries; i++) {
> +		if (ctx->stream == fd.entry[i].stream) {
> +			ctx->vc = fd.entry[i].bus.csi2.vc;
> +			ctx->dt = fd.entry[i].bus.csi2.dt;
> +		}
> +	}

I think you can "break" when you find the stream. But this should also
catch the case when there's no matching stream, and give an error in
that case.

 Tomi

> +
> +	return 0;
> +}
> +
>  static int ti_csi2rx_start_streaming(struct vb2_queue *vq, unsigned int count)
>  {
>  	struct ti_csi2rx_ctx *ctx = vb2_get_drv_priv(vq);
>  	struct ti_csi2rx_dev *csi = ctx->csi;
>  	struct ti_csi2rx_dma *dma = &ctx->dma;
>  	struct ti_csi2rx_buffer *buf;
> +	const struct ti_csi2rx_fmt *fmt;
>  	unsigned long flags;
>  	int ret = 0;
>  
> @@ -904,6 +938,15 @@ static int ti_csi2rx_start_streaming(struct vb2_queue *vq, unsigned int count)
>  	if (ret)
>  		goto err;
>  
> +	ret = ti_csi2rx_get_vc_and_dt(ctx);
> +	if (ret == -ENOIOCTLCMD) {
> +		ctx->vc = 0;
> +		fmt = find_format_by_fourcc(ctx->v_fmt.fmt.pix.pixelformat);
> +		ctx->dt = fmt->csi_dt;
> +	} else if (ret < 0) {
> +		goto err;
> +	}
> +
>  	ti_csi2rx_setup_shim(ctx);
>  
>  	ctx->sequence = 0;


^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v9 17/19] media: cadence: csi2rx: Support runtime PM
  2025-12-30  8:32 ` [PATCH v9 17/19] media: cadence: csi2rx: Support runtime PM Rishikesh Donadkar
@ 2026-01-14 17:04   ` Tomi Valkeinen
  0 siblings, 0 replies; 54+ messages in thread
From: Tomi Valkeinen @ 2026-01-14 17:04 UTC (permalink / raw)
  To: Rishikesh Donadkar
  Cc: y-abhilashchandra, devarsht, s-jain1, vigneshr, mchehab, robh,
	krzk+dt, p.zabel, conor+dt, sakari.ailus, hverkuil-cisco,
	jai.luthra, changhuang.liang, jack.zhu, sjoerd, dan.carpenter,
	hverkuil+cisco, linux-kernel, linux-media, devicetree, jai.luthra,
	laurent.pinchart, mripard

Hi,

On 30/12/2025 10:32, Rishikesh Donadkar wrote:
> From: Changhuang Liang <changhuang.liang@starfivetech.com>
> 
> Use runtime power management hooks to save power when CSI-RX is not in
> use. Also, shift to goto based error handling in
> csi2rx_enable_streams() function
> 
> Signed-off-by: Changhuang Liang <changhuang.liang@starfivetech.com>
> Tested-by: Rishikesh Donadkar <r-donadkar@ti.com>
> Reviewed-by: Rishikesh Donadkar <r-donadkar@ti.com>
> Signed-off-by: Jai Luthra <jai.luthra@ideasonboard.com>
> Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
> ---
>  drivers/media/platform/cadence/Kconfig       |   1 +
>  drivers/media/platform/cadence/cdns-csi2rx.c | 136 ++++++++++++-------
>  2 files changed, 88 insertions(+), 49 deletions(-)

Reviewed-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>

 Tomi

> diff --git a/drivers/media/platform/cadence/Kconfig b/drivers/media/platform/cadence/Kconfig
> index 1aa608c00dbce..ea85ef82760e6 100644
> --- a/drivers/media/platform/cadence/Kconfig
> +++ b/drivers/media/platform/cadence/Kconfig
> @@ -5,6 +5,7 @@ comment "Cadence media platform drivers"
>  config VIDEO_CADENCE_CSI2RX
>  	tristate "Cadence MIPI-CSI2 RX Controller"
>  	depends on VIDEO_DEV
> +	depends on PM
>  	select MEDIA_CONTROLLER
>  	select VIDEO_V4L2_SUBDEV_API
>  	select V4L2_FWNODE
> diff --git a/drivers/media/platform/cadence/cdns-csi2rx.c b/drivers/media/platform/cadence/cdns-csi2rx.c
> index 5c16a2e509136..40c947c813248 100644
> --- a/drivers/media/platform/cadence/cdns-csi2rx.c
> +++ b/drivers/media/platform/cadence/cdns-csi2rx.c
> @@ -337,11 +337,6 @@ static int csi2rx_start(struct csi2rx_priv *csi2rx)
>  	u32 reg;
>  	int ret;
>  
> -	ret = clk_prepare_enable(csi2rx->p_clk);
> -	if (ret)
> -		return ret;
> -
> -	reset_control_deassert(csi2rx->p_rst);
>  	csi2rx_reset(csi2rx);
>  
>  	if (csi2rx->error_irq >= 0)
> @@ -382,7 +377,7 @@ static int csi2rx_start(struct csi2rx_priv *csi2rx)
>  		if (ret) {
>  			dev_err(csi2rx->dev,
>  				"Failed to configure external DPHY: %d\n", ret);
> -			goto err_disable_pclk;
> +			return ret;
>  		}
>  	}
>  
> @@ -397,12 +392,6 @@ static int csi2rx_start(struct csi2rx_priv *csi2rx)
>  	 * hence the reference counting.
>  	 */
>  	for (i = 0; i < csi2rx->max_streams; i++) {
> -		ret = clk_prepare_enable(csi2rx->pixel_clk[i]);
> -		if (ret)
> -			goto err_disable_pixclk;
> -
> -		reset_control_deassert(csi2rx->pixel_rst[i]);
> -
>  		writel(CSI2RX_STREAM_CFG_FIFO_MODE_LARGE_BUF |
>  			       FIELD_PREP(CSI2RX_STREAM_CFG_NUM_PIXELS_MASK,
>  					  csi2rx->num_pixels[i]),
> @@ -415,30 +404,8 @@ static int csi2rx_start(struct csi2rx_priv *csi2rx)
>  		       csi2rx->base + CSI2RX_STREAM_CTRL_REG(i));
>  	}
>  
> -	ret = clk_prepare_enable(csi2rx->sys_clk);
> -	if (ret)
> -		goto err_disable_pixclk;
> -
> -	reset_control_deassert(csi2rx->sys_rst);
> -
> -	clk_disable_unprepare(csi2rx->p_clk);
>  
>  	return 0;
> -
> -err_disable_pixclk:
> -	for (; i > 0; i--) {
> -		reset_control_assert(csi2rx->pixel_rst[i - 1]);
> -		clk_disable_unprepare(csi2rx->pixel_clk[i - 1]);
> -	}
> -
> -	if (csi2rx->dphy) {
> -		writel(0, csi2rx->base + CSI2RX_DPHY_LANE_CTRL_REG);
> -		phy_power_off(csi2rx->dphy);
> -	}
> -err_disable_pclk:
> -	clk_disable_unprepare(csi2rx->p_clk);
> -
> -	return ret;
>  }
>  
>  static void csi2rx_stop(struct csi2rx_priv *csi2rx)
> @@ -447,10 +414,6 @@ static void csi2rx_stop(struct csi2rx_priv *csi2rx)
>  	u32 val;
>  	int ret;
>  
> -	clk_prepare_enable(csi2rx->p_clk);
> -	reset_control_assert(csi2rx->sys_rst);
> -	clk_disable_unprepare(csi2rx->sys_clk);
> -
>  	writel(0, csi2rx->base + CSI2RX_ERROR_IRQS_MASK_REG);
>  
>  	for (i = 0; i < csi2rx->max_streams; i++) {
> @@ -465,14 +428,8 @@ static void csi2rx_stop(struct csi2rx_priv *csi2rx)
>  		if (ret)
>  			dev_warn(csi2rx->dev,
>  				 "Failed to stop streaming on pad%u\n", i);
> -
> -		reset_control_assert(csi2rx->pixel_rst[i]);
> -		clk_disable_unprepare(csi2rx->pixel_clk[i]);
>  	}
>  
> -	reset_control_assert(csi2rx->p_rst);
> -	clk_disable_unprepare(csi2rx->p_clk);
> -
>  	if (csi2rx->dphy) {
>  		writel(0, csi2rx->base + CSI2RX_DPHY_LANE_CTRL_REG);
>  
> @@ -548,10 +505,15 @@ static int csi2rx_enable_streams(struct v4l2_subdev *subdev,
>  	 * enable the whole controller.
>  	 */
>  	if (!csi2rx->count) {
> +		ret = pm_runtime_resume_and_get(csi2rx->dev);
> +		if (ret < 0)
> +			goto err;
> +
>  		csi2rx_update_vc_select(csi2rx, state);
> +
>  		ret = csi2rx_start(csi2rx);
>  		if (ret)
> -			return ret;
> +			goto err_put_pm;
>  	}
>  
>  	/* Start streaming on the source */
> @@ -561,13 +523,20 @@ static int csi2rx_enable_streams(struct v4l2_subdev *subdev,
>  		dev_err(csi2rx->dev,
>  			"Failed to start streams %#llx on subdev\n",
>  			sink_streams);
> -		if (!csi2rx->count)
> -			csi2rx_stop(csi2rx);
> -		return ret;
> +		goto err_stop_csi;
>  	}
>  
>  	csi2rx->count++;
>  	return 0;
> +
> +err_stop_csi:
> +	if (!csi2rx->count)
> +		csi2rx_stop(csi2rx);
> +err_put_pm:
> +	if (!csi2rx->count)
> +		pm_runtime_put(csi2rx->dev);
> +err:
> +	return ret;
>  }
>  
>  static int csi2rx_disable_streams(struct v4l2_subdev *subdev,
> @@ -589,8 +558,10 @@ static int csi2rx_disable_streams(struct v4l2_subdev *subdev,
>  	csi2rx->count--;
>  
>  	/* Let the last user turn off the lights. */
> -	if (!csi2rx->count)
> +	if (!csi2rx->count) {
>  		csi2rx_stop(csi2rx);
> +		pm_runtime_put(csi2rx->dev);
> +	}
>  
>  	return 0;
>  }
> @@ -1057,6 +1028,7 @@ static int csi2rx_probe(struct platform_device *pdev)
>  	if (ret)
>  		goto err_cleanup;
>  
> +	pm_runtime_enable(csi2rx->dev);
>  	ret = v4l2_async_register_subdev(&csi2rx->subdev);
>  	if (ret < 0)
>  		goto err_free_state;
> @@ -1071,6 +1043,7 @@ static int csi2rx_probe(struct platform_device *pdev)
>  
>  err_free_state:
>  	v4l2_subdev_cleanup(&csi2rx->subdev);
> +	pm_runtime_disable(csi2rx->dev);
>  err_cleanup:
>  	v4l2_async_nf_unregister(&csi2rx->notifier);
>  	v4l2_async_nf_cleanup(&csi2rx->notifier);
> @@ -1089,9 +1062,73 @@ static void csi2rx_remove(struct platform_device *pdev)
>  	v4l2_async_unregister_subdev(&csi2rx->subdev);
>  	v4l2_subdev_cleanup(&csi2rx->subdev);
>  	media_entity_cleanup(&csi2rx->subdev.entity);
> +	pm_runtime_disable(csi2rx->dev);
>  	kfree(csi2rx);
>  }
>  
> +static int csi2rx_runtime_suspend(struct device *dev)
> +{
> +	struct csi2rx_priv *csi2rx = dev_get_drvdata(dev);
> +	unsigned int i;
> +
> +	reset_control_assert(csi2rx->sys_rst);
> +	clk_disable_unprepare(csi2rx->sys_clk);
> +
> +	for (i = 0; i < csi2rx->max_streams; i++) {
> +		reset_control_assert(csi2rx->pixel_rst[i]);
> +		clk_disable_unprepare(csi2rx->pixel_clk[i]);
> +	}
> +
> +	reset_control_assert(csi2rx->p_rst);
> +	clk_disable_unprepare(csi2rx->p_clk);
> +
> +	return 0;
> +}
> +
> +static int csi2rx_runtime_resume(struct device *dev)
> +{
> +	struct csi2rx_priv *csi2rx = dev_get_drvdata(dev);
> +	unsigned int i;
> +	int ret;
> +
> +	ret = clk_prepare_enable(csi2rx->p_clk);
> +	if (ret)
> +		return ret;
> +
> +	reset_control_deassert(csi2rx->p_rst);
> +
> +	for (i = 0; i < csi2rx->max_streams; i++) {
> +		ret = clk_prepare_enable(csi2rx->pixel_clk[i]);
> +		if (ret)
> +			goto err_disable_pixclk;
> +
> +		reset_control_deassert(csi2rx->pixel_rst[i]);
> +	}
> +
> +	ret = clk_prepare_enable(csi2rx->sys_clk);
> +	if (ret)
> +		goto err_disable_pixclk;
> +
> +	reset_control_deassert(csi2rx->sys_rst);
> +
> +	return 0;
> +
> +err_disable_pixclk:
> +	for (; i > 0; i--) {
> +		reset_control_assert(csi2rx->pixel_rst[i - 1]);
> +		clk_disable_unprepare(csi2rx->pixel_clk[i - 1]);
> +	}
> +
> +	reset_control_assert(csi2rx->p_rst);
> +	clk_disable_unprepare(csi2rx->p_clk);
> +
> +	return ret;
> +}
> +
> +static const struct dev_pm_ops csi2rx_pm_ops = {
> +	RUNTIME_PM_OPS(csi2rx_runtime_suspend, csi2rx_runtime_resume, NULL)
> +};
> +
>  static const struct of_device_id csi2rx_of_table[] = {
>  	{ .compatible = "starfive,jh7110-csi2rx" },
>  	{ .compatible = "cdns,csi2rx" },
> @@ -1106,6 +1143,7 @@ static struct platform_driver csi2rx_driver = {
>  	.driver	= {
>  		.name		= "cdns-csi2rx",
>  		.of_match_table	= csi2rx_of_table,
> +		.pm		= &csi2rx_pm_ops,
>  	},
>  };
>  module_platform_driver(csi2rx_driver);


^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v9 06/19] media: ti: j721e-csi2rx: add a subdev for the core device
  2026-01-14 15:21   ` Tomi Valkeinen
@ 2026-01-15  6:36     ` Jai Luthra
  2026-01-15 12:56       ` Tomi Valkeinen
  2026-01-21  9:10     ` Rishikesh Donadkar
  1 sibling, 1 reply; 54+ messages in thread
From: Jai Luthra @ 2026-01-15  6:36 UTC (permalink / raw)
  To: Rishikesh Donadkar, Tomi Valkeinen, Laurent Pinchart,
	Sakari Ailus
  Cc: y-abhilashchandra, devarsht, s-jain1, vigneshr, mchehab, robh,
	krzk+dt, p.zabel, conor+dt, hverkuil-cisco, changhuang.liang,
	jack.zhu, sjoerd, dan.carpenter, hverkuil+cisco, linux-kernel,
	linux-media, devicetree, jai.luthra, mripard

Hi Tomi,

+Sakari, Laurent

Quoting Tomi Valkeinen (2026-01-14 20:51:49)
> Hi,
> 
> On 30/12/2025 10:32, Rishikesh Donadkar wrote:
> > From: Jai Luthra <j-luthra@ti.com>
> > 
> > With single stream capture, it was simpler to use the video device as
> > the media entity representing the main TI CSI2RX device. Now with multi
> > stream capture coming into the picture, the model has shifted to each
> > video device having a link to the main device's subdev. The routing
> > would then be set on this subdev.
> > 
> > Add this subdev, link each context to this subdev's entity and link the
> > subdev's entity to the source. Also add an array of media pads. It will
> > have one sink pad and source pads equal to the number of contexts.
> > 
> > Support the new enable_stream()/disable_stream() APIs in the subdev
> > instead of s_stream() hook.
> > 
> > Reviewed-by: Yemike Abhilash Chandra <y-abhilashchandra@ti.com>
> > Co-developed-by: Pratyush Yadav <p.yadav@ti.com>
> > Signed-off-by: Pratyush Yadav <p.yadav@ti.com>
> > Signed-off-by: Jai Luthra <j-luthra@ti.com>
> > Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
> > ---

[...]

> > @@ -981,48 +1138,52 @@ static int ti_csi2rx_link_validate(struct media_link *link)
> >       struct ti_csi2rx_ctx *ctx = container_of(vdev, struct ti_csi2rx_ctx, vdev);
> >       struct ti_csi2rx_dev *csi = ctx->csi;
> >       struct v4l2_pix_format *csi_fmt = &ctx->v_fmt.fmt.pix;
> > -     struct v4l2_subdev_format source_fmt = {
> > -             .which  = V4L2_SUBDEV_FORMAT_ACTIVE,
> > -             .pad    = link->source->index,
> > -     };
> > +     struct v4l2_mbus_framefmt *format;
> > +     struct v4l2_subdev_state *state;
> >       const struct ti_csi2rx_fmt *ti_fmt;
> > -     int ret;
> >  
> > -     ret = v4l2_subdev_call_state_active(csi->source, pad,
> > -                                         get_fmt, &source_fmt);
> > -     if (ret)
> > -             return ret;
> > +     state = v4l2_subdev_lock_and_get_active_state(&csi->subdev);
> > +     format = v4l2_subdev_state_get_format(state, link->source->index, 0);
> > +     v4l2_subdev_unlock_state(state);
> >  
> > -     if (source_fmt.format.width != csi_fmt->width) {
> > +     if (!format) {
> > +             dev_dbg(csi->dev,
> > +                     "Skipping validation as no format present on \"%s\":%u:0\n",
> > +                     link->source->entity->name, link->source->index);
> > +             return 0;
> 
> Isn't this an error?

Well, the j7 shim subdev introduced here has immutable and active links to
all the video nodes, for each DMA channel (taken from DT), many of which
may be unused for certain setups, and thus there might not be any valid
format on the subdev source pad corresponding to an unused video node.

Jacopo had a similar comment on v2, see this discussion (grep for Mali):
https://lore.kernel.org/linux-media/4mnlnsj4co3agvln4qsasmgvgwiyoo7yu2h5wyh4rmzzafhm5u@avhnbw7iknms/

I know other drivers use a different approach with mutable links, so it
would be good if you/Laurent/Sakari can give your opinions on if only one
of these two approaches should be taken for multi-stream pipelines.

> 
>  Tomi
> 

Thanks,
    Jai

[...]

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v9 12/19] media: cadence: csi2rx: add multistream support
  2025-12-30  8:32 ` [PATCH v9 12/19] media: cadence: csi2rx: add multistream support Rishikesh Donadkar
@ 2026-01-15 12:01   ` Tomi Valkeinen
  2026-01-16 11:04     ` Rishikesh Donadkar
  0 siblings, 1 reply; 54+ messages in thread
From: Tomi Valkeinen @ 2026-01-15 12:01 UTC (permalink / raw)
  To: Rishikesh Donadkar
  Cc: y-abhilashchandra, devarsht, s-jain1, vigneshr, mchehab, robh,
	krzk+dt, p.zabel, conor+dt, sakari.ailus, hverkuil-cisco,
	jai.luthra, changhuang.liang, jack.zhu, sjoerd, dan.carpenter,
	hverkuil+cisco, linux-kernel, linux-media, devicetree, jai.luthra,
	laurent.pinchart, mripard

Hi,

On 30/12/2025 10:32, Rishikesh Donadkar wrote:
> From: Jai Luthra <j-luthra@ti.com>
> 
> 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 <j-luthra@ti.com>
> Reviewed-by: Changhuang Liang <changhuang.liang@starfivetech.com>
> Reviewed-by: Yemike Abhilash Chandra <y-abhilashchandra@ti.com>
> Co-developed-by: Rishikesh Donadkar <r-donadkar@ti.com>
> Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
> ---
>  drivers/media/platform/cadence/cdns-csi2rx.c | 248 +++++++++++++++----
>  1 file changed, 201 insertions(+), 47 deletions(-)
> 
> diff --git a/drivers/media/platform/cadence/cdns-csi2rx.c b/drivers/media/platform/cadence/cdns-csi2rx.c
> index 65c6acb02f85b..5c16a2e509136 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;
> @@ -273,30 +274,43 @@ 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;
> +	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 {
> +		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;
> +		}
>  
> -	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);
> @@ -394,11 +408,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,
> @@ -486,18 +496,59 @@ 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;
> +
> +	/* Capture VC=0 by default */
> +	for (i = 0; i < CSI2RX_STREAMS_MAX; i++)
> +		csi2rx->vc_select[i] = CSI2RX_STREAM_DATA_CFG_VC_SELECT(0);

This should be inside the if-block below, as in the other code path you
just memset the whole vc_select.

With that fixed:

Reviewed-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>

 Tomi


^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v9 13/19] media: ti: j721e-csi2rx: add multistream support
  2025-12-30  8:32 ` [PATCH v9 13/19] media: ti: j721e-csi2rx: " Rishikesh Donadkar
@ 2026-01-15 12:27   ` Tomi Valkeinen
  2026-01-20  8:48     ` Rishikesh Donadkar
  2026-01-15 12:28   ` Tomi Valkeinen
  1 sibling, 1 reply; 54+ messages in thread
From: Tomi Valkeinen @ 2026-01-15 12:27 UTC (permalink / raw)
  To: Rishikesh Donadkar
  Cc: y-abhilashchandra, devarsht, s-jain1, vigneshr, mchehab, robh,
	krzk+dt, p.zabel, conor+dt, sakari.ailus, hverkuil-cisco,
	jai.luthra, changhuang.liang, jack.zhu, sjoerd, dan.carpenter,
	hverkuil+cisco, linux-kernel, linux-media, devicetree, jai.luthra,
	laurent.pinchart, mripard

Hi,

On 30/12/2025 10:32, Rishikesh Donadkar wrote:
> From: Jai Luthra <j-luthra@ti.com>
> 
> Each CSI2 stream can be multiplexed into 4 independent streams, each
> identified by its virtual channel number and data type. The incoming
> data from these streams can be filtered on the basis of either the
> virtual channel or the data type.
> 
> To capture this multiplexed stream, the application needs to tell
> the driver how it wants to route the data. It needs to specify
> which context should process which stream. This is done via the
> new routing APIs.
> 
> Add ioctls to accept routing information from the application and save
> that in the driver. This can be used when starting streaming on a
> context to determine which route and consequently which virtual channel
> it should process.
> 
> De-assert the pixel interface reset on first start_streaming() and assert
> it on the last stop_streaming().
> 
> Reviewed-by: Yemike Abhilash Chandra <y-abhilashchandra@ti.com>
> Co-developed-by: Pratyush Yadav <p.yadav@ti.com>
> Signed-off-by: Pratyush Yadav <p.yadav@ti.com>
> Signed-off-by: Jai Luthra <j-luthra@ti.com>
> Co-developed-by: Rishikesh Donadkar <r-donadkar@ti.com>
> Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
> ---
>  .../platform/ti/j721e-csi2rx/j721e-csi2rx.c   | 259 +++++++++++++-----
>  1 file changed, 189 insertions(+), 70 deletions(-)
> 
> diff --git a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
> index 6f9f34aa26f1b..4a063364f893e 100644
> --- a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
> +++ b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
> @@ -137,6 +137,7 @@ struct ti_csi2rx_dev {
>  		dma_addr_t		paddr;
>  		size_t			len;
>  	} drain;
> +	bool                            vc_cached;
>  };
>  
>  static inline struct ti_csi2rx_dev *to_csi2rx_dev(struct v4l2_subdev *sd)
> @@ -144,17 +145,6 @@ static inline struct ti_csi2rx_dev *to_csi2rx_dev(struct v4l2_subdev *sd)
>  	return container_of(sd, struct ti_csi2rx_dev, subdev);
>  }
>  
> -static const struct v4l2_mbus_framefmt ti_csi2rx_default_fmt = {
> -	.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,
> -};
> -
>  static const struct ti_csi2rx_fmt ti_csi2rx_formats[] = {
>  	{
>  		.fourcc			= V4L2_PIX_FMT_YUYV,
> @@ -567,8 +557,10 @@ static void ti_csi2rx_setup_shim(struct ti_csi2rx_ctx *ctx)
>  	fmt = find_format_by_fourcc(ctx->v_fmt.fmt.pix.pixelformat);
>  
>  	/* De-assert the pixel interface reset. */
> -	reg = SHIM_CNTL_PIX_RST;
> -	writel(reg, csi->shim + SHIM_CNTL);
> +	if (!csi->enable_count) {
> +		reg = SHIM_CNTL_PIX_RST;
> +		writel(reg, csi->shim + SHIM_CNTL);
> +	}

I think this is a bit messy. Here we have ti_csi2rx_setup_shim() which
is passed ctx as a parameter, but it also does non-context stuff, if
this is the first enable.

In ti_csi2rx_sd_enable_streams(), we check !csi->vc_cached (which,
afaics, is essentially also "is this the first enable"), and do stuff.

The structure should be so that we have a clear
setup-things-on-the-first-enable function, and
setup-things-for-a-context function. Now these two things are mixed in
together, also with the 'vc_cached' field which I think is not needed at
all.

Actually, maybe I'm mixing the operation of the video dev and the subdev
here, as the single file contains both. It still looks confusing: in
ti_csi2rx_stop_streaming() we assert reset, but we don't de-assert in
ti_csi2rx_start_streaming(). Instead we deassert in
ti_csi2rx_setup_shim() which is called from ti_csi2rx_sd_enable_streams().

Please check these out. There most likely should be more symmetry here
wrt. enabling and disabling things.

>  
>  	/* Negotiate pixel count from the source */
>  	ti_csi2rx_request_max_ppc(csi);
> @@ -889,29 +881,69 @@ static void ti_csi2rx_buffer_queue(struct vb2_buffer *vb)
>  	}
>  }
>  
> +static int ti_csi2rx_get_stream(struct ti_csi2rx_ctx *ctx)
> +{
> +	struct ti_csi2rx_dev *csi = ctx->csi;
> +	struct media_pad *pad;
> +	struct v4l2_subdev_state *state;
> +	struct v4l2_subdev_route *r;
> +
> +	/* Get the source pad connected to this ctx */
> +	pad = media_entity_remote_source_pad_unique(ctx->pad.entity);
> +	if (!pad) {
> +		dev_err(csi->dev, "No pad connected to ctx %d\n", ctx->idx);
> +		return -ENODEV;
> +	}
> +
> +	state = v4l2_subdev_get_locked_active_state(&csi->subdev);
> +
> +	for_each_active_route(&state->routing, r) {
> +		if (r->source_pad == pad->index) {
> +			ctx->stream = r->sink_stream;
> +			return 0;
> +		}
> +	}
> +
> +	/* No route found for this ctx */
> +	return -ENODEV;
> +}
> +
>  static int ti_csi2rx_get_vc_and_dt(struct ti_csi2rx_ctx *ctx)
>  {
>  	struct ti_csi2rx_dev *csi = ctx->csi;
> +	struct ti_csi2rx_ctx *curr_ctx;
>  	struct v4l2_mbus_frame_desc fd;
> -	struct media_pad *pad;
> -	int ret, i;
> +	struct media_pad *source_pad;
> +	int ret;
> +	unsigned int i, j;
>  
> -	pad = media_entity_remote_pad_unique(&csi->subdev.entity, MEDIA_PAD_FL_SOURCE);
> -	if (!pad)
> +	/* Get the frame desc form source */
> +	source_pad = media_entity_remote_pad_unique(&csi->subdev.entity, MEDIA_PAD_FL_SOURCE);
> +	if (!source_pad)
>  		return -ENODEV;
>  
> -	ret = v4l2_subdev_call(csi->source, pad, get_frame_desc, pad->index, &fd);
> +	ret = v4l2_subdev_call(csi->source, pad, get_frame_desc, source_pad->index, &fd);
>  	if (ret)
>  		return ret;
>  
>  	if (fd.type != V4L2_MBUS_FRAME_DESC_TYPE_CSI2)
>  		return -EINVAL;
>  
> -	for (i = 0; i < fd.num_entries; i++) {
> -		if (ctx->stream == fd.entry[i].stream) {
> -			ctx->vc = fd.entry[i].bus.csi2.vc;
> -			ctx->dt = fd.entry[i].bus.csi2.dt;
> -		}
> +	for (i = 0; i < csi->num_ctx; i++) {
> +		curr_ctx = &csi->ctx[i];
> +
> +		/* Capture VC 0 by default */
> +		curr_ctx->vc = 0;
> +
> +		ret = ti_csi2rx_get_stream(curr_ctx);
> +		if (ret)
> +			continue;
> +
> +		for (j = 0; j < fd.num_entries; j++)
> +			if (curr_ctx->stream == fd.entry[j].stream) {
> +				curr_ctx->vc = fd.entry[j].bus.csi2.vc;
> +				curr_ctx->dt = fd.entry[j].bus.csi2.dt;
> +			}
>  	}
>  
>  	return 0;
> @@ -922,8 +954,6 @@ static int ti_csi2rx_start_streaming(struct vb2_queue *vq, unsigned int count)
>  	struct ti_csi2rx_ctx *ctx = vb2_get_drv_priv(vq);
>  	struct ti_csi2rx_dev *csi = ctx->csi;
>  	struct ti_csi2rx_dma *dma = &ctx->dma;
> -	struct ti_csi2rx_buffer *buf;
> -	const struct ti_csi2rx_fmt *fmt;
>  	unsigned long flags;
>  	int ret = 0;
>  
> @@ -938,35 +968,9 @@ static int ti_csi2rx_start_streaming(struct vb2_queue *vq, unsigned int count)
>  	if (ret)
>  		goto err;
>  
> -	ret = ti_csi2rx_get_vc_and_dt(ctx);
> -	if (ret == -ENOIOCTLCMD) {
> -		ctx->vc = 0;
> -		fmt = find_format_by_fourcc(ctx->v_fmt.fmt.pix.pixelformat);
> -		ctx->dt = fmt->csi_dt;
> -	} else if (ret < 0) {
> -		goto err;
> -	}
> -
> -	ti_csi2rx_setup_shim(ctx);
> -
> -	ctx->sequence = 0;
> -
> -	spin_lock_irqsave(&dma->lock, flags);
> -	buf = list_entry(dma->queue.next, struct ti_csi2rx_buffer, list);
> -
> -	ret = ti_csi2rx_start_dma(ctx, buf);
> -	if (ret) {
> -		dev_err(csi->dev, "Failed to start DMA: %d\n", ret);
> -		spin_unlock_irqrestore(&dma->lock, flags);
> -		goto err_pipeline;
> -	}
> -
> -	list_move_tail(&buf->list, &dma->submitted);
> -	dma->state = TI_CSI2RX_DMA_ACTIVE;
> -	spin_unlock_irqrestore(&dma->lock, flags);
> -
> +	/* Start stream 0, we don't allow multiple streams on the source pad */
>  	ret = v4l2_subdev_enable_streams(&csi->subdev,
> -					 TI_CSI2RX_PAD_FIRST_SOURCE,
> +					 TI_CSI2RX_PAD_FIRST_SOURCE + ctx->idx,
>  					 BIT_U64(0));
>  	if (ret)
>  		goto err_dma;
> @@ -975,7 +979,6 @@ static int ti_csi2rx_start_streaming(struct vb2_queue *vq, unsigned int count)
>  
>  err_dma:
>  	ti_csi2rx_stop_dma(ctx);
> -err_pipeline:
>  	video_device_pipeline_stop(&ctx->vdev);
>  	writel(0, csi->shim + SHIM_CNTL);
>  	writel(0, csi->shim + SHIM_DMACNTX(ctx->idx));
> @@ -990,17 +993,26 @@ static void ti_csi2rx_stop_streaming(struct vb2_queue *vq)
>  	struct ti_csi2rx_dev *csi = ctx->csi;
>  	int ret;
>  
> -	video_device_pipeline_stop(&ctx->vdev);
> +	mutex_lock(&csi->mutex);
>  
> -	writel(0, csi->shim + SHIM_CNTL);
>  	writel(0, csi->shim + SHIM_DMACNTX(ctx->idx));
>  
> +	/* assert pixel reset to prevent stale data */
> +	if (csi->enable_count == 1) {
> +		writel(0, csi->shim + SHIM_CNTL);
> +		csi->vc_cached = false;
> +	}
> +
> +	video_device_pipeline_stop(&ctx->vdev);
> +
>  	ret = v4l2_subdev_disable_streams(&csi->subdev,
> -					  TI_CSI2RX_PAD_FIRST_SOURCE,
> +					  TI_CSI2RX_PAD_FIRST_SOURCE + ctx->idx,
>  					  BIT_U64(0));
>  	if (ret)
>  		dev_err(csi->dev, "Failed to stop subdev stream\n");
>  
> +	mutex_unlock(&csi->mutex);
> +
>  	ti_csi2rx_stop_dma(ctx);
>  	ti_csi2rx_cleanup_buffers(ctx, VB2_BUF_STATE_ERROR);
>  }
> @@ -1043,25 +1055,84 @@ static int ti_csi2rx_sd_set_fmt(struct v4l2_subdev *sd,
>  	fmt = v4l2_subdev_state_get_format(state, format->pad, format->stream);
>  	*fmt = format->format;
>  
> -	fmt = v4l2_subdev_state_get_format(state, TI_CSI2RX_PAD_FIRST_SOURCE,
> -					   format->stream);
> +	fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad,
> +							   format->stream);
> +	if (!fmt)
> +		return -EINVAL;
> +
>  	*fmt = format->format;
>  
>  	return 0;
>  }
>  
> -static int ti_csi2rx_sd_init_state(struct v4l2_subdev *sd,
> -				   struct v4l2_subdev_state *state)
> +static int _ti_csi2rx_sd_set_routing(struct v4l2_subdev *sd,
> +				     struct v4l2_subdev_state *state,
> +				     struct v4l2_subdev_krouting *routing)
>  {
> -	struct v4l2_mbus_framefmt *fmt;
> +	int ret;
> +
> +	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,
> +	};
>  
> -	fmt = v4l2_subdev_state_get_format(state, TI_CSI2RX_PAD_SINK);
> -	*fmt = ti_csi2rx_default_fmt;
> +	ret = v4l2_subdev_routing_validate(sd, routing,
> +					   V4L2_SUBDEV_ROUTING_ONLY_1_TO_1 |
> +					   V4L2_SUBDEV_ROUTING_NO_SOURCE_MULTIPLEXING);
>  
> -	fmt = v4l2_subdev_state_get_format(state, TI_CSI2RX_PAD_FIRST_SOURCE);
> -	*fmt = ti_csi2rx_default_fmt;
> +	if (ret)
> +		return ret;
>  
> -	return 0;
> +	/* Only stream ID 0 allowed on source pads */
> +	for (unsigned int i = 0; i < routing->num_routes; ++i) {
> +		const struct v4l2_subdev_route *route = &routing->routes[i];
> +
> +		if (route->source_stream != 0)
> +			return -EINVAL;
> +	}
> +
> +	ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
> +
> +	return ret;
> +}
> +
> +static int ti_csi2rx_sd_set_routing(struct v4l2_subdev *sd,
> +				    struct v4l2_subdev_state *state,
> +				    enum v4l2_subdev_format_whence which,
> +				    struct v4l2_subdev_krouting *routing)
> +{
> +	struct ti_csi2rx_dev *csi = to_csi2rx_dev(sd);
> +
> +	if (csi->enable_count > 0)
> +		return -EBUSY;
> +
> +	return _ti_csi2rx_sd_set_routing(sd, state, routing);
> +}
> +
> +static int ti_csi2rx_sd_init_state(struct v4l2_subdev *sd,
> +				   struct v4l2_subdev_state *state)
> +{
> +	struct v4l2_subdev_route routes[] = { {
> +		.sink_pad = 0,
> +		.sink_stream = 0,
> +		.source_pad = TI_CSI2RX_PAD_FIRST_SOURCE,
> +		.source_stream = 0,
> +		.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
> +	} };
> +
> +	struct v4l2_subdev_krouting routing = {
> +		.num_routes = 1,
> +		.routes = routes,
> +	};
> +
> +	/* Initialize routing to single route to the fist source pad */
> +	return _ti_csi2rx_sd_set_routing(sd, state, &routing);
>  }
>  
>  static int ti_csi2rx_sd_enable_streams(struct v4l2_subdev *sd,
> @@ -1069,15 +1140,58 @@ static int ti_csi2rx_sd_enable_streams(struct v4l2_subdev *sd,
>  				       u32 pad, u64 streams_mask)
>  {
>  	struct ti_csi2rx_dev *csi = to_csi2rx_dev(sd);
> +	struct ti_csi2rx_ctx *ctx = &csi->ctx[pad - TI_CSI2RX_PAD_FIRST_SOURCE];
> +	struct ti_csi2rx_dma *dma = &ctx->dma;
>  	struct media_pad *remote_pad;
> +	struct ti_csi2rx_buffer *buf;
> +	const struct ti_csi2rx_fmt *fmt;
> +	unsigned long flags;
> +	u64 sink_streams;
>  	int ret = 0;
>  
> +	ret = ti_csi2rx_get_stream(ctx);
> +	if (ret)
> +		return ret;
> +
> +	/* Get the VC and DT for all enabled ctx on first stream start */
> +	if (!csi->vc_cached) {
> +		ret = ti_csi2rx_get_vc_and_dt(ctx);
> +		if (ret == -ENOIOCTLCMD) {
> +			ctx->vc = 0;
> +			fmt = find_format_by_fourcc(ctx->v_fmt.fmt.pix.pixelformat);
> +			ctx->dt = fmt->csi_dt;
> +		} else if (ret < 0) {
> +			return ret;
> +		}
> +		csi->vc_cached = true;
> +	}
> +
> +	ti_csi2rx_setup_shim(ctx);
> +	ctx->sequence = 0;
> +
> +	spin_lock_irqsave(&dma->lock, flags);
> +	buf = list_entry(dma->queue.next, struct ti_csi2rx_buffer, list);
> +
> +	ret = ti_csi2rx_start_dma(ctx, buf);
> +	if (ret) {
> +		dev_err(csi->dev, "Failed to start DMA: %d\n", ret);
> +		spin_unlock_irqrestore(&dma->lock, flags);
> +		return ret;
> +	}
> +
> +	list_move_tail(&buf->list, &dma->submitted);
> +	dma->state = TI_CSI2RX_DMA_ACTIVE;
> +	spin_unlock_irqrestore(&dma->lock, flags);
> +
>  	remote_pad = media_entity_remote_source_pad_unique(&csi->subdev.entity);
>  	if (!remote_pad)
>  		return -ENODEV;
> +	sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
> +						       TI_CSI2RX_PAD_SINK,
> +						       &streams_mask);
>  
>  	ret = v4l2_subdev_enable_streams(csi->source, remote_pad->index,
> -					 BIT_U64(0));
> +					 sink_streams);
>  	if (ret)
>  		return ret;
>  
> @@ -1092,17 +1206,21 @@ static int ti_csi2rx_sd_disable_streams(struct v4l2_subdev *sd,
>  {
>  	struct ti_csi2rx_dev *csi = to_csi2rx_dev(sd);
>  	struct media_pad *remote_pad;
> +	u64 sink_streams;
>  	int ret = 0;
>  
>  	remote_pad = media_entity_remote_source_pad_unique(&csi->subdev.entity);
>  	if (!remote_pad)
>  		return -ENODEV;
> +	sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
> +						       TI_CSI2RX_PAD_SINK,
> +						       &streams_mask);
>  
>  	if (csi->enable_count == 0)
>  		return -EINVAL;

Correct me if I'm wrong, but I don't think this should ever happen. If
you want, you can place it in the beginning of the func, with a WARN_ON().

>  
>  	ret = v4l2_subdev_disable_streams(csi->source, remote_pad->index,
> -					  BIT_U64(0));
> +					  sink_streams);
>  	if (!ret)
>  		--csi->enable_count;
>  
> @@ -1111,6 +1229,7 @@ static int ti_csi2rx_sd_disable_streams(struct v4l2_subdev *sd,
>  
>  static const struct v4l2_subdev_pad_ops ti_csi2rx_subdev_pad_ops = {
>  	.enum_mbus_code	= ti_csi2rx_enum_mbus_code,
> +	.set_routing = ti_csi2rx_sd_set_routing,
>  	.get_fmt = v4l2_subdev_get_fmt,
>  	.set_fmt = ti_csi2rx_sd_set_fmt,
>  	.enable_streams = ti_csi2rx_sd_enable_streams,
> @@ -1289,7 +1408,7 @@ static int ti_csi2rx_v4l2_init(struct ti_csi2rx_dev *csi)
>  	v4l2_subdev_init(sd, &ti_csi2rx_subdev_ops);
>  	sd->internal_ops = &ti_csi2rx_internal_ops;
>  	sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
> -	sd->flags = V4L2_SUBDEV_FL_HAS_DEVNODE;
> +	sd->flags = V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS;
>  	strscpy(sd->name, dev_name(csi->dev), sizeof(sd->name));
>  	sd->dev = csi->dev;
>  	sd->entity.ops = &ti_csi2rx_subdev_entity_ops;


^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v9 13/19] media: ti: j721e-csi2rx: add multistream support
  2025-12-30  8:32 ` [PATCH v9 13/19] media: ti: j721e-csi2rx: " Rishikesh Donadkar
  2026-01-15 12:27   ` Tomi Valkeinen
@ 2026-01-15 12:28   ` Tomi Valkeinen
  2026-01-20  8:52     ` Rishikesh Donadkar
  1 sibling, 1 reply; 54+ messages in thread
From: Tomi Valkeinen @ 2026-01-15 12:28 UTC (permalink / raw)
  To: Rishikesh Donadkar
  Cc: y-abhilashchandra, devarsht, s-jain1, vigneshr, mchehab, robh,
	krzk+dt, p.zabel, conor+dt, sakari.ailus, hverkuil-cisco,
	jai.luthra, changhuang.liang, jack.zhu, sjoerd, dan.carpenter,
	hverkuil+cisco, linux-kernel, linux-media, devicetree, jai.luthra,
	laurent.pinchart, mripard

Hi,

On 30/12/2025 10:32, Rishikesh Donadkar wrote:
> From: Jai Luthra <j-luthra@ti.com>
> 
> Each CSI2 stream can be multiplexed into 4 independent streams, each
> identified by its virtual channel number and data type. The incoming

Forgot to ask about this one. Why 4?

 Tomi


^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v9 15/19] media: ti: j721e-csi2rx: Change the drain architecture for multistream
  2025-12-30  8:32 ` [PATCH v9 15/19] media: ti: j721e-csi2rx: Change the drain architecture for multistream Rishikesh Donadkar
@ 2026-01-15 12:37   ` Tomi Valkeinen
  2026-01-15 16:02     ` Jai Luthra
  2026-01-19  5:04     ` Rishikesh Donadkar
  0 siblings, 2 replies; 54+ messages in thread
From: Tomi Valkeinen @ 2026-01-15 12:37 UTC (permalink / raw)
  To: Rishikesh Donadkar
  Cc: y-abhilashchandra, devarsht, s-jain1, vigneshr, mchehab, robh,
	krzk+dt, p.zabel, conor+dt, sakari.ailus, hverkuil-cisco,
	jai.luthra, changhuang.liang, jack.zhu, sjoerd, dan.carpenter,
	hverkuil+cisco, linux-kernel, linux-media, devicetree, jai.luthra,
	laurent.pinchart, mripard

Hi,

On 30/12/2025 10:32, Rishikesh Donadkar wrote:
> On buffer starvation the DMA is marked IDLE, and the stale data in the
> internal FIFOs gets drained only on the next VIDIOC_QBUF call from the
> userspace. This approach works fine for a single stream case.
> 
> But in multistream scenarios, buffer starvation for one stream i.e. one
> virtual channel, can block the shared HW FIFO of the CSI2RX IP. This can
> stall the pipeline for all other virtual channels, even if buffers are
> available for them.

One stream is filtered based on VC & DT, but the above only mentions VC.
And then later uses VC when referring to the stream. I think you can
drop the VC parts, and just talk about streams.

> This patch introduces a new architecture, that continuously drains data
> from the shared HW FIFO into a small (32KiB) buffer if no buffers are made
> available to the driver from the userspace. This ensures independence
> between different streams, where a slower downstream element for one
> camera does not block streaming for other cameras.
> 
> Additionally, after a drain is done for a VC, the next frame will be a
> partial frame, as a portion of its data will have already been drained
> before a valid buffer is queued by user space to the driver.

This makes it sounds we drain a single 32KB piece. But won't we continue
draining that stream until the stream is stopped or the user provides a
buffer?

Also, does the DMA not offer us ways to drain a full frame? There's no
way to e.g. set the DMA TX increment to 0, so that it would just write
to a single location in memory? Or just set the target to void.

 Tomi

> Use wait for completion barrier to make sure the shared hardware FIFO
> is cleared of the data at the end of stream after the source has stopped
> sending data.
> 
> Reviewed-by: Jai Luthra <jai.luthra@ideasonboard.com>
> Reviewed-by: Yemike Abhilash Chandra <y-abhilashchandra@ti.com>
> Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
> ---
>  .../platform/ti/j721e-csi2rx/j721e-csi2rx.c   | 112 ++++++++----------
>  1 file changed, 50 insertions(+), 62 deletions(-)
> 
> diff --git a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
> index fa6152464d4b6..e713293696eb1 100644
> --- a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
> +++ b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
> @@ -82,7 +82,6 @@ struct ti_csi2rx_buffer {
>  
>  enum ti_csi2rx_dma_state {
>  	TI_CSI2RX_DMA_STOPPED,	/* Streaming not started yet. */
> -	TI_CSI2RX_DMA_IDLE,	/* Streaming but no pending DMA operation. */
>  	TI_CSI2RX_DMA_ACTIVE,	/* Streaming and pending DMA operation. */
>  };
>  
> @@ -109,6 +108,7 @@ struct ti_csi2rx_ctx {
>  	struct v4l2_format		v_fmt;
>  	struct ti_csi2rx_dma		dma;
>  	struct media_pad		pad;
> +	struct completion		drain_complete;
>  	u32				sequence;
>  	u32				idx;
>  	u32				vc;
> @@ -251,6 +251,10 @@ static const struct ti_csi2rx_fmt ti_csi2rx_formats[] = {
>  static int ti_csi2rx_start_dma(struct ti_csi2rx_ctx *ctx,
>  			       struct ti_csi2rx_buffer *buf);
>  
> +/* Forward declarations needed by ti_csi2rx_drain_callback. */
> +static int ti_csi2rx_drain_dma(struct ti_csi2rx_ctx *ctx);
> +static int ti_csi2rx_dma_submit_pending(struct ti_csi2rx_ctx *ctx);
> +
>  static const struct ti_csi2rx_fmt *find_format_by_fourcc(u32 pixelformat)
>  {
>  	unsigned int i;
> @@ -617,9 +621,32 @@ static void ti_csi2rx_setup_shim(struct ti_csi2rx_ctx *ctx)
>  
>  static void ti_csi2rx_drain_callback(void *param)
>  {
> -	struct completion *drain_complete = param;
> +	struct ti_csi2rx_ctx *ctx = param;
> +	struct ti_csi2rx_dma *dma = &ctx->dma;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&dma->lock, flags);
> +
> +	if (dma->state == TI_CSI2RX_DMA_STOPPED) {
> +		complete(&ctx->drain_complete);
> +		spin_unlock_irqrestore(&dma->lock, flags);
> +		return;
> +	}
>  
> -	complete(drain_complete);
> +	/*
> +	 * If dma->queue is empty, it indicates that no buffer has been
> +	 * provided by user space. In this case, initiate a transactions
> +	 * to drain the DMA. Since one drain of size DRAIN_BUFFER_SIZE
> +	 * will be done here, the subsequent frame will be a
> +	 * partial frame, with a size of frame_size - DRAIN_BUFFER_SIZE
> +	 */
> +	if (list_empty(&dma->queue)) {
> +		if (ti_csi2rx_drain_dma(ctx))
> +			dev_warn(ctx->csi->dev, "DMA drain failed\n");
> +	} else {
> +		ti_csi2rx_dma_submit_pending(ctx);
> +	}
> +	spin_unlock_irqrestore(&dma->lock, flags);
>  }
>  
>  /*
> @@ -637,12 +664,9 @@ static int ti_csi2rx_drain_dma(struct ti_csi2rx_ctx *ctx)
>  {
>  	struct ti_csi2rx_dev *csi = ctx->csi;
>  	struct dma_async_tx_descriptor *desc;
> -	struct completion drain_complete;
>  	dma_cookie_t cookie;
>  	int ret;
>  
> -	init_completion(&drain_complete);
> -
>  	desc = dmaengine_prep_slave_single(ctx->dma.chan, csi->drain.paddr,
>  					   csi->drain.len, DMA_DEV_TO_MEM,
>  					   DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
> @@ -652,7 +676,7 @@ static int ti_csi2rx_drain_dma(struct ti_csi2rx_ctx *ctx)
>  	}
>  
>  	desc->callback = ti_csi2rx_drain_callback;
> -	desc->callback_param = &drain_complete;
> +	desc->callback_param = ctx;
>  
>  	cookie = dmaengine_submit(desc);
>  	ret = dma_submit_error(cookie);
> @@ -661,13 +685,6 @@ static int ti_csi2rx_drain_dma(struct ti_csi2rx_ctx *ctx)
>  
>  	dma_async_issue_pending(ctx->dma.chan);
>  
> -	if (!wait_for_completion_timeout(&drain_complete,
> -					 msecs_to_jiffies(DRAIN_TIMEOUT_MS))) {
> -		dmaengine_terminate_sync(ctx->dma.chan);
> -		dev_dbg(csi->dev, "DMA transfer timed out for drain buffer\n");
> -		ret = -ETIMEDOUT;
> -		goto out;
> -	}
>  out:
>  	return ret;
>  }
> @@ -716,9 +733,11 @@ static void ti_csi2rx_dma_callback(void *param)
>  
>  	ti_csi2rx_dma_submit_pending(ctx);
>  
> -	if (list_empty(&dma->submitted))
> -		dma->state = TI_CSI2RX_DMA_IDLE;
> -
> +	if (list_empty(&dma->submitted)) {
> +		if (ti_csi2rx_drain_dma(ctx))
> +			dev_warn(ctx->csi->dev,
> +				 "DMA drain failed on one of the transactions\n");
> +	}
>  	spin_unlock_irqrestore(&dma->lock, flags);
>  }
>  
> @@ -754,6 +773,7 @@ static int ti_csi2rx_start_dma(struct ti_csi2rx_ctx *ctx,
>  static void ti_csi2rx_stop_dma(struct ti_csi2rx_ctx *ctx)
>  {
>  	struct ti_csi2rx_dma *dma = &ctx->dma;
> +	struct ti_csi2rx_dev *csi = ctx->csi;
>  	enum ti_csi2rx_dma_state state;
>  	unsigned long flags;
>  	int ret;
> @@ -763,6 +783,8 @@ static void ti_csi2rx_stop_dma(struct ti_csi2rx_ctx *ctx)
>  	dma->state = TI_CSI2RX_DMA_STOPPED;
>  	spin_unlock_irqrestore(&dma->lock, flags);
>  
> +	init_completion(&ctx->drain_complete);
> +
>  	if (state != TI_CSI2RX_DMA_STOPPED) {
>  		/*
>  		 * Normal DMA termination does not clean up pending data on
> @@ -771,11 +793,20 @@ static void ti_csi2rx_stop_dma(struct ti_csi2rx_ctx *ctx)
>  		 * enforced before terminating DMA.
>  		 */
>  		ret = ti_csi2rx_drain_dma(ctx);
> -		if (ret && ret != -ETIMEDOUT)
> +		if (ret)
>  			dev_warn(ctx->csi->dev,
>  				 "Failed to drain DMA. Next frame might be bogus\n");
>  	}
>  
> +	/* We wait for the drain to complete so that the stream stops
> +	 * cleanly, making sure the shared hardware FIFO is cleared of
> +	 * data from the current stream. No more data will be coming from
> +	 * the source after this.
> +	 */
> +	if (!wait_for_completion_timeout(&ctx->drain_complete,
> +					 msecs_to_jiffies(DRAIN_TIMEOUT_MS)))
> +		dev_dbg(csi->dev, "DMA transfer timed out for drain buffer\n");
> +
>  	ret = dmaengine_terminate_sync(ctx->dma.chan);
>  	if (ret)
>  		dev_err(ctx->csi->dev, "Failed to stop DMA: %d\n", ret);
> @@ -838,57 +869,14 @@ static void ti_csi2rx_buffer_queue(struct vb2_buffer *vb)
>  	struct ti_csi2rx_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
>  	struct ti_csi2rx_buffer *buf;
>  	struct ti_csi2rx_dma *dma = &ctx->dma;
> -	bool restart_dma = false;
>  	unsigned long flags = 0;
> -	int ret;
>  
>  	buf = container_of(vb, struct ti_csi2rx_buffer, vb.vb2_buf);
>  	buf->ctx = ctx;
>  
>  	spin_lock_irqsave(&dma->lock, flags);
> -	/*
> -	 * Usually the DMA callback takes care of queueing the pending buffers.
> -	 * But if DMA has stalled due to lack of buffers, restart it now.
> -	 */
> -	if (dma->state == TI_CSI2RX_DMA_IDLE) {
> -		/*
> -		 * Do not restart DMA with the lock held because
> -		 * ti_csi2rx_drain_dma() might block for completion.
> -		 * There won't be a race on queueing DMA anyway since the
> -		 * callback is not being fired.
> -		 */
> -		restart_dma = true;
> -		dma->state = TI_CSI2RX_DMA_ACTIVE;
> -	} else {
> -		list_add_tail(&buf->list, &dma->queue);
> -	}
> +	list_add_tail(&buf->list, &dma->queue);
>  	spin_unlock_irqrestore(&dma->lock, flags);
> -
> -	if (restart_dma) {
> -		/*
> -		 * Once frames start dropping, some data gets stuck in the DMA
> -		 * pipeline somewhere. So the first DMA transfer after frame
> -		 * drops gives a partial frame. This is obviously not useful to
> -		 * the application and will only confuse it. Issue a DMA
> -		 * transaction to drain that up.
> -		 */
> -		ret = ti_csi2rx_drain_dma(ctx);
> -		if (ret && ret != -ETIMEDOUT)
> -			dev_warn(ctx->csi->dev,
> -				 "Failed to drain DMA. Next frame might be bogus\n");
> -
> -		spin_lock_irqsave(&dma->lock, flags);
> -		ret = ti_csi2rx_start_dma(ctx, buf);
> -		if (ret) {
> -			vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
> -			dma->state = TI_CSI2RX_DMA_IDLE;
> -			spin_unlock_irqrestore(&dma->lock, flags);
> -			dev_err(ctx->csi->dev, "Failed to start DMA: %d\n", ret);
> -		} else {
> -			list_add_tail(&buf->list, &dma->submitted);
> -			spin_unlock_irqrestore(&dma->lock, flags);
> -		}
> -	}
>  }
>  
>  static int ti_csi2rx_get_stream(struct ti_csi2rx_ctx *ctx)


^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v9 16/19] media: ti: j721e-csi2rx: Return the partial frame as error
  2025-12-30  8:32 ` [PATCH v9 16/19] media: ti: j721e-csi2rx: Return the partial frame as error Rishikesh Donadkar
  2026-01-06 11:15   ` Jai Luthra
@ 2026-01-15 12:39   ` Tomi Valkeinen
  1 sibling, 0 replies; 54+ messages in thread
From: Tomi Valkeinen @ 2026-01-15 12:39 UTC (permalink / raw)
  To: Rishikesh Donadkar
  Cc: y-abhilashchandra, devarsht, s-jain1, vigneshr, mchehab, robh,
	krzk+dt, p.zabel, conor+dt, sakari.ailus, hverkuil-cisco,
	jai.luthra, changhuang.liang, jack.zhu, sjoerd, dan.carpenter,
	hverkuil+cisco, linux-kernel, linux-media, devicetree, jai.luthra,
	laurent.pinchart, mripard

Hi,

On 30/12/2025 10:32, Rishikesh Donadkar wrote:
> After draining, when a buffer is queued to the driver, ti will fill out

s/ti/it/

> the buffer with a partial frame as some part of the frame is drained.
> Return the partial frame with VB2_BUF_STATE_ERROR.

Makes sense, if we have to return a partial frame. It would be nicer to
be able to skip that frame altogether (draining it fully).

In any case, as Jai said, please squash.

 Tomi

> Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
> ---
>  drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c | 11 ++++++++++-
>  1 file changed, 10 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
> index e713293696eb1..3922bd67e78da 100644
> --- a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
> +++ b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
> @@ -83,6 +83,7 @@ struct ti_csi2rx_buffer {
>  enum ti_csi2rx_dma_state {
>  	TI_CSI2RX_DMA_STOPPED,	/* Streaming not started yet. */
>  	TI_CSI2RX_DMA_ACTIVE,	/* Streaming and pending DMA operation. */
> +	TI_CSI2RX_DMA_DRAINING, /* Dumping all the data in drain buffer */
>  };
>  
>  struct ti_csi2rx_dma {
> @@ -728,12 +729,20 @@ static void ti_csi2rx_dma_callback(void *param)
>  	spin_lock_irqsave(&dma->lock, flags);
>  
>  	WARN_ON(!list_is_first(&buf->list, &dma->submitted));
> -	vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
> +
> +	if (dma->state == TI_CSI2RX_DMA_DRAINING) {
> +		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
> +		dma->state = TI_CSI2RX_DMA_ACTIVE;
> +	} else {
> +		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
> +	}
> +
>  	list_del(&buf->list);
>  
>  	ti_csi2rx_dma_submit_pending(ctx);
>  
>  	if (list_empty(&dma->submitted)) {
> +		dma->state = TI_CSI2RX_DMA_DRAINING;
>  		if (ti_csi2rx_drain_dma(ctx))
>  			dev_warn(ctx->csi->dev,
>  				 "DMA drain failed on one of the transactions\n");


^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v9 18/19] media: ti: j721e-csi2rx: Support runtime suspend
  2025-12-30  8:32 ` [PATCH v9 18/19] media: ti: j721e-csi2rx: Support runtime suspend Rishikesh Donadkar
@ 2026-01-15 12:46   ` Tomi Valkeinen
  2026-01-19  6:04     ` Jai Luthra
  0 siblings, 1 reply; 54+ messages in thread
From: Tomi Valkeinen @ 2026-01-15 12:46 UTC (permalink / raw)
  To: Rishikesh Donadkar
  Cc: y-abhilashchandra, devarsht, s-jain1, vigneshr, mchehab, robh,
	krzk+dt, p.zabel, conor+dt, sakari.ailus, hverkuil-cisco,
	jai.luthra, changhuang.liang, jack.zhu, sjoerd, dan.carpenter,
	hverkuil+cisco, linux-kernel, linux-media, devicetree, jai.luthra,
	laurent.pinchart, mripard

Hi,

On 30/12/2025 10:32, Rishikesh Donadkar wrote:
> From: Jai Luthra <jai.luthra@ideasonboard.com>
> 
> Add support for runtime power-management to enable powering off the
> shared power domain between Cadence CSI2RX and TI CSI2RX wrapper when
> the device(s) are not in use.
> 
> When powering off the IP, the PSI-L endpoint loses the paired DMA
> channels. Thus we have to release the DMA channels at runtime suspend
> and request them again at resume.
> 
> Tested-by: Rishikesh Donadkar <r-donadkar@ti.com>
> Reviewed-by: Rishikesh Donadkar <r-donadkar@ti.com>
> Signed-off-by: Jai Luthra <jai.luthra@ideasonboard.com>
> Co-developed-by: Rishikesh Donadkar <r-donadkar@ti.com>
> Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
> ---
>  drivers/media/platform/ti/Kconfig             |  1 +
>  .../platform/ti/j721e-csi2rx/j721e-csi2rx.c   | 59 +++++++++++++++----
>  2 files changed, 50 insertions(+), 10 deletions(-)

Should pixel interface reset belong to the runtime suspend/resume
functions? (Not a suggestion, just a question =).

Reviewed-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>

 Tomi

> diff --git a/drivers/media/platform/ti/Kconfig b/drivers/media/platform/ti/Kconfig
> index 3bc4aa35887e6..a808063e24779 100644
> --- a/drivers/media/platform/ti/Kconfig
> +++ b/drivers/media/platform/ti/Kconfig
> @@ -70,6 +70,7 @@ config VIDEO_TI_J721E_CSI2RX
>  	depends on VIDEO_CADENCE_CSI2RX
>  	depends on PHY_CADENCE_DPHY_RX || COMPILE_TEST
>  	depends on ARCH_K3 || COMPILE_TEST
> +	depends on PM
>  	select VIDEOBUF2_DMA_CONTIG
>  	select V4L2_FWNODE
>  	help
> diff --git a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
> index 3922bd67e78da..72da58738e16e 100644
> --- a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
> +++ b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
> @@ -13,6 +13,7 @@
>  #include <linux/module.h>
>  #include <linux/of_platform.h>
>  #include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
>  #include <linux/property.h>
>  
>  #include <media/cadence/cdns-csi2rx.h>
> @@ -964,12 +965,16 @@ static int ti_csi2rx_start_streaming(struct vb2_queue *vq, unsigned int count)
>  	unsigned long flags;
>  	int ret = 0;
>  
> +	ret = pm_runtime_resume_and_get(csi->dev);
> +	if (ret)
> +		return ret;
> +
>  	spin_lock_irqsave(&dma->lock, flags);
>  	if (list_empty(&dma->queue))
>  		ret = -EIO;
>  	spin_unlock_irqrestore(&dma->lock, flags);
>  	if (ret)
> -		return ret;
> +		goto err;
>  
>  	ret = video_device_pipeline_start(&ctx->vdev, &csi->pipe);
>  	if (ret)
> @@ -991,6 +996,8 @@ static int ti_csi2rx_start_streaming(struct vb2_queue *vq, unsigned int count)
>  	writel(0, csi->shim + SHIM_DMACNTX(ctx->idx));
>  err:
>  	ti_csi2rx_cleanup_buffers(ctx, VB2_BUF_STATE_QUEUED);
> +	pm_runtime_put(csi->dev);
> +
>  	return ret;
>  }
>  
> @@ -1022,6 +1029,7 @@ static void ti_csi2rx_stop_streaming(struct vb2_queue *vq)
>  
>  	ti_csi2rx_stop_dma(ctx);
>  	ti_csi2rx_cleanup_buffers(ctx, VB2_BUF_STATE_ERROR);
> +	pm_runtime_put(csi->dev);
>  }
>  
>  static const struct vb2_ops csi_vb2_qops = {
> @@ -1263,7 +1271,6 @@ static void ti_csi2rx_cleanup_notifier(struct ti_csi2rx_dev *csi)
>  
>  static void ti_csi2rx_cleanup_ctx(struct ti_csi2rx_ctx *ctx)
>  {
> -	dma_release_channel(ctx->dma.chan);
>  	vb2_queue_release(&ctx->vidq);
>  
>  	video_unregister_device(&ctx->vdev);
> @@ -1283,7 +1290,7 @@ static int ti_csi2rx_init_vb2q(struct ti_csi2rx_ctx *ctx)
>  	q->ops = &csi_vb2_qops;
>  	q->mem_ops = &vb2_dma_contig_memops;
>  	q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
> -	q->dev = dmaengine_get_dma_device(ctx->dma.chan);
> +	q->dev = ctx->csi->dev;
>  	q->lock = &ctx->mutex;
>  	q->min_queued_buffers = 1;
>  	q->allow_cache_hints = 1;
> @@ -1497,21 +1504,46 @@ static int ti_csi2rx_init_ctx(struct ti_csi2rx_ctx *ctx)
>  	spin_lock_init(&ctx->dma.lock);
>  	ctx->dma.state = TI_CSI2RX_DMA_STOPPED;
>  
> -	ret = ti_csi2rx_init_dma(ctx);
> +	ret = ti_csi2rx_init_vb2q(ctx);
>  	if (ret)
>  		return ret;
>  
> -	ret = ti_csi2rx_init_vb2q(ctx);
> -	if (ret)
> -		goto cleanup_dma;
> +	return 0;
> +}
> +
> +static int ti_csi2rx_runtime_suspend(struct device *dev)
> +{
> +	struct ti_csi2rx_dev *csi = dev_get_drvdata(dev);
> +	int i;
> +
> +	if (csi->enable_count != 0)
> +		return -EBUSY;
> +
> +	for (i = 0; i < csi->num_ctx; i++)
> +		dma_release_channel(csi->ctx[i].dma.chan);
>  
>  	return 0;
> +}
>  
> -cleanup_dma:
> -	dma_release_channel(ctx->dma.chan);
> -	return ret;
> +static int ti_csi2rx_runtime_resume(struct device *dev)
> +{
> +	struct ti_csi2rx_dev *csi = dev_get_drvdata(dev);
> +	unsigned int ret, i;
> +
> +	for (i = 0; i < csi->num_ctx; i++) {
> +		ret = ti_csi2rx_init_dma(&csi->ctx[i]);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
>  }
>  
> +static const struct dev_pm_ops ti_csi2rx_pm_ops = {
> +	RUNTIME_PM_OPS(ti_csi2rx_runtime_suspend, ti_csi2rx_runtime_resume,
> +		       NULL)
> +};
> +
>  static int ti_csi2rx_probe(struct platform_device *pdev)
>  {
>  	struct device_node *np = pdev->dev.of_node;
> @@ -1569,6 +1601,8 @@ static int ti_csi2rx_probe(struct platform_device *pdev)
>  			goto err_ctx;
>  	}
>  
> +	pm_runtime_enable(csi->dev);
> +
>  	ret = ti_csi2rx_notifier_register(csi);
>  	if (ret)
>  		goto err_ctx;
> @@ -1601,6 +1635,9 @@ static void ti_csi2rx_remove(struct platform_device *pdev)
>  	struct ti_csi2rx_dev *csi = platform_get_drvdata(pdev);
>  	unsigned int i;
>  
> +	if (!pm_runtime_status_suspended(&pdev->dev))
> +		pm_runtime_set_suspended(&pdev->dev);
> +
>  	for (i = 0; i < csi->num_ctx; i++)
>  		ti_csi2rx_cleanup_ctx(&csi->ctx[i]);
>  
> @@ -1609,6 +1646,7 @@ static void ti_csi2rx_remove(struct platform_device *pdev)
>  	mutex_destroy(&csi->mutex);
>  	dma_free_coherent(csi->dev, csi->drain.len, csi->drain.vaddr,
>  			  csi->drain.paddr);
> +	pm_runtime_disable(&pdev->dev);
>  }
>  
>  static const struct of_device_id ti_csi2rx_of_match[] = {
> @@ -1623,6 +1661,7 @@ static struct platform_driver ti_csi2rx_pdrv = {
>  	.driver = {
>  		.name = TI_CSI2RX_MODULE_NAME,
>  		.of_match_table = ti_csi2rx_of_match,
> +		.pm		= &ti_csi2rx_pm_ops,
>  	},
>  };
>  


^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v9 19/19] media: ti: j721e-csi2rx: Support system suspend using pm_notifier
  2025-12-30  8:32 ` [PATCH v9 19/19] media: ti: j721e-csi2rx: Support system suspend using pm_notifier Rishikesh Donadkar
@ 2026-01-15 12:50   ` Tomi Valkeinen
  2026-01-19  5:25     ` Rishikesh Donadkar
  0 siblings, 1 reply; 54+ messages in thread
From: Tomi Valkeinen @ 2026-01-15 12:50 UTC (permalink / raw)
  To: Rishikesh Donadkar
  Cc: y-abhilashchandra, devarsht, s-jain1, vigneshr, mchehab, robh,
	krzk+dt, p.zabel, conor+dt, sakari.ailus, hverkuil-cisco,
	jai.luthra, changhuang.liang, jack.zhu, sjoerd, dan.carpenter,
	hverkuil+cisco, linux-kernel, linux-media, devicetree, jai.luthra,
	laurent.pinchart, mripard

Hi,

On 30/12/2025 10:32, Rishikesh Donadkar wrote:
> From: Jai Luthra <jai.luthra@ideasonboard.com>
> 
> As this device is the "orchestrator" for the rest of the media
> pipeline, we need to stop all on-going streams before system suspend and
> enable them back when the system wakes up from sleep.
> 
> Using .suspend/.resume callbacks does not work, as the order of those
> callbacks amongst various devices in the camera pipeline like the sensor,
> FPD serdes, CSI bridge etc. is impossible to enforce, even with
> device links. For example, the Cadence CSI bridge is a child device of
> this device, thus we cannot create a device link with the CSI bridge as
> a provider and this device as consumer. This can lead to situations
> where all the dependencies for the bridge have not yet resumed when we
> request the subdev to start streaming again through the .resume callback
> defined in this device.
> 
> Instead here we register a notifier callback with the PM framework
> which is triggered when the system is fully functional. At this point we
> can cleanly stop or start the streams, because we know all other devices
> and their dependencies are functional. A downside of this approach is
> that the userspace is also alive (not frozen yet, or just thawed), so
> the suspend notifier might complete before the userspace has completed
> all ioctls, like QBUF/DQBUF/STREAMON/STREAMOFF.

It would be good to have at least parts of the explanation here in a
comment, before the register_pm_notifier() call.

 Tomi

> Tested-by: Rishikesh Donadkar <r-donadkar@ti.com>
> Reviewed-by: Rishikesh Donadkar <r-donadkar@ti.com>
> Signed-off-by: Jai Luthra <jai.luthra@ideasonboard.com>
> Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
> ---
>  .../platform/ti/j721e-csi2rx/j721e-csi2rx.c   | 128 ++++++++++++++++++
>  1 file changed, 128 insertions(+)
> 
> diff --git a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
> index 72da58738e16e..f8e55aa402e0b 100644
> --- a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
> +++ b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
> @@ -132,6 +132,7 @@ struct ti_csi2rx_dev {
>  	struct v4l2_subdev		*source;
>  	struct v4l2_subdev		subdev;
>  	struct ti_csi2rx_ctx		ctx[TI_CSI2RX_MAX_CTX];
> +	struct notifier_block		pm_notifier;
>  	u8				pix_per_clk;
>  	/* Buffer to drain stale data from PSI-L endpoint */
>  	struct {
> @@ -1539,6 +1540,124 @@ static int ti_csi2rx_runtime_resume(struct device *dev)
>  	return 0;
>  }
>  
> +static int ti_csi2rx_suspend(struct device *dev)
> +{
> +	struct ti_csi2rx_dev *csi = dev_get_drvdata(dev);
> +	enum ti_csi2rx_dma_state state;
> +	struct ti_csi2rx_ctx *ctx;
> +	struct ti_csi2rx_dma *dma;
> +	unsigned long flags = 0;
> +	int i, ret = 0;
> +
> +	/* If device was not in use we can simply suspend */
> +	if (pm_runtime_status_suspended(dev))
> +		return 0;
> +
> +	/*
> +	 * If device is running, assert the pixel reset to cleanly stop any
> +	 * on-going streams before we suspend.
> +	 */
> +	writel(0, csi->shim + SHIM_CNTL);
> +
> +	for (i = 0; i < csi->num_ctx; i++) {
> +		ctx = &csi->ctx[i];
> +		dma = &ctx->dma;
> +
> +		spin_lock_irqsave(&dma->lock, flags);
> +		state = dma->state;
> +		spin_unlock_irqrestore(&dma->lock, flags);
> +
> +		if (state != TI_CSI2RX_DMA_STOPPED) {
> +			/* Disable source */
> +			ret = v4l2_subdev_disable_streams(&csi->subdev,
> +							  TI_CSI2RX_PAD_FIRST_SOURCE + ctx->idx,
> +							  BIT(0));
> +			if (ret)
> +				dev_err(csi->dev, "Failed to stop subdev stream\n");
> +		}
> +
> +		/* Stop any on-going streams */
> +		writel(0, csi->shim + SHIM_DMACNTX(ctx->idx));
> +
> +		/* Drain DMA */
> +		ti_csi2rx_drain_dma(ctx);
> +
> +		/* Terminate DMA */
> +		ret = dmaengine_terminate_sync(ctx->dma.chan);
> +		if (ret)
> +			dev_err(csi->dev, "Failed to stop DMA\n");
> +	}
> +
> +	return ret;
> +}
> +
> +static int ti_csi2rx_resume(struct device *dev)
> +{
> +	struct ti_csi2rx_dev *csi = dev_get_drvdata(dev);
> +	struct ti_csi2rx_ctx *ctx;
> +	struct ti_csi2rx_dma *dma;
> +	struct ti_csi2rx_buffer *buf;
> +	unsigned long flags = 0;
> +	unsigned int reg;
> +	int i, ret = 0;
> +
> +	/* If device was not in use, we can simply wakeup */
> +	if (pm_runtime_status_suspended(dev))
> +		return 0;
> +
> +	/* If device was in use before, restore all the running streams */
> +	reg = SHIM_CNTL_PIX_RST;
> +	writel(reg, csi->shim + SHIM_CNTL);
> +
> +	for (i = 0; i < csi->num_ctx; i++) {
> +		ctx = &csi->ctx[i];
> +		dma = &ctx->dma;
> +		spin_lock_irqsave(&dma->lock, flags);
> +		if (dma->state != TI_CSI2RX_DMA_STOPPED) {
> +			/* Re-submit all previously submitted buffers to DMA */
> +			list_for_each_entry(buf, &ctx->dma.submitted, list) {
> +				ti_csi2rx_start_dma(ctx, buf);
> +			}
> +			spin_unlock_irqrestore(&dma->lock, flags);
> +
> +			/* Restore stream config */
> +			ti_csi2rx_setup_shim(ctx);
> +
> +			ret = v4l2_subdev_enable_streams(&csi->subdev,
> +							 TI_CSI2RX_PAD_FIRST_SOURCE + ctx->idx,
> +							 BIT(0));
> +			if (ret)
> +				dev_err(ctx->csi->dev, "Failed to start subdev\n");
> +		} else {
> +			spin_unlock_irqrestore(&dma->lock, flags);
> +		}
> +	}
> +
> +	return ret;
> +}
> +
> +static int ti_csi2rx_pm_notifier(struct notifier_block *nb,
> +				 unsigned long action, void *data)
> +{
> +	struct ti_csi2rx_dev *csi =
> +		container_of(nb, struct ti_csi2rx_dev, pm_notifier);
> +
> +	switch (action) {
> +	case PM_HIBERNATION_PREPARE:
> +	case PM_SUSPEND_PREPARE:
> +	case PM_RESTORE_PREPARE:
> +		ti_csi2rx_suspend(csi->dev);
> +		break;
> +	case PM_POST_SUSPEND:
> +	case PM_POST_HIBERNATION:
> +	case PM_POST_RESTORE:
> +		ti_csi2rx_resume(csi->dev);
> +		break;
> +	}
> +
> +	return NOTIFY_DONE;
> +}
> +
>  static const struct dev_pm_ops ti_csi2rx_pm_ops = {
>  	RUNTIME_PM_OPS(ti_csi2rx_runtime_suspend, ti_csi2rx_runtime_resume,
>  		       NULL)
> @@ -1613,6 +1732,13 @@ static int ti_csi2rx_probe(struct platform_device *pdev)
>  		goto err_notifier;
>  	}
>  
> +	csi->pm_notifier.notifier_call = ti_csi2rx_pm_notifier;
> +	ret = register_pm_notifier(&csi->pm_notifier);
> +	if (ret) {
> +		dev_err(csi->dev, "Failed to create PM notifier: %d\n", ret);
> +		goto err_notifier;
> +	}
> +
>  	return 0;
>  
>  err_notifier:
> @@ -1642,6 +1768,8 @@ static void ti_csi2rx_remove(struct platform_device *pdev)
>  		ti_csi2rx_cleanup_ctx(&csi->ctx[i]);
>  
>  	ti_csi2rx_cleanup_notifier(csi);
> +	unregister_pm_notifier(&csi->pm_notifier);
> +
>  	ti_csi2rx_cleanup_v4l2(csi);
>  	mutex_destroy(&csi->mutex);
>  	dma_free_coherent(csi->dev, csi->drain.len, csi->drain.vaddr,


^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v9 06/19] media: ti: j721e-csi2rx: add a subdev for the core device
  2026-01-15  6:36     ` Jai Luthra
@ 2026-01-15 12:56       ` Tomi Valkeinen
  2026-01-20 23:25         ` Laurent Pinchart
  0 siblings, 1 reply; 54+ messages in thread
From: Tomi Valkeinen @ 2026-01-15 12:56 UTC (permalink / raw)
  To: Jai Luthra, Laurent Pinchart, Sakari Ailus
  Cc: y-abhilashchandra, devarsht, s-jain1, vigneshr, mchehab, robh,
	krzk+dt, p.zabel, conor+dt, hverkuil-cisco, changhuang.liang,
	jack.zhu, sjoerd, dan.carpenter, hverkuil+cisco, linux-kernel,
	linux-media, devicetree, jai.luthra, mripard, Rishikesh Donadkar,
	Laurent Pinchart, Sakari Ailus

Hi,

On 15/01/2026 08:36, Jai Luthra wrote:
> Hi Tomi,
> 
> +Sakari, Laurent
> 
> Quoting Tomi Valkeinen (2026-01-14 20:51:49)
>> Hi,
>>
>> On 30/12/2025 10:32, Rishikesh Donadkar wrote:
>>> From: Jai Luthra <j-luthra@ti.com>
>>>
>>> With single stream capture, it was simpler to use the video device as
>>> the media entity representing the main TI CSI2RX device. Now with multi
>>> stream capture coming into the picture, the model has shifted to each
>>> video device having a link to the main device's subdev. The routing
>>> would then be set on this subdev.
>>>
>>> Add this subdev, link each context to this subdev's entity and link the
>>> subdev's entity to the source. Also add an array of media pads. It will
>>> have one sink pad and source pads equal to the number of contexts.
>>>
>>> Support the new enable_stream()/disable_stream() APIs in the subdev
>>> instead of s_stream() hook.
>>>
>>> Reviewed-by: Yemike Abhilash Chandra <y-abhilashchandra@ti.com>
>>> Co-developed-by: Pratyush Yadav <p.yadav@ti.com>
>>> Signed-off-by: Pratyush Yadav <p.yadav@ti.com>
>>> Signed-off-by: Jai Luthra <j-luthra@ti.com>
>>> Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
>>> ---
> 
> [...]
> 
>>> @@ -981,48 +1138,52 @@ static int ti_csi2rx_link_validate(struct media_link *link)
>>>       struct ti_csi2rx_ctx *ctx = container_of(vdev, struct ti_csi2rx_ctx, vdev);
>>>       struct ti_csi2rx_dev *csi = ctx->csi;
>>>       struct v4l2_pix_format *csi_fmt = &ctx->v_fmt.fmt.pix;
>>> -     struct v4l2_subdev_format source_fmt = {
>>> -             .which  = V4L2_SUBDEV_FORMAT_ACTIVE,
>>> -             .pad    = link->source->index,
>>> -     };
>>> +     struct v4l2_mbus_framefmt *format;
>>> +     struct v4l2_subdev_state *state;
>>>       const struct ti_csi2rx_fmt *ti_fmt;
>>> -     int ret;
>>>  
>>> -     ret = v4l2_subdev_call_state_active(csi->source, pad,
>>> -                                         get_fmt, &source_fmt);
>>> -     if (ret)
>>> -             return ret;
>>> +     state = v4l2_subdev_lock_and_get_active_state(&csi->subdev);
>>> +     format = v4l2_subdev_state_get_format(state, link->source->index, 0);
>>> +     v4l2_subdev_unlock_state(state);
>>>  
>>> -     if (source_fmt.format.width != csi_fmt->width) {
>>> +     if (!format) {
>>> +             dev_dbg(csi->dev,
>>> +                     "Skipping validation as no format present on \"%s\":%u:0\n",
>>> +                     link->source->entity->name, link->source->index);
>>> +             return 0;
>>
>> Isn't this an error?
> 
> Well, the j7 shim subdev introduced here has immutable and active links to
> all the video nodes, for each DMA channel (taken from DT), many of which
> may be unused for certain setups, and thus there might not be any valid
> format on the subdev source pad corresponding to an unused video node.
> 
> Jacopo had a similar comment on v2, see this discussion (grep for Mali):
> https://lore.kernel.org/linux-media/4mnlnsj4co3agvln4qsasmgvgwiyoo7yu2h5wyh4rmzzafhm5u@avhnbw7iknms/
> 
> I know other drivers use a different approach with mutable links, so it
> would be good if you/Laurent/Sakari can give your opinions on if only one
> of these two approaches should be taken for multi-stream pipelines.
I see.

Well, I don't have a definite answer. With some thinking both options
make certain sense. It makes sense to keep the links immutable and
always enabled, as there's no configuration that can be done. On the
other hand, it makes sense to require the unused links to be disabled,
as, well, they are not used.

 Tomi


^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v9 15/19] media: ti: j721e-csi2rx: Change the drain architecture for multistream
  2026-01-15 12:37   ` Tomi Valkeinen
@ 2026-01-15 16:02     ` Jai Luthra
  2026-01-16 10:11       ` Vignesh Raghavendra
  2026-01-19  5:08       ` Rishikesh Donadkar
  2026-01-19  5:04     ` Rishikesh Donadkar
  1 sibling, 2 replies; 54+ messages in thread
From: Jai Luthra @ 2026-01-15 16:02 UTC (permalink / raw)
  To: Rishikesh Donadkar, Tomi Valkeinen, Vignesh Raghavendra
  Cc: y-abhilashchandra, devarsht, s-jain1, mchehab, robh, krzk+dt,
	p.zabel, conor+dt, sakari.ailus, hverkuil-cisco, changhuang.liang,
	jack.zhu, sjoerd, dan.carpenter, hverkuil+cisco, linux-kernel,
	linux-media, devicetree, jai.luthra, laurent.pinchart, mripard

Hi Tomi,

Quoting Tomi Valkeinen (2026-01-15 18:07:07)
> Hi,
> 
> On 30/12/2025 10:32, Rishikesh Donadkar wrote:
> > On buffer starvation the DMA is marked IDLE, and the stale data in the
> > internal FIFOs gets drained only on the next VIDIOC_QBUF call from the
> > userspace. This approach works fine for a single stream case.
> > 
> > But in multistream scenarios, buffer starvation for one stream i.e. one
> > virtual channel, can block the shared HW FIFO of the CSI2RX IP. This can
> > stall the pipeline for all other virtual channels, even if buffers are
> > available for them.
> 
> One stream is filtered based on VC & DT, but the above only mentions VC.
> And then later uses VC when referring to the stream. I think you can
> drop the VC parts, and just talk about streams.
> 
> > This patch introduces a new architecture, that continuously drains data
> > from the shared HW FIFO into a small (32KiB) buffer if no buffers are made
> > available to the driver from the userspace. This ensures independence
> > between different streams, where a slower downstream element for one
> > camera does not block streaming for other cameras.
> > 
> > Additionally, after a drain is done for a VC, the next frame will be a
> > partial frame, as a portion of its data will have already been drained
> > before a valid buffer is queued by user space to the driver.
> 
> This makes it sounds we drain a single 32KB piece. But won't we continue
> draining that stream until the stream is stopped or the user provides a
> buffer?
> 
> Also, does the DMA not offer us ways to drain a full frame? There's no
> way to e.g. set the DMA TX increment to 0, so that it would just write
> to a single location in memory? Or just set the target to void.
> 

+ Vignesh

AFAIU the dmaengine API is the first limitation here, and the actual
Transfer Record (TR) structure for BCDMA might support writing to a single
address. It also might be possible to use dmaengine_prep_dma_cyclic to
setup a auto-repeating TR like it's used for audio.. maybe that can be
explored separate from this series.

But yes, ideally we need to set the target to void, which I don't think is
supported by the HW (TI folks please correct me if I'm wrong :)

Thanks,
    Jai

>  Tomi
> 
> > Use wait for completion barrier to make sure the shared hardware FIFO
> > is cleared of the data at the end of stream after the source has stopped
> > sending data.
> > 
> > Reviewed-by: Jai Luthra <jai.luthra@ideasonboard.com>
> > Reviewed-by: Yemike Abhilash Chandra <y-abhilashchandra@ti.com>
> > Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>

[...]

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v9 15/19] media: ti: j721e-csi2rx: Change the drain architecture for multistream
  2026-01-15 16:02     ` Jai Luthra
@ 2026-01-16 10:11       ` Vignesh Raghavendra
  2026-01-19  5:08       ` Rishikesh Donadkar
  1 sibling, 0 replies; 54+ messages in thread
From: Vignesh Raghavendra @ 2026-01-16 10:11 UTC (permalink / raw)
  To: Jai Luthra, Rishikesh Donadkar, Tomi Valkeinen
  Cc: y-abhilashchandra, devarsht, s-jain1, mchehab, robh, krzk+dt,
	p.zabel, conor+dt, sakari.ailus, hverkuil-cisco, changhuang.liang,
	jack.zhu, sjoerd, dan.carpenter, hverkuil+cisco, linux-kernel,
	linux-media, devicetree, jai.luthra, laurent.pinchart, mripard



On 15/01/26 21:32, Jai Luthra wrote:
> Hi Tomi,
> 
> Quoting Tomi Valkeinen (2026-01-15 18:07:07)
>> Hi,
>>
>> On 30/12/2025 10:32, Rishikesh Donadkar wrote:
>>> On buffer starvation the DMA is marked IDLE, and the stale data in the
>>> internal FIFOs gets drained only on the next VIDIOC_QBUF call from the
>>> userspace. This approach works fine for a single stream case.
>>>
>>> But in multistream scenarios, buffer starvation for one stream i.e. one
>>> virtual channel, can block the shared HW FIFO of the CSI2RX IP. This can
>>> stall the pipeline for all other virtual channels, even if buffers are
>>> available for them.
>>
>> One stream is filtered based on VC & DT, but the above only mentions VC.
>> And then later uses VC when referring to the stream. I think you can
>> drop the VC parts, and just talk about streams.
>>
>>> This patch introduces a new architecture, that continuously drains data
>>> from the shared HW FIFO into a small (32KiB) buffer if no buffers are made
>>> available to the driver from the userspace. This ensures independence
>>> between different streams, where a slower downstream element for one
>>> camera does not block streaming for other cameras.
>>>
>>> Additionally, after a drain is done for a VC, the next frame will be a
>>> partial frame, as a portion of its data will have already been drained
>>> before a valid buffer is queued by user space to the driver.
>>
>> This makes it sounds we drain a single 32KB piece. But won't we continue
>> draining that stream until the stream is stopped or the user provides a
>> buffer?
>>
>> Also, does the DMA not offer us ways to drain a full frame? There's no
>> way to e.g. set the DMA TX increment to 0, so that it would just write
>> to a single location in memory? Or just set the target to void.
>>
> 
> + Vignesh
> 
> AFAIU the dmaengine API is the first limitation here, and the actual
> Transfer Record (TR) structure for BCDMA might support writing to a single
> address. It also might be possible to use dmaengine_prep_dma_cyclic to
> setup a auto-repeating TR like it's used for audio.. maybe that can be
> explored separate from this series.
> 

Yeah. there is no dmaengine API that can do mem-to-mem transfer with
constant addressing mode on src or dst. But the DMA HW on TI K3 SoCs are
capable of doing so.


[...]

-- 
Regards
Vignesh
https://ti.com/opensource


^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v9 11/19] media: ti: j721e-csi2rx: add support for processing virtual channels
  2026-01-14 15:31   ` Tomi Valkeinen
@ 2026-01-16 10:28     ` Rishikesh Donadkar
  0 siblings, 0 replies; 54+ messages in thread
From: Rishikesh Donadkar @ 2026-01-16 10:28 UTC (permalink / raw)
  To: Tomi Valkeinen
  Cc: y-abhilashchandra, devarsht, s-jain1, vigneshr, mchehab, robh,
	krzk+dt, p.zabel, conor+dt, sakari.ailus, hverkuil-cisco,
	jai.luthra, changhuang.liang, jack.zhu, sjoerd, dan.carpenter,
	hverkuil+cisco, linux-kernel, linux-media, devicetree, jai.luthra,
	laurent.pinchart, mripard


On 14/01/26 21:01, Tomi Valkeinen wrote:
> Hi,


Hi Tomi,

Thank you for the review !

>
> On 30/12/2025 10:32, Rishikesh Donadkar wrote:
>> From: Jai Luthra <j-luthra@ti.com>
>>
>> Use get_frame_desc() to get the frame desc from the connected source,
>> and use the provided virtual channel instead of VC 0.
>>
>> get_frame_desc() works for single stream case, but as we don't
> Is that supposed to say "get_frame_desc works for multi-stream use case"?


This line has been misleading from many previous versions of this 
series. The main point to highlight in this part of the commit message 
is that we are enabling multi stream support in the later patches of the 
series, so in this patch we will stick to using stream 0. I will remove 
the line talking about "get_frame_desc() works for single/multi stream 
use case".

>
>> support multiple streams yet, we will just always use stream 0.
>> If the source doesn't support get_frame_desc(), fall back to
>> the previous method of always capturing virtual channel 0.
>>
>> Reviewed-by: Yemike Abhilash Chandra <y-abhilashchandra@ti.com>
>> Co-developed-by: Pratyush Yadav <p.yadav@ti.com>
>> Signed-off-by: Pratyush Yadav <p.yadav@ti.com>
>> Signed-off-by: Jai Luthra <j-luthra@ti.com>
>> Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
>> ---
>>   .../platform/ti/j721e-csi2rx/j721e-csi2rx.c   | 45 ++++++++++++++++++-
>>   1 file changed, 44 insertions(+), 1 deletion(-)
>>
>> diff --git a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
>> index f54ad67ff3f9d..6f9f34aa26f1b 100644
>> --- a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
>> +++ b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
>> @@ -32,6 +32,7 @@
>>   #define SHIM_DMACNTX_YUV422		GENMASK(27, 26)
>>   #define SHIM_DMACNTX_DUAL_PCK_CFG	BIT(24)
>>   #define SHIM_DMACNTX_SIZE		GENMASK(21, 20)
>> +#define SHIM_DMACNTX_VC			GENMASK(9, 6)
>>   #define SHIM_DMACNTX_FMT		GENMASK(5, 0)
>>   #define SHIM_DMACNTX_YUV422_MODE_11	3
>>   #define SHIM_DMACNTX_SIZE_8		0
>> @@ -110,6 +111,9 @@ struct ti_csi2rx_ctx {
>>   	struct media_pad		pad;
>>   	u32				sequence;
>>   	u32				idx;
>> +	u32				vc;
>> +	u32				dt;
>> +	u32				stream;
>>   };
>>   
>>   struct ti_csi2rx_dev {
>> @@ -570,7 +574,7 @@ static void ti_csi2rx_setup_shim(struct ti_csi2rx_ctx *ctx)
>>   	ti_csi2rx_request_max_ppc(csi);
>>   
>>   	reg = SHIM_DMACNTX_EN;
>> -	reg |= FIELD_PREP(SHIM_DMACNTX_FMT, fmt->csi_dt);
>> +	reg |= FIELD_PREP(SHIM_DMACNTX_FMT, ctx->dt);
>>   
>>   	/*
>>   	 * The hardware assumes incoming YUV422 8-bit data on MIPI CSI2 bus
>> @@ -610,6 +614,7 @@ static void ti_csi2rx_setup_shim(struct ti_csi2rx_ctx *ctx)
>>   	}
>>   
>>   	reg |= FIELD_PREP(SHIM_DMACNTX_SIZE, fmt->size);
>> +	reg |= FIELD_PREP(SHIM_DMACNTX_VC, ctx->vc);
>>   
>>   	writel(reg, csi->shim + SHIM_DMACNTX(ctx->idx));
>>   
>> @@ -884,12 +889,41 @@ static void ti_csi2rx_buffer_queue(struct vb2_buffer *vb)
>>   	}
>>   }
>>   
>> +static int ti_csi2rx_get_vc_and_dt(struct ti_csi2rx_ctx *ctx)
>> +{
>> +	struct ti_csi2rx_dev *csi = ctx->csi;
>> +	struct v4l2_mbus_frame_desc fd;
>> +	struct media_pad *pad;
>> +	int ret, i;
>> +
>> +	pad = media_entity_remote_pad_unique(&csi->subdev.entity, MEDIA_PAD_FL_SOURCE);
>> +	if (!pad)
>> +		return -ENODEV;
>> +
>> +	ret = v4l2_subdev_call(csi->source, pad, get_frame_desc, pad->index, &fd);
>> +	if (ret)
>> +		return ret;
>> +
>> +	if (fd.type != V4L2_MBUS_FRAME_DESC_TYPE_CSI2)
>> +		return -EINVAL;
>> +
>> +	for (i = 0; i < fd.num_entries; i++) {
>> +		if (ctx->stream == fd.entry[i].stream) {
>> +			ctx->vc = fd.entry[i].bus.csi2.vc;
>> +			ctx->dt = fd.entry[i].bus.csi2.dt;
>> +		}
>> +	}
> I think you can "break" when you find the stream. But this should also
> catch the case when there's no matching stream, and give an error in
> that case.


Right. Will add.


Rishikesh

>
>   Tomi
>
>> +
>> +	return 0;
>> +}
>> +
>>   static int ti_csi2rx_start_streaming(struct vb2_queue *vq, unsigned int count)
>>   {
>>   	struct ti_csi2rx_ctx *ctx = vb2_get_drv_priv(vq);
>>   	struct ti_csi2rx_dev *csi = ctx->csi;
>>   	struct ti_csi2rx_dma *dma = &ctx->dma;
>>   	struct ti_csi2rx_buffer *buf;
>> +	const struct ti_csi2rx_fmt *fmt;
>>   	unsigned long flags;
>>   	int ret = 0;
>>   
>> @@ -904,6 +938,15 @@ static int ti_csi2rx_start_streaming(struct vb2_queue *vq, unsigned int count)
>>   	if (ret)
>>   		goto err;
>>   
>> +	ret = ti_csi2rx_get_vc_and_dt(ctx);
>> +	if (ret == -ENOIOCTLCMD) {
>> +		ctx->vc = 0;
>> +		fmt = find_format_by_fourcc(ctx->v_fmt.fmt.pix.pixelformat);
>> +		ctx->dt = fmt->csi_dt;
>> +	} else if (ret < 0) {
>> +		goto err;
>> +	}
>> +
>>   	ti_csi2rx_setup_shim(ctx);
>>   
>>   	ctx->sequence = 0;

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v9 12/19] media: cadence: csi2rx: add multistream support
  2026-01-15 12:01   ` Tomi Valkeinen
@ 2026-01-16 11:04     ` Rishikesh Donadkar
  0 siblings, 0 replies; 54+ messages in thread
From: Rishikesh Donadkar @ 2026-01-16 11:04 UTC (permalink / raw)
  To: Tomi Valkeinen
  Cc: y-abhilashchandra, devarsht, s-jain1, vigneshr, mchehab, robh,
	krzk+dt, p.zabel, conor+dt, sakari.ailus, hverkuil-cisco,
	jai.luthra, changhuang.liang, jack.zhu, sjoerd, dan.carpenter,
	hverkuil+cisco, linux-kernel, linux-media, devicetree, jai.luthra,
	laurent.pinchart, mripard


On 15/01/26 17:31, Tomi Valkeinen wrote:
> Hi,

Hi Tomi,

Thank you for the review !

>
> On 30/12/2025 10:32, Rishikesh Donadkar wrote:
>> From: Jai Luthra <j-luthra@ti.com>
>>
>> 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 <j-luthra@ti.com>
>> Reviewed-by: Changhuang Liang <changhuang.liang@starfivetech.com>
>> Reviewed-by: Yemike Abhilash Chandra <y-abhilashchandra@ti.com>
>> Co-developed-by: Rishikesh Donadkar <r-donadkar@ti.com>
>> Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
>> ---
>>   drivers/media/platform/cadence/cdns-csi2rx.c | 248 +++++++++++++++----
>>   1 file changed, 201 insertions(+), 47 deletions(-)
>>
>> diff --git a/drivers/media/platform/cadence/cdns-csi2rx.c b/drivers/media/platform/cadence/cdns-csi2rx.c
>> index 65c6acb02f85b..5c16a2e509136 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;
>> @@ -273,30 +274,43 @@ 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;
>> +	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 {
>> +		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;
>> +		}
>>   
>> -	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);
>> @@ -394,11 +408,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,
>> @@ -486,18 +496,59 @@ 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;
>> +
>> +	/* Capture VC=0 by default */
>> +	for (i = 0; i < CSI2RX_STREAMS_MAX; i++)
>> +		csi2rx->vc_select[i] = CSI2RX_STREAM_DATA_CFG_VC_SELECT(0);
> This should be inside the if-block below, as in the other code path you
> just memset the whole vc_select.

Will do

Rishikesh

>
> With that fixed:
>
> Reviewed-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
>
>   Tomi
>

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v9 15/19] media: ti: j721e-csi2rx: Change the drain architecture for multistream
  2026-01-15 12:37   ` Tomi Valkeinen
  2026-01-15 16:02     ` Jai Luthra
@ 2026-01-19  5:04     ` Rishikesh Donadkar
  1 sibling, 0 replies; 54+ messages in thread
From: Rishikesh Donadkar @ 2026-01-19  5:04 UTC (permalink / raw)
  To: Tomi Valkeinen
  Cc: y-abhilashchandra, devarsht, s-jain1, vigneshr, mchehab, robh,
	krzk+dt, p.zabel, conor+dt, sakari.ailus, hverkuil-cisco,
	jai.luthra, changhuang.liang, jack.zhu, sjoerd, dan.carpenter,
	hverkuil+cisco, linux-kernel, linux-media, devicetree, jai.luthra,
	laurent.pinchart, mripard


On 15/01/26 18:07, Tomi Valkeinen wrote:
> Hi,


Hi Tomi,

Thank you for the review !

>
> On 30/12/2025 10:32, Rishikesh Donadkar wrote:
>> On buffer starvation the DMA is marked IDLE, and the stale data in the
>> internal FIFOs gets drained only on the next VIDIOC_QBUF call from the
>> userspace. This approach works fine for a single stream case.
>>
>> But in multistream scenarios, buffer starvation for one stream i.e. one
>> virtual channel, can block the shared HW FIFO of the CSI2RX IP. This can
>> stall the pipeline for all other virtual channels, even if buffers are
>> available for them.
> One stream is filtered based on VC & DT, but the above only mentions VC.
> And then later uses VC when referring to the stream. I think you can
> drop the VC parts, and just talk about streams.


Okay, will do !

>
>> This patch introduces a new architecture, that continuously drains data
>> from the shared HW FIFO into a small (32KiB) buffer if no buffers are made
>> available to the driver from the userspace. This ensures independence
>> between different streams, where a slower downstream element for one
>> camera does not block streaming for other cameras.
>>
>> Additionally, after a drain is done for a VC, the next frame will be a
>> partial frame, as a portion of its data will have already been drained
>> before a valid buffer is queued by user space to the driver.
> This makes it sounds we drain a single 32KB piece. But won't we continue
> draining that stream until the stream is stopped or the user provides a
> buffer?


Thanks for pointing out, I will change it to talk about continuous draining.

Rishikesh

>
> Also, does the DMA not offer us ways to drain a full frame? There's no
> way to e.g. set the DMA TX increment to 0, so that it would just write
> to a single location in memory? Or just set the target to void.
>
>   Tomi
>
>> Use wait for completion barrier to make sure the shared hardware FIFO
>> is cleared of the data at the end of stream after the source has stopped
>> sending data.
>>
>> Reviewed-by: Jai Luthra <jai.luthra@ideasonboard.com>
>> Reviewed-by: Yemike Abhilash Chandra <y-abhilashchandra@ti.com>
>> Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
>> ---
>>   .../platform/ti/j721e-csi2rx/j721e-csi2rx.c   | 112 ++++++++----------
>>   1 file changed, 50 insertions(+), 62 deletions(-)
>>
>> diff --git a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
>> index fa6152464d4b6..e713293696eb1 100644
>> --- a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
>> +++ b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
>> @@ -82,7 +82,6 @@ struct ti_csi2rx_buffer {
>>   
>>   enum ti_csi2rx_dma_state {
>>   	TI_CSI2RX_DMA_STOPPED,	/* Streaming not started yet. */
>> -	TI_CSI2RX_DMA_IDLE,	/* Streaming but no pending DMA operation. */
>>   	TI_CSI2RX_DMA_ACTIVE,	/* Streaming and pending DMA operation. */
>>   };
>>   
>> @@ -109,6 +108,7 @@ struct ti_csi2rx_ctx {
>>   	struct v4l2_format		v_fmt;
>>   	struct ti_csi2rx_dma		dma;
>>   	struct media_pad		pad;
>> +	struct completion		drain_complete;
>>   	u32				sequence;
>>   	u32				idx;
>>   	u32				vc;
>> @@ -251,6 +251,10 @@ static const struct ti_csi2rx_fmt ti_csi2rx_formats[] = {
>>   static int ti_csi2rx_start_dma(struct ti_csi2rx_ctx *ctx,
>>   			       struct ti_csi2rx_buffer *buf);
>>   
>> +/* Forward declarations needed by ti_csi2rx_drain_callback. */
>> +static int ti_csi2rx_drain_dma(struct ti_csi2rx_ctx *ctx);
>> +static int ti_csi2rx_dma_submit_pending(struct ti_csi2rx_ctx *ctx);
>> +
>>   static const struct ti_csi2rx_fmt *find_format_by_fourcc(u32 pixelformat)
>>   {
>>   	unsigned int i;
>> @@ -617,9 +621,32 @@ static void ti_csi2rx_setup_shim(struct ti_csi2rx_ctx *ctx)
>>   
>>   static void ti_csi2rx_drain_callback(void *param)
>>   {
>> -	struct completion *drain_complete = param;
>> +	struct ti_csi2rx_ctx *ctx = param;
>> +	struct ti_csi2rx_dma *dma = &ctx->dma;
>> +	unsigned long flags;
>> +
>> +	spin_lock_irqsave(&dma->lock, flags);
>> +
>> +	if (dma->state == TI_CSI2RX_DMA_STOPPED) {
>> +		complete(&ctx->drain_complete);
>> +		spin_unlock_irqrestore(&dma->lock, flags);
>> +		return;
>> +	}
>>   
>> -	complete(drain_complete);
>> +	/*
>> +	 * If dma->queue is empty, it indicates that no buffer has been
>> +	 * provided by user space. In this case, initiate a transactions
>> +	 * to drain the DMA. Since one drain of size DRAIN_BUFFER_SIZE
>> +	 * will be done here, the subsequent frame will be a
>> +	 * partial frame, with a size of frame_size - DRAIN_BUFFER_SIZE
>> +	 */
>> +	if (list_empty(&dma->queue)) {
>> +		if (ti_csi2rx_drain_dma(ctx))
>> +			dev_warn(ctx->csi->dev, "DMA drain failed\n");
>> +	} else {
>> +		ti_csi2rx_dma_submit_pending(ctx);
>> +	}
>> +	spin_unlock_irqrestore(&dma->lock, flags);
>>   }
>>   
>>   /*
>> @@ -637,12 +664,9 @@ static int ti_csi2rx_drain_dma(struct ti_csi2rx_ctx *ctx)
>>   {
>>   	struct ti_csi2rx_dev *csi = ctx->csi;
>>   	struct dma_async_tx_descriptor *desc;
>> -	struct completion drain_complete;
>>   	dma_cookie_t cookie;
>>   	int ret;
>>   
>> -	init_completion(&drain_complete);
>> -
>>   	desc = dmaengine_prep_slave_single(ctx->dma.chan, csi->drain.paddr,
>>   					   csi->drain.len, DMA_DEV_TO_MEM,
>>   					   DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
>> @@ -652,7 +676,7 @@ static int ti_csi2rx_drain_dma(struct ti_csi2rx_ctx *ctx)
>>   	}
>>   
>>   	desc->callback = ti_csi2rx_drain_callback;
>> -	desc->callback_param = &drain_complete;
>> +	desc->callback_param = ctx;
>>   
>>   	cookie = dmaengine_submit(desc);
>>   	ret = dma_submit_error(cookie);
>> @@ -661,13 +685,6 @@ static int ti_csi2rx_drain_dma(struct ti_csi2rx_ctx *ctx)
>>   
>>   	dma_async_issue_pending(ctx->dma.chan);
>>   
>> -	if (!wait_for_completion_timeout(&drain_complete,
>> -					 msecs_to_jiffies(DRAIN_TIMEOUT_MS))) {
>> -		dmaengine_terminate_sync(ctx->dma.chan);
>> -		dev_dbg(csi->dev, "DMA transfer timed out for drain buffer\n");
>> -		ret = -ETIMEDOUT;
>> -		goto out;
>> -	}
>>   out:
>>   	return ret;
>>   }
>> @@ -716,9 +733,11 @@ static void ti_csi2rx_dma_callback(void *param)
>>   
>>   	ti_csi2rx_dma_submit_pending(ctx);
>>   
>> -	if (list_empty(&dma->submitted))
>> -		dma->state = TI_CSI2RX_DMA_IDLE;
>> -
>> +	if (list_empty(&dma->submitted)) {
>> +		if (ti_csi2rx_drain_dma(ctx))
>> +			dev_warn(ctx->csi->dev,
>> +				 "DMA drain failed on one of the transactions\n");
>> +	}
>>   	spin_unlock_irqrestore(&dma->lock, flags);
>>   }
>>   
>> @@ -754,6 +773,7 @@ static int ti_csi2rx_start_dma(struct ti_csi2rx_ctx *ctx,
>>   static void ti_csi2rx_stop_dma(struct ti_csi2rx_ctx *ctx)
>>   {
>>   	struct ti_csi2rx_dma *dma = &ctx->dma;
>> +	struct ti_csi2rx_dev *csi = ctx->csi;
>>   	enum ti_csi2rx_dma_state state;
>>   	unsigned long flags;
>>   	int ret;
>> @@ -763,6 +783,8 @@ static void ti_csi2rx_stop_dma(struct ti_csi2rx_ctx *ctx)
>>   	dma->state = TI_CSI2RX_DMA_STOPPED;
>>   	spin_unlock_irqrestore(&dma->lock, flags);
>>   
>> +	init_completion(&ctx->drain_complete);
>> +
>>   	if (state != TI_CSI2RX_DMA_STOPPED) {
>>   		/*
>>   		 * Normal DMA termination does not clean up pending data on
>> @@ -771,11 +793,20 @@ static void ti_csi2rx_stop_dma(struct ti_csi2rx_ctx *ctx)
>>   		 * enforced before terminating DMA.
>>   		 */
>>   		ret = ti_csi2rx_drain_dma(ctx);
>> -		if (ret && ret != -ETIMEDOUT)
>> +		if (ret)
>>   			dev_warn(ctx->csi->dev,
>>   				 "Failed to drain DMA. Next frame might be bogus\n");
>>   	}
>>   
>> +	/* We wait for the drain to complete so that the stream stops
>> +	 * cleanly, making sure the shared hardware FIFO is cleared of
>> +	 * data from the current stream. No more data will be coming from
>> +	 * the source after this.
>> +	 */
>> +	if (!wait_for_completion_timeout(&ctx->drain_complete,
>> +					 msecs_to_jiffies(DRAIN_TIMEOUT_MS)))
>> +		dev_dbg(csi->dev, "DMA transfer timed out for drain buffer\n");
>> +
>>   	ret = dmaengine_terminate_sync(ctx->dma.chan);
>>   	if (ret)
>>   		dev_err(ctx->csi->dev, "Failed to stop DMA: %d\n", ret);
>> @@ -838,57 +869,14 @@ static void ti_csi2rx_buffer_queue(struct vb2_buffer *vb)
>>   	struct ti_csi2rx_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
>>   	struct ti_csi2rx_buffer *buf;
>>   	struct ti_csi2rx_dma *dma = &ctx->dma;
>> -	bool restart_dma = false;
>>   	unsigned long flags = 0;
>> -	int ret;
>>   
>>   	buf = container_of(vb, struct ti_csi2rx_buffer, vb.vb2_buf);
>>   	buf->ctx = ctx;
>>   
>>   	spin_lock_irqsave(&dma->lock, flags);
>> -	/*
>> -	 * Usually the DMA callback takes care of queueing the pending buffers.
>> -	 * But if DMA has stalled due to lack of buffers, restart it now.
>> -	 */
>> -	if (dma->state == TI_CSI2RX_DMA_IDLE) {
>> -		/*
>> -		 * Do not restart DMA with the lock held because
>> -		 * ti_csi2rx_drain_dma() might block for completion.
>> -		 * There won't be a race on queueing DMA anyway since the
>> -		 * callback is not being fired.
>> -		 */
>> -		restart_dma = true;
>> -		dma->state = TI_CSI2RX_DMA_ACTIVE;
>> -	} else {
>> -		list_add_tail(&buf->list, &dma->queue);
>> -	}
>> +	list_add_tail(&buf->list, &dma->queue);
>>   	spin_unlock_irqrestore(&dma->lock, flags);
>> -
>> -	if (restart_dma) {
>> -		/*
>> -		 * Once frames start dropping, some data gets stuck in the DMA
>> -		 * pipeline somewhere. So the first DMA transfer after frame
>> -		 * drops gives a partial frame. This is obviously not useful to
>> -		 * the application and will only confuse it. Issue a DMA
>> -		 * transaction to drain that up.
>> -		 */
>> -		ret = ti_csi2rx_drain_dma(ctx);
>> -		if (ret && ret != -ETIMEDOUT)
>> -			dev_warn(ctx->csi->dev,
>> -				 "Failed to drain DMA. Next frame might be bogus\n");
>> -
>> -		spin_lock_irqsave(&dma->lock, flags);
>> -		ret = ti_csi2rx_start_dma(ctx, buf);
>> -		if (ret) {
>> -			vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
>> -			dma->state = TI_CSI2RX_DMA_IDLE;
>> -			spin_unlock_irqrestore(&dma->lock, flags);
>> -			dev_err(ctx->csi->dev, "Failed to start DMA: %d\n", ret);
>> -		} else {
>> -			list_add_tail(&buf->list, &dma->submitted);
>> -			spin_unlock_irqrestore(&dma->lock, flags);
>> -		}
>> -	}
>>   }
>>   
>>   static int ti_csi2rx_get_stream(struct ti_csi2rx_ctx *ctx)

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v9 15/19] media: ti: j721e-csi2rx: Change the drain architecture for multistream
  2026-01-15 16:02     ` Jai Luthra
  2026-01-16 10:11       ` Vignesh Raghavendra
@ 2026-01-19  5:08       ` Rishikesh Donadkar
  1 sibling, 0 replies; 54+ messages in thread
From: Rishikesh Donadkar @ 2026-01-19  5:08 UTC (permalink / raw)
  To: Jai Luthra, Tomi Valkeinen, Vignesh Raghavendra
  Cc: y-abhilashchandra, devarsht, s-jain1, mchehab, robh, krzk+dt,
	p.zabel, conor+dt, sakari.ailus, hverkuil-cisco, changhuang.liang,
	jack.zhu, sjoerd, dan.carpenter, hverkuil+cisco, linux-kernel,
	linux-media, devicetree, jai.luthra, laurent.pinchart, mripard


On 15/01/26 21:32, Jai Luthra wrote:
> Hi Tomi,
>
> Quoting Tomi Valkeinen (2026-01-15 18:07:07)
>> Hi,
>>
>> On 30/12/2025 10:32, Rishikesh Donadkar wrote:
>>> On buffer starvation the DMA is marked IDLE, and the stale data in the
>>> internal FIFOs gets drained only on the next VIDIOC_QBUF call from the
>>> userspace. This approach works fine for a single stream case.
>>>
>>> But in multistream scenarios, buffer starvation for one stream i.e. one
>>> virtual channel, can block the shared HW FIFO of the CSI2RX IP. This can
>>> stall the pipeline for all other virtual channels, even if buffers are
>>> available for them.
>> One stream is filtered based on VC & DT, but the above only mentions VC.
>> And then later uses VC when referring to the stream. I think you can
>> drop the VC parts, and just talk about streams.
>>
>>> This patch introduces a new architecture, that continuously drains data
>>> from the shared HW FIFO into a small (32KiB) buffer if no buffers are made
>>> available to the driver from the userspace. This ensures independence
>>> between different streams, where a slower downstream element for one
>>> camera does not block streaming for other cameras.
>>>
>>> Additionally, after a drain is done for a VC, the next frame will be a
>>> partial frame, as a portion of its data will have already been drained
>>> before a valid buffer is queued by user space to the driver.
>> This makes it sounds we drain a single 32KB piece. But won't we continue
>> draining that stream until the stream is stopped or the user provides a
>> buffer?
>>
>> Also, does the DMA not offer us ways to drain a full frame? There's no
>> way to e.g. set the DMA TX increment to 0, so that it would just write
>> to a single location in memory? Or just set the target to void.
>>
> + Vignesh
>
> AFAIU the dmaengine API is the first limitation here, and the actual
> Transfer Record (TR) structure for BCDMA might support writing to a single
> address. It also might be possible to use dmaengine_prep_dma_cyclic to
> setup a auto-repeating TR like it's used for audio.. maybe that can be
> explored separate from this series.
Makes sense, if we have such API we can drain a full frame as Tomi 
suggested and also not care about the next frame being a partial one.
>
> But yes, ideally we need to set the target to void, which I don't think is
> supported by the HW (TI folks please correct me if I'm wrong :)
>
> Thanks,
>      Jai
>
>>   Tomi
>>
>>> Use wait for completion barrier to make sure the shared hardware FIFO
>>> is cleared of the data at the end of stream after the source has stopped
>>> sending data.
>>>
>>> Reviewed-by: Jai Luthra <jai.luthra@ideasonboard.com>
>>> Reviewed-by: Yemike Abhilash Chandra <y-abhilashchandra@ti.com>
>>> Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
> [...]

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v9 19/19] media: ti: j721e-csi2rx: Support system suspend using pm_notifier
  2026-01-15 12:50   ` Tomi Valkeinen
@ 2026-01-19  5:25     ` Rishikesh Donadkar
  0 siblings, 0 replies; 54+ messages in thread
From: Rishikesh Donadkar @ 2026-01-19  5:25 UTC (permalink / raw)
  To: Tomi Valkeinen
  Cc: y-abhilashchandra, devarsht, s-jain1, vigneshr, mchehab, robh,
	krzk+dt, p.zabel, conor+dt, sakari.ailus, hverkuil-cisco,
	jai.luthra, changhuang.liang, jack.zhu, sjoerd, dan.carpenter,
	hverkuil+cisco, linux-kernel, linux-media, devicetree, jai.luthra,
	laurent.pinchart, mripard


On 15/01/26 18:20, Tomi Valkeinen wrote:
> Hi,

Hi Tomi,

Thank you for the review.

>
> On 30/12/2025 10:32, Rishikesh Donadkar wrote:
>> From: Jai Luthra <jai.luthra@ideasonboard.com>
>>
>> As this device is the "orchestrator" for the rest of the media
>> pipeline, we need to stop all on-going streams before system suspend and
>> enable them back when the system wakes up from sleep.
>>
>> Using .suspend/.resume callbacks does not work, as the order of those
>> callbacks amongst various devices in the camera pipeline like the sensor,
>> FPD serdes, CSI bridge etc. is impossible to enforce, even with
>> device links. For example, the Cadence CSI bridge is a child device of
>> this device, thus we cannot create a device link with the CSI bridge as
>> a provider and this device as consumer. This can lead to situations
>> where all the dependencies for the bridge have not yet resumed when we
>> request the subdev to start streaming again through the .resume callback
>> defined in this device.
>>
>> Instead here we register a notifier callback with the PM framework
>> which is triggered when the system is fully functional. At this point we
>> can cleanly stop or start the streams, because we know all other devices
>> and their dependencies are functional. A downside of this approach is
>> that the userspace is also alive (not frozen yet, or just thawed), so
>> the suspend notifier might complete before the userspace has completed
>> all ioctls, like QBUF/DQBUF/STREAMON/STREAMOFF.
> It would be good to have at least parts of the explanation here in a
> comment, before the register_pm_notifier() call.

Okay, will add a comment.

Rishikesh

>
>   Tomi
>
>> Tested-by: Rishikesh Donadkar <r-donadkar@ti.com>
>> Reviewed-by: Rishikesh Donadkar <r-donadkar@ti.com>
>> Signed-off-by: Jai Luthra <jai.luthra@ideasonboard.com>
>> Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
>> ---
>>   .../platform/ti/j721e-csi2rx/j721e-csi2rx.c   | 128 ++++++++++++++++++
>>   1 file changed, 128 insertions(+)
>>
>> diff --git a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
>> index 72da58738e16e..f8e55aa402e0b 100644
>> --- a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
>> +++ b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
>> @@ -132,6 +132,7 @@ struct ti_csi2rx_dev {
>>   	struct v4l2_subdev		*source;
>>   	struct v4l2_subdev		subdev;
>>   	struct ti_csi2rx_ctx		ctx[TI_CSI2RX_MAX_CTX];
>> +	struct notifier_block		pm_notifier;
>>   	u8				pix_per_clk;
>>   	/* Buffer to drain stale data from PSI-L endpoint */
>>   	struct {
>> @@ -1539,6 +1540,124 @@ static int ti_csi2rx_runtime_resume(struct device *dev)
>>   	return 0;
>>   }
>>   
>> +static int ti_csi2rx_suspend(struct device *dev)
>> +{
>> +	struct ti_csi2rx_dev *csi = dev_get_drvdata(dev);
>> +	enum ti_csi2rx_dma_state state;
>> +	struct ti_csi2rx_ctx *ctx;
>> +	struct ti_csi2rx_dma *dma;
>> +	unsigned long flags = 0;
>> +	int i, ret = 0;
>> +
>> +	/* If device was not in use we can simply suspend */
>> +	if (pm_runtime_status_suspended(dev))
>> +		return 0;
>> +
>> +	/*
>> +	 * If device is running, assert the pixel reset to cleanly stop any
>> +	 * on-going streams before we suspend.
>> +	 */
>> +	writel(0, csi->shim + SHIM_CNTL);
>> +
>> +	for (i = 0; i < csi->num_ctx; i++) {
>> +		ctx = &csi->ctx[i];
>> +		dma = &ctx->dma;
>> +
>> +		spin_lock_irqsave(&dma->lock, flags);
>> +		state = dma->state;
>> +		spin_unlock_irqrestore(&dma->lock, flags);
>> +
>> +		if (state != TI_CSI2RX_DMA_STOPPED) {
>> +			/* Disable source */
>> +			ret = v4l2_subdev_disable_streams(&csi->subdev,
>> +							  TI_CSI2RX_PAD_FIRST_SOURCE + ctx->idx,
>> +							  BIT(0));
>> +			if (ret)
>> +				dev_err(csi->dev, "Failed to stop subdev stream\n");
>> +		}
>> +
>> +		/* Stop any on-going streams */
>> +		writel(0, csi->shim + SHIM_DMACNTX(ctx->idx));
>> +
>> +		/* Drain DMA */
>> +		ti_csi2rx_drain_dma(ctx);
>> +
>> +		/* Terminate DMA */
>> +		ret = dmaengine_terminate_sync(ctx->dma.chan);
>> +		if (ret)
>> +			dev_err(csi->dev, "Failed to stop DMA\n");
>> +	}
>> +
>> +	return ret;
>> +}
>> +
>> +static int ti_csi2rx_resume(struct device *dev)
>> +{
>> +	struct ti_csi2rx_dev *csi = dev_get_drvdata(dev);
>> +	struct ti_csi2rx_ctx *ctx;
>> +	struct ti_csi2rx_dma *dma;
>> +	struct ti_csi2rx_buffer *buf;
>> +	unsigned long flags = 0;
>> +	unsigned int reg;
>> +	int i, ret = 0;
>> +
>> +	/* If device was not in use, we can simply wakeup */
>> +	if (pm_runtime_status_suspended(dev))
>> +		return 0;
>> +
>> +	/* If device was in use before, restore all the running streams */
>> +	reg = SHIM_CNTL_PIX_RST;
>> +	writel(reg, csi->shim + SHIM_CNTL);
>> +
>> +	for (i = 0; i < csi->num_ctx; i++) {
>> +		ctx = &csi->ctx[i];
>> +		dma = &ctx->dma;
>> +		spin_lock_irqsave(&dma->lock, flags);
>> +		if (dma->state != TI_CSI2RX_DMA_STOPPED) {
>> +			/* Re-submit all previously submitted buffers to DMA */
>> +			list_for_each_entry(buf, &ctx->dma.submitted, list) {
>> +				ti_csi2rx_start_dma(ctx, buf);
>> +			}
>> +			spin_unlock_irqrestore(&dma->lock, flags);
>> +
>> +			/* Restore stream config */
>> +			ti_csi2rx_setup_shim(ctx);
>> +
>> +			ret = v4l2_subdev_enable_streams(&csi->subdev,
>> +							 TI_CSI2RX_PAD_FIRST_SOURCE + ctx->idx,
>> +							 BIT(0));
>> +			if (ret)
>> +				dev_err(ctx->csi->dev, "Failed to start subdev\n");
>> +		} else {
>> +			spin_unlock_irqrestore(&dma->lock, flags);
>> +		}
>> +	}
>> +
>> +	return ret;
>> +}
>> +
>> +static int ti_csi2rx_pm_notifier(struct notifier_block *nb,
>> +				 unsigned long action, void *data)
>> +{
>> +	struct ti_csi2rx_dev *csi =
>> +		container_of(nb, struct ti_csi2rx_dev, pm_notifier);
>> +
>> +	switch (action) {
>> +	case PM_HIBERNATION_PREPARE:
>> +	case PM_SUSPEND_PREPARE:
>> +	case PM_RESTORE_PREPARE:
>> +		ti_csi2rx_suspend(csi->dev);
>> +		break;
>> +	case PM_POST_SUSPEND:
>> +	case PM_POST_HIBERNATION:
>> +	case PM_POST_RESTORE:
>> +		ti_csi2rx_resume(csi->dev);
>> +		break;
>> +	}
>> +
>> +	return NOTIFY_DONE;
>> +}
>> +
>>   static const struct dev_pm_ops ti_csi2rx_pm_ops = {
>>   	RUNTIME_PM_OPS(ti_csi2rx_runtime_suspend, ti_csi2rx_runtime_resume,
>>   		       NULL)
>> @@ -1613,6 +1732,13 @@ static int ti_csi2rx_probe(struct platform_device *pdev)
>>   		goto err_notifier;
>>   	}
>>   
>> +	csi->pm_notifier.notifier_call = ti_csi2rx_pm_notifier;
>> +	ret = register_pm_notifier(&csi->pm_notifier);
>> +	if (ret) {
>> +		dev_err(csi->dev, "Failed to create PM notifier: %d\n", ret);
>> +		goto err_notifier;
>> +	}
>> +
>>   	return 0;
>>   
>>   err_notifier:
>> @@ -1642,6 +1768,8 @@ static void ti_csi2rx_remove(struct platform_device *pdev)
>>   		ti_csi2rx_cleanup_ctx(&csi->ctx[i]);
>>   
>>   	ti_csi2rx_cleanup_notifier(csi);
>> +	unregister_pm_notifier(&csi->pm_notifier);
>> +
>>   	ti_csi2rx_cleanup_v4l2(csi);
>>   	mutex_destroy(&csi->mutex);
>>   	dma_free_coherent(csi->dev, csi->drain.len, csi->drain.vaddr,

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v9 18/19] media: ti: j721e-csi2rx: Support runtime suspend
  2026-01-15 12:46   ` Tomi Valkeinen
@ 2026-01-19  6:04     ` Jai Luthra
  0 siblings, 0 replies; 54+ messages in thread
From: Jai Luthra @ 2026-01-19  6:04 UTC (permalink / raw)
  To: Rishikesh Donadkar, Tomi Valkeinen
  Cc: y-abhilashchandra, devarsht, s-jain1, vigneshr, mchehab, robh,
	krzk+dt, p.zabel, conor+dt, sakari.ailus, hverkuil-cisco,
	changhuang.liang, jack.zhu, sjoerd, dan.carpenter, hverkuil+cisco,
	linux-kernel, linux-media, devicetree, jai.luthra,
	laurent.pinchart, mripard

Hi

Quoting Tomi Valkeinen (2026-01-15 18:16:14)
> Hi,
> 
> On 30/12/2025 10:32, Rishikesh Donadkar wrote:
> > From: Jai Luthra <jai.luthra@ideasonboard.com>
> > 
> > Add support for runtime power-management to enable powering off the
> > shared power domain between Cadence CSI2RX and TI CSI2RX wrapper when
> > the device(s) are not in use.
> > 
> > When powering off the IP, the PSI-L endpoint loses the paired DMA
> > channels. Thus we have to release the DMA channels at runtime suspend
> > and request them again at resume.
> > 
> > Tested-by: Rishikesh Donadkar <r-donadkar@ti.com>
> > Reviewed-by: Rishikesh Donadkar <r-donadkar@ti.com>
> > Signed-off-by: Jai Luthra <jai.luthra@ideasonboard.com>
> > Co-developed-by: Rishikesh Donadkar <r-donadkar@ti.com>
> > Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
> > ---
> >  drivers/media/platform/ti/Kconfig             |  1 +
> >  .../platform/ti/j721e-csi2rx/j721e-csi2rx.c   | 59 +++++++++++++++----
> >  2 files changed, 50 insertions(+), 10 deletions(-)
> 
> Should pixel interface reset belong to the runtime suspend/resume
> functions? (Not a suggestion, just a question =).

Yeah that would indeed make things cleaner, but the problem is that pixel
reset needs to be asserted before we stop streaming on the source, as it is
currently done in ti_csi2rx_stop_streaming(), to prevent issues with stale
data on SoCs where the power domain doesn't turn off due to other
dependencies.

I am also not sure the correct ordering would be possible if it is tied to
the pm_runtime_put due to the two subdevs (cadence and shim) between the
video node and the camera, where both need to be awake for DMA transactions
to complete.

It still might be worth investigating, but I think for this iteration of
the driver it is better to keep it separate, as moving the pixel reset has
lead to weird bugs due to stale data in the past.

> 
> Reviewed-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> 
>  Tomi
> 

Thanks,
    Jai

> > diff --git a/drivers/media/platform/ti/Kconfig b/drivers/media/platform/ti/Kconfig
> > index 3bc4aa35887e6..a808063e24779 100644
> > --- a/drivers/media/platform/ti/Kconfig
> > +++ b/drivers/media/platform/ti/Kconfig
> > @@ -70,6 +70,7 @@ config VIDEO_TI_J721E_CSI2RX
> >       depends on VIDEO_CADENCE_CSI2RX
> >       depends on PHY_CADENCE_DPHY_RX || COMPILE_TEST
> >       depends on ARCH_K3 || COMPILE_TEST
> > +     depends on PM
> >       select VIDEOBUF2_DMA_CONTIG
> >       select V4L2_FWNODE
> >       help
> > diff --git a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
> > index 3922bd67e78da..72da58738e16e 100644
> > --- a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
> > +++ b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
> > @@ -13,6 +13,7 @@
> >  #include <linux/module.h>
> >  #include <linux/of_platform.h>
> >  #include <linux/platform_device.h>
> > +#include <linux/pm_runtime.h>
> >  #include <linux/property.h>
> >  
> >  #include <media/cadence/cdns-csi2rx.h>
> > @@ -964,12 +965,16 @@ static int ti_csi2rx_start_streaming(struct vb2_queue *vq, unsigned int count)
> >       unsigned long flags;
> >       int ret = 0;
> >  
> > +     ret = pm_runtime_resume_and_get(csi->dev);
> > +     if (ret)
> > +             return ret;
> > +
> >       spin_lock_irqsave(&dma->lock, flags);
> >       if (list_empty(&dma->queue))
> >               ret = -EIO;
> >       spin_unlock_irqrestore(&dma->lock, flags);
> >       if (ret)
> > -             return ret;
> > +             goto err;
> >  
> >       ret = video_device_pipeline_start(&ctx->vdev, &csi->pipe);
> >       if (ret)
> > @@ -991,6 +996,8 @@ static int ti_csi2rx_start_streaming(struct vb2_queue *vq, unsigned int count)
> >       writel(0, csi->shim + SHIM_DMACNTX(ctx->idx));
> >  err:
> >       ti_csi2rx_cleanup_buffers(ctx, VB2_BUF_STATE_QUEUED);
> > +     pm_runtime_put(csi->dev);
> > +
> >       return ret;
> >  }
> >  
> > @@ -1022,6 +1029,7 @@ static void ti_csi2rx_stop_streaming(struct vb2_queue *vq)
> >  
> >       ti_csi2rx_stop_dma(ctx);
> >       ti_csi2rx_cleanup_buffers(ctx, VB2_BUF_STATE_ERROR);
> > +     pm_runtime_put(csi->dev);
> >  }
> >  
> >  static const struct vb2_ops csi_vb2_qops = {
> > @@ -1263,7 +1271,6 @@ static void ti_csi2rx_cleanup_notifier(struct ti_csi2rx_dev *csi)
> >  
> >  static void ti_csi2rx_cleanup_ctx(struct ti_csi2rx_ctx *ctx)
> >  {
> > -     dma_release_channel(ctx->dma.chan);
> >       vb2_queue_release(&ctx->vidq);
> >  
> >       video_unregister_device(&ctx->vdev);
> > @@ -1283,7 +1290,7 @@ static int ti_csi2rx_init_vb2q(struct ti_csi2rx_ctx *ctx)
> >       q->ops = &csi_vb2_qops;
> >       q->mem_ops = &vb2_dma_contig_memops;
> >       q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
> > -     q->dev = dmaengine_get_dma_device(ctx->dma.chan);
> > +     q->dev = ctx->csi->dev;
> >       q->lock = &ctx->mutex;
> >       q->min_queued_buffers = 1;
> >       q->allow_cache_hints = 1;
> > @@ -1497,21 +1504,46 @@ static int ti_csi2rx_init_ctx(struct ti_csi2rx_ctx *ctx)
> >       spin_lock_init(&ctx->dma.lock);
> >       ctx->dma.state = TI_CSI2RX_DMA_STOPPED;
> >  
> > -     ret = ti_csi2rx_init_dma(ctx);
> > +     ret = ti_csi2rx_init_vb2q(ctx);
> >       if (ret)
> >               return ret;
> >  
> > -     ret = ti_csi2rx_init_vb2q(ctx);
> > -     if (ret)
> > -             goto cleanup_dma;
> > +     return 0;
> > +}
> > +
> > +static int ti_csi2rx_runtime_suspend(struct device *dev)
> > +{
> > +     struct ti_csi2rx_dev *csi = dev_get_drvdata(dev);
> > +     int i;
> > +
> > +     if (csi->enable_count != 0)
> > +             return -EBUSY;
> > +
> > +     for (i = 0; i < csi->num_ctx; i++)
> > +             dma_release_channel(csi->ctx[i].dma.chan);
> >  
> >       return 0;
> > +}
> >  
> > -cleanup_dma:
> > -     dma_release_channel(ctx->dma.chan);
> > -     return ret;
> > +static int ti_csi2rx_runtime_resume(struct device *dev)
> > +{
> > +     struct ti_csi2rx_dev *csi = dev_get_drvdata(dev);
> > +     unsigned int ret, i;
> > +
> > +     for (i = 0; i < csi->num_ctx; i++) {
> > +             ret = ti_csi2rx_init_dma(&csi->ctx[i]);
> > +             if (ret)
> > +                     return ret;
> > +     }
> > +
> > +     return 0;
> >  }
> >  
> > +static const struct dev_pm_ops ti_csi2rx_pm_ops = {
> > +     RUNTIME_PM_OPS(ti_csi2rx_runtime_suspend, ti_csi2rx_runtime_resume,
> > +                    NULL)
> > +};
> > +
> >  static int ti_csi2rx_probe(struct platform_device *pdev)
> >  {
> >       struct device_node *np = pdev->dev.of_node;
> > @@ -1569,6 +1601,8 @@ static int ti_csi2rx_probe(struct platform_device *pdev)
> >                       goto err_ctx;
> >       }
> >  
> > +     pm_runtime_enable(csi->dev);
> > +
> >       ret = ti_csi2rx_notifier_register(csi);
> >       if (ret)
> >               goto err_ctx;
> > @@ -1601,6 +1635,9 @@ static void ti_csi2rx_remove(struct platform_device *pdev)
> >       struct ti_csi2rx_dev *csi = platform_get_drvdata(pdev);
> >       unsigned int i;
> >  
> > +     if (!pm_runtime_status_suspended(&pdev->dev))
> > +             pm_runtime_set_suspended(&pdev->dev);
> > +
> >       for (i = 0; i < csi->num_ctx; i++)
> >               ti_csi2rx_cleanup_ctx(&csi->ctx[i]);
> >  
> > @@ -1609,6 +1646,7 @@ static void ti_csi2rx_remove(struct platform_device *pdev)
> >       mutex_destroy(&csi->mutex);
> >       dma_free_coherent(csi->dev, csi->drain.len, csi->drain.vaddr,
> >                         csi->drain.paddr);
> > +     pm_runtime_disable(&pdev->dev);
> >  }
> >  
> >  static const struct of_device_id ti_csi2rx_of_match[] = {
> > @@ -1623,6 +1661,7 @@ static struct platform_driver ti_csi2rx_pdrv = {
> >       .driver = {
> >               .name = TI_CSI2RX_MODULE_NAME,
> >               .of_match_table = ti_csi2rx_of_match,
> > +             .pm             = &ti_csi2rx_pm_ops,
> >       },
> >  };
> >  
>

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v9 13/19] media: ti: j721e-csi2rx: add multistream support
  2026-01-15 12:27   ` Tomi Valkeinen
@ 2026-01-20  8:48     ` Rishikesh Donadkar
  0 siblings, 0 replies; 54+ messages in thread
From: Rishikesh Donadkar @ 2026-01-20  8:48 UTC (permalink / raw)
  To: Tomi Valkeinen
  Cc: y-abhilashchandra, devarsht, s-jain1, vigneshr, mchehab, robh,
	krzk+dt, p.zabel, conor+dt, sakari.ailus, hverkuil-cisco,
	jai.luthra, changhuang.liang, jack.zhu, sjoerd, dan.carpenter,
	hverkuil+cisco, linux-kernel, linux-media, devicetree, jai.luthra,
	laurent.pinchart, mripard


On 15/01/26 17:57, Tomi Valkeinen wrote:
> Hi,
>
> On 30/12/2025 10:32, Rishikesh Donadkar wrote:
>> From: Jai Luthra <j-luthra@ti.com>
>>
>> Each CSI2 stream can be multiplexed into 4 independent streams, each
>> identified by its virtual channel number and data type. The incoming
>> data from these streams can be filtered on the basis of either the
>> virtual channel or the data type.
>>
>> To capture this multiplexed stream, the application needs to tell
>> the driver how it wants to route the data. It needs to specify
>> which context should process which stream. This is done via the
>> new routing APIs.
>>
>> Add ioctls to accept routing information from the application and save
>> that in the driver. This can be used when starting streaming on a
>> context to determine which route and consequently which virtual channel
>> it should process.
>>
>> De-assert the pixel interface reset on first start_streaming() and assert
>> it on the last stop_streaming().
>>
>> Reviewed-by: Yemike Abhilash Chandra <y-abhilashchandra@ti.com>
>> Co-developed-by: Pratyush Yadav <p.yadav@ti.com>
>> Signed-off-by: Pratyush Yadav <p.yadav@ti.com>
>> Signed-off-by: Jai Luthra <j-luthra@ti.com>
>> Co-developed-by: Rishikesh Donadkar <r-donadkar@ti.com>
>> Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
>> ---
>>   .../platform/ti/j721e-csi2rx/j721e-csi2rx.c   | 259 +++++++++++++-----
>>   1 file changed, 189 insertions(+), 70 deletions(-)
>>
>> diff --git a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
>> index 6f9f34aa26f1b..4a063364f893e 100644
>> --- a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
>> +++ b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
>> @@ -137,6 +137,7 @@ struct ti_csi2rx_dev {
>>   		dma_addr_t		paddr;
>>   		size_t			len;
>>   	} drain;
>> +	bool                            vc_cached;
>>   };
>>   
>>   static inline struct ti_csi2rx_dev *to_csi2rx_dev(struct v4l2_subdev *sd)
>> @@ -144,17 +145,6 @@ static inline struct ti_csi2rx_dev *to_csi2rx_dev(struct v4l2_subdev *sd)
>>   	return container_of(sd, struct ti_csi2rx_dev, subdev);
>>   }
>>   
>> -static const struct v4l2_mbus_framefmt ti_csi2rx_default_fmt = {
>> -	.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,
>> -};
>> -
>>   static const struct ti_csi2rx_fmt ti_csi2rx_formats[] = {
>>   	{
>>   		.fourcc			= V4L2_PIX_FMT_YUYV,
>> @@ -567,8 +557,10 @@ static void ti_csi2rx_setup_shim(struct ti_csi2rx_ctx *ctx)
>>   	fmt = find_format_by_fourcc(ctx->v_fmt.fmt.pix.pixelformat);
>>   
>>   	/* De-assert the pixel interface reset. */
>> -	reg = SHIM_CNTL_PIX_RST;
>> -	writel(reg, csi->shim + SHIM_CNTL);
>> +	if (!csi->enable_count) {
>> +		reg = SHIM_CNTL_PIX_RST;
>> +		writel(reg, csi->shim + SHIM_CNTL);
>> +	}
> I think this is a bit messy. Here we have ti_csi2rx_setup_shim() which
> is passed ctx as a parameter, but it also does non-context stuff, if
> this is the first enable.
Right, I will shift the pixel reset code from ti_csi2rx_setup_shim() to 
ti_csi2rx_sd_enable_streams() function to make things symmetric in the 
start and stop path.
>
> In ti_csi2rx_sd_enable_streams(), we check !csi->vc_cached (which,
> afaics, is essentially also "is this the first enable"), and do stuff.
>
> The structure should be so that we have a clear
> setup-things-on-the-first-enable function, and
> setup-things-for-a-context function. Now these two things are mixed in
> together, also with the 'vc_cached' field which I think is not needed at
> all.


Yes, Thanks for pointing out, the vc_cached field can be removed entirely.

>
> Actually, maybe I'm mixing the operation of the video dev and the subdev
> here, as the single file contains both. It still looks confusing: in
> ti_csi2rx_stop_streaming() we assert reset, but we don't de-assert in
> ti_csi2rx_start_streaming(). Instead we deassert in
> ti_csi2rx_setup_shim() which is called from ti_csi2rx_sd_enable_streams().
>
> Please check these out. There most likely should be more symmetry here
> wrt. enabling and disabling things.
>
>>   
>>   	/* Negotiate pixel count from the source */
>>   	ti_csi2rx_request_max_ppc(csi);
>> @@ -889,29 +881,69 @@ static void ti_csi2rx_buffer_queue(struct vb2_buffer *vb)
>>   	}
>>   }
>>   
>> +static int ti_csi2rx_get_stream(struct ti_csi2rx_ctx *ctx)
>> +{
>> +	struct ti_csi2rx_dev *csi = ctx->csi;
>> +	struct media_pad *pad;
>> +	struct v4l2_subdev_state *state;
>> +	struct v4l2_subdev_route *r;
>> +
>> +	/* Get the source pad connected to this ctx */
>> +	pad = media_entity_remote_source_pad_unique(ctx->pad.entity);
>> +	if (!pad) {
>> +		dev_err(csi->dev, "No pad connected to ctx %d\n", ctx->idx);
>> +		return -ENODEV;
>> +	}
>> +
>> +	state = v4l2_subdev_get_locked_active_state(&csi->subdev);
>> +
>> +	for_each_active_route(&state->routing, r) {
>> +		if (r->source_pad == pad->index) {
>> +			ctx->stream = r->sink_stream;
>> +			return 0;
>> +		}
>> +	}
>> +
>> +	/* No route found for this ctx */
>> +	return -ENODEV;
>> +}
>> +
>>   static int ti_csi2rx_get_vc_and_dt(struct ti_csi2rx_ctx *ctx)
>>   {
>>   	struct ti_csi2rx_dev *csi = ctx->csi;
>> +	struct ti_csi2rx_ctx *curr_ctx;
>>   	struct v4l2_mbus_frame_desc fd;
>> -	struct media_pad *pad;
>> -	int ret, i;
>> +	struct media_pad *source_pad;
>> +	int ret;
>> +	unsigned int i, j;
>>   
>> -	pad = media_entity_remote_pad_unique(&csi->subdev.entity, MEDIA_PAD_FL_SOURCE);
>> -	if (!pad)
>> +	/* Get the frame desc form source */
>> +	source_pad = media_entity_remote_pad_unique(&csi->subdev.entity, MEDIA_PAD_FL_SOURCE);
>> +	if (!source_pad)
>>   		return -ENODEV;
>>   
>> -	ret = v4l2_subdev_call(csi->source, pad, get_frame_desc, pad->index, &fd);
>> +	ret = v4l2_subdev_call(csi->source, pad, get_frame_desc, source_pad->index, &fd);
>>   	if (ret)
>>   		return ret;
>>   
>>   	if (fd.type != V4L2_MBUS_FRAME_DESC_TYPE_CSI2)
>>   		return -EINVAL;
>>   
>> -	for (i = 0; i < fd.num_entries; i++) {
>> -		if (ctx->stream == fd.entry[i].stream) {
>> -			ctx->vc = fd.entry[i].bus.csi2.vc;
>> -			ctx->dt = fd.entry[i].bus.csi2.dt;
>> -		}
>> +	for (i = 0; i < csi->num_ctx; i++) {
>> +		curr_ctx = &csi->ctx[i];
>> +
>> +		/* Capture VC 0 by default */
>> +		curr_ctx->vc = 0;
>> +
>> +		ret = ti_csi2rx_get_stream(curr_ctx);
>> +		if (ret)
>> +			continue;
>> +
>> +		for (j = 0; j < fd.num_entries; j++)
>> +			if (curr_ctx->stream == fd.entry[j].stream) {
>> +				curr_ctx->vc = fd.entry[j].bus.csi2.vc;
>> +				curr_ctx->dt = fd.entry[j].bus.csi2.dt;
>> +			}
>>   	}
>>   
>>   	return 0;
>> @@ -922,8 +954,6 @@ static int ti_csi2rx_start_streaming(struct vb2_queue *vq, unsigned int count)
>>   	struct ti_csi2rx_ctx *ctx = vb2_get_drv_priv(vq);
>>   	struct ti_csi2rx_dev *csi = ctx->csi;
>>   	struct ti_csi2rx_dma *dma = &ctx->dma;
>> -	struct ti_csi2rx_buffer *buf;
>> -	const struct ti_csi2rx_fmt *fmt;
>>   	unsigned long flags;
>>   	int ret = 0;
>>   
>> @@ -938,35 +968,9 @@ static int ti_csi2rx_start_streaming(struct vb2_queue *vq, unsigned int count)
>>   	if (ret)
>>   		goto err;
>>   
>> -	ret = ti_csi2rx_get_vc_and_dt(ctx);
>> -	if (ret == -ENOIOCTLCMD) {
>> -		ctx->vc = 0;
>> -		fmt = find_format_by_fourcc(ctx->v_fmt.fmt.pix.pixelformat);
>> -		ctx->dt = fmt->csi_dt;
>> -	} else if (ret < 0) {
>> -		goto err;
>> -	}
>> -
>> -	ti_csi2rx_setup_shim(ctx);
>> -
>> -	ctx->sequence = 0;
>> -
>> -	spin_lock_irqsave(&dma->lock, flags);
>> -	buf = list_entry(dma->queue.next, struct ti_csi2rx_buffer, list);
>> -
>> -	ret = ti_csi2rx_start_dma(ctx, buf);
>> -	if (ret) {
>> -		dev_err(csi->dev, "Failed to start DMA: %d\n", ret);
>> -		spin_unlock_irqrestore(&dma->lock, flags);
>> -		goto err_pipeline;
>> -	}
>> -
>> -	list_move_tail(&buf->list, &dma->submitted);
>> -	dma->state = TI_CSI2RX_DMA_ACTIVE;
>> -	spin_unlock_irqrestore(&dma->lock, flags);
>> -
>> +	/* Start stream 0, we don't allow multiple streams on the source pad */
>>   	ret = v4l2_subdev_enable_streams(&csi->subdev,
>> -					 TI_CSI2RX_PAD_FIRST_SOURCE,
>> +					 TI_CSI2RX_PAD_FIRST_SOURCE + ctx->idx,
>>   					 BIT_U64(0));
>>   	if (ret)
>>   		goto err_dma;
>> @@ -975,7 +979,6 @@ static int ti_csi2rx_start_streaming(struct vb2_queue *vq, unsigned int count)
>>   
>>   err_dma:
>>   	ti_csi2rx_stop_dma(ctx);
>> -err_pipeline:
>>   	video_device_pipeline_stop(&ctx->vdev);
>>   	writel(0, csi->shim + SHIM_CNTL);
>>   	writel(0, csi->shim + SHIM_DMACNTX(ctx->idx));
>> @@ -990,17 +993,26 @@ static void ti_csi2rx_stop_streaming(struct vb2_queue *vq)
>>   	struct ti_csi2rx_dev *csi = ctx->csi;
>>   	int ret;
>>   
>> -	video_device_pipeline_stop(&ctx->vdev);
>> +	mutex_lock(&csi->mutex);
>>   
>> -	writel(0, csi->shim + SHIM_CNTL);
>>   	writel(0, csi->shim + SHIM_DMACNTX(ctx->idx));
>>   
>> +	/* assert pixel reset to prevent stale data */
>> +	if (csi->enable_count == 1) {
>> +		writel(0, csi->shim + SHIM_CNTL);
>> +		csi->vc_cached = false;
>> +	}
>> +
>> +	video_device_pipeline_stop(&ctx->vdev);
>> +
>>   	ret = v4l2_subdev_disable_streams(&csi->subdev,
>> -					  TI_CSI2RX_PAD_FIRST_SOURCE,
>> +					  TI_CSI2RX_PAD_FIRST_SOURCE + ctx->idx,
>>   					  BIT_U64(0));
>>   	if (ret)
>>   		dev_err(csi->dev, "Failed to stop subdev stream\n");
>>   
>> +	mutex_unlock(&csi->mutex);
>> +
>>   	ti_csi2rx_stop_dma(ctx);
>>   	ti_csi2rx_cleanup_buffers(ctx, VB2_BUF_STATE_ERROR);
>>   }
>> @@ -1043,25 +1055,84 @@ static int ti_csi2rx_sd_set_fmt(struct v4l2_subdev *sd,
>>   	fmt = v4l2_subdev_state_get_format(state, format->pad, format->stream);
>>   	*fmt = format->format;
>>   
>> -	fmt = v4l2_subdev_state_get_format(state, TI_CSI2RX_PAD_FIRST_SOURCE,
>> -					   format->stream);
>> +	fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad,
>> +							   format->stream);
>> +	if (!fmt)
>> +		return -EINVAL;
>> +
>>   	*fmt = format->format;
>>   
>>   	return 0;
>>   }
>>   
>> -static int ti_csi2rx_sd_init_state(struct v4l2_subdev *sd,
>> -				   struct v4l2_subdev_state *state)
>> +static int _ti_csi2rx_sd_set_routing(struct v4l2_subdev *sd,
>> +				     struct v4l2_subdev_state *state,
>> +				     struct v4l2_subdev_krouting *routing)
>>   {
>> -	struct v4l2_mbus_framefmt *fmt;
>> +	int ret;
>> +
>> +	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,
>> +	};
>>   
>> -	fmt = v4l2_subdev_state_get_format(state, TI_CSI2RX_PAD_SINK);
>> -	*fmt = ti_csi2rx_default_fmt;
>> +	ret = v4l2_subdev_routing_validate(sd, routing,
>> +					   V4L2_SUBDEV_ROUTING_ONLY_1_TO_1 |
>> +					   V4L2_SUBDEV_ROUTING_NO_SOURCE_MULTIPLEXING);
>>   
>> -	fmt = v4l2_subdev_state_get_format(state, TI_CSI2RX_PAD_FIRST_SOURCE);
>> -	*fmt = ti_csi2rx_default_fmt;
>> +	if (ret)
>> +		return ret;
>>   
>> -	return 0;
>> +	/* Only stream ID 0 allowed on source pads */
>> +	for (unsigned int i = 0; i < routing->num_routes; ++i) {
>> +		const struct v4l2_subdev_route *route = &routing->routes[i];
>> +
>> +		if (route->source_stream != 0)
>> +			return -EINVAL;
>> +	}
>> +
>> +	ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
>> +
>> +	return ret;
>> +}
>> +
>> +static int ti_csi2rx_sd_set_routing(struct v4l2_subdev *sd,
>> +				    struct v4l2_subdev_state *state,
>> +				    enum v4l2_subdev_format_whence which,
>> +				    struct v4l2_subdev_krouting *routing)
>> +{
>> +	struct ti_csi2rx_dev *csi = to_csi2rx_dev(sd);
>> +
>> +	if (csi->enable_count > 0)
>> +		return -EBUSY;
>> +
>> +	return _ti_csi2rx_sd_set_routing(sd, state, routing);
>> +}
>> +
>> +static int ti_csi2rx_sd_init_state(struct v4l2_subdev *sd,
>> +				   struct v4l2_subdev_state *state)
>> +{
>> +	struct v4l2_subdev_route routes[] = { {
>> +		.sink_pad = 0,
>> +		.sink_stream = 0,
>> +		.source_pad = TI_CSI2RX_PAD_FIRST_SOURCE,
>> +		.source_stream = 0,
>> +		.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
>> +	} };
>> +
>> +	struct v4l2_subdev_krouting routing = {
>> +		.num_routes = 1,
>> +		.routes = routes,
>> +	};
>> +
>> +	/* Initialize routing to single route to the fist source pad */
>> +	return _ti_csi2rx_sd_set_routing(sd, state, &routing);
>>   }
>>   
>>   static int ti_csi2rx_sd_enable_streams(struct v4l2_subdev *sd,
>> @@ -1069,15 +1140,58 @@ static int ti_csi2rx_sd_enable_streams(struct v4l2_subdev *sd,
>>   				       u32 pad, u64 streams_mask)
>>   {
>>   	struct ti_csi2rx_dev *csi = to_csi2rx_dev(sd);
>> +	struct ti_csi2rx_ctx *ctx = &csi->ctx[pad - TI_CSI2RX_PAD_FIRST_SOURCE];
>> +	struct ti_csi2rx_dma *dma = &ctx->dma;
>>   	struct media_pad *remote_pad;
>> +	struct ti_csi2rx_buffer *buf;
>> +	const struct ti_csi2rx_fmt *fmt;
>> +	unsigned long flags;
>> +	u64 sink_streams;
>>   	int ret = 0;
>>   
>> +	ret = ti_csi2rx_get_stream(ctx);
>> +	if (ret)
>> +		return ret;
>> +
>> +	/* Get the VC and DT for all enabled ctx on first stream start */
>> +	if (!csi->vc_cached) {
>> +		ret = ti_csi2rx_get_vc_and_dt(ctx);
>> +		if (ret == -ENOIOCTLCMD) {
>> +			ctx->vc = 0;
>> +			fmt = find_format_by_fourcc(ctx->v_fmt.fmt.pix.pixelformat);
>> +			ctx->dt = fmt->csi_dt;
>> +		} else if (ret < 0) {
>> +			return ret;
>> +		}
>> +		csi->vc_cached = true;
>> +	}
>> +
>> +	ti_csi2rx_setup_shim(ctx);
>> +	ctx->sequence = 0;
>> +
>> +	spin_lock_irqsave(&dma->lock, flags);
>> +	buf = list_entry(dma->queue.next, struct ti_csi2rx_buffer, list);
>> +
>> +	ret = ti_csi2rx_start_dma(ctx, buf);
>> +	if (ret) {
>> +		dev_err(csi->dev, "Failed to start DMA: %d\n", ret);
>> +		spin_unlock_irqrestore(&dma->lock, flags);
>> +		return ret;
>> +	}
>> +
>> +	list_move_tail(&buf->list, &dma->submitted);
>> +	dma->state = TI_CSI2RX_DMA_ACTIVE;
>> +	spin_unlock_irqrestore(&dma->lock, flags);
>> +
>>   	remote_pad = media_entity_remote_source_pad_unique(&csi->subdev.entity);
>>   	if (!remote_pad)
>>   		return -ENODEV;
>> +	sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
>> +						       TI_CSI2RX_PAD_SINK,
>> +						       &streams_mask);
>>   
>>   	ret = v4l2_subdev_enable_streams(csi->source, remote_pad->index,
>> -					 BIT_U64(0));
>> +					 sink_streams);
>>   	if (ret)
>>   		return ret;
>>   
>> @@ -1092,17 +1206,21 @@ static int ti_csi2rx_sd_disable_streams(struct v4l2_subdev *sd,
>>   {
>>   	struct ti_csi2rx_dev *csi = to_csi2rx_dev(sd);
>>   	struct media_pad *remote_pad;
>> +	u64 sink_streams;
>>   	int ret = 0;
>>   
>>   	remote_pad = media_entity_remote_source_pad_unique(&csi->subdev.entity);
>>   	if (!remote_pad)
>>   		return -ENODEV;
>> +	sink_streams = v4l2_subdev_state_xlate_streams(state, pad,
>> +						       TI_CSI2RX_PAD_SINK,
>> +						       &streams_mask);
>>   
>>   	if (csi->enable_count == 0)
>>   		return -EINVAL;
> Correct me if I'm wrong, but I don't think this should ever happen. If
> you want, you can place it in the beginning of the func, with a WARN_ON().


Will do.


Rishikesh

>
>>   
>>   	ret = v4l2_subdev_disable_streams(csi->source, remote_pad->index,
>> -					  BIT_U64(0));
>> +					  sink_streams);
>>   	if (!ret)
>>   		--csi->enable_count;
>>   
>> @@ -1111,6 +1229,7 @@ static int ti_csi2rx_sd_disable_streams(struct v4l2_subdev *sd,
>>   
>>   static const struct v4l2_subdev_pad_ops ti_csi2rx_subdev_pad_ops = {
>>   	.enum_mbus_code	= ti_csi2rx_enum_mbus_code,
>> +	.set_routing = ti_csi2rx_sd_set_routing,
>>   	.get_fmt = v4l2_subdev_get_fmt,
>>   	.set_fmt = ti_csi2rx_sd_set_fmt,
>>   	.enable_streams = ti_csi2rx_sd_enable_streams,
>> @@ -1289,7 +1408,7 @@ static int ti_csi2rx_v4l2_init(struct ti_csi2rx_dev *csi)
>>   	v4l2_subdev_init(sd, &ti_csi2rx_subdev_ops);
>>   	sd->internal_ops = &ti_csi2rx_internal_ops;
>>   	sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
>> -	sd->flags = V4L2_SUBDEV_FL_HAS_DEVNODE;
>> +	sd->flags = V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS;
>>   	strscpy(sd->name, dev_name(csi->dev), sizeof(sd->name));
>>   	sd->dev = csi->dev;
>>   	sd->entity.ops = &ti_csi2rx_subdev_entity_ops;
>

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v9 13/19] media: ti: j721e-csi2rx: add multistream support
  2026-01-15 12:28   ` Tomi Valkeinen
@ 2026-01-20  8:52     ` Rishikesh Donadkar
  0 siblings, 0 replies; 54+ messages in thread
From: Rishikesh Donadkar @ 2026-01-20  8:52 UTC (permalink / raw)
  To: Tomi Valkeinen
  Cc: y-abhilashchandra, devarsht, s-jain1, vigneshr, mchehab, robh,
	krzk+dt, p.zabel, conor+dt, sakari.ailus, hverkuil-cisco,
	jai.luthra, changhuang.liang, jack.zhu, sjoerd, dan.carpenter,
	hverkuil+cisco, linux-kernel, linux-media, devicetree, jai.luthra,
	laurent.pinchart, mripard


On 15/01/26 17:58, Tomi Valkeinen wrote:
> Hi,


Hi Tomi,

Thank you for the review !

>
> On 30/12/2025 10:32, Rishikesh Donadkar wrote:
>> From: Jai Luthra <j-luthra@ti.com>
>>
>> Each CSI2 stream can be multiplexed into 4 independent streams, each
>> identified by its virtual channel number and data type. The incoming
> Forgot to ask about this one. Why 4?


Yes, this should be 32, as discussed here 
https://lore.kernel.org/all/176528199956.20066.17866034505160159556@freya/

Will change


Rishikesh

>
>   Tomi
>

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v9 06/19] media: ti: j721e-csi2rx: add a subdev for the core device
  2026-01-15 12:56       ` Tomi Valkeinen
@ 2026-01-20 23:25         ` Laurent Pinchart
  2026-01-21  7:38           ` Tomi Valkeinen
  0 siblings, 1 reply; 54+ messages in thread
From: Laurent Pinchart @ 2026-01-20 23:25 UTC (permalink / raw)
  To: Tomi Valkeinen
  Cc: Jai Luthra, Sakari Ailus, y-abhilashchandra, devarsht, s-jain1,
	vigneshr, mchehab, robh, krzk+dt, p.zabel, conor+dt,
	hverkuil-cisco, changhuang.liang, jack.zhu, sjoerd, dan.carpenter,
	hverkuil+cisco, linux-kernel, linux-media, devicetree, jai.luthra,
	mripard, Rishikesh Donadkar

On Thu, Jan 15, 2026 at 02:56:21PM +0200, Tomi Valkeinen wrote:
> On 15/01/2026 08:36, Jai Luthra wrote:
> > Quoting Tomi Valkeinen (2026-01-14 20:51:49)
> >> On 30/12/2025 10:32, Rishikesh Donadkar wrote:
> >>> From: Jai Luthra <j-luthra@ti.com>
> >>>
> >>> With single stream capture, it was simpler to use the video device as
> >>> the media entity representing the main TI CSI2RX device. Now with multi
> >>> stream capture coming into the picture, the model has shifted to each
> >>> video device having a link to the main device's subdev. The routing
> >>> would then be set on this subdev.
> >>>
> >>> Add this subdev, link each context to this subdev's entity and link the
> >>> subdev's entity to the source. Also add an array of media pads. It will
> >>> have one sink pad and source pads equal to the number of contexts.
> >>>
> >>> Support the new enable_stream()/disable_stream() APIs in the subdev
> >>> instead of s_stream() hook.
> >>>
> >>> Reviewed-by: Yemike Abhilash Chandra <y-abhilashchandra@ti.com>
> >>> Co-developed-by: Pratyush Yadav <p.yadav@ti.com>
> >>> Signed-off-by: Pratyush Yadav <p.yadav@ti.com>
> >>> Signed-off-by: Jai Luthra <j-luthra@ti.com>
> >>> Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
> >>> ---
> > 
> > [...]
> > 
> >>> @@ -981,48 +1138,52 @@ static int ti_csi2rx_link_validate(struct media_link *link)
> >>>       struct ti_csi2rx_ctx *ctx = container_of(vdev, struct ti_csi2rx_ctx, vdev);
> >>>       struct ti_csi2rx_dev *csi = ctx->csi;
> >>>       struct v4l2_pix_format *csi_fmt = &ctx->v_fmt.fmt.pix;
> >>> -     struct v4l2_subdev_format source_fmt = {
> >>> -             .which  = V4L2_SUBDEV_FORMAT_ACTIVE,
> >>> -             .pad    = link->source->index,
> >>> -     };
> >>> +     struct v4l2_mbus_framefmt *format;
> >>> +     struct v4l2_subdev_state *state;
> >>>       const struct ti_csi2rx_fmt *ti_fmt;
> >>> -     int ret;
> >>>  
> >>> -     ret = v4l2_subdev_call_state_active(csi->source, pad,
> >>> -                                         get_fmt, &source_fmt);
> >>> -     if (ret)
> >>> -             return ret;
> >>> +     state = v4l2_subdev_lock_and_get_active_state(&csi->subdev);
> >>> +     format = v4l2_subdev_state_get_format(state, link->source->index, 0);
> >>> +     v4l2_subdev_unlock_state(state);
> >>>  
> >>> -     if (source_fmt.format.width != csi_fmt->width) {
> >>> +     if (!format) {
> >>> +             dev_dbg(csi->dev,
> >>> +                     "Skipping validation as no format present on \"%s\":%u:0\n",
> >>> +                     link->source->entity->name, link->source->index);
> >>> +             return 0;
> >>
> >> Isn't this an error?
> > 
> > Well, the j7 shim subdev introduced here has immutable and active links to
> > all the video nodes, for each DMA channel (taken from DT), many of which
> > may be unused for certain setups, and thus there might not be any valid
> > format on the subdev source pad corresponding to an unused video node.
> > 
> > Jacopo had a similar comment on v2, see this discussion (grep for Mali):
> > https://lore.kernel.org/linux-media/4mnlnsj4co3agvln4qsasmgvgwiyoo7yu2h5wyh4rmzzafhm5u@avhnbw7iknms/
> > 
> > I know other drivers use a different approach with mutable links, so it
> > would be good if you/Laurent/Sakari can give your opinions on if only one
> > of these two approaches should be taken for multi-stream pipelines.
>
> I see.
> 
> Well, I don't have a definite answer. With some thinking both options
> make certain sense. It makes sense to keep the links immutable and
> always enabled, as there's no configuration that can be done. On the
> other hand, it makes sense to require the unused links to be disabled,
> as, well, they are not used.

I'm not familiar with the implications this would have on this driver,
but generally speaking, if a stream is added to the media pipeline by
the pipeline build algorithm, then it is expected that applications
would have configured it correctly. Streams that are not used are
expected to be disabled if they would otherwise be added to the
pipeline.

-- 
Regards,

Laurent Pinchart

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v9 06/19] media: ti: j721e-csi2rx: add a subdev for the core device
  2026-01-20 23:25         ` Laurent Pinchart
@ 2026-01-21  7:38           ` Tomi Valkeinen
  2026-01-21 10:52             ` Laurent Pinchart
  0 siblings, 1 reply; 54+ messages in thread
From: Tomi Valkeinen @ 2026-01-21  7:38 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Jai Luthra, Sakari Ailus, y-abhilashchandra, devarsht, s-jain1,
	vigneshr, mchehab, robh, krzk+dt, p.zabel, conor+dt,
	hverkuil-cisco, changhuang.liang, jack.zhu, sjoerd, dan.carpenter,
	hverkuil+cisco, linux-kernel, linux-media, devicetree, jai.luthra,
	mripard, Rishikesh Donadkar

Hi,

On 21/01/2026 01:25, Laurent Pinchart wrote:
> On Thu, Jan 15, 2026 at 02:56:21PM +0200, Tomi Valkeinen wrote:
>> On 15/01/2026 08:36, Jai Luthra wrote:
>>> Quoting Tomi Valkeinen (2026-01-14 20:51:49)
>>>> On 30/12/2025 10:32, Rishikesh Donadkar wrote:
>>>>> From: Jai Luthra <j-luthra@ti.com>
>>>>>
>>>>> With single stream capture, it was simpler to use the video device as
>>>>> the media entity representing the main TI CSI2RX device. Now with multi
>>>>> stream capture coming into the picture, the model has shifted to each
>>>>> video device having a link to the main device's subdev. The routing
>>>>> would then be set on this subdev.
>>>>>
>>>>> Add this subdev, link each context to this subdev's entity and link the
>>>>> subdev's entity to the source. Also add an array of media pads. It will
>>>>> have one sink pad and source pads equal to the number of contexts.
>>>>>
>>>>> Support the new enable_stream()/disable_stream() APIs in the subdev
>>>>> instead of s_stream() hook.
>>>>>
>>>>> Reviewed-by: Yemike Abhilash Chandra <y-abhilashchandra@ti.com>
>>>>> Co-developed-by: Pratyush Yadav <p.yadav@ti.com>
>>>>> Signed-off-by: Pratyush Yadav <p.yadav@ti.com>
>>>>> Signed-off-by: Jai Luthra <j-luthra@ti.com>
>>>>> Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
>>>>> ---
>>>
>>> [...]
>>>
>>>>> @@ -981,48 +1138,52 @@ static int ti_csi2rx_link_validate(struct media_link *link)
>>>>>       struct ti_csi2rx_ctx *ctx = container_of(vdev, struct ti_csi2rx_ctx, vdev);
>>>>>       struct ti_csi2rx_dev *csi = ctx->csi;
>>>>>       struct v4l2_pix_format *csi_fmt = &ctx->v_fmt.fmt.pix;
>>>>> -     struct v4l2_subdev_format source_fmt = {
>>>>> -             .which  = V4L2_SUBDEV_FORMAT_ACTIVE,
>>>>> -             .pad    = link->source->index,
>>>>> -     };
>>>>> +     struct v4l2_mbus_framefmt *format;
>>>>> +     struct v4l2_subdev_state *state;
>>>>>       const struct ti_csi2rx_fmt *ti_fmt;
>>>>> -     int ret;
>>>>>  
>>>>> -     ret = v4l2_subdev_call_state_active(csi->source, pad,
>>>>> -                                         get_fmt, &source_fmt);
>>>>> -     if (ret)
>>>>> -             return ret;
>>>>> +     state = v4l2_subdev_lock_and_get_active_state(&csi->subdev);
>>>>> +     format = v4l2_subdev_state_get_format(state, link->source->index, 0);
>>>>> +     v4l2_subdev_unlock_state(state);
>>>>>  
>>>>> -     if (source_fmt.format.width != csi_fmt->width) {
>>>>> +     if (!format) {
>>>>> +             dev_dbg(csi->dev,
>>>>> +                     "Skipping validation as no format present on \"%s\":%u:0\n",
>>>>> +                     link->source->entity->name, link->source->index);
>>>>> +             return 0;
>>>>
>>>> Isn't this an error?
>>>
>>> Well, the j7 shim subdev introduced here has immutable and active links to
>>> all the video nodes, for each DMA channel (taken from DT), many of which
>>> may be unused for certain setups, and thus there might not be any valid
>>> format on the subdev source pad corresponding to an unused video node.
>>>
>>> Jacopo had a similar comment on v2, see this discussion (grep for Mali):
>>> https://lore.kernel.org/linux-media/4mnlnsj4co3agvln4qsasmgvgwiyoo7yu2h5wyh4rmzzafhm5u@avhnbw7iknms/
>>>
>>> I know other drivers use a different approach with mutable links, so it
>>> would be good if you/Laurent/Sakari can give your opinions on if only one
>>> of these two approaches should be taken for multi-stream pipelines.
>>
>> I see.
>>
>> Well, I don't have a definite answer. With some thinking both options
>> make certain sense. It makes sense to keep the links immutable and
>> always enabled, as there's no configuration that can be done. On the
>> other hand, it makes sense to require the unused links to be disabled,
>> as, well, they are not used.
> 
> I'm not familiar with the implications this would have on this driver,
> but generally speaking, if a stream is added to the media pipeline by
> the pipeline build algorithm, then it is expected that applications
> would have configured it correctly. Streams that are not used are
> expected to be disabled if they would otherwise be added to the
> pipeline.
> 

I think the thing here is that the driver creates immutable
always-enabled media links between the videodevs and the first subdev.
Then, say, if only one stream is being used, only one of those links is
actually used, and for every other link the above check fails as there's
no stream, so no format.

In TI CAL driver the links were mutable, and unused links had to be
disabled. There it made sense as the links had to be configurable (there
were two PHYs). Here, there's no configuration needed, so immutable
links make sense, but then they're enabled even when actually not used.

 Tomi


^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v9 06/19] media: ti: j721e-csi2rx: add a subdev for the core device
  2026-01-14 15:21   ` Tomi Valkeinen
  2026-01-15  6:36     ` Jai Luthra
@ 2026-01-21  9:10     ` Rishikesh Donadkar
  1 sibling, 0 replies; 54+ messages in thread
From: Rishikesh Donadkar @ 2026-01-21  9:10 UTC (permalink / raw)
  To: Tomi Valkeinen
  Cc: y-abhilashchandra, devarsht, s-jain1, vigneshr, mchehab, robh,
	krzk+dt, p.zabel, conor+dt, sakari.ailus, hverkuil-cisco,
	jai.luthra, changhuang.liang, jack.zhu, sjoerd, dan.carpenter,
	hverkuil+cisco, linux-kernel, linux-media, devicetree, jai.luthra,
	laurent.pinchart, mripard


On 14/01/26 20:51, Tomi Valkeinen wrote:
> Hi,

Hi Tomi,

Thank you for the review !

>
> On 30/12/2025 10:32, Rishikesh Donadkar wrote:
>> From: Jai Luthra <j-luthra@ti.com>
>>
>> With single stream capture, it was simpler to use the video device as
>> the media entity representing the main TI CSI2RX device. Now with multi
>> stream capture coming into the picture, the model has shifted to each
>> video device having a link to the main device's subdev. The routing
>> would then be set on this subdev.
>>
>> Add this subdev, link each context to this subdev's entity and link the
>> subdev's entity to the source. Also add an array of media pads. It will
>> have one sink pad and source pads equal to the number of contexts.
>>
>> Support the new enable_stream()/disable_stream() APIs in the subdev
>> instead of s_stream() hook.
>>
>> Reviewed-by: Yemike Abhilash Chandra <y-abhilashchandra@ti.com>
>> Co-developed-by: Pratyush Yadav <p.yadav@ti.com>
>> Signed-off-by: Pratyush Yadav <p.yadav@ti.com>
>> Signed-off-by: Jai Luthra <j-luthra@ti.com>
>> Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
>> ---
>>   .../platform/ti/j721e-csi2rx/j721e-csi2rx.c   | 292 +++++++++++++++---
>>   1 file changed, 248 insertions(+), 44 deletions(-)
>>
>> diff --git a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
>> index f66d68edcd57a..8f49ea2638585 100644
>> --- a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
>> +++ b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
>> @@ -54,6 +54,11 @@
>>   #define MAX_WIDTH_BYTES			SZ_16K
>>   #define MAX_HEIGHT_LINES		SZ_16K
>>   
>> +#define TI_CSI2RX_PAD_SINK		0
>> +#define TI_CSI2RX_PAD_FIRST_SOURCE	1
>> +#define TI_CSI2RX_NUM_SOURCE_PADS	1
>> +#define TI_CSI2RX_NUM_PADS		(1 + TI_CSI2RX_NUM_SOURCE_PADS)
>> +
>>   #define DRAIN_TIMEOUT_MS		50
>>   #define DRAIN_BUFFER_SIZE		SZ_32K
>>   
>> @@ -102,6 +107,7 @@ struct ti_csi2rx_ctx {
>>   	struct mutex			mutex; /* To serialize ioctls. */
>>   	struct v4l2_format		v_fmt;
>>   	struct ti_csi2rx_dma		dma;
>> +	struct media_pad		pad;
>>   	u32				sequence;
>>   	u32				idx;
>>   };
>> @@ -109,12 +115,15 @@ struct ti_csi2rx_ctx {
>>   struct ti_csi2rx_dev {
>>   	struct device			*dev;
>>   	void __iomem			*shim;
>> +	struct mutex			mutex; /* To serialize ioctls. */
> The mutex is not used (in this patch at least).


Yes, now since in the later patches that add multi stream support, we 
access all the shared variables in the enable/disable_streams call which 
are serialized by the framework, I think this mutex is note required. I 
will remove it entirely.

Rishikesh

>
>> +	unsigned int			enable_count;
>>   	struct v4l2_device		v4l2_dev;
>>   	struct media_device		mdev;
>>   	struct media_pipeline		pipe;
>> -	struct media_pad		pad;
>> +	struct media_pad		pads[TI_CSI2RX_NUM_PADS];
>>   	struct v4l2_async_notifier	notifier;
>>   	struct v4l2_subdev		*source;
>> +	struct v4l2_subdev		subdev;
>>   	struct ti_csi2rx_ctx		ctx[TI_CSI2RX_NUM_CTX];
>>   	u8				pix_per_clk;
>>   	/* Buffer to drain stale data from PSI-L endpoint */
>> @@ -125,6 +134,22 @@ struct ti_csi2rx_dev {
>>   	} drain;
>>   };
>>   
>> +static inline struct ti_csi2rx_dev *to_csi2rx_dev(struct v4l2_subdev *sd)
>> +{
>> +	return container_of(sd, struct ti_csi2rx_dev, subdev);
>> +}
>> +
>> +static const struct v4l2_mbus_framefmt ti_csi2rx_default_fmt = {
>> +	.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,
>> +};
>> +
>>   static const struct ti_csi2rx_fmt ti_csi2rx_formats[] = {
>>   	{
>>   		.fourcc			= V4L2_PIX_FMT_YUYV,
>> @@ -422,6 +447,18 @@ static int csi_async_notifier_complete(struct v4l2_async_notifier *notifier)
>>   	struct ti_csi2rx_dev *csi = dev_get_drvdata(notifier->v4l2_dev->dev);
>>   	int ret, i;
>>   
>> +	/* Create link from source to subdev */
>> +	ret = media_create_pad_link(&csi->source->entity,
>> +				    CSI2RX_BRIDGE_SOURCE_PAD,
>> +				    &csi->subdev.entity,
>> +				    TI_CSI2RX_PAD_SINK,
>> +				    MEDIA_LNK_FL_IMMUTABLE |
>> +				    MEDIA_LNK_FL_ENABLED);
>> +
>> +	if (ret)
>> +		return ret;
>> +
>> +	/* Create and link video nodes for all DMA contexts */
>>   	for (i = 0; i < TI_CSI2RX_NUM_CTX; i++) {
>>   		struct ti_csi2rx_ctx *ctx = &csi->ctx[i];
>>   		struct video_device *vdev = &ctx->vdev;
>> @@ -429,15 +466,17 @@ static int csi_async_notifier_complete(struct v4l2_async_notifier *notifier)
>>   		ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
>>   		if (ret)
>>   			goto unregister_dev;
>> -	}
>>   
>> -	ret = media_create_pad_link(&csi->source->entity,
>> -				    CSI2RX_BRIDGE_SOURCE_PAD,
>> -				    &csi->ctx[0].vdev.entity, csi->pad.index,
>> -				    MEDIA_LNK_FL_IMMUTABLE |
>> -				    MEDIA_LNK_FL_ENABLED);
>> -	if (ret)
>> -		goto unregister_dev;
>> +		ret = media_create_pad_link(&csi->subdev.entity,
>> +					    TI_CSI2RX_PAD_FIRST_SOURCE + ctx->idx,
>> +					    &vdev->entity, 0,
>> +					    MEDIA_LNK_FL_IMMUTABLE |
>> +					    MEDIA_LNK_FL_ENABLED);
>> +		if (ret) {
>> +			video_unregister_device(vdev);
>> +			goto unregister_dev;
>> +		}
>> +	}
>>   
>>   	ret = v4l2_device_register_subdev_nodes(&csi->v4l2_dev);
>>   	if (ret)
>> @@ -447,8 +486,10 @@ static int csi_async_notifier_complete(struct v4l2_async_notifier *notifier)
>>   
>>   unregister_dev:
>>   	i--;
>> -	for (; i >= 0; i--)
>> +	for (; i >= 0; i--) {
>> +		media_entity_remove_links(&csi->ctx[i].vdev.entity);
>>   		video_unregister_device(&csi->ctx[i].vdev);
>> +	}
>>   	return ret;
>>   }
>>   
>> @@ -493,14 +534,13 @@ static int ti_csi2rx_notifier_register(struct ti_csi2rx_dev *csi)
>>   }
>>   
>>   /* Request maximum possible pixels per clock from the bridge */
>> -static void ti_csi2rx_request_max_ppc(struct ti_csi2rx_ctx *ctx)
>> +static void ti_csi2rx_request_max_ppc(struct ti_csi2rx_dev *csi)
>>   {
>> -	struct ti_csi2rx_dev *csi = ctx->csi;
>>   	u8 ppc = TI_CSI2RX_MAX_PIX_PER_CLK;
>>   	struct media_pad *pad;
>>   	int ret;
>>   
>> -	pad = media_entity_remote_source_pad_unique(&ctx->vdev.entity);
>> +	pad = media_entity_remote_source_pad_unique(&csi->subdev.entity);
>>   	if (IS_ERR(pad))
>>   		return;
>>   
>> @@ -526,7 +566,7 @@ static void ti_csi2rx_setup_shim(struct ti_csi2rx_ctx *ctx)
>>   	writel(reg, csi->shim + SHIM_CNTL);
>>   
>>   	/* Negotiate pixel count from the source */
>> -	ti_csi2rx_request_max_ppc(ctx);
>> +	ti_csi2rx_request_max_ppc(csi);
>>   
>>   	reg = SHIM_DMACNTX_EN;
>>   	reg |= FIELD_PREP(SHIM_DMACNTX_FMT, fmt->csi_dt);
>> @@ -881,7 +921,9 @@ static int ti_csi2rx_start_streaming(struct vb2_queue *vq, unsigned int count)
>>   	dma->state = TI_CSI2RX_DMA_ACTIVE;
>>   	spin_unlock_irqrestore(&dma->lock, flags);
>>   
>> -	ret = v4l2_subdev_call(csi->source, video, s_stream, 1);
>> +	ret = v4l2_subdev_enable_streams(&csi->subdev,
>> +					 TI_CSI2RX_PAD_FIRST_SOURCE,
>> +					 BIT_U64(0));
>>   	if (ret)
>>   		goto err_dma;
>>   
>> @@ -909,7 +951,9 @@ static void ti_csi2rx_stop_streaming(struct vb2_queue *vq)
>>   	writel(0, csi->shim + SHIM_CNTL);
>>   	writel(0, csi->shim + SHIM_DMACNTX(ctx->idx));
>>   
>> -	ret = v4l2_subdev_call(csi->source, video, s_stream, 0);
>> +	ret = v4l2_subdev_disable_streams(&csi->subdev,
>> +					  TI_CSI2RX_PAD_FIRST_SOURCE,
>> +					  BIT_U64(0));
>>   	if (ret)
>>   		dev_err(csi->dev, "Failed to stop subdev stream\n");
>>   
>> @@ -925,8 +969,121 @@ static const struct vb2_ops csi_vb2_qops = {
>>   	.stop_streaming = ti_csi2rx_stop_streaming,
>>   };
>>   
>> +static int ti_csi2rx_enum_mbus_code(struct v4l2_subdev *subdev,
>> +				    struct v4l2_subdev_state *state,
>> +				    struct v4l2_subdev_mbus_code_enum *code_enum)
>> +{
>> +	if (code_enum->index >= ARRAY_SIZE(ti_csi2rx_formats))
>> +		return -EINVAL;
>> +
>> +	code_enum->code = ti_csi2rx_formats[code_enum->index].code;
>> +
>> +	return 0;
>> +}
>> +
>> +static int ti_csi2rx_sd_set_fmt(struct v4l2_subdev *sd,
>> +				struct v4l2_subdev_state *state,
>> +				struct v4l2_subdev_format *format)
>> +{
>> +	struct v4l2_mbus_framefmt *fmt;
>> +
>> +	/* No transcoding, don't allow setting source fmt */
>> +	if (format->pad > TI_CSI2RX_PAD_SINK)
>> +		return v4l2_subdev_get_fmt(sd, state, format);
>> +
>> +	if (!find_format_by_code(format->format.code))
>> +		format->format.code = ti_csi2rx_formats[0].code;
>> +
>> +	format->format.field = V4L2_FIELD_NONE;
>> +
>> +	fmt = v4l2_subdev_state_get_format(state, format->pad, format->stream);
>> +	*fmt = format->format;
>> +
>> +	fmt = v4l2_subdev_state_get_format(state, TI_CSI2RX_PAD_FIRST_SOURCE,
>> +					   format->stream);
>> +	*fmt = format->format;
>> +
>> +	return 0;
>> +}
>> +
>> +static int ti_csi2rx_sd_init_state(struct v4l2_subdev *sd,
>> +				   struct v4l2_subdev_state *state)
>> +{
>> +	struct v4l2_mbus_framefmt *fmt;
>> +
>> +	fmt = v4l2_subdev_state_get_format(state, TI_CSI2RX_PAD_SINK);
>> +	*fmt = ti_csi2rx_default_fmt;
>> +
>> +	fmt = v4l2_subdev_state_get_format(state, TI_CSI2RX_PAD_FIRST_SOURCE);
>> +	*fmt = ti_csi2rx_default_fmt;
>> +
>> +	return 0;
>> +}
>> +
>> +static int ti_csi2rx_sd_enable_streams(struct v4l2_subdev *sd,
>> +				       struct v4l2_subdev_state *state,
>> +				       u32 pad, u64 streams_mask)
>> +{
>> +	struct ti_csi2rx_dev *csi = to_csi2rx_dev(sd);
>> +	struct media_pad *remote_pad;
>> +	int ret = 0;
>> +
>> +	remote_pad = media_entity_remote_source_pad_unique(&csi->subdev.entity);
>> +	if (!remote_pad)
>> +		return -ENODEV;
>> +
>> +	ret = v4l2_subdev_enable_streams(csi->source, remote_pad->index,
>> +					 BIT_U64(0));
>> +	if (ret)
>> +		return ret;
>> +
>> +	csi->enable_count++;
>> +
>> +	return 0;
>> +}
>> +
>> +static int ti_csi2rx_sd_disable_streams(struct v4l2_subdev *sd,
>> +					struct v4l2_subdev_state *state,
>> +					u32 pad, u64 streams_mask)
>> +{
>> +	struct ti_csi2rx_dev *csi = to_csi2rx_dev(sd);
>> +	struct media_pad *remote_pad;
>> +	int ret = 0;
>> +
>> +	remote_pad = media_entity_remote_source_pad_unique(&csi->subdev.entity);
>> +	if (!remote_pad)
>> +		return -ENODEV;
>> +
>> +	if (csi->enable_count == 0)
>> +		return -EINVAL;
>> +
>> +	ret = v4l2_subdev_disable_streams(csi->source, remote_pad->index,
>> +					  BIT_U64(0));
>> +	if (!ret)
>> +		--csi->enable_count;
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct v4l2_subdev_pad_ops ti_csi2rx_subdev_pad_ops = {
>> +	.enum_mbus_code	= ti_csi2rx_enum_mbus_code,
>> +	.get_fmt = v4l2_subdev_get_fmt,
>> +	.set_fmt = ti_csi2rx_sd_set_fmt,
>> +	.enable_streams = ti_csi2rx_sd_enable_streams,
>> +	.disable_streams = ti_csi2rx_sd_disable_streams,
>> +};
>> +
>> +static const struct v4l2_subdev_ops ti_csi2rx_subdev_ops = {
>> +	.pad = &ti_csi2rx_subdev_pad_ops,
>> +};
>> +
>> +static const struct v4l2_subdev_internal_ops ti_csi2rx_internal_ops = {
>> +	.init_state = ti_csi2rx_sd_init_state,
>> +};
>> +
>>   static void ti_csi2rx_cleanup_v4l2(struct ti_csi2rx_dev *csi)
>>   {
>> +	v4l2_subdev_cleanup(&csi->subdev);
>>   	media_device_unregister(&csi->mdev);
>>   	v4l2_device_unregister(&csi->v4l2_dev);
>>   	media_device_cleanup(&csi->mdev);
>> @@ -981,48 +1138,52 @@ static int ti_csi2rx_link_validate(struct media_link *link)
>>   	struct ti_csi2rx_ctx *ctx = container_of(vdev, struct ti_csi2rx_ctx, vdev);
>>   	struct ti_csi2rx_dev *csi = ctx->csi;
>>   	struct v4l2_pix_format *csi_fmt = &ctx->v_fmt.fmt.pix;
>> -	struct v4l2_subdev_format source_fmt = {
>> -		.which	= V4L2_SUBDEV_FORMAT_ACTIVE,
>> -		.pad	= link->source->index,
>> -	};
>> +	struct v4l2_mbus_framefmt *format;
>> +	struct v4l2_subdev_state *state;
>>   	const struct ti_csi2rx_fmt *ti_fmt;
>> -	int ret;
>>   
>> -	ret = v4l2_subdev_call_state_active(csi->source, pad,
>> -					    get_fmt, &source_fmt);
>> -	if (ret)
>> -		return ret;
>> +	state = v4l2_subdev_lock_and_get_active_state(&csi->subdev);
>> +	format = v4l2_subdev_state_get_format(state, link->source->index, 0);
>> +	v4l2_subdev_unlock_state(state);
>>   
>> -	if (source_fmt.format.width != csi_fmt->width) {
>> +	if (!format) {
>> +		dev_dbg(csi->dev,
>> +			"Skipping validation as no format present on \"%s\":%u:0\n",
>> +			link->source->entity->name, link->source->index);
>> +		return 0;
> Isn't this an error?
>
>   Tomi
>
>
>> +	}
>> +
>> +	if (format->width != csi_fmt->width) {
>>   		dev_dbg(csi->dev, "Width does not match (source %u, sink %u)\n",
>> -			source_fmt.format.width, csi_fmt->width);
>> +			format->width, csi_fmt->width);
>>   		return -EPIPE;
>>   	}
>>   
>> -	if (source_fmt.format.height != csi_fmt->height) {
>> +	if (format->height != csi_fmt->height) {
>>   		dev_dbg(csi->dev, "Height does not match (source %u, sink %u)\n",
>> -			source_fmt.format.height, csi_fmt->height);
>> +			format->height, csi_fmt->height);
>>   		return -EPIPE;
>>   	}
>>   
>> -	if (source_fmt.format.field != csi_fmt->field &&
>> +	if (format->field != csi_fmt->field &&
>>   	    csi_fmt->field != V4L2_FIELD_NONE) {
>>   		dev_dbg(csi->dev, "Field does not match (source %u, sink %u)\n",
>> -			source_fmt.format.field, csi_fmt->field);
>> +			format->field, csi_fmt->field);
>>   		return -EPIPE;
>>   	}
>>   
>> -	ti_fmt = find_format_by_code(source_fmt.format.code);
>> +	ti_fmt = find_format_by_code(format->code);
>>   	if (!ti_fmt) {
>>   		dev_dbg(csi->dev, "Media bus format 0x%x not supported\n",
>> -			source_fmt.format.code);
>> +			format->code);
>>   		return -EPIPE;
>>   	}
>>   
>>   	if (ti_fmt->fourcc != csi_fmt->pixelformat) {
>>   		dev_dbg(csi->dev,
>> -			"Cannot transform source fmt 0x%x to sink fmt 0x%x\n",
>> -			ti_fmt->fourcc, csi_fmt->pixelformat);
>> +			"Cannot transform \"%s\":%u format %p4cc to %p4cc\n",
>> +			link->source->entity->name, link->source->index,
>> +			&ti_fmt->fourcc, &csi_fmt->pixelformat);
>>   		return -EPIPE;
>>   	}
>>   
>> @@ -1033,6 +1194,10 @@ static const struct media_entity_operations ti_csi2rx_video_entity_ops = {
>>   	.link_validate = ti_csi2rx_link_validate,
>>   };
>>   
>> +static const struct media_entity_operations ti_csi2rx_subdev_entity_ops = {
>> +	.link_validate = v4l2_subdev_link_validate,
>> +};
>> +
>>   static int ti_csi2rx_init_dma(struct ti_csi2rx_ctx *ctx)
>>   {
>>   	struct dma_slave_config cfg = {
>> @@ -1058,6 +1223,7 @@ static int ti_csi2rx_init_dma(struct ti_csi2rx_ctx *ctx)
>>   static int ti_csi2rx_v4l2_init(struct ti_csi2rx_dev *csi)
>>   {
>>   	struct media_device *mdev = &csi->mdev;
>> +	struct v4l2_subdev *sd = &csi->subdev;
>>   	int ret;
>>   
>>   	mdev->dev = csi->dev;
>> @@ -1070,16 +1236,51 @@ static int ti_csi2rx_v4l2_init(struct ti_csi2rx_dev *csi)
>>   
>>   	ret = v4l2_device_register(csi->dev, &csi->v4l2_dev);
>>   	if (ret)
>> -		return ret;
>> +		goto cleanup_media;
>>   
>>   	ret = media_device_register(mdev);
>> -	if (ret) {
>> -		v4l2_device_unregister(&csi->v4l2_dev);
>> -		media_device_cleanup(mdev);
>> -		return ret;
>> -	}
>> +	if (ret)
>> +		goto unregister_v4l2;
>> +
>> +	v4l2_subdev_init(sd, &ti_csi2rx_subdev_ops);
>> +	sd->internal_ops = &ti_csi2rx_internal_ops;
>> +	sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
>> +	sd->flags = V4L2_SUBDEV_FL_HAS_DEVNODE;
>> +	strscpy(sd->name, dev_name(csi->dev), sizeof(sd->name));
>> +	sd->dev = csi->dev;
>> +	sd->entity.ops = &ti_csi2rx_subdev_entity_ops;
>> +
>> +	csi->pads[TI_CSI2RX_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
>> +
>> +	for (unsigned int i = TI_CSI2RX_PAD_FIRST_SOURCE;
>> +	     i < TI_CSI2RX_NUM_PADS; i++)
>> +		csi->pads[i].flags = MEDIA_PAD_FL_SOURCE;
>> +
>> +	ret = media_entity_pads_init(&sd->entity, ARRAY_SIZE(csi->pads),
>> +				     csi->pads);
>> +	if (ret)
>> +		goto unregister_media;
>> +
>> +	ret = v4l2_subdev_init_finalize(sd);
>> +	if (ret)
>> +		goto unregister_media;
>> +
>> +	ret = v4l2_device_register_subdev(&csi->v4l2_dev, sd);
>> +	if (ret)
>> +		goto cleanup_subdev;
>>   
>>   	return 0;
>> +
>> +cleanup_subdev:
>> +	v4l2_subdev_cleanup(sd);
>> +unregister_media:
>> +	media_device_unregister(mdev);
>> +unregister_v4l2:
>> +	v4l2_device_unregister(&csi->v4l2_dev);
>> +cleanup_media:
>> +	media_device_cleanup(mdev);
>> +
>> +	return ret;
>>   }
>>   
>>   static int ti_csi2rx_init_ctx(struct ti_csi2rx_ctx *ctx)
>> @@ -1106,9 +1307,9 @@ static int ti_csi2rx_init_ctx(struct ti_csi2rx_ctx *ctx)
>>   
>>   	ti_csi2rx_fill_fmt(fmt, &ctx->v_fmt);
>>   
>> -	csi->pad.flags = MEDIA_PAD_FL_SINK;
>> +	ctx->pad.flags = MEDIA_PAD_FL_SINK;
>>   	vdev->entity.ops = &ti_csi2rx_video_entity_ops;
>> -	ret = media_entity_pads_init(&ctx->vdev.entity, 1, &csi->pad);
>> +	ret = media_entity_pads_init(&ctx->vdev.entity, 1, &ctx->pad);
>>   	if (ret)
>>   		return ret;
>>   
>> @@ -1169,6 +1370,8 @@ static int ti_csi2rx_probe(struct platform_device *pdev)
>>   	if (!csi->drain.vaddr)
>>   		return -ENOMEM;
>>   
>> +	mutex_init(&csi->mutex);
>> +
>>   	ret = ti_csi2rx_v4l2_init(csi);
>>   	if (ret)
>>   		goto err_v4l2;
>> @@ -1201,6 +1404,7 @@ static int ti_csi2rx_probe(struct platform_device *pdev)
>>   		ti_csi2rx_cleanup_ctx(&csi->ctx[i]);
>>   	ti_csi2rx_cleanup_v4l2(csi);
>>   err_v4l2:
>> +	mutex_destroy(&csi->mutex);
>>   	dma_free_coherent(csi->dev, csi->drain.len, csi->drain.vaddr,
>>   			  csi->drain.paddr);
>>   	return ret;
>> @@ -1216,7 +1420,7 @@ static void ti_csi2rx_remove(struct platform_device *pdev)
>>   
>>   	ti_csi2rx_cleanup_notifier(csi);
>>   	ti_csi2rx_cleanup_v4l2(csi);
>> -
>> +	mutex_destroy(&csi->mutex);
>>   	dma_free_coherent(csi->dev, csi->drain.len, csi->drain.vaddr,
>>   			  csi->drain.paddr);
>>   }
>

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v9 06/19] media: ti: j721e-csi2rx: add a subdev for the core device
  2026-01-21  7:38           ` Tomi Valkeinen
@ 2026-01-21 10:52             ` Laurent Pinchart
  2026-01-22  6:53               ` Jai Luthra
  0 siblings, 1 reply; 54+ messages in thread
From: Laurent Pinchart @ 2026-01-21 10:52 UTC (permalink / raw)
  To: Tomi Valkeinen
  Cc: Jai Luthra, Sakari Ailus, y-abhilashchandra, devarsht, s-jain1,
	vigneshr, mchehab, robh, krzk+dt, p.zabel, conor+dt,
	hverkuil-cisco, changhuang.liang, jack.zhu, sjoerd, dan.carpenter,
	hverkuil+cisco, linux-kernel, linux-media, devicetree, jai.luthra,
	mripard, Rishikesh Donadkar

On Wed, Jan 21, 2026 at 09:38:29AM +0200, Tomi Valkeinen wrote:
> On 21/01/2026 01:25, Laurent Pinchart wrote:
> > On Thu, Jan 15, 2026 at 02:56:21PM +0200, Tomi Valkeinen wrote:
> >> On 15/01/2026 08:36, Jai Luthra wrote:
> >>> Quoting Tomi Valkeinen (2026-01-14 20:51:49)
> >>>> On 30/12/2025 10:32, Rishikesh Donadkar wrote:
> >>>>> From: Jai Luthra <j-luthra@ti.com>
> >>>>>
> >>>>> With single stream capture, it was simpler to use the video device as
> >>>>> the media entity representing the main TI CSI2RX device. Now with multi
> >>>>> stream capture coming into the picture, the model has shifted to each
> >>>>> video device having a link to the main device's subdev. The routing
> >>>>> would then be set on this subdev.
> >>>>>
> >>>>> Add this subdev, link each context to this subdev's entity and link the
> >>>>> subdev's entity to the source. Also add an array of media pads. It will
> >>>>> have one sink pad and source pads equal to the number of contexts.
> >>>>>
> >>>>> Support the new enable_stream()/disable_stream() APIs in the subdev
> >>>>> instead of s_stream() hook.
> >>>>>
> >>>>> Reviewed-by: Yemike Abhilash Chandra <y-abhilashchandra@ti.com>
> >>>>> Co-developed-by: Pratyush Yadav <p.yadav@ti.com>
> >>>>> Signed-off-by: Pratyush Yadav <p.yadav@ti.com>
> >>>>> Signed-off-by: Jai Luthra <j-luthra@ti.com>
> >>>>> Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
> >>>>> ---
> >>>
> >>> [...]
> >>>
> >>>>> @@ -981,48 +1138,52 @@ static int ti_csi2rx_link_validate(struct media_link *link)
> >>>>>       struct ti_csi2rx_ctx *ctx = container_of(vdev, struct ti_csi2rx_ctx, vdev);
> >>>>>       struct ti_csi2rx_dev *csi = ctx->csi;
> >>>>>       struct v4l2_pix_format *csi_fmt = &ctx->v_fmt.fmt.pix;
> >>>>> -     struct v4l2_subdev_format source_fmt = {
> >>>>> -             .which  = V4L2_SUBDEV_FORMAT_ACTIVE,
> >>>>> -             .pad    = link->source->index,
> >>>>> -     };
> >>>>> +     struct v4l2_mbus_framefmt *format;
> >>>>> +     struct v4l2_subdev_state *state;
> >>>>>       const struct ti_csi2rx_fmt *ti_fmt;
> >>>>> -     int ret;
> >>>>>  
> >>>>> -     ret = v4l2_subdev_call_state_active(csi->source, pad,
> >>>>> -                                         get_fmt, &source_fmt);
> >>>>> -     if (ret)
> >>>>> -             return ret;
> >>>>> +     state = v4l2_subdev_lock_and_get_active_state(&csi->subdev);
> >>>>> +     format = v4l2_subdev_state_get_format(state, link->source->index, 0);
> >>>>> +     v4l2_subdev_unlock_state(state);
> >>>>>  
> >>>>> -     if (source_fmt.format.width != csi_fmt->width) {
> >>>>> +     if (!format) {
> >>>>> +             dev_dbg(csi->dev,
> >>>>> +                     "Skipping validation as no format present on \"%s\":%u:0\n",
> >>>>> +                     link->source->entity->name, link->source->index);
> >>>>> +             return 0;
> >>>>
> >>>> Isn't this an error?
> >>>
> >>> Well, the j7 shim subdev introduced here has immutable and active links to
> >>> all the video nodes, for each DMA channel (taken from DT), many of which
> >>> may be unused for certain setups, and thus there might not be any valid
> >>> format on the subdev source pad corresponding to an unused video node.
> >>>
> >>> Jacopo had a similar comment on v2, see this discussion (grep for Mali):
> >>> https://lore.kernel.org/linux-media/4mnlnsj4co3agvln4qsasmgvgwiyoo7yu2h5wyh4rmzzafhm5u@avhnbw7iknms/
> >>>
> >>> I know other drivers use a different approach with mutable links, so it
> >>> would be good if you/Laurent/Sakari can give your opinions on if only one
> >>> of these two approaches should be taken for multi-stream pipelines.
> >>
> >> I see.
> >>
> >> Well, I don't have a definite answer. With some thinking both options
> >> make certain sense. It makes sense to keep the links immutable and
> >> always enabled, as there's no configuration that can be done. On the
> >> other hand, it makes sense to require the unused links to be disabled,
> >> as, well, they are not used.
> > 
> > I'm not familiar with the implications this would have on this driver,
> > but generally speaking, if a stream is added to the media pipeline by
> > the pipeline build algorithm, then it is expected that applications
> > would have configured it correctly. Streams that are not used are
> > expected to be disabled if they would otherwise be added to the
> > pipeline.
> 
> I think the thing here is that the driver creates immutable
> always-enabled media links between the videodevs and the first subdev.
> Then, say, if only one stream is being used, only one of those links is
> actually used, and for every other link the above check fails as there's
> no stream, so no format.
> 
> In TI CAL driver the links were mutable, and unused links had to be
> disabled. There it made sense as the links had to be configurable (there
> were two PHYs). Here, there's no configuration needed, so immutable
> links make sense, but then they're enabled even when actually not used.

If the routing table in the subdev does not contain any route that goes
towards a video node, then that video node should not be added to the
pipeline by the validation code, and no validation will be attempted. At
least that's the theory.

I see that this driver implements .link_validate() as a
media_entity_operations, not a subdev operation. I wonder if that could
explain the issue.

-- 
Regards,

Laurent Pinchart

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v9 06/19] media: ti: j721e-csi2rx: add a subdev for the core device
  2026-01-21 10:52             ` Laurent Pinchart
@ 2026-01-22  6:53               ` Jai Luthra
  2026-01-22  9:53                 ` Laurent Pinchart
  0 siblings, 1 reply; 54+ messages in thread
From: Jai Luthra @ 2026-01-22  6:53 UTC (permalink / raw)
  To: Laurent Pinchart, Tomi Valkeinen
  Cc: Sakari Ailus, y-abhilashchandra, devarsht, s-jain1, vigneshr,
	mchehab, robh, krzk+dt, p.zabel, conor+dt, hverkuil-cisco,
	changhuang.liang, jack.zhu, sjoerd, dan.carpenter, hverkuil+cisco,
	linux-kernel, linux-media, devicetree, jai.luthra, mripard,
	Rishikesh Donadkar

Hi Laurent,

Quoting Laurent Pinchart (2026-01-21 16:22:32)
> On Wed, Jan 21, 2026 at 09:38:29AM +0200, Tomi Valkeinen wrote:
> > On 21/01/2026 01:25, Laurent Pinchart wrote:
> > > On Thu, Jan 15, 2026 at 02:56:21PM +0200, Tomi Valkeinen wrote:
> > >> On 15/01/2026 08:36, Jai Luthra wrote:
> > >>> Quoting Tomi Valkeinen (2026-01-14 20:51:49)
> > >>>> On 30/12/2025 10:32, Rishikesh Donadkar wrote:
> > >>>>> From: Jai Luthra <j-luthra@ti.com>
> > >>>>>
> > >>>>> With single stream capture, it was simpler to use the video device as
> > >>>>> the media entity representing the main TI CSI2RX device. Now with multi
> > >>>>> stream capture coming into the picture, the model has shifted to each
> > >>>>> video device having a link to the main device's subdev. The routing
> > >>>>> would then be set on this subdev.
> > >>>>>
> > >>>>> Add this subdev, link each context to this subdev's entity and link the
> > >>>>> subdev's entity to the source. Also add an array of media pads. It will
> > >>>>> have one sink pad and source pads equal to the number of contexts.
> > >>>>>
> > >>>>> Support the new enable_stream()/disable_stream() APIs in the subdev
> > >>>>> instead of s_stream() hook.
> > >>>>>
> > >>>>> Reviewed-by: Yemike Abhilash Chandra <y-abhilashchandra@ti.com>
> > >>>>> Co-developed-by: Pratyush Yadav <p.yadav@ti.com>
> > >>>>> Signed-off-by: Pratyush Yadav <p.yadav@ti.com>
> > >>>>> Signed-off-by: Jai Luthra <j-luthra@ti.com>
> > >>>>> Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
> > >>>>> ---
> > >>>
> > >>> [...]
> > >>>
> > >>>>> @@ -981,48 +1138,52 @@ static int ti_csi2rx_link_validate(struct media_link *link)
> > >>>>>       struct ti_csi2rx_ctx *ctx = container_of(vdev, struct ti_csi2rx_ctx, vdev);
> > >>>>>       struct ti_csi2rx_dev *csi = ctx->csi;
> > >>>>>       struct v4l2_pix_format *csi_fmt = &ctx->v_fmt.fmt.pix;
> > >>>>> -     struct v4l2_subdev_format source_fmt = {
> > >>>>> -             .which  = V4L2_SUBDEV_FORMAT_ACTIVE,
> > >>>>> -             .pad    = link->source->index,
> > >>>>> -     };
> > >>>>> +     struct v4l2_mbus_framefmt *format;
> > >>>>> +     struct v4l2_subdev_state *state;
> > >>>>>       const struct ti_csi2rx_fmt *ti_fmt;
> > >>>>> -     int ret;
> > >>>>>  
> > >>>>> -     ret = v4l2_subdev_call_state_active(csi->source, pad,
> > >>>>> -                                         get_fmt, &source_fmt);
> > >>>>> -     if (ret)
> > >>>>> -             return ret;
> > >>>>> +     state = v4l2_subdev_lock_and_get_active_state(&csi->subdev);
> > >>>>> +     format = v4l2_subdev_state_get_format(state, link->source->index, 0);
> > >>>>> +     v4l2_subdev_unlock_state(state);
> > >>>>>  
> > >>>>> -     if (source_fmt.format.width != csi_fmt->width) {
> > >>>>> +     if (!format) {
> > >>>>> +             dev_dbg(csi->dev,
> > >>>>> +                     "Skipping validation as no format present on \"%s\":%u:0\n",
> > >>>>> +                     link->source->entity->name, link->source->index);
> > >>>>> +             return 0;
> > >>>>
> > >>>> Isn't this an error?
> > >>>
> > >>> Well, the j7 shim subdev introduced here has immutable and active links to
> > >>> all the video nodes, for each DMA channel (taken from DT), many of which
> > >>> may be unused for certain setups, and thus there might not be any valid
> > >>> format on the subdev source pad corresponding to an unused video node.
> > >>>
> > >>> Jacopo had a similar comment on v2, see this discussion (grep for Mali):
> > >>> https://lore.kernel.org/linux-media/4mnlnsj4co3agvln4qsasmgvgwiyoo7yu2h5wyh4rmzzafhm5u@avhnbw7iknms/
> > >>>
> > >>> I know other drivers use a different approach with mutable links, so it
> > >>> would be good if you/Laurent/Sakari can give your opinions on if only one
> > >>> of these two approaches should be taken for multi-stream pipelines.
> > >>
> > >> I see.
> > >>
> > >> Well, I don't have a definite answer. With some thinking both options
> > >> make certain sense. It makes sense to keep the links immutable and
> > >> always enabled, as there's no configuration that can be done. On the
> > >> other hand, it makes sense to require the unused links to be disabled,
> > >> as, well, they are not used.
> > > 
> > > I'm not familiar with the implications this would have on this driver,
> > > but generally speaking, if a stream is added to the media pipeline by
> > > the pipeline build algorithm, then it is expected that applications
> > > would have configured it correctly. Streams that are not used are
> > > expected to be disabled if they would otherwise be added to the
> > > pipeline.
> > 
> > I think the thing here is that the driver creates immutable
> > always-enabled media links between the videodevs and the first subdev.
> > Then, say, if only one stream is being used, only one of those links is
> > actually used, and for every other link the above check fails as there's
> > no stream, so no format.
> > 
> > In TI CAL driver the links were mutable, and unused links had to be
> > disabled. There it made sense as the links had to be configurable (there
> > were two PHYs). Here, there's no configuration needed, so immutable
> > links make sense, but then they're enabled even when actually not used.
> 
> If the routing table in the subdev does not contain any route that goes
> towards a video node, then that video node should not be added to the
> pipeline by the validation code, and no validation will be attempted. At
> least that's the theory.

Okay that sounds reasonable. I can take a look into the media pipeline
validation code next week. @Rishikesh, given you already have a working
setup, feel free to test if the link_validate callback is triggered on
video nodes that don't have any streams/routes pointing to them.

> 
> I see that this driver implements .link_validate() as a
> media_entity_operations, not a subdev operation. I wonder if that could
> explain the issue.
> 

Well earlier I was partially confused, now I'm fully confused :-)

How is v4l2_subdev_pad_ops.link_validate different from
media_entity_operations.link_validate?

I see mc-core.rst and v4l2-subdev.rst both talk about their own variant,
without making it clear which should be used for a subdev.

Anyway, I'll try to dig through the framework code to understand what's
going wrong.

> -- 
> Regards,
> 
> Laurent Pinchart

Thanks,
Jai

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v9 06/19] media: ti: j721e-csi2rx: add a subdev for the core device
  2026-01-22  6:53               ` Jai Luthra
@ 2026-01-22  9:53                 ` Laurent Pinchart
  2026-02-28 17:49                   ` Jai Luthra
  0 siblings, 1 reply; 54+ messages in thread
From: Laurent Pinchart @ 2026-01-22  9:53 UTC (permalink / raw)
  To: Jai Luthra
  Cc: Tomi Valkeinen, Sakari Ailus, y-abhilashchandra, devarsht,
	s-jain1, vigneshr, mchehab, robh, krzk+dt, p.zabel, conor+dt,
	hverkuil-cisco, changhuang.liang, jack.zhu, sjoerd, dan.carpenter,
	hverkuil+cisco, linux-kernel, linux-media, devicetree, jai.luthra,
	mripard, Rishikesh Donadkar

On Thu, Jan 22, 2026 at 12:23:50PM +0530, Jai Luthra wrote:
> Quoting Laurent Pinchart (2026-01-21 16:22:32)
> > On Wed, Jan 21, 2026 at 09:38:29AM +0200, Tomi Valkeinen wrote:
> > > On 21/01/2026 01:25, Laurent Pinchart wrote:
> > > > On Thu, Jan 15, 2026 at 02:56:21PM +0200, Tomi Valkeinen wrote:
> > > >> On 15/01/2026 08:36, Jai Luthra wrote:
> > > >>> Quoting Tomi Valkeinen (2026-01-14 20:51:49)
> > > >>>> On 30/12/2025 10:32, Rishikesh Donadkar wrote:
> > > >>>>> From: Jai Luthra <j-luthra@ti.com>
> > > >>>>>
> > > >>>>> With single stream capture, it was simpler to use the video device as
> > > >>>>> the media entity representing the main TI CSI2RX device. Now with multi
> > > >>>>> stream capture coming into the picture, the model has shifted to each
> > > >>>>> video device having a link to the main device's subdev. The routing
> > > >>>>> would then be set on this subdev.
> > > >>>>>
> > > >>>>> Add this subdev, link each context to this subdev's entity and link the
> > > >>>>> subdev's entity to the source. Also add an array of media pads. It will
> > > >>>>> have one sink pad and source pads equal to the number of contexts.
> > > >>>>>
> > > >>>>> Support the new enable_stream()/disable_stream() APIs in the subdev
> > > >>>>> instead of s_stream() hook.
> > > >>>>>
> > > >>>>> Reviewed-by: Yemike Abhilash Chandra <y-abhilashchandra@ti.com>
> > > >>>>> Co-developed-by: Pratyush Yadav <p.yadav@ti.com>
> > > >>>>> Signed-off-by: Pratyush Yadav <p.yadav@ti.com>
> > > >>>>> Signed-off-by: Jai Luthra <j-luthra@ti.com>
> > > >>>>> Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
> > > >>>>> ---
> > > >>>
> > > >>> [...]
> > > >>>
> > > >>>>> @@ -981,48 +1138,52 @@ static int ti_csi2rx_link_validate(struct media_link *link)
> > > >>>>>       struct ti_csi2rx_ctx *ctx = container_of(vdev, struct ti_csi2rx_ctx, vdev);
> > > >>>>>       struct ti_csi2rx_dev *csi = ctx->csi;
> > > >>>>>       struct v4l2_pix_format *csi_fmt = &ctx->v_fmt.fmt.pix;
> > > >>>>> -     struct v4l2_subdev_format source_fmt = {
> > > >>>>> -             .which  = V4L2_SUBDEV_FORMAT_ACTIVE,
> > > >>>>> -             .pad    = link->source->index,
> > > >>>>> -     };
> > > >>>>> +     struct v4l2_mbus_framefmt *format;
> > > >>>>> +     struct v4l2_subdev_state *state;
> > > >>>>>       const struct ti_csi2rx_fmt *ti_fmt;
> > > >>>>> -     int ret;
> > > >>>>>  
> > > >>>>> -     ret = v4l2_subdev_call_state_active(csi->source, pad,
> > > >>>>> -                                         get_fmt, &source_fmt);
> > > >>>>> -     if (ret)
> > > >>>>> -             return ret;
> > > >>>>> +     state = v4l2_subdev_lock_and_get_active_state(&csi->subdev);
> > > >>>>> +     format = v4l2_subdev_state_get_format(state, link->source->index, 0);
> > > >>>>> +     v4l2_subdev_unlock_state(state);
> > > >>>>>  
> > > >>>>> -     if (source_fmt.format.width != csi_fmt->width) {
> > > >>>>> +     if (!format) {
> > > >>>>> +             dev_dbg(csi->dev,
> > > >>>>> +                     "Skipping validation as no format present on \"%s\":%u:0\n",
> > > >>>>> +                     link->source->entity->name, link->source->index);
> > > >>>>> +             return 0;
> > > >>>>
> > > >>>> Isn't this an error?
> > > >>>
> > > >>> Well, the j7 shim subdev introduced here has immutable and active links to
> > > >>> all the video nodes, for each DMA channel (taken from DT), many of which
> > > >>> may be unused for certain setups, and thus there might not be any valid
> > > >>> format on the subdev source pad corresponding to an unused video node.
> > > >>>
> > > >>> Jacopo had a similar comment on v2, see this discussion (grep for Mali):
> > > >>> https://lore.kernel.org/linux-media/4mnlnsj4co3agvln4qsasmgvgwiyoo7yu2h5wyh4rmzzafhm5u@avhnbw7iknms/
> > > >>>
> > > >>> I know other drivers use a different approach with mutable links, so it
> > > >>> would be good if you/Laurent/Sakari can give your opinions on if only one
> > > >>> of these two approaches should be taken for multi-stream pipelines.
> > > >>
> > > >> I see.
> > > >>
> > > >> Well, I don't have a definite answer. With some thinking both options
> > > >> make certain sense. It makes sense to keep the links immutable and
> > > >> always enabled, as there's no configuration that can be done. On the
> > > >> other hand, it makes sense to require the unused links to be disabled,
> > > >> as, well, they are not used.
> > > > 
> > > > I'm not familiar with the implications this would have on this driver,
> > > > but generally speaking, if a stream is added to the media pipeline by
> > > > the pipeline build algorithm, then it is expected that applications
> > > > would have configured it correctly. Streams that are not used are
> > > > expected to be disabled if they would otherwise be added to the
> > > > pipeline.
> > > 
> > > I think the thing here is that the driver creates immutable
> > > always-enabled media links between the videodevs and the first subdev.
> > > Then, say, if only one stream is being used, only one of those links is
> > > actually used, and for every other link the above check fails as there's
> > > no stream, so no format.
> > > 
> > > In TI CAL driver the links were mutable, and unused links had to be
> > > disabled. There it made sense as the links had to be configurable (there
> > > were two PHYs). Here, there's no configuration needed, so immutable
> > > links make sense, but then they're enabled even when actually not used.
> > 
> > If the routing table in the subdev does not contain any route that goes
> > towards a video node, then that video node should not be added to the
> > pipeline by the validation code, and no validation will be attempted. At
> > least that's the theory.
> 
> Okay that sounds reasonable. I can take a look into the media pipeline
> validation code next week. @Rishikesh, given you already have a working
> setup, feel free to test if the link_validate callback is triggered on
> video nodes that don't have any streams/routes pointing to them.
> 
> > I see that this driver implements .link_validate() as a
> > media_entity_operations, not a subdev operation. I wonder if that could
> > explain the issue.
> 
> Well earlier I was partially confused, now I'm fully confused :-)
> 
> How is v4l2_subdev_pad_ops.link_validate different from
> media_entity_operations.link_validate?

media_entity_operations.link_validate() is the entry point, called by
the media pipeline validation code that is agnostic to entity types. It
is called on the sink entity of each link.

When the sink is a subdev, the common practice is to implement
media_entity_operations.link_validate() using
v4l2_subdev_link_validate(), which iterates over streams and validate
them individually with v4l2_subdev_pad_ops.link_validate(). That
operation can be left out if the default implementation
v4l2_subdev_link_validate_default() is enough, or a custom
.link_validate() operation can be provided that calls
v4l2_subdev_link_validate_default() and performs additional checks (or
implements all checks manually without calling
v4l2_subdev_link_validate_default() for very uncommon cases).

In this case, though, the sink is a video device, so
v4l2_subdev_link_validate() can't be used. I overlooked that in my
previous reply, sorry about it. As a link to a video node can only carry
a single stream, it seems that the issue here is caused by the link
being included in the media pipeline in the first place.
drivers/media/mc/mc-entity.c contains detailed debug messages that
explain how a pipeline is constructed, I would start by enabling them
and investigating what happens.

> I see mc-core.rst and v4l2-subdev.rst both talk about their own variant,
> without making it clear which should be used for a subdev.
> 
> Anyway, I'll try to dig through the framework code to understand what's
> going wrong.

-- 
Regards,

Laurent Pinchart

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH v9 06/19] media: ti: j721e-csi2rx: add a subdev for the core device
  2026-01-22  9:53                 ` Laurent Pinchart
@ 2026-02-28 17:49                   ` Jai Luthra
  0 siblings, 0 replies; 54+ messages in thread
From: Jai Luthra @ 2026-02-28 17:49 UTC (permalink / raw)
  To: Laurent Pinchart, Tomi Valkeinen, Rishikesh Donadkar
  Cc: Sakari Ailus, y-abhilashchandra, devarsht, s-jain1, vigneshr,
	mchehab, robh, krzk+dt, p.zabel, conor+dt, hverkuil-cisco,
	changhuang.liang, jack.zhu, sjoerd, dan.carpenter, hverkuil+cisco,
	linux-kernel, linux-media, devicetree, jai.luthra, mripard

Quoting Laurent Pinchart (2026-01-22 15:23:08)
> On Thu, Jan 22, 2026 at 12:23:50PM +0530, Jai Luthra wrote:
> > Quoting Laurent Pinchart (2026-01-21 16:22:32)
> > > On Wed, Jan 21, 2026 at 09:38:29AM +0200, Tomi Valkeinen wrote:
> > > > On 21/01/2026 01:25, Laurent Pinchart wrote:
> > > > > On Thu, Jan 15, 2026 at 02:56:21PM +0200, Tomi Valkeinen wrote:
> > > > >> On 15/01/2026 08:36, Jai Luthra wrote:
> > > > >>> Quoting Tomi Valkeinen (2026-01-14 20:51:49)
> > > > >>>> On 30/12/2025 10:32, Rishikesh Donadkar wrote:
> > > > >>>>> From: Jai Luthra <j-luthra@ti.com>
> > > > >>>>>
> > > > >>>>> With single stream capture, it was simpler to use the video device as
> > > > >>>>> the media entity representing the main TI CSI2RX device. Now with multi
> > > > >>>>> stream capture coming into the picture, the model has shifted to each
> > > > >>>>> video device having a link to the main device's subdev. The routing
> > > > >>>>> would then be set on this subdev.
> > > > >>>>>
> > > > >>>>> Add this subdev, link each context to this subdev's entity and link the
> > > > >>>>> subdev's entity to the source. Also add an array of media pads. It will
> > > > >>>>> have one sink pad and source pads equal to the number of contexts.
> > > > >>>>>
> > > > >>>>> Support the new enable_stream()/disable_stream() APIs in the subdev
> > > > >>>>> instead of s_stream() hook.
> > > > >>>>>
> > > > >>>>> Reviewed-by: Yemike Abhilash Chandra <y-abhilashchandra@ti.com>
> > > > >>>>> Co-developed-by: Pratyush Yadav <p.yadav@ti.com>
> > > > >>>>> Signed-off-by: Pratyush Yadav <p.yadav@ti.com>
> > > > >>>>> Signed-off-by: Jai Luthra <j-luthra@ti.com>
> > > > >>>>> Signed-off-by: Rishikesh Donadkar <r-donadkar@ti.com>
> > > > >>>>> ---
> > > > >>>
> > > > >>> [...]
> > > > >>>
> > > > >>>>> @@ -981,48 +1138,52 @@ static int ti_csi2rx_link_validate(struct media_link *link)
> > > > >>>>>       struct ti_csi2rx_ctx *ctx = container_of(vdev, struct ti_csi2rx_ctx, vdev);
> > > > >>>>>       struct ti_csi2rx_dev *csi = ctx->csi;
> > > > >>>>>       struct v4l2_pix_format *csi_fmt = &ctx->v_fmt.fmt.pix;
> > > > >>>>> -     struct v4l2_subdev_format source_fmt = {
> > > > >>>>> -             .which  = V4L2_SUBDEV_FORMAT_ACTIVE,
> > > > >>>>> -             .pad    = link->source->index,
> > > > >>>>> -     };
> > > > >>>>> +     struct v4l2_mbus_framefmt *format;
> > > > >>>>> +     struct v4l2_subdev_state *state;
> > > > >>>>>       const struct ti_csi2rx_fmt *ti_fmt;
> > > > >>>>> -     int ret;
> > > > >>>>>  
> > > > >>>>> -     ret = v4l2_subdev_call_state_active(csi->source, pad,
> > > > >>>>> -                                         get_fmt, &source_fmt);
> > > > >>>>> -     if (ret)
> > > > >>>>> -             return ret;
> > > > >>>>> +     state = v4l2_subdev_lock_and_get_active_state(&csi->subdev);
> > > > >>>>> +     format = v4l2_subdev_state_get_format(state, link->source->index, 0);
> > > > >>>>> +     v4l2_subdev_unlock_state(state);
> > > > >>>>>  
> > > > >>>>> -     if (source_fmt.format.width != csi_fmt->width) {
> > > > >>>>> +     if (!format) {
> > > > >>>>> +             dev_dbg(csi->dev,
> > > > >>>>> +                     "Skipping validation as no format present on \"%s\":%u:0\n",
> > > > >>>>> +                     link->source->entity->name, link->source->index);
> > > > >>>>> +             return 0;
> > > > >>>>
> > > > >>>> Isn't this an error?
> > > > >>>
> > > > >>> Well, the j7 shim subdev introduced here has immutable and active links to
> > > > >>> all the video nodes, for each DMA channel (taken from DT), many of which
> > > > >>> may be unused for certain setups, and thus there might not be any valid
> > > > >>> format on the subdev source pad corresponding to an unused video node.
> > > > >>>
> > > > >>> Jacopo had a similar comment on v2, see this discussion (grep for Mali):
> > > > >>> https://lore.kernel.org/linux-media/4mnlnsj4co3agvln4qsasmgvgwiyoo7yu2h5wyh4rmzzafhm5u@avhnbw7iknms/
> > > > >>>
> > > > >>> I know other drivers use a different approach with mutable links, so it
> > > > >>> would be good if you/Laurent/Sakari can give your opinions on if only one
> > > > >>> of these two approaches should be taken for multi-stream pipelines.
> > > > >>
> > > > >> I see.
> > > > >>
> > > > >> Well, I don't have a definite answer. With some thinking both options
> > > > >> make certain sense. It makes sense to keep the links immutable and
> > > > >> always enabled, as there's no configuration that can be done. On the
> > > > >> other hand, it makes sense to require the unused links to be disabled,
> > > > >> as, well, they are not used.
> > > > > 
> > > > > I'm not familiar with the implications this would have on this driver,
> > > > > but generally speaking, if a stream is added to the media pipeline by
> > > > > the pipeline build algorithm, then it is expected that applications
> > > > > would have configured it correctly. Streams that are not used are
> > > > > expected to be disabled if they would otherwise be added to the
> > > > > pipeline.
> > > > 
> > > > I think the thing here is that the driver creates immutable
> > > > always-enabled media links between the videodevs and the first subdev.
> > > > Then, say, if only one stream is being used, only one of those links is
> > > > actually used, and for every other link the above check fails as there's
> > > > no stream, so no format.
> > > > 
> > > > In TI CAL driver the links were mutable, and unused links had to be
> > > > disabled. There it made sense as the links had to be configurable (there
> > > > were two PHYs). Here, there's no configuration needed, so immutable
> > > > links make sense, but then they're enabled even when actually not used.
> > > 
> > > If the routing table in the subdev does not contain any route that goes
> > > towards a video node, then that video node should not be added to the
> > > pipeline by the validation code, and no validation will be attempted. At
> > > least that's the theory.
> > 
> > Okay that sounds reasonable. I can take a look into the media pipeline
> > validation code next week. @Rishikesh, given you already have a working
> > setup, feel free to test if the link_validate callback is triggered on
> > video nodes that don't have any streams/routes pointing to them.

I finally got time to check this out, and yes, the subdev pads with no
route going to them are added in the pipeline currently, as the subdev is
missing the .has_pad_interdep operation. Using the framework helper, which
marks two pads as interdependent only if they have an active route, fixes it.

Rishikesh, can you please apply the following before you post v12 of your
series:

---------
diff --git a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
index f126649ba36c..42da8bd6b53c 100644
--- a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
+++ b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
@@ -1340,10 +1340,10 @@ static int ti_csi2rx_link_validate(struct media_link *link)
        v4l2_subdev_unlock_state(state);

        if (!format) {
-               dev_dbg(csi->dev,
-                       "Skipping validation as no format present on \"%s\":%u:0\n",
+               dev_err(csi->dev,
+                       "No format present on \"%s\":%u:0\n",
                        link->source->entity->name, link->source->index);
-               return 0;
+               return -EPIPE;
        }

        if (format->width != csi_fmt->width) {
@@ -1389,6 +1389,7 @@ static const struct media_entity_operations ti_csi2rx_video_entity_ops = {

 static const struct media_entity_operations ti_csi2rx_subdev_entity_ops = {
        .link_validate = v4l2_subdev_link_validate,
+       .has_pad_interdep = v4l2_subdev_has_pad_interdep,
 };

 static int ti_csi2rx_init_dma(struct ti_csi2rx_ctx *ctx)
---------

And while above alone fixes this issue, I saw cdns-csi2rx pads that are unused
also get added to the pipeline, which seems wrong even if it doesn't break
anything. So please apply the below in the patch that adds multistream support
for cdns-csi2rx:

---------
diff --git a/drivers/media/platform/cadence/cdns-csi2rx.c b/drivers/media/platform/cadence/cdns-csi2rx.c
index 7164a7d6f4a1..18737d00a7d7 100644
--- a/drivers/media/platform/cadence/cdns-csi2rx.c
+++ b/drivers/media/platform/cadence/cdns-csi2rx.c
@@ -803,6 +803,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,
---------

Thanks,
Jai

> > 
> > > I see that this driver implements .link_validate() as a
> > > media_entity_operations, not a subdev operation. I wonder if that could
> > > explain the issue.
> > 
> > Well earlier I was partially confused, now I'm fully confused :-)
> > 
> > How is v4l2_subdev_pad_ops.link_validate different from
> > media_entity_operations.link_validate?
> 
> media_entity_operations.link_validate() is the entry point, called by
> the media pipeline validation code that is agnostic to entity types. It
> is called on the sink entity of each link.
> 
> When the sink is a subdev, the common practice is to implement
> media_entity_operations.link_validate() using
> v4l2_subdev_link_validate(), which iterates over streams and validate
> them individually with v4l2_subdev_pad_ops.link_validate(). That
> operation can be left out if the default implementation
> v4l2_subdev_link_validate_default() is enough, or a custom
> .link_validate() operation can be provided that calls
> v4l2_subdev_link_validate_default() and performs additional checks (or
> implements all checks manually without calling
> v4l2_subdev_link_validate_default() for very uncommon cases).
> 
> In this case, though, the sink is a video device, so
> v4l2_subdev_link_validate() can't be used. I overlooked that in my
> previous reply, sorry about it. As a link to a video node can only carry
> a single stream, it seems that the issue here is caused by the link
> being included in the media pipeline in the first place.
> drivers/media/mc/mc-entity.c contains detailed debug messages that
> explain how a pipeline is constructed, I would start by enabling them
> and investigating what happens.
> 
> > I see mc-core.rst and v4l2-subdev.rst both talk about their own variant,
> > without making it clear which should be used for a subdev.
> > 
> > Anyway, I'll try to dig through the framework code to understand what's
> > going wrong.
> 
> -- 
> Regards,
> 
> Laurent Pinchart

^ permalink raw reply related	[flat|nested] 54+ messages in thread

end of thread, other threads:[~2026-02-28 17:49 UTC | newest]

Thread overview: 54+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-12-30  8:32 [PATCH v9 00/19] media: cadence,ti: CSI2RX Multistream Support Rishikesh Donadkar
2025-12-30  8:32 ` [PATCH v9 01/19] media: ti: j721e-csi2rx: Remove word size alignment on frame width Rishikesh Donadkar
2025-12-30  8:32 ` [PATCH v9 02/19] dt-bindings: media: ti,j721e-csi2rx-shim: Support 32 dma chans Rishikesh Donadkar
2025-12-30  8:32 ` [PATCH v9 03/19] media: ti: j721e-csi2rx: separate out device and context Rishikesh Donadkar
2025-12-30  8:32 ` [PATCH v9 04/19] media: ti: j721e-csi2rx: prepare SHIM code for multiple contexts Rishikesh Donadkar
2025-12-30  8:32 ` [PATCH v9 05/19] media: ti: j721e-csi2rx: allocate DMA channel based on context index Rishikesh Donadkar
2025-12-30  8:32 ` [PATCH v9 06/19] media: ti: j721e-csi2rx: add a subdev for the core device Rishikesh Donadkar
2026-01-14 15:21   ` Tomi Valkeinen
2026-01-15  6:36     ` Jai Luthra
2026-01-15 12:56       ` Tomi Valkeinen
2026-01-20 23:25         ` Laurent Pinchart
2026-01-21  7:38           ` Tomi Valkeinen
2026-01-21 10:52             ` Laurent Pinchart
2026-01-22  6:53               ` Jai Luthra
2026-01-22  9:53                 ` Laurent Pinchart
2026-02-28 17:49                   ` Jai Luthra
2026-01-21  9:10     ` Rishikesh Donadkar
2025-12-30  8:32 ` [PATCH v9 07/19] media: cadence: csi2rx: Move to .enable/disable_streams API Rishikesh Donadkar
2026-01-14 15:25   ` Tomi Valkeinen
2025-12-30  8:32 ` [PATCH v9 08/19] media: staging: starfive: Move to enabel-disable streams in starfive drivers Rishikesh Donadkar
2026-01-14 12:51   ` Jai Luthra
2026-01-14 13:05     ` Laurent Pinchart
2025-12-30  8:32 ` [PATCH v9 09/19] media: ti: j721e-csi2rx: get number of contexts from device tree Rishikesh Donadkar
2025-12-30  8:32 ` [PATCH v9 10/19] media: cadence: csi2rx: add get_frame_desc wrapper Rishikesh Donadkar
2025-12-30  8:32 ` [PATCH v9 11/19] media: ti: j721e-csi2rx: add support for processing virtual channels Rishikesh Donadkar
2026-01-14 15:31   ` Tomi Valkeinen
2026-01-16 10:28     ` Rishikesh Donadkar
2025-12-30  8:32 ` [PATCH v9 12/19] media: cadence: csi2rx: add multistream support Rishikesh Donadkar
2026-01-15 12:01   ` Tomi Valkeinen
2026-01-16 11:04     ` Rishikesh Donadkar
2025-12-30  8:32 ` [PATCH v9 13/19] media: ti: j721e-csi2rx: " Rishikesh Donadkar
2026-01-15 12:27   ` Tomi Valkeinen
2026-01-20  8:48     ` Rishikesh Donadkar
2026-01-15 12:28   ` Tomi Valkeinen
2026-01-20  8:52     ` Rishikesh Donadkar
2025-12-30  8:32 ` [PATCH v9 14/19] media: ti: j721e-csi2rx: Submit all available buffers Rishikesh Donadkar
2025-12-30  8:32 ` [PATCH v9 15/19] media: ti: j721e-csi2rx: Change the drain architecture for multistream Rishikesh Donadkar
2026-01-15 12:37   ` Tomi Valkeinen
2026-01-15 16:02     ` Jai Luthra
2026-01-16 10:11       ` Vignesh Raghavendra
2026-01-19  5:08       ` Rishikesh Donadkar
2026-01-19  5:04     ` Rishikesh Donadkar
2025-12-30  8:32 ` [PATCH v9 16/19] media: ti: j721e-csi2rx: Return the partial frame as error Rishikesh Donadkar
2026-01-06 11:15   ` Jai Luthra
2026-01-08  5:37     ` Rishikesh Donadkar
2026-01-15 12:39   ` Tomi Valkeinen
2025-12-30  8:32 ` [PATCH v9 17/19] media: cadence: csi2rx: Support runtime PM Rishikesh Donadkar
2026-01-14 17:04   ` Tomi Valkeinen
2025-12-30  8:32 ` [PATCH v9 18/19] media: ti: j721e-csi2rx: Support runtime suspend Rishikesh Donadkar
2026-01-15 12:46   ` Tomi Valkeinen
2026-01-19  6:04     ` Jai Luthra
2025-12-30  8:32 ` [PATCH v9 19/19] media: ti: j721e-csi2rx: Support system suspend using pm_notifier Rishikesh Donadkar
2026-01-15 12:50   ` Tomi Valkeinen
2026-01-19  5:25     ` Rishikesh Donadkar

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox