devicetree.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v6 00/13] media: rockchip: add a driver for the rockchip camera interface
@ 2025-04-30  9:15 Michael Riesch via B4 Relay
  2025-04-30  9:15 ` [PATCH v6 01/13] Documentation: admin-guide: media: add " Michael Riesch via B4 Relay
                   ` (12 more replies)
  0 siblings, 13 replies; 34+ messages in thread
From: Michael Riesch via B4 Relay @ 2025-04-30  9:15 UTC (permalink / raw)
  To: Mehdi Djait, Maxime Chevallier, Théo Lebrun, Gerald Loacker,
	Thomas Petazzoni, Laurent Pinchart, Mauro Carvalho Chehab,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner,
	Kever Yang, Nicolas Dufresne, Sebastian Reichel,
	Collabora Kernel Team, Paul Kocialkowski, Alexander Shiyan,
	Val Packett, Rob Herring, Philipp Zabel, Sakari Ailus
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-rockchip, Michael Riesch, Michael Riesch, Mehdi Djait

Habidere,

This series introduces support for the Rockchip Camera Interface (CIF),
which is featured in many Rockchip SoCs in different variations.
For example, the PX30 Video Input Processor (VIP) is able to receive
video data via the Digital Video Port (DVP, a parallel data interface)
and transfer it into system memory using a double-buffering mechanism
called ping-pong mode.
The RK3568 Video Capture (VICAP) unit, on the other hand, features a
DVP and a MIPI CSI-2 receiver that can receive video data independently
(both using the ping-pong scheme).
The different variants may have additional features, such as scaling
and/or cropping.
Finally, the RK3588 VICAP unit constitutes an essential piece of the
camera interface with one DVP, six MIPI CSI-2 receivers, scale/crop
units, and a data path multiplexer (to scaler units, to ISP, ...).

The v6 of the series adds a media controller centric V4L2 driver for
the Rockchip CIF with
 - support for the PX30 VIP (not tested, though, due to the lack of HW)
 - support for the RK3568 VICAP DVP
 - support for the RK3568 VICAP MIPI CSI-2 receiver
 - abstraction for the ping-pong scheme to allow for future extensions
 - abstraction for the INTERFACE and CROP parts to allow for future
   extensions
 - initial support for different virtual channels (not tested, though,
   due to the lack of HW)

The patches are functional and have been tested successfully on a
custom RK3568 board including the ITE Tech. IT6801 HDMI receiver and
the Sony IMX415 image sensor as subdevices attached to the DVP and the
MIPI CSI-2 receiver, respectively.
The IT6801 driver still needs some loving care but shall be submitted
as well at some point.

However, several features are not yet addressed, such as
 - support for the RK3588 variant (-> next item on my TODO)
 - support for the scaling unit in the PX30 (-> cannot do due to the
   lack of HW)
 - support for the interface to the Rockchip ISP in the RK3568
   (apparently, data receive via VICAP DVP and the VICAP MIPI CSI-2
   receiver can be processed by the RK3568 ISP)
 - support for the MUX/SCALE/TOISP block in the RK3588 VICAP (which
   provides the base for image processing on the RK3588)

Looking forward to your comments!

To: Mehdi Djait <mehdi.djait@linux.intel.com>
To: Maxime Chevallier <maxime.chevallier@bootlin.com>
To: Théo Lebrun <theo.lebrun@bootlin.com>
To: Gerald Loacker <gerald.loacker@wolfvision.net>
To: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
To: Sakari Ailus <sakari.ailus@iki.fi>
To: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
To: Mauro Carvalho Chehab <mchehab@kernel.org>
To: Rob Herring <robh+dt@kernel.org>
To: Krzysztof Kozlowski <krzk+dt@kernel.org>
To: Conor Dooley <conor+dt@kernel.org>
To: Heiko Stuebner <heiko@sntech.de>
To: Kever Yang <kever.yang@rock-chips.com>
To: Nicolas Dufresne <nicolas.dufresne@collabora.com>
To: Sebastian Reichel <sebastian.reichel@collabora.com>
To: Collabora Kernel Team <kernel@collabora.com>
To: Paul Kocialkowski <paulk@sys-base.io>
To: Alexander Shiyan <eagle.alexander923@gmail.com>
To: Val Packett <val@packett.cool>
To: Rob Herring <robh@kernel.org>
To: Philipp Zabel <p.zabel@pengutronix.de>
Cc: linux-media@vger.kernel.org
Cc: devicetree@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-rockchip@lists.infradead.org
Signed-off-by: Michael Riesch <michael.riesch@wolfvision.net>
Signed-off-by: Michael Riesch <michael.riesch@collabora.com>

Changes in v6:
- rebased onto v6.15-rc1
- renamed "MIPI CSI HOST" -> "MIPI CSI RECEIVER" (Laurent)
- s/@wolfvision.net/@collabora.com where appropriate
- renamed DVP delay property and moved it to the endpoint (Sakari)
- implemented DT review comments (Krzysztof and Sakari)
- implemented driver review comments (Sakari)
- fixed issues raised by media-ci (yet again)
- added documentation including a RK3568 topology (new patch 1)
  (Sakari)
- added patch that enables rkcif in the defconfig (new patch 9)
- Link to v5: https://lore.kernel.org/r/20250306-v6-8-topic-rk3568-vicap-v5-0-f02152534f3c@wolfvision.net

Changes in v5:
- fixed issues raised by media-ci
- fixed dt bindings (comments by Rob and Sakari)
- fixed probe on systems with no DVP in DT (comment by Alexander)
- fixed error path in register offset calculation
- split off MIPI CSI host driver into separate module (comment
  by Mehdi)
- added MODULE_DEVICE_TABLE() for both drivers (comment by Mehdi)
- Link to v4: https://lore.kernel.org/r/20250219-v6-8-topic-rk3568-vicap-v4-0-e906600ae3b0@wolfvision.net

Changes in v4:
- added support for the MIPI CSI-2 receiver (new patches 4, 6, 7, 10)
- fixed asserts on stream stop
- fixed register address lookup
- fixed link validiation callback
- fixed issues raised by Rob's bot, kernel test robot, and media-ci
- Link to v3: https://lore.kernel.org/r/20250206-v6-8-topic-rk3568-vicap-v3-0-69d1f19e5c40@wolfvision.net

Changes in v3:
- renamed the driver "cif" -> "rkcif"
- rebased onto v6.14-rc1
- abstracted the generic INTERFACE+CROP part
- addressed comments by Rob and Sakari
- added V4L2 MPLANE formats to DVP
- added patch that enables the RK3568 VICAP DVP on PF5 IO Expander
- fixed formatting issues raised by media-ci bot
- Link to v2: https://lore.kernel.org/r/20241217-v6-8-topic-rk3568-vicap-v2-0-b1d488fcc0d3@wolfvision.net

Changes in v2:
- merged with Mehdi's v13
- refactored the complete driver towards a media controller centric driver
- abstracted the generic ping-pong stream (can be used for DVP as well as for CSI-2)
- switched to MPLANE API
- added support for notifications
- Link to v1: https://lore.kernel.org/r/20240220-v6-8-topic-rk3568-vicap-v1-0-2680a1fa640b@wolfvision.net

---
Mehdi Djait (2):
      media: dt-bindings: add rockchip px30 vip
      arm64: dts: rockchip: add the vip node to px30

Michael Riesch (11):
      Documentation: admin-guide: media: add rockchip camera interface
      media: dt-bindings: video-interfaces: add defines for sampling modes
      media: dt-bindings: add rockchip rk3568 vicap
      media: dt-bindings: add rockchip rk3568 mipi csi receiver
      media: rockchip: add a driver for the rockchip camera interface
      media: rockchip: rkcif: add driver for mipi csi-2 receiver
      media: rockchip: rkcif: add support for mipi csi-2 capture
      arm64: defconfig: enable rockchip camera interface
      arm64: dts: rockchip: add vicap node to rk356x
      arm64: dts: rockchip: add mipi csi receiver node to rk356x
      arm64: dts: rockchip: enable vicap dvp on wolfvision pf5 io expander

 .../admin-guide/media/rkcif-rk3568-vicap.dot       |  21 +
 Documentation/admin-guide/media/rkcif.rst          |  83 ++
 Documentation/admin-guide/media/v4l-drivers.rst    |   1 +
 .../bindings/media/rockchip,px30-vip.yaml          | 122 +++
 .../bindings/media/rockchip,rk3568-mipi-csi.yaml   | 113 +++
 .../bindings/media/rockchip,rk3568-vicap.yaml      | 170 ++++
 MAINTAINERS                                        |  11 +
 arch/arm64/boot/dts/rockchip/px30.dtsi             |  12 +
 .../rk3568-wolfvision-pf5-io-expander.dtso         |  20 +
 arch/arm64/boot/dts/rockchip/rk356x-base.dtsi      |  75 ++
 arch/arm64/configs/defconfig                       |   1 +
 drivers/media/platform/rockchip/Kconfig            |   1 +
 drivers/media/platform/rockchip/Makefile           |   1 +
 drivers/media/platform/rockchip/rkcif/Kconfig      |  15 +
 drivers/media/platform/rockchip/rkcif/Makefile     |  10 +
 .../platform/rockchip/rkcif/rkcif-capture-dvp.c    | 858 +++++++++++++++++++++
 .../platform/rockchip/rkcif/rkcif-capture-dvp.h    |  24 +
 .../platform/rockchip/rkcif/rkcif-capture-mipi.c   | 722 +++++++++++++++++
 .../platform/rockchip/rkcif/rkcif-capture-mipi.h   |  22 +
 .../media/platform/rockchip/rkcif/rkcif-common.h   | 236 ++++++
 drivers/media/platform/rockchip/rkcif/rkcif-dev.c  | 300 +++++++
 .../platform/rockchip/rkcif/rkcif-interface.c      | 426 ++++++++++
 .../platform/rockchip/rkcif/rkcif-interface.h      |  30 +
 .../rockchip/rkcif/rkcif-mipi-csi-receiver.c       | 731 ++++++++++++++++++
 drivers/media/platform/rockchip/rkcif/rkcif-regs.h | 154 ++++
 .../media/platform/rockchip/rkcif/rkcif-stream.c   | 622 +++++++++++++++
 .../media/platform/rockchip/rkcif/rkcif-stream.h   |  31 +
 include/dt-bindings/media/video-interfaces.h       |   4 +
 28 files changed, 4816 insertions(+)
---
base-commit: 0af2f6be1b4281385b618cb86ad946eded089ac8
change-id: 20240220-rk3568-vicap-b9b3f9925f44

Best regards,
-- 
Michael Riesch <michael.riesch@collabora.com>



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

* [PATCH v6 01/13] Documentation: admin-guide: media: add rockchip camera interface
  2025-04-30  9:15 [PATCH v6 00/13] media: rockchip: add a driver for the rockchip camera interface Michael Riesch via B4 Relay
@ 2025-04-30  9:15 ` Michael Riesch via B4 Relay
  2025-04-30  9:42   ` Heiko Stübner
  2025-04-30  9:15 ` [PATCH v6 02/13] media: dt-bindings: video-interfaces: add defines for sampling modes Michael Riesch via B4 Relay
                   ` (11 subsequent siblings)
  12 siblings, 1 reply; 34+ messages in thread
From: Michael Riesch via B4 Relay @ 2025-04-30  9:15 UTC (permalink / raw)
  To: Mehdi Djait, Maxime Chevallier, Théo Lebrun, Gerald Loacker,
	Thomas Petazzoni, Laurent Pinchart, Mauro Carvalho Chehab,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner,
	Kever Yang, Nicolas Dufresne, Sebastian Reichel,
	Collabora Kernel Team, Paul Kocialkowski, Alexander Shiyan,
	Val Packett, Rob Herring, Philipp Zabel, Sakari Ailus
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-rockchip, Michael Riesch, Michael Riesch

From: Michael Riesch <michael.riesch@collabora.com>

Add a document that describes the different variants of the Rockchip
Camera Interface (CIF), their hardware layout, as well as their
representation in the media controller centric rkcif device driver,
which is located under drivers/media/platform/rockchip/rkcif.

Signed-off-by: Michael Riesch <michael.riesch@collabora.com>
---
 .../admin-guide/media/rkcif-rk3568-vicap.dot       | 21 ++++++
 Documentation/admin-guide/media/rkcif.rst          | 83 ++++++++++++++++++++++
 Documentation/admin-guide/media/v4l-drivers.rst    |  1 +
 MAINTAINERS                                        |  7 ++
 4 files changed, 112 insertions(+)

diff --git a/Documentation/admin-guide/media/rkcif-rk3568-vicap.dot b/Documentation/admin-guide/media/rkcif-rk3568-vicap.dot
new file mode 100644
index 000000000000..4cc6963e681e
--- /dev/null
+++ b/Documentation/admin-guide/media/rkcif-rk3568-vicap.dot
@@ -0,0 +1,21 @@
+digraph board {
+        rankdir=TB
+        n00000001 [label="{{<port0> 0} | rkcif-dvp0\n/dev/v4l-subdev0 | {<port1> 1}}", shape=Mrecord, style=filled, fillcolor=green]
+        n00000001:port1 -> n00000004
+        n00000004 [label="rkcif-dvp0-id0\n/dev/video0", shape=box, style=filled, fillcolor=yellow]
+        n0000000d [label="{{} | it6801 2-0048\n/dev/v4l-subdev1 | {<port0> 0}}", shape=Mrecord, style=filled, fillcolor=green]
+        n0000000d:port0 -> n00000001:port0
+        n00000011 [label="{{} | imx415 4-001a\n/dev/v4l-subdev4 | {<port0> 0}}", shape=Mrecord, style=filled, fillcolor=green]
+        n00000011:port1 -> n00000014
+        n00000014 [label="{{<port0> 0} | rockchip-mipi-csi fdfb0000.csi\n/dev/v4l-subdev3 | {<port1> 1}}", shape=Mrecord, style=filled, fillcolor=green]
+        n00000014:port1 -> n0000001d
+        n0000001d [label="{{<port0> 0} | rkcif-mipi0\n/dev/v4l-subdev2 | {<port1> 1}}", shape=Mrecord, style=filled, fillcolor=green]
+        n0000001d:port1 -> n00000021
+        n0000001d:port1 -> n00000022
+        n0000001d:port1 -> n00000023
+        n0000001d:port1 -> n00000024
+        n00000021 [label="rkcif-mipi0-id0\n/dev/video1", shape=box, style=filled, fillcolor=yellow]
+        n00000022 [label="rkcif-mipi0-id1\n/dev/video2", shape=box, style=filled, fillcolor=yellow]
+        n00000023 [label="rkcif-mipi0-id2\n/dev/video3", shape=box, style=filled, fillcolor=yellow]
+        n00000024 [label="rkcif-mipi0-id3\n/dev/video4", shape=box, style=filled, fillcolor=yellow]
+}
diff --git a/Documentation/admin-guide/media/rkcif.rst b/Documentation/admin-guide/media/rkcif.rst
new file mode 100644
index 000000000000..f35f644a54a0
--- /dev/null
+++ b/Documentation/admin-guide/media/rkcif.rst
@@ -0,0 +1,83 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+=========================================
+Rockchip Camera Interface (CIF)
+=========================================
+
+Introduction
+============
+
+The Rockchip Camera Interface (CIF) is featured in many Rockchip SoCs in
+different variants.
+The different variants are combinations of common building blocks, such as
+
+* INTERFACE blocks of different types, namely
+
+  * the Digital Video Port (DVP, a parallel data interface)
+  * the interface block for the MIPI CSI-2 receiver
+
+* CROP units
+
+* MIPI CSI-2 receiver (not available on all variants): This unit is referred
+  to as MIPI CSI HOST in the Rockchip documentation.
+  Technically, it is a separate hardware block, but it is strongly coupled to
+  the CIF and therefore included here.
+
+* MUX units (not available on all variants) that pass the video data to an
+  image signal processor (ISP)
+
+* SCALE units (not available on all variants)
+
+* DMA engines that transfer video data into system memory using a
+  double-buffering mechanism called ping-pong mode
+
+* Support for four streams per INTERFACE block (not available on all
+  variants), e.g., for MIPI CSI-2 Virtual Channels (VCs)
+
+This document describes the different variants of the CIF, their hardware
+layout, as well as their representation in the media controller centric rkcif
+device driver, which is located under drivers/media/platform/rockchip/rkcif.
+
+Variants
+========
+
+Rockchip PX30 Video Input Processor (VIP)
+-----------------------------------------
+
+The PX30 Video Input Processor (VIP) features a digital video port that accepts
+parallel video data or BT.656.
+Since these protocols do not feature multiple streams, the VIP has one DMA
+engine that transfers the input video data into system memory.
+
+The rkcif driver represents this hardware variant by exposing one V4L2 subdevice
+(the DVP INTERFACE/CROP block) and one V4L2 device (the DVP DMA engine).
+
+Rockchip RK3568 Video Capture (VICAP)
+-------------------------------------
+
+The RK3568 Video Capture (VICAP) unit features a digital video port and a MIPI
+CSI-2 receiver that can receive video data independently.
+The DVP accepts parallel video data, BT.656 and BT.1120.
+Since the BT.1120 protocol may feature more than one stream, the RK3568 VICAP
+DVP features four DMA engines that can capture different streams.
+Similarly, the RK3568 VICAP MIPI CSI-2 receiver features four DMA engines to
+handle different Virtual Channels (VCs).
+
+The rkcif driver represents this hardware variant by exposing up to three V4L2
+subdevices:
+
+* rkcif-dvp0: INTERFACE/CROP block for the DVP
+* rockchip-mipi-csi fdfb0000.csi: MIPI CSI-2 receiver
+* rkcif-mipi0: INTERFACE/CROP block for the MIPI CSI-2 receiver
+
+and up to five V4L2 devices:
+
+* rkcif-dvp0-id0: The support for multiple streams on the DVP is not yet
+  implemented, as it is hard to find test hardware. Thus, this video device
+  represents the first DMA engine of the RK3568 DVP.
+* rkcif-mipi0-id[0...3]: The four DMA engines of the RK3568 MIPI CSI-2
+  receiver. Each DMA engine can capture a certain MIPI CSI-2 Virtual Channel.
+
+.. kernel-figure:: rkcif-rk3568-vicap.dot
+    :alt:   Topology of the RK3568 Video Capture (VICAP) unit
+    :align: center
diff --git a/Documentation/admin-guide/media/v4l-drivers.rst b/Documentation/admin-guide/media/v4l-drivers.rst
index e8761561b2fe..f6497541d55b 100644
--- a/Documentation/admin-guide/media/v4l-drivers.rst
+++ b/Documentation/admin-guide/media/v4l-drivers.rst
@@ -24,6 +24,7 @@ Video4Linux (V4L) driver-specific documentation
 	qcom_camss
 	raspberrypi-pisp-be
 	rcar-fdp1
+	rkcif
 	rkisp1
 	raspberrypi-rp1-cfe
 	saa7134
diff --git a/MAINTAINERS b/MAINTAINERS
index 96b827049501..d1184d571b64 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20859,6 +20859,13 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/net/can/rockchip,rk3568v2-canfd.yaml
 F:	drivers/net/can/rockchip/
 
+ROCKCHIP CAMERA INTERFACE (RKCIF) DRIVER
+M:	Mehdi Djait <mehdi.djait@linux.intel.com>
+M:	Michael Riesch <michael.riesch@collabora.com>
+L:	linux-media@vger.kernel.org
+S:	Maintained
+F:	Documentation/admin-guide/media/rkcif*
+
 ROCKCHIP CRYPTO DRIVERS
 M:	Corentin Labbe <clabbe@baylibre.com>
 L:	linux-crypto@vger.kernel.org

-- 
2.39.5



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

* [PATCH v6 02/13] media: dt-bindings: video-interfaces: add defines for sampling modes
  2025-04-30  9:15 [PATCH v6 00/13] media: rockchip: add a driver for the rockchip camera interface Michael Riesch via B4 Relay
  2025-04-30  9:15 ` [PATCH v6 01/13] Documentation: admin-guide: media: add " Michael Riesch via B4 Relay
@ 2025-04-30  9:15 ` Michael Riesch via B4 Relay
  2025-04-30  9:15 ` [PATCH v6 03/13] media: dt-bindings: add rockchip px30 vip Michael Riesch via B4 Relay
                   ` (10 subsequent siblings)
  12 siblings, 0 replies; 34+ messages in thread
From: Michael Riesch via B4 Relay @ 2025-04-30  9:15 UTC (permalink / raw)
  To: Mehdi Djait, Maxime Chevallier, Théo Lebrun, Gerald Loacker,
	Thomas Petazzoni, Laurent Pinchart, Mauro Carvalho Chehab,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner,
	Kever Yang, Nicolas Dufresne, Sebastian Reichel,
	Collabora Kernel Team, Paul Kocialkowski, Alexander Shiyan,
	Val Packett, Rob Herring, Philipp Zabel, Sakari Ailus
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-rockchip, Michael Riesch, Michael Riesch

From: Michael Riesch <michael.riesch@wolfvision.net>

Add defines for the pixel clock sampling modes (rising edge, falling edge,
dual edge) for parallel video interfaces.
This avoids hardcoded constants in device tree sources.

Acked-by: Rob Herring <robh@kernel.org>
Signed-off-by: Michael Riesch <michael.riesch@wolfvision.net>
Signed-off-by: Michael Riesch <michael.riesch@collabora.com>
---
 include/dt-bindings/media/video-interfaces.h | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/include/dt-bindings/media/video-interfaces.h b/include/dt-bindings/media/video-interfaces.h
index 88b9d05d8075..0b19c9b2e627 100644
--- a/include/dt-bindings/media/video-interfaces.h
+++ b/include/dt-bindings/media/video-interfaces.h
@@ -20,4 +20,8 @@
 #define MEDIA_BUS_CSI2_CPHY_LINE_ORDER_CAB	4
 #define MEDIA_BUS_CSI2_CPHY_LINE_ORDER_CBA	5
 
+#define MEDIA_PCLK_SAMPLE_FALLING_EDGE		0
+#define MEDIA_PCLK_SAMPLE_RISING_EDGE		1
+#define MEDIA_PCLK_SAMPLE_DUAL_EDGE		2
+
 #endif /* __DT_BINDINGS_MEDIA_VIDEO_INTERFACES_H__ */

-- 
2.39.5



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

* [PATCH v6 03/13] media: dt-bindings: add rockchip px30 vip
  2025-04-30  9:15 [PATCH v6 00/13] media: rockchip: add a driver for the rockchip camera interface Michael Riesch via B4 Relay
  2025-04-30  9:15 ` [PATCH v6 01/13] Documentation: admin-guide: media: add " Michael Riesch via B4 Relay
  2025-04-30  9:15 ` [PATCH v6 02/13] media: dt-bindings: video-interfaces: add defines for sampling modes Michael Riesch via B4 Relay
@ 2025-04-30  9:15 ` Michael Riesch via B4 Relay
  2025-04-30  9:15 ` [PATCH v6 04/13] media: dt-bindings: add rockchip rk3568 vicap Michael Riesch via B4 Relay
                   ` (9 subsequent siblings)
  12 siblings, 0 replies; 34+ messages in thread
From: Michael Riesch via B4 Relay @ 2025-04-30  9:15 UTC (permalink / raw)
  To: Mehdi Djait, Maxime Chevallier, Théo Lebrun, Gerald Loacker,
	Thomas Petazzoni, Laurent Pinchart, Mauro Carvalho Chehab,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner,
	Kever Yang, Nicolas Dufresne, Sebastian Reichel,
	Collabora Kernel Team, Paul Kocialkowski, Alexander Shiyan,
	Val Packett, Rob Herring, Philipp Zabel, Sakari Ailus
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-rockchip, Michael Riesch, Michael Riesch, Mehdi Djait

From: Mehdi Djait <mehdi.djait@bootlin.com>

Add documentation for the Rockchip PX30 Video Input Processor (VIP).

Signed-off-by: Mehdi Djait <mehdi.djait@bootlin.com>
[revised description]
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
Signed-off-by: Michael Riesch <michael.riesch@wolfvision.net>
Signed-off-by: Michael Riesch <michael.riesch@collabora.com>
---
 .../bindings/media/rockchip,px30-vip.yaml          | 122 +++++++++++++++++++++
 MAINTAINERS                                        |   1 +
 2 files changed, 123 insertions(+)

diff --git a/Documentation/devicetree/bindings/media/rockchip,px30-vip.yaml b/Documentation/devicetree/bindings/media/rockchip,px30-vip.yaml
new file mode 100644
index 000000000000..9f7ab6965636
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/rockchip,px30-vip.yaml
@@ -0,0 +1,122 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/rockchip,px30-vip.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Rockchip PX30 Video Input Processor (VIP)
+
+maintainers:
+  - Mehdi Djait <mehdi.djait@linux.intel.com>
+  - Michael Riesch <michael.riesch@collabora.com>
+
+description:
+  The Rockchip PX30 Video Input Processor (VIP) receives the data from a camera
+  sensor or CCIR656 encoder and transfers it into system main memory by AXI bus.
+
+properties:
+  compatible:
+    const: rockchip,px30-vip
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+
+  clocks:
+    items:
+      - description: ACLK
+      - description: HCLK
+      - description: PCLK
+
+  clock-names:
+    items:
+      - const: aclk
+      - const: hclk
+      - const: pclk
+
+  resets:
+    items:
+      - description: AXI
+      - description: AHB
+      - description: PCLK IN
+
+  reset-names:
+    items:
+      - const: axi
+      - const: ahb
+      - const: pclkin
+
+  power-domains:
+    maxItems: 1
+
+  ports:
+    $ref: /schemas/graph.yaml#/properties/ports
+
+    properties:
+      port@0:
+        $ref: /schemas/graph.yaml#/$defs/port-base
+        unevaluatedProperties: false
+        description: input port on the parallel interface
+
+        properties:
+          endpoint:
+            $ref: video-interfaces.yaml#
+            unevaluatedProperties: false
+
+            properties:
+              bus-type:
+                enum: [5, 6]
+
+            required:
+              - bus-type
+
+    required:
+      - port@0
+
+required:
+  - compatible
+  - reg
+  - interrupts
+  - clocks
+  - ports
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/clock/px30-cru.h>
+    #include <dt-bindings/interrupt-controller/arm-gic.h>
+    #include <dt-bindings/media/video-interfaces.h>
+    #include <dt-bindings/power/px30-power.h>
+
+    soc {
+        #address-cells = <2>;
+        #size-cells = <2>;
+
+        video-capture@ff490000 {
+            compatible = "rockchip,px30-vip";
+            reg = <0x0 0xff490000 0x0 0x200>;
+            interrupts = <GIC_SPI 69 IRQ_TYPE_LEVEL_HIGH>;
+            clocks = <&cru ACLK_CIF>, <&cru HCLK_CIF>, <&cru PCLK_CIF>;
+            clock-names = "aclk", "hclk", "pclk";
+            power-domains = <&power PX30_PD_VI>;
+            resets = <&cru SRST_CIF_A>, <&cru SRST_CIF_H>, <&cru SRST_CIF_PCLKIN>;
+            reset-names = "axi", "ahb", "pclkin";
+
+            ports {
+                #address-cells = <1>;
+                #size-cells = <0>;
+
+                port@0 {
+                    reg = <0>;
+
+                    cif_in: endpoint {
+                        remote-endpoint = <&tw9900_out>;
+                        bus-type = <MEDIA_BUS_TYPE_BT656>;
+                    };
+                };
+            };
+        };
+    };
diff --git a/MAINTAINERS b/MAINTAINERS
index d1184d571b64..2e24210f4e60 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20865,6 +20865,7 @@ M:	Michael Riesch <michael.riesch@collabora.com>
 L:	linux-media@vger.kernel.org
 S:	Maintained
 F:	Documentation/admin-guide/media/rkcif*
+F:	Documentation/devicetree/bindings/media/rockchip,px30-vip.yaml
 
 ROCKCHIP CRYPTO DRIVERS
 M:	Corentin Labbe <clabbe@baylibre.com>

-- 
2.39.5



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

* [PATCH v6 04/13] media: dt-bindings: add rockchip rk3568 vicap
  2025-04-30  9:15 [PATCH v6 00/13] media: rockchip: add a driver for the rockchip camera interface Michael Riesch via B4 Relay
                   ` (2 preceding siblings ...)
  2025-04-30  9:15 ` [PATCH v6 03/13] media: dt-bindings: add rockchip px30 vip Michael Riesch via B4 Relay
@ 2025-04-30  9:15 ` Michael Riesch via B4 Relay
  2025-05-01 11:49   ` Krzysztof Kozlowski
  2025-04-30  9:15 ` [PATCH v6 05/13] media: dt-bindings: add rockchip rk3568 mipi csi receiver Michael Riesch via B4 Relay
                   ` (8 subsequent siblings)
  12 siblings, 1 reply; 34+ messages in thread
From: Michael Riesch via B4 Relay @ 2025-04-30  9:15 UTC (permalink / raw)
  To: Mehdi Djait, Maxime Chevallier, Théo Lebrun, Gerald Loacker,
	Thomas Petazzoni, Laurent Pinchart, Mauro Carvalho Chehab,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner,
	Kever Yang, Nicolas Dufresne, Sebastian Reichel,
	Collabora Kernel Team, Paul Kocialkowski, Alexander Shiyan,
	Val Packett, Rob Herring, Philipp Zabel, Sakari Ailus
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-rockchip, Michael Riesch, Michael Riesch

From: Michael Riesch <michael.riesch@wolfvision.net>

Add documentation for the Rockchip RK3568 Video Capture (VICAP) unit.

Signed-off-by: Michael Riesch <michael.riesch@wolfvision.net>
Signed-off-by: Michael Riesch <michael.riesch@collabora.com>
---
 .../bindings/media/rockchip,rk3568-vicap.yaml      | 170 +++++++++++++++++++++
 MAINTAINERS                                        |   1 +
 2 files changed, 171 insertions(+)

diff --git a/Documentation/devicetree/bindings/media/rockchip,rk3568-vicap.yaml b/Documentation/devicetree/bindings/media/rockchip,rk3568-vicap.yaml
new file mode 100644
index 000000000000..99861d236f5e
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/rockchip,rk3568-vicap.yaml
@@ -0,0 +1,170 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/rockchip,rk3568-vicap.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Rockchip RK3568 Video Capture (VICAP)
+
+maintainers:
+  - Michael Riesch <michael.riesch@collabora.com>
+
+description:
+  The Rockchip RK3568 Video Capture (VICAP) block features a digital video
+  port (DVP, a parallel video interface) and a MIPI CSI-2 port. It receives
+  the data from camera sensors, video decoders, or other companion ICs and
+  transfers it into system main memory by AXI bus.
+
+properties:
+  compatible:
+    const: rockchip,rk3568-vicap
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+
+  clocks:
+    items:
+      - description: ACLK
+      - description: HCLK
+      - description: DCLK
+      - description: ICLK
+
+  clock-names:
+    items:
+      - const: aclk
+      - const: hclk
+      - const: dclk
+      - const: iclk
+
+  iommus:
+    maxItems: 1
+
+  resets:
+    items:
+      - description: ARST
+      - description: HRST
+      - description: DRST
+      - description: PRST
+      - description: IRST
+
+  reset-names:
+    items:
+      - const: arst
+      - const: hrst
+      - const: drst
+      - const: prst
+      - const: irst
+
+  rockchip,grf:
+    $ref: /schemas/types.yaml#/definitions/phandle
+    description: Phandle to general register file used for video input block control.
+
+  power-domains:
+    maxItems: 1
+
+  ports:
+    $ref: /schemas/graph.yaml#/properties/ports
+
+    properties:
+      port@0:
+        $ref: /schemas/graph.yaml#/$defs/port-base
+        unevaluatedProperties: false
+        description: The digital video port (DVP, a parallel video interface).
+
+        properties:
+          endpoint:
+            $ref: video-interfaces.yaml#
+            unevaluatedProperties: false
+
+            properties:
+              bus-type:
+                enum: [5, 6]
+
+              rockchip,dvp-clk-delay:
+                $ref: /schemas/types.yaml#/definitions/uint32
+                default: 0
+                minimum: 0
+                maximum: 127
+                description:
+                  Delay the DVP path clock input to align the sampling phase,
+                  only valid in dual edge sampling mode. Delay is zero by
+                  default and can be adjusted optionally.
+
+            required:
+              - bus-type
+
+      port@1:
+        $ref: /schemas/graph.yaml#/properties/port
+        description: Port connected to the MIPI CSI-2 receiver output.
+
+        properties:
+          endpoint:
+            $ref: video-interfaces.yaml#
+            unevaluatedProperties: false
+
+required:
+  - compatible
+  - reg
+  - interrupts
+  - clocks
+  - ports
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/clock/rk3568-cru.h>
+    #include <dt-bindings/interrupt-controller/arm-gic.h>
+    #include <dt-bindings/interrupt-controller/irq.h>
+    #include <dt-bindings/power/rk3568-power.h>
+    #include <dt-bindings/media/video-interfaces.h>
+
+    soc {
+        #address-cells = <2>;
+        #size-cells = <2>;
+
+        vicap: video-capture@fdfe0000 {
+            compatible = "rockchip,rk3568-vicap";
+            reg = <0x0 0xfdfe0000 0x0 0x200>;
+            interrupts = <GIC_SPI 146 IRQ_TYPE_LEVEL_HIGH>;
+            assigned-clocks = <&cru DCLK_VICAP>;
+            assigned-clock-rates = <300000000>;
+            clocks = <&cru ACLK_VICAP>, <&cru HCLK_VICAP>,
+                     <&cru DCLK_VICAP>, <&cru ICLK_VICAP_G>;
+            clock-names = "aclk", "hclk", "dclk", "iclk";
+            iommus = <&vicap_mmu>;
+            power-domains = <&power RK3568_PD_VI>;
+            resets = <&cru SRST_A_VICAP>, <&cru SRST_H_VICAP>,
+                     <&cru SRST_D_VICAP>, <&cru SRST_P_VICAP>,
+                     <&cru SRST_I_VICAP>;
+            reset-names = "arst", "hrst", "drst", "prst", "irst";
+            rockchip,grf = <&grf>;
+
+            ports {
+                #address-cells = <1>;
+                #size-cells = <0>;
+
+                vicap_dvp: port@0 {
+                    reg = <0>;
+
+                    vicap_dvp_input: endpoint {
+                        bus-type = <MEDIA_BUS_TYPE_BT656>;
+                        bus-width = <16>;
+                        pclk-sample = <MEDIA_PCLK_SAMPLE_DUAL_EDGE>;
+                        remote-endpoint = <&it6801_output>;
+                    };
+                };
+
+                vicap_mipi: port@1 {
+                    reg = <1>;
+
+                    vicap_mipi_input: endpoint {
+                        remote-endpoint = <&csi_output>;
+                    };
+                };
+            };
+        };
+    };
diff --git a/MAINTAINERS b/MAINTAINERS
index 2e24210f4e60..99a29a905cae 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20866,6 +20866,7 @@ L:	linux-media@vger.kernel.org
 S:	Maintained
 F:	Documentation/admin-guide/media/rkcif*
 F:	Documentation/devicetree/bindings/media/rockchip,px30-vip.yaml
+F:	Documentation/devicetree/bindings/media/rockchip,rk3568-vicap.yaml
 
 ROCKCHIP CRYPTO DRIVERS
 M:	Corentin Labbe <clabbe@baylibre.com>

-- 
2.39.5



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

* [PATCH v6 05/13] media: dt-bindings: add rockchip rk3568 mipi csi receiver
  2025-04-30  9:15 [PATCH v6 00/13] media: rockchip: add a driver for the rockchip camera interface Michael Riesch via B4 Relay
                   ` (3 preceding siblings ...)
  2025-04-30  9:15 ` [PATCH v6 04/13] media: dt-bindings: add rockchip rk3568 vicap Michael Riesch via B4 Relay
@ 2025-04-30  9:15 ` Michael Riesch via B4 Relay
  2025-04-30 10:50   ` Rob Herring (Arm)
  2025-05-06 12:09   ` Mehdi Djait
  2025-04-30  9:15 ` [PATCH v6 06/13] media: rockchip: add a driver for the rockchip camera interface Michael Riesch via B4 Relay
                   ` (7 subsequent siblings)
  12 siblings, 2 replies; 34+ messages in thread
From: Michael Riesch via B4 Relay @ 2025-04-30  9:15 UTC (permalink / raw)
  To: Mehdi Djait, Maxime Chevallier, Théo Lebrun, Gerald Loacker,
	Thomas Petazzoni, Laurent Pinchart, Mauro Carvalho Chehab,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner,
	Kever Yang, Nicolas Dufresne, Sebastian Reichel,
	Collabora Kernel Team, Paul Kocialkowski, Alexander Shiyan,
	Val Packett, Rob Herring, Philipp Zabel, Sakari Ailus
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-rockchip, Michael Riesch, Michael Riesch

From: Michael Riesch <michael.riesch@wolfvision.net>

Add documentation for the Rockchip RK3568 MIPI CSI-2 Receiver.

Signed-off-by: Michael Riesch <michael.riesch@wolfvision.net>
Signed-off-by: Michael Riesch <michael.riesch@collabora.com>
---
 .../bindings/media/rockchip,rk3568-mipi-csi.yaml   | 113 +++++++++++++++++++++
 MAINTAINERS                                        |   1 +
 2 files changed, 114 insertions(+)

diff --git a/Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi.yaml b/Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi.yaml
new file mode 100644
index 000000000000..d5004cb288dd
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi.yaml
@@ -0,0 +1,113 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/rockchip,rk3568-mipi-csi.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Rockchip RK3568 MIPI CSI-2 Receiver
+
+maintainers:
+  - Michael Riesch <michael.riesch@collabora.com>
+
+description:
+  The Rockchip RK3568 MIPI CSI-2 Receiver is a CSI-2 bridge with one input port
+  and one output port. It receives the data with the help of an external
+  MIPI PHY (C-PHY or D-PHY) and passes it to the Rockchip RK3568 Video Capture
+  (VICAP) block.
+
+properties:
+  compatible:
+    const: rockchip,rk3568-mipi-csi
+
+  reg:
+    maxItems: 1
+
+  clocks:
+    maxItems: 1
+
+  phys:
+    maxItems: 1
+    description: MIPI C-PHY or D-PHY.
+
+  power-domains:
+    maxItems: 1
+
+  resets:
+    maxItems: 1
+
+  ports:
+    $ref: /schemas/graph.yaml#/properties/ports
+
+    properties:
+      port@0:
+        $ref: /schemas/graph.yaml#/$defs/port-base
+        unevaluatedProperties: false
+        description: Input port node. Connect to e.g., a MIPI CSI-2 image sensor.
+
+        properties:
+          endpoint:
+            $ref: video-interfaces.yaml#
+            unevaluatedProperties: false
+
+            properties:
+              bus-type:
+                enum: [1, 4]
+
+            required:
+              - bus-type
+
+      port@1:
+        $ref: /schemas/graph.yaml#/properties/port
+        description: Output port connected to a RK3568 VICAP port.
+
+    required:
+      - port@0
+      - port@1
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - phys
+  - phy-names
+  - ports
+  - power-domains
+  - resets
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/clock/rk3568-cru.h>
+    #include <dt-bindings/power/rk3568-power.h>
+
+    soc {
+        #address-cells = <2>;
+        #size-cells = <2>;
+
+        csi: csi@fdfb0000 {
+            compatible = "rockchip,rk3568-mipi-csi";
+            reg = <0x0 0xfdfb0000 0x0 0x10000>;
+            clocks = <&cru PCLK_CSI2HOST1>;
+            phys = <&csi_dphy>;
+            power-domains = <&power RK3568_PD_VI>;
+            resets = <&cru SRST_P_CSI2HOST1>;
+
+            ports {
+                #address-cells = <1>;
+                #size-cells = <0>;
+
+                csi_in: port@0 {
+                    reg = <0>;
+                };
+
+                csi_out: port@1 {
+                    reg = <1>;
+
+                    csi_output: endpoint {
+                        remote-endpoint = <&vicap_mipi_input>;
+                    };
+                };
+            };
+        };
+    };
diff --git a/MAINTAINERS b/MAINTAINERS
index 99a29a905cae..e993ef6f8771 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20866,6 +20866,7 @@ L:	linux-media@vger.kernel.org
 S:	Maintained
 F:	Documentation/admin-guide/media/rkcif*
 F:	Documentation/devicetree/bindings/media/rockchip,px30-vip.yaml
+F:	Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi.yaml
 F:	Documentation/devicetree/bindings/media/rockchip,rk3568-vicap.yaml
 
 ROCKCHIP CRYPTO DRIVERS

-- 
2.39.5



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

* [PATCH v6 06/13] media: rockchip: add a driver for the rockchip camera interface
  2025-04-30  9:15 [PATCH v6 00/13] media: rockchip: add a driver for the rockchip camera interface Michael Riesch via B4 Relay
                   ` (4 preceding siblings ...)
  2025-04-30  9:15 ` [PATCH v6 05/13] media: dt-bindings: add rockchip rk3568 mipi csi receiver Michael Riesch via B4 Relay
@ 2025-04-30  9:15 ` Michael Riesch via B4 Relay
  2025-05-01  0:43   ` Bryan O'Donoghue
  2025-05-06 10:37   ` Mehdi Djait
  2025-04-30  9:15 ` [PATCH v6 07/13] media: rockchip: rkcif: add driver for mipi csi-2 receiver Michael Riesch via B4 Relay
                   ` (6 subsequent siblings)
  12 siblings, 2 replies; 34+ messages in thread
From: Michael Riesch via B4 Relay @ 2025-04-30  9:15 UTC (permalink / raw)
  To: Mehdi Djait, Maxime Chevallier, Théo Lebrun, Gerald Loacker,
	Thomas Petazzoni, Laurent Pinchart, Mauro Carvalho Chehab,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner,
	Kever Yang, Nicolas Dufresne, Sebastian Reichel,
	Collabora Kernel Team, Paul Kocialkowski, Alexander Shiyan,
	Val Packett, Rob Herring, Philipp Zabel, Sakari Ailus
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-rockchip, Michael Riesch, Michael Riesch, Mehdi Djait

From: Michael Riesch <michael.riesch@wolfvision.net>

The Rockchip Camera Interface (CIF) is featured in many Rockchip SoCs
in different variations. For example, the PX30 Video Input Processor (VIP)
is able to receive video data via the Digital Video Port (DVP, a parallel
data interface) and transfer it into system memory using a
double-buffering mechanism called ping-pong mode.
The RK3568 Video Capture (VICAP) unit, on the other hand, features a DVP
and a MIPI CSI-2 receiver that can receive video data independently
(both using the ping-pong scheme).
The different variants may have additional features, such as scaling
and/or cropping.
Finally, the RK3588 VICAP unit constitutes an essential piece of the
camera interface with one DVP, six MIPI CSI-2 receivers, scale/crop
units, and a data path multiplexer (to scaler units, to ISP, ...).

Add a basic media controller centric V4L2 driver for the Rockchip CIF with
 - support for the PX30 VIP
 - support for the RK3568 VICAP DVP
 - abstraction for the ping-pong scheme to allow for future extensions
 - abstraction for the INTERFACE and CROP parts to allow for future
   extensions

[PX30 VIP support v1-v5]
Co-developed-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
Signed-off-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
[PX30 VIP support v6-v13]
Co-developed-by: Mehdi Djait <mehdi.djait@bootlin.com>
Signed-off-by: Mehdi Djait <mehdi.djait@bootlin.com>
[added RK3568 VICAP DVP support]
[refactored to media controller centric driver, added mplane support]
Co-developed-by: Gerald Loacker <gerald.loacker@wolfvision.net>
Signed-off-by: Gerald Loacker <gerald.loacker@wolfvision.net>
Signed-off-by: Michael Riesch <michael.riesch@wolfvision.net>
Signed-off-by: Michael Riesch <michael.riesch@collabora.com>
---
 MAINTAINERS                                        |   1 +
 drivers/media/platform/rockchip/Kconfig            |   1 +
 drivers/media/platform/rockchip/Makefile           |   1 +
 drivers/media/platform/rockchip/rkcif/Kconfig      |  15 +
 drivers/media/platform/rockchip/rkcif/Makefile     |   7 +
 .../platform/rockchip/rkcif/rkcif-capture-dvp.c    | 858 +++++++++++++++++++++
 .../platform/rockchip/rkcif/rkcif-capture-dvp.h    |  24 +
 .../platform/rockchip/rkcif/rkcif-capture-mipi.c   |  27 +
 .../platform/rockchip/rkcif/rkcif-capture-mipi.h   |  20 +
 .../media/platform/rockchip/rkcif/rkcif-common.h   | 220 ++++++
 drivers/media/platform/rockchip/rkcif/rkcif-dev.c  | 299 +++++++
 .../platform/rockchip/rkcif/rkcif-interface.c      | 423 ++++++++++
 .../platform/rockchip/rkcif/rkcif-interface.h      |  30 +
 drivers/media/platform/rockchip/rkcif/rkcif-regs.h | 132 ++++
 .../media/platform/rockchip/rkcif/rkcif-stream.c   | 622 +++++++++++++++
 .../media/platform/rockchip/rkcif/rkcif-stream.h   |  31 +
 16 files changed, 2711 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index e993ef6f8771..1f67709c2184 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20868,6 +20868,7 @@ F:	Documentation/admin-guide/media/rkcif*
 F:	Documentation/devicetree/bindings/media/rockchip,px30-vip.yaml
 F:	Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi.yaml
 F:	Documentation/devicetree/bindings/media/rockchip,rk3568-vicap.yaml
+F:	drivers/media/platform/rockchip/rkcif/
 
 ROCKCHIP CRYPTO DRIVERS
 M:	Corentin Labbe <clabbe@baylibre.com>
diff --git a/drivers/media/platform/rockchip/Kconfig b/drivers/media/platform/rockchip/Kconfig
index b41d3960c1b4..549f4e9f443e 100644
--- a/drivers/media/platform/rockchip/Kconfig
+++ b/drivers/media/platform/rockchip/Kconfig
@@ -3,4 +3,5 @@
 comment "Rockchip media platform drivers"
 
 source "drivers/media/platform/rockchip/rga/Kconfig"
+source "drivers/media/platform/rockchip/rkcif/Kconfig"
 source "drivers/media/platform/rockchip/rkisp1/Kconfig"
diff --git a/drivers/media/platform/rockchip/Makefile b/drivers/media/platform/rockchip/Makefile
index 4f782b876ac9..6aba32c8830c 100644
--- a/drivers/media/platform/rockchip/Makefile
+++ b/drivers/media/platform/rockchip/Makefile
@@ -1,3 +1,4 @@
 # SPDX-License-Identifier: GPL-2.0-only
 obj-y += rga/
+obj-y += rkcif/
 obj-y += rkisp1/
diff --git a/drivers/media/platform/rockchip/rkcif/Kconfig b/drivers/media/platform/rockchip/rkcif/Kconfig
new file mode 100644
index 000000000000..f53e79a4b42d
--- /dev/null
+++ b/drivers/media/platform/rockchip/rkcif/Kconfig
@@ -0,0 +1,15 @@
+config VIDEO_ROCKCHIP_CIF
+	tristate "Rockchip Camera Interface (CIF)"
+	depends on VIDEO_DEV
+	depends on ARCH_ROCKCHIP || COMPILE_TEST
+	depends on V4L_PLATFORM_DRIVERS
+	depends on PM && COMMON_CLK
+	select MEDIA_CONTROLLER
+	select VIDEOBUF2_DMA_CONTIG
+	select V4L2_FWNODE
+	select VIDEO_V4L2_SUBDEV_API
+	help
+	  This is a driver for Rockchip Camera Interface (CIF). It is featured
+	  in many Rockchips SoCs in different variations, such as the PX30
+	  Video Input Processor (VIP, one Digital Video Port (DVP)) or the
+	  RK3568 Video Capture (VICAP, one DVP, one MIPI CSI-2 receiver) unit.
diff --git a/drivers/media/platform/rockchip/rkcif/Makefile b/drivers/media/platform/rockchip/rkcif/Makefile
new file mode 100644
index 000000000000..818424972c7b
--- /dev/null
+++ b/drivers/media/platform/rockchip/rkcif/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_VIDEO_ROCKCHIP_CIF) += rockchip-cif.o
+rockchip-cif-objs += rkcif-dev.o \
+	rkcif-capture-dvp.o \
+	rkcif-capture-mipi.o \
+	rkcif-interface.o \
+	rkcif-stream.o
diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-capture-dvp.c b/drivers/media/platform/rockchip/rkcif/rkcif-capture-dvp.c
new file mode 100644
index 000000000000..b1370ee4d900
--- /dev/null
+++ b/drivers/media/platform/rockchip/rkcif/rkcif-capture-dvp.c
@@ -0,0 +1,858 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Rockchip Camera Interface (CIF) Driver
+ *
+ * Copyright (C) 2018 Rockchip Electronics Co., Ltd.
+ * Copyright (C) 2020 Maxime Chevallier <maxime.chevallier@bootlin.com>
+ * Copyright (C) 2023 Mehdi Djait <mehdi.djait@bootlin.com>
+ * Copyright (C) 2025 Michael Riesch <michael.riesch@wolfvision.net>
+ */
+
+#include <media/v4l2-common.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-mc.h>
+#include <media/v4l2-subdev.h>
+
+#include "rkcif-capture-dvp.h"
+#include "rkcif-common.h"
+#include "rkcif-interface.h"
+#include "rkcif-regs.h"
+#include "rkcif-stream.h"
+
+static const struct rkcif_output_fmt dvp_out_fmts[] = {
+	{
+		.fourcc = V4L2_PIX_FMT_NV16,
+		.dvp_fmt_val = RKCIF_FORMAT_YUV_OUTPUT_422 |
+			       RKCIF_FORMAT_UV_STORAGE_ORDER_UVUV,
+		.cplanes = 2,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_NV16M,
+		.dvp_fmt_val = RKCIF_FORMAT_YUV_OUTPUT_422 |
+			       RKCIF_FORMAT_UV_STORAGE_ORDER_UVUV,
+		.cplanes = 2,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_NV61,
+		.dvp_fmt_val = RKCIF_FORMAT_YUV_OUTPUT_422 |
+			       RKCIF_FORMAT_UV_STORAGE_ORDER_VUVU,
+		.cplanes = 2,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_NV61M,
+		.dvp_fmt_val = RKCIF_FORMAT_YUV_OUTPUT_422 |
+			       RKCIF_FORMAT_UV_STORAGE_ORDER_VUVU,
+		.cplanes = 2,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_NV12,
+		.dvp_fmt_val = RKCIF_FORMAT_YUV_OUTPUT_420 |
+			       RKCIF_FORMAT_UV_STORAGE_ORDER_UVUV,
+		.cplanes = 2,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_NV12M,
+		.dvp_fmt_val = RKCIF_FORMAT_YUV_OUTPUT_420 |
+			       RKCIF_FORMAT_UV_STORAGE_ORDER_UVUV,
+		.cplanes = 2,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_NV21,
+		.dvp_fmt_val = RKCIF_FORMAT_YUV_OUTPUT_420 |
+			       RKCIF_FORMAT_UV_STORAGE_ORDER_VUVU,
+		.cplanes = 2,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_NV21M,
+		.dvp_fmt_val = RKCIF_FORMAT_YUV_OUTPUT_420 |
+			       RKCIF_FORMAT_UV_STORAGE_ORDER_VUVU,
+		.cplanes = 2,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_RGB24,
+		.cplanes = 1,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_RGB565,
+		.cplanes = 1,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_BGR666,
+		.cplanes = 1,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SRGGB8,
+		.cplanes = 1,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SGRBG8,
+		.cplanes = 1,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SGBRG8,
+		.cplanes = 1,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SBGGR8,
+		.cplanes = 1,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SRGGB10,
+		.cplanes = 1,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SGRBG10,
+		.cplanes = 1,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SGBRG10,
+		.cplanes = 1,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SBGGR10,
+		.cplanes = 1,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SRGGB12,
+		.cplanes = 1,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SGRBG12,
+		.cplanes = 1,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SGBRG12,
+		.cplanes = 1,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SBGGR12,
+		.cplanes = 1,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SBGGR16,
+		.cplanes = 1,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_Y16,
+		.cplanes = 1,
+	},
+};
+
+static const struct rkcif_input_fmt px30_dvp_in_fmts[] = {
+	{
+		.mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
+		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
+			       RKCIF_FORMAT_YUV_INPUT_ORDER_YUYV,
+		.fmt_type = RKCIF_FMT_TYPE_YUV,
+		.field = V4L2_FIELD_NONE,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
+		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
+			       RKCIF_FORMAT_YUV_INPUT_ORDER_YUYV,
+		.fmt_type = RKCIF_FMT_TYPE_YUV,
+		.field = V4L2_FIELD_INTERLACED,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_YVYU8_2X8,
+		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
+			       RKCIF_FORMAT_YUV_INPUT_ORDER_YVYU,
+		.fmt_type = RKCIF_FMT_TYPE_YUV,
+		.field = V4L2_FIELD_NONE,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_YVYU8_2X8,
+		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
+			       RKCIF_FORMAT_YUV_INPUT_ORDER_YVYU,
+		.fmt_type = RKCIF_FMT_TYPE_YUV,
+		.field = V4L2_FIELD_INTERLACED,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_UYVY8_2X8,
+		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
+			       RKCIF_FORMAT_YUV_INPUT_ORDER_UYVY,
+		.fmt_type = RKCIF_FMT_TYPE_YUV,
+		.field = V4L2_FIELD_NONE,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_UYVY8_2X8,
+		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
+			       RKCIF_FORMAT_YUV_INPUT_ORDER_UYVY,
+		.fmt_type = RKCIF_FMT_TYPE_YUV,
+		.field = V4L2_FIELD_INTERLACED,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_VYUY8_2X8,
+		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
+			       RKCIF_FORMAT_YUV_INPUT_ORDER_VYUY,
+		.fmt_type = RKCIF_FMT_TYPE_YUV,
+		.field = V4L2_FIELD_NONE,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_VYUY8_2X8,
+		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
+			       RKCIF_FORMAT_YUV_INPUT_ORDER_VYUY,
+		.fmt_type = RKCIF_FMT_TYPE_YUV,
+		.field = V4L2_FIELD_INTERLACED,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8,
+		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
+			       RKCIF_FORMAT_RAW_DATA_WIDTH_8,
+		.fmt_type = RKCIF_FMT_TYPE_RAW,
+		.field = V4L2_FIELD_NONE,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_SGBRG8_1X8,
+		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
+			       RKCIF_FORMAT_RAW_DATA_WIDTH_8,
+		.fmt_type = RKCIF_FMT_TYPE_RAW,
+		.field = V4L2_FIELD_NONE,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8,
+		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
+			       RKCIF_FORMAT_RAW_DATA_WIDTH_8,
+		.fmt_type = RKCIF_FMT_TYPE_RAW,
+		.field = V4L2_FIELD_NONE,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_SRGGB8_1X8,
+		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
+			       RKCIF_FORMAT_RAW_DATA_WIDTH_8,
+		.fmt_type = RKCIF_FMT_TYPE_RAW,
+		.field = V4L2_FIELD_NONE,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10,
+		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
+			       RKCIF_FORMAT_RAW_DATA_WIDTH_10,
+		.fmt_type = RKCIF_FMT_TYPE_RAW,
+		.field = V4L2_FIELD_NONE,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10,
+		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
+			       RKCIF_FORMAT_RAW_DATA_WIDTH_10,
+		.fmt_type = RKCIF_FMT_TYPE_RAW,
+		.field = V4L2_FIELD_NONE,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10,
+		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
+			       RKCIF_FORMAT_RAW_DATA_WIDTH_10,
+		.fmt_type = RKCIF_FMT_TYPE_RAW,
+		.field = V4L2_FIELD_NONE,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10,
+		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
+			       RKCIF_FORMAT_RAW_DATA_WIDTH_10,
+		.fmt_type = RKCIF_FMT_TYPE_RAW,
+		.field = V4L2_FIELD_NONE,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12,
+		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
+			       RKCIF_FORMAT_RAW_DATA_WIDTH_12,
+		.fmt_type = RKCIF_FMT_TYPE_RAW,
+		.field = V4L2_FIELD_NONE,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_SGBRG12_1X12,
+		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
+			       RKCIF_FORMAT_RAW_DATA_WIDTH_12,
+		.fmt_type = RKCIF_FMT_TYPE_RAW,
+		.field = V4L2_FIELD_NONE,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_SGRBG12_1X12,
+		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
+			       RKCIF_FORMAT_RAW_DATA_WIDTH_12,
+		.fmt_type = RKCIF_FMT_TYPE_RAW,
+		.field = V4L2_FIELD_NONE,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_SRGGB12_1X12,
+		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
+			       RKCIF_FORMAT_RAW_DATA_WIDTH_12,
+		.fmt_type = RKCIF_FMT_TYPE_RAW,
+		.field = V4L2_FIELD_NONE,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_RGB888_1X24,
+		.field = V4L2_FIELD_NONE,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_Y8_1X8,
+		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
+			       RKCIF_FORMAT_RAW_DATA_WIDTH_8,
+		.fmt_type = RKCIF_FMT_TYPE_RAW,
+		.field = V4L2_FIELD_NONE,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_Y10_1X10,
+		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
+			       RKCIF_FORMAT_RAW_DATA_WIDTH_10,
+		.fmt_type = RKCIF_FMT_TYPE_RAW,
+		.field = V4L2_FIELD_NONE,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_Y12_1X12,
+		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
+			       RKCIF_FORMAT_RAW_DATA_WIDTH_12,
+		.fmt_type = RKCIF_FMT_TYPE_RAW,
+		.field = V4L2_FIELD_NONE,
+	}
+};
+
+const struct rkcif_dvp_match_data rkcif_px30_vip_dvp_match_data = {
+	.in_fmts = px30_dvp_in_fmts,
+	.in_fmts_num = ARRAY_SIZE(px30_dvp_in_fmts),
+	.out_fmts = dvp_out_fmts,
+	.out_fmts_num = ARRAY_SIZE(dvp_out_fmts),
+	.has_scaler = true,
+	.regs = {
+		[RKCIF_DVP_CTRL] = 0x00,
+		[RKCIF_DVP_INTEN] = 0x04,
+		[RKCIF_DVP_INTSTAT] = 0x08,
+		[RKCIF_DVP_FOR] = 0x0c,
+		[RKCIF_DVP_LINE_NUM_ADDR] = 0x10,
+		[RKCIF_DVP_FRM0_ADDR_Y] = 0x14,
+		[RKCIF_DVP_FRM0_ADDR_UV] = 0x18,
+		[RKCIF_DVP_FRM1_ADDR_Y] = 0x1c,
+		[RKCIF_DVP_FRM1_ADDR_UV] = 0x20,
+		[RKCIF_DVP_VIR_LINE_WIDTH] = 0x24,
+		[RKCIF_DVP_SET_SIZE] = 0x28,
+		[RKCIF_DVP_SCL_CTRL] = 0x48,
+		[RKCIF_DVP_FRAME_STATUS] = 0x60,
+		[RKCIF_DVP_LAST_LINE] = 0x68,
+		[RKCIF_DVP_LAST_PIX] = 0x6c,
+	},
+};
+
+static const struct rkcif_input_fmt rk3568_dvp_in_fmts[] = {
+	{
+		.mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
+		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
+			       RKCIF_FORMAT_YUV_INPUT_ORDER_YUYV,
+		.fmt_type = RKCIF_FMT_TYPE_YUV,
+		.field = V4L2_FIELD_NONE,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
+		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
+			       RKCIF_FORMAT_YUV_INPUT_ORDER_YUYV,
+		.fmt_type = RKCIF_FMT_TYPE_YUV,
+		.field = V4L2_FIELD_INTERLACED,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_YVYU8_2X8,
+		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
+			       RKCIF_FORMAT_YUV_INPUT_ORDER_YVYU,
+		.fmt_type = RKCIF_FMT_TYPE_YUV,
+		.field = V4L2_FIELD_NONE,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_YVYU8_2X8,
+		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
+			       RKCIF_FORMAT_YUV_INPUT_ORDER_YVYU,
+		.fmt_type = RKCIF_FMT_TYPE_YUV,
+		.field = V4L2_FIELD_INTERLACED,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_UYVY8_2X8,
+		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
+			       RKCIF_FORMAT_YUV_INPUT_ORDER_UYVY,
+		.fmt_type = RKCIF_FMT_TYPE_YUV,
+		.field = V4L2_FIELD_NONE,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_UYVY8_2X8,
+		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
+			       RKCIF_FORMAT_YUV_INPUT_ORDER_UYVY,
+		.fmt_type = RKCIF_FMT_TYPE_YUV,
+		.field = V4L2_FIELD_INTERLACED,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_VYUY8_2X8,
+		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
+			       RKCIF_FORMAT_YUV_INPUT_ORDER_VYUY,
+		.fmt_type = RKCIF_FMT_TYPE_YUV,
+		.field = V4L2_FIELD_NONE,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_VYUY8_2X8,
+		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
+			       RKCIF_FORMAT_YUV_INPUT_ORDER_VYUY,
+		.fmt_type = RKCIF_FMT_TYPE_YUV,
+		.field = V4L2_FIELD_INTERLACED,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_YUYV8_1X16,
+		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
+			       RKCIF_FORMAT_YUV_INPUT_ORDER_YUYV |
+			       RKCIF_FORMAT_INPUT_MODE_BT1120 |
+			       RKCIF_FORMAT_BT1120_TRANSMIT_PROGRESS,
+		.field = V4L2_FIELD_NONE,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_YUYV8_1X16,
+		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
+			       RKCIF_FORMAT_YUV_INPUT_ORDER_YUYV |
+			       RKCIF_FORMAT_INPUT_MODE_BT1120,
+		.field = V4L2_FIELD_INTERLACED,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_YVYU8_1X16,
+		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
+			       RKCIF_FORMAT_YUV_INPUT_ORDER_YVYU |
+			       RKCIF_FORMAT_INPUT_MODE_BT1120 |
+			       RKCIF_FORMAT_BT1120_TRANSMIT_PROGRESS,
+		.field = V4L2_FIELD_NONE,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_YVYU8_1X16,
+		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
+			       RKCIF_FORMAT_YUV_INPUT_ORDER_YVYU |
+			       RKCIF_FORMAT_INPUT_MODE_BT1120,
+		.field = V4L2_FIELD_INTERLACED,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_UYVY8_1X16,
+		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
+			       RKCIF_FORMAT_YUV_INPUT_ORDER_YUYV |
+			       RKCIF_FORMAT_INPUT_MODE_BT1120 |
+			       RKCIF_FORMAT_BT1120_YC_SWAP |
+			       RKCIF_FORMAT_BT1120_TRANSMIT_PROGRESS,
+		.field = V4L2_FIELD_NONE,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_UYVY8_1X16,
+		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
+			       RKCIF_FORMAT_YUV_INPUT_ORDER_YUYV |
+			       RKCIF_FORMAT_BT1120_YC_SWAP |
+			       RKCIF_FORMAT_INPUT_MODE_BT1120,
+		.field = V4L2_FIELD_INTERLACED,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_VYUY8_1X16,
+		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
+			       RKCIF_FORMAT_YUV_INPUT_ORDER_YVYU |
+			       RKCIF_FORMAT_INPUT_MODE_BT1120 |
+			       RKCIF_FORMAT_BT1120_YC_SWAP |
+			       RKCIF_FORMAT_BT1120_TRANSMIT_PROGRESS,
+		.field = V4L2_FIELD_NONE,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_VYUY8_1X16,
+		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
+			       RKCIF_FORMAT_YUV_INPUT_ORDER_YVYU |
+			       RKCIF_FORMAT_BT1120_YC_SWAP |
+			       RKCIF_FORMAT_INPUT_MODE_BT1120,
+		.field = V4L2_FIELD_INTERLACED,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8,
+		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
+			       RKCIF_FORMAT_RAW_DATA_WIDTH_8,
+		.fmt_type = RKCIF_FMT_TYPE_RAW,
+		.field = V4L2_FIELD_NONE,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_SGBRG8_1X8,
+		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
+			       RKCIF_FORMAT_RAW_DATA_WIDTH_8,
+		.fmt_type = RKCIF_FMT_TYPE_RAW,
+		.field = V4L2_FIELD_NONE,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8,
+		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
+			       RKCIF_FORMAT_RAW_DATA_WIDTH_8,
+		.fmt_type = RKCIF_FMT_TYPE_RAW,
+		.field = V4L2_FIELD_NONE,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_SRGGB8_1X8,
+		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
+			       RKCIF_FORMAT_RAW_DATA_WIDTH_8,
+		.fmt_type = RKCIF_FMT_TYPE_RAW,
+		.field = V4L2_FIELD_NONE,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10,
+		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
+			       RKCIF_FORMAT_RAW_DATA_WIDTH_10,
+		.fmt_type = RKCIF_FMT_TYPE_RAW,
+		.field = V4L2_FIELD_NONE,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10,
+		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
+			       RKCIF_FORMAT_RAW_DATA_WIDTH_10,
+		.fmt_type = RKCIF_FMT_TYPE_RAW,
+		.field = V4L2_FIELD_NONE,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10,
+		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
+			       RKCIF_FORMAT_RAW_DATA_WIDTH_10,
+		.fmt_type = RKCIF_FMT_TYPE_RAW,
+		.field = V4L2_FIELD_NONE,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10,
+		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
+			       RKCIF_FORMAT_RAW_DATA_WIDTH_10,
+		.fmt_type = RKCIF_FMT_TYPE_RAW,
+		.field = V4L2_FIELD_NONE,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12,
+		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
+			       RKCIF_FORMAT_RAW_DATA_WIDTH_12,
+		.fmt_type = RKCIF_FMT_TYPE_RAW,
+		.field = V4L2_FIELD_NONE,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_SGBRG12_1X12,
+		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
+			       RKCIF_FORMAT_RAW_DATA_WIDTH_12,
+		.fmt_type = RKCIF_FMT_TYPE_RAW,
+		.field = V4L2_FIELD_NONE,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_SGRBG12_1X12,
+		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
+			       RKCIF_FORMAT_RAW_DATA_WIDTH_12,
+		.fmt_type = RKCIF_FMT_TYPE_RAW,
+		.field = V4L2_FIELD_NONE,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_SRGGB12_1X12,
+		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
+			       RKCIF_FORMAT_RAW_DATA_WIDTH_12,
+		.fmt_type = RKCIF_FMT_TYPE_RAW,
+		.field = V4L2_FIELD_NONE,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_RGB888_1X24,
+		.field = V4L2_FIELD_NONE,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_Y8_1X8,
+		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
+			       RKCIF_FORMAT_RAW_DATA_WIDTH_8,
+		.fmt_type = RKCIF_FMT_TYPE_RAW,
+		.field = V4L2_FIELD_NONE,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_Y10_1X10,
+		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
+			       RKCIF_FORMAT_RAW_DATA_WIDTH_10,
+		.fmt_type = RKCIF_FMT_TYPE_RAW,
+		.field = V4L2_FIELD_NONE,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_Y12_1X12,
+		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
+			       RKCIF_FORMAT_RAW_DATA_WIDTH_12,
+		.fmt_type = RKCIF_FMT_TYPE_RAW,
+		.field = V4L2_FIELD_NONE,
+	},
+};
+
+static void rk3568_dvp_grf_setup(struct rkcif_device *rkcif)
+{
+	u32 con1 = RK3568_GRF_WRITE_ENABLE(RK3568_GRF_VI_CON1_CIF_DATAPATH |
+					   RK3568_GRF_VI_CON1_CIF_CLK_DELAYNUM);
+
+	if (!rkcif->grf)
+		return;
+
+	con1 |= rkcif->interfaces[RKCIF_DVP].dvp.dvp_clk_delay &
+		RK3568_GRF_VI_CON1_CIF_CLK_DELAYNUM;
+
+	if (rkcif->interfaces[RKCIF_DVP].vep.bus.parallel.flags &
+	    V4L2_MBUS_PCLK_SAMPLE_DUALEDGE)
+		con1 |= RK3568_GRF_VI_CON1_CIF_DATAPATH;
+
+	regmap_write(rkcif->grf, RK3568_GRF_VI_CON1, con1);
+}
+
+const struct rkcif_dvp_match_data rkcif_rk3568_vicap_dvp_match_data = {
+	.in_fmts = rk3568_dvp_in_fmts,
+	.in_fmts_num = ARRAY_SIZE(rk3568_dvp_in_fmts),
+	.out_fmts = dvp_out_fmts,
+	.out_fmts_num = ARRAY_SIZE(dvp_out_fmts),
+	.setup = rk3568_dvp_grf_setup,
+	.has_scaler = false,
+	.regs = {
+		[RKCIF_DVP_CTRL] = 0x00,
+		[RKCIF_DVP_INTEN] = 0x04,
+		[RKCIF_DVP_INTSTAT] = 0x08,
+		[RKCIF_DVP_FOR] = 0x0c,
+		[RKCIF_DVP_LINE_NUM_ADDR] = 0x2c,
+		[RKCIF_DVP_FRM0_ADDR_Y] = 0x14,
+		[RKCIF_DVP_FRM0_ADDR_UV] = 0x18,
+		[RKCIF_DVP_FRM1_ADDR_Y] = 0x1c,
+		[RKCIF_DVP_FRM1_ADDR_UV] = 0x20,
+		[RKCIF_DVP_VIR_LINE_WIDTH] = 0x24,
+		[RKCIF_DVP_SET_SIZE] = 0x28,
+		[RKCIF_DVP_CROP] = 0x34,
+		[RKCIF_DVP_FRAME_STATUS] = 0x3c,
+		[RKCIF_DVP_LAST_LINE] = 0x44,
+		[RKCIF_DVP_LAST_PIX] = 0x48,
+	},
+};
+
+static inline unsigned int cif_dvp_get_addr(struct rkcif_device *rkcif,
+					    unsigned int index)
+{
+	if (WARN_ON_ONCE(index >= RKCIF_DVP_REGISTER_MAX))
+		return RKCIF_REGISTER_NOTSUPPORTED;
+
+	return rkcif->match_data->dvp->regs[index];
+}
+
+static inline __maybe_unused void cif_dvp_write(struct rkcif_device *rkcif,
+						unsigned int index, u32 val)
+{
+	unsigned int addr = cif_dvp_get_addr(rkcif, index);
+
+	if (addr == RKCIF_REGISTER_NOTSUPPORTED)
+		return;
+
+	writel(val, rkcif->base_addr + addr);
+}
+
+static inline __maybe_unused u32 cif_dvp_read(struct rkcif_device *rkcif,
+					      unsigned int index)
+{
+	unsigned int addr = cif_dvp_get_addr(rkcif, index);
+
+	if (addr == RKCIF_REGISTER_NOTSUPPORTED)
+		return 0;
+
+	return readl(rkcif->base_addr + addr);
+}
+
+static void cif_dvp_queue_buffer(struct rkcif_stream *stream,
+				 unsigned int index)
+{
+	struct rkcif_device *rkcif = stream->rkcif;
+	struct rkcif_buffer *buffer = stream->buffers[index];
+	u32 frm_addr_y, frm_addr_uv;
+
+	frm_addr_y = index ? RKCIF_DVP_FRM1_ADDR_Y : RKCIF_DVP_FRM0_ADDR_Y;
+	frm_addr_uv = index ? RKCIF_DVP_FRM1_ADDR_UV : RKCIF_DVP_FRM0_ADDR_UV;
+
+	cif_dvp_write(rkcif, frm_addr_y, buffer->buff_addr[RKCIF_PLANE_Y]);
+	cif_dvp_write(rkcif, frm_addr_uv, buffer->buff_addr[RKCIF_PLANE_UV]);
+}
+
+static int cif_dvp_start_streaming(struct rkcif_stream *stream)
+{
+	struct rkcif_device *rkcif = stream->rkcif;
+	struct rkcif_interface *interface = stream->interface;
+	struct v4l2_mbus_config_parallel *parallel;
+	struct v4l2_mbus_framefmt *source_fmt;
+	struct v4l2_subdev_state *state;
+	const struct rkcif_input_fmt *active_in_fmt;
+	const struct rkcif_output_fmt *active_out_fmt;
+	u32 val = 0;
+	int ret = -EINVAL;
+
+	state = v4l2_subdev_lock_and_get_active_state(&interface->sd);
+	source_fmt = v4l2_subdev_state_get_format(state, RKCIF_IF_PAD_SRC,
+						  stream->id);
+	if (!source_fmt)
+		goto out;
+
+	active_in_fmt = rkcif_interface_find_input_fmt(interface, false,
+						       source_fmt->code);
+	active_out_fmt = rkcif_stream_find_output_fmt(stream, false,
+						      stream->pix.pixelformat);
+	if (!active_in_fmt || !active_out_fmt)
+		goto out;
+
+	parallel = &interface->vep.bus.parallel;
+	if (parallel->bus_width == 16 &&
+	    (parallel->flags & V4L2_MBUS_PCLK_SAMPLE_DUALEDGE))
+		val |= RKCIF_FORMAT_BT1120_CLOCK_DOUBLE_EDGES;
+	val |= active_in_fmt->dvp_fmt_val;
+	val |= active_out_fmt->dvp_fmt_val;
+	cif_dvp_write(rkcif, RKCIF_DVP_FOR, val);
+
+	val = stream->pix.width;
+	if (active_in_fmt->fmt_type == RKCIF_FMT_TYPE_RAW)
+		val = stream->pix.width * 2;
+	cif_dvp_write(rkcif, RKCIF_DVP_VIR_LINE_WIDTH, val);
+
+	val = RKCIF_XY_COORD(stream->pix.width, stream->pix.height);
+	cif_dvp_write(rkcif, RKCIF_DVP_SET_SIZE, val);
+
+	cif_dvp_write(rkcif, RKCIF_DVP_FRAME_STATUS, RKCIF_FRAME_STAT_CLS);
+	cif_dvp_write(rkcif, RKCIF_DVP_INTSTAT, RKCIF_INTSTAT_CLS);
+	if (rkcif->match_data->dvp->has_scaler) {
+		val = active_in_fmt->fmt_type == RKCIF_FMT_TYPE_YUV ?
+			      RKCIF_SCL_CTRL_ENABLE_YUV_16BIT_BYPASS :
+			      RKCIF_SCL_CTRL_ENABLE_RAW_16BIT_BYPASS;
+		cif_dvp_write(rkcif, RKCIF_DVP_SCL_CTRL, val);
+	}
+
+	cif_dvp_write(rkcif, RKCIF_DVP_INTEN,
+		      RKCIF_INTEN_FRAME_END_EN |
+			      RKCIF_INTEN_PST_INF_FRAME_END_EN);
+
+	cif_dvp_write(rkcif, RKCIF_DVP_CTRL,
+		      RKCIF_CTRL_AXI_BURST_16 | RKCIF_CTRL_MODE_PINGPONG |
+			      RKCIF_CTRL_ENABLE_CAPTURE);
+
+	ret = 0;
+
+out:
+	v4l2_subdev_unlock_state(state);
+	return ret;
+}
+
+static void cif_dvp_stop_streaming(struct rkcif_stream *stream)
+{
+	struct rkcif_device *rkcif = stream->rkcif;
+	u32 val;
+
+	val = cif_dvp_read(rkcif, RKCIF_DVP_CTRL);
+	cif_dvp_write(rkcif, RKCIF_DVP_CTRL,
+		      val & (~RKCIF_CTRL_ENABLE_CAPTURE));
+	cif_dvp_write(rkcif, RKCIF_DVP_INTEN, 0x0);
+	cif_dvp_write(rkcif, RKCIF_DVP_INTSTAT, 0x3ff);
+	cif_dvp_write(rkcif, RKCIF_DVP_FRAME_STATUS, 0x0);
+
+	stream->stopping = false;
+}
+
+static void cif_dvp_reset_stream(struct rkcif_device *rkcif)
+{
+	u32 ctl = cif_dvp_read(rkcif, RKCIF_DVP_CTRL);
+
+	cif_dvp_write(rkcif, RKCIF_DVP_CTRL,
+		      ctl & (~RKCIF_CTRL_ENABLE_CAPTURE));
+	cif_dvp_write(rkcif, RKCIF_DVP_CTRL, ctl | RKCIF_CTRL_ENABLE_CAPTURE);
+}
+
+static void rkcif_dvp_set_crop(struct rkcif_stream *stream, u16 left, u16 top)
+{
+	struct rkcif_device *rkcif = stream->rkcif;
+	u32 val;
+
+	val = RKCIF_XY_COORD(left, top);
+	cif_dvp_write(rkcif, RKCIF_DVP_CROP, val);
+}
+
+irqreturn_t rkcif_dvp_isr(int irq, void *ctx)
+{
+	struct device *dev = ctx;
+	struct rkcif_device *rkcif = dev_get_drvdata(dev);
+	struct rkcif_stream *stream;
+	u32 intstat, lastline, lastpix, cif_frmst;
+	irqreturn_t ret = IRQ_NONE;
+
+	if (!rkcif->match_data->dvp)
+		return ret;
+
+	intstat = cif_dvp_read(rkcif, RKCIF_DVP_INTSTAT);
+	cif_frmst = cif_dvp_read(rkcif, RKCIF_DVP_FRAME_STATUS);
+	lastline = RKCIF_FETCH_Y(cif_dvp_read(rkcif, RKCIF_DVP_LAST_LINE));
+	lastpix = RKCIF_FETCH_Y(cif_dvp_read(rkcif, RKCIF_DVP_LAST_PIX));
+
+	if (intstat & RKCIF_INTSTAT_FRAME_END) {
+		cif_dvp_write(rkcif, RKCIF_DVP_INTSTAT,
+			      RKCIF_INTSTAT_FRAME_END_CLR |
+				      RKCIF_INTSTAT_LINE_END_CLR);
+
+		stream = &rkcif->interfaces[RKCIF_DVP].streams[RKCIF_ID0];
+
+		if (stream->stopping) {
+			cif_dvp_stop_streaming(stream);
+			wake_up(&stream->wq_stopped);
+			return IRQ_HANDLED;
+		}
+
+		if (lastline != stream->pix.height) {
+			v4l2_err(&rkcif->v4l2_dev,
+				 "bad frame, irq:%#x frmst:%#x size:%dx%d\n",
+				 intstat, cif_frmst, lastpix, lastline);
+
+			cif_dvp_reset_stream(rkcif);
+		}
+
+		rkcif_stream_pingpong(stream);
+
+		ret = IRQ_HANDLED;
+	}
+
+	return ret;
+}
+
+int rkcif_dvp_register(struct rkcif_device *rkcif)
+{
+	struct rkcif_interface *interface;
+	int ret, i;
+
+	if (!rkcif->match_data->dvp)
+		return 0;
+
+	interface = &rkcif->interfaces[RKCIF_DVP];
+	interface->index = RKCIF_DVP;
+	interface->type = RKCIF_IF_DVP;
+	interface->in_fmts = rkcif->match_data->dvp->in_fmts;
+	interface->in_fmts_num = rkcif->match_data->dvp->in_fmts_num;
+	interface->set_crop = rkcif_dvp_set_crop;
+	ret = rkcif_interface_register(rkcif, interface);
+	if (ret)
+		return 0;
+
+	if (rkcif->match_data->dvp->setup)
+		rkcif->match_data->dvp->setup(rkcif);
+
+	interface->streams_num = rkcif->match_data->dvp->has_ids ? 4 : 1;
+	for (i = 0; i < interface->streams_num; i++) {
+		struct rkcif_stream *stream = &interface->streams[i];
+
+		stream->id = i;
+		stream->interface = interface;
+		stream->out_fmts = rkcif->match_data->dvp->out_fmts;
+		stream->out_fmts_num = rkcif->match_data->dvp->out_fmts_num;
+		stream->queue_buffer = cif_dvp_queue_buffer;
+		stream->start_streaming = cif_dvp_start_streaming;
+		stream->stop_streaming = cif_dvp_stop_streaming;
+
+		ret = rkcif_stream_register(rkcif, stream);
+		if (ret)
+			goto err_streams_unregister;
+	}
+	return 0;
+
+err_streams_unregister:
+	for (; i >= 0; i--)
+		rkcif_stream_unregister(&interface->streams[i]);
+	rkcif_interface_unregister(interface);
+
+	return ret;
+}
+
+void rkcif_dvp_unregister(struct rkcif_device *rkcif)
+{
+	struct rkcif_interface *interface;
+	int i;
+
+	if (!rkcif->match_data->dvp)
+		return;
+
+	interface = &rkcif->interfaces[RKCIF_DVP];
+
+	for (i = 0; i < interface->streams_num; i++)
+		rkcif_stream_unregister(&interface->streams[i]);
+	rkcif_interface_unregister(interface);
+}
diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-capture-dvp.h b/drivers/media/platform/rockchip/rkcif/rkcif-capture-dvp.h
new file mode 100644
index 000000000000..4bd72d41ec2f
--- /dev/null
+++ b/drivers/media/platform/rockchip/rkcif/rkcif-capture-dvp.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Rockchip Camera Interface (CIF) Driver
+ *
+ * Copyright (C) 2018 Rockchip Electronics Co., Ltd.
+ * Copyright (C) 2023 Mehdi Djait <mehdi.djait@bootlin.com>
+ * Copyright (C) 2025 Michael Riesch <michael.riesch@wolfvision.net>
+ */
+
+#ifndef _RKCIF_CAPTURE_DVP_H
+#define _RKCIF_CAPTURE_DVP_H
+
+#include "rkcif-common.h"
+
+extern const struct rkcif_dvp_match_data rkcif_px30_vip_dvp_match_data;
+extern const struct rkcif_dvp_match_data rkcif_rk3568_vicap_dvp_match_data;
+
+int rkcif_dvp_register(struct rkcif_device *rkcif);
+
+void rkcif_dvp_unregister(struct rkcif_device *rkcif);
+
+irqreturn_t rkcif_dvp_isr(int irq, void *ctx);
+
+#endif
diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.c b/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.c
new file mode 100644
index 000000000000..0c3f7b8cfa18
--- /dev/null
+++ b/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.c
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Rockchip Camera Interface (CIF) Driver
+ *
+ * Copyright (C) 2018 Rockchip Electronics Co., Ltd.
+ * Copyright (C) 2025 Michael Riesch <michael.riesch@wolfvision.net>
+ */
+
+#include "rkcif-capture-mipi.h"
+#include "rkcif-common.h"
+#include "rkcif-stream.h"
+
+irqreturn_t rkcif_mipi_isr(int irq, void *ctx)
+{
+	irqreturn_t ret = IRQ_NONE;
+
+	return ret;
+}
+
+int rkcif_mipi_register(struct rkcif_device *rkcif)
+{
+	return 0;
+}
+
+void rkcif_mipi_unregister(struct rkcif_device *rkcif)
+{
+}
diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.h b/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.h
new file mode 100644
index 000000000000..ee1a50a59505
--- /dev/null
+++ b/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Rockchip Camera Interface (CIF) Driver
+ *
+ * Copyright (C) 2018 Rockchip Electronics Co., Ltd.
+ * Copyright (C) 2025 Michael Riesch <michael.riesch@wolfvision.net>
+ */
+
+#ifndef _RKCIF_CAPTURE_MIPI_H
+#define _RKCIF_CAPTURE_MIPI_H
+
+#include "rkcif-common.h"
+
+int rkcif_mipi_register(struct rkcif_device *rkcif);
+
+void rkcif_mipi_unregister(struct rkcif_device *rkcif);
+
+irqreturn_t rkcif_mipi_isr(int irq, void *ctx);
+
+#endif
diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-common.h b/drivers/media/platform/rockchip/rkcif/rkcif-common.h
new file mode 100644
index 000000000000..62fb3580eec5
--- /dev/null
+++ b/drivers/media/platform/rockchip/rkcif/rkcif-common.h
@@ -0,0 +1,220 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Rockchip Camera Interface (CIF) Driver
+ *
+ * Copyright (C) 2018 Rockchip Electronics Co., Ltd.
+ * Copyright (C) 2023 Mehdi Djait <mehdi.djait@bootlin.com>
+ * Copyright (C) 2025 Michael Riesch <michael.riesch@wolfvision.net>
+ */
+
+#ifndef _RKCIF_COMMON_H
+#define _RKCIF_COMMON_H
+
+#include <linux/clk.h>
+#include <linux/mutex.h>
+#include <linux/regmap.h>
+
+#include <media/media-device.h>
+#include <media/media-entity.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-mc.h>
+#include <media/v4l2-subdev.h>
+#include <media/videobuf2-v4l2.h>
+
+#include "rkcif-regs.h"
+
+#define RKCIF_DRIVER_NAME "rockchip-cif"
+#define RKCIF_CLK_MAX	  4
+
+enum rkcif_format_type {
+	RKCIF_FMT_TYPE_INVALID,
+	RKCIF_FMT_TYPE_YUV,
+	RKCIF_FMT_TYPE_RAW,
+};
+
+enum rkcif_id_index {
+	RKCIF_ID0,
+	RKCIF_ID1,
+	RKCIF_ID2,
+	RKCIF_ID3,
+	RKCIF_ID_MAX
+};
+
+enum rkcif_interface_index {
+	RKCIF_DVP,
+	RKCIF_MIPI_BASE,
+	RKCIF_MIPI1 = RKCIF_MIPI_BASE,
+	RKCIF_MIPI2,
+	RKCIF_MIPI3,
+	RKCIF_MIPI4,
+	RKCIF_MIPI5,
+	RKCIF_MIPI6,
+	RKCIF_MIPI_MAX = RKCIF_MIPI6,
+	RKCIF_IF_MAX = RKCIF_MIPI_MAX
+};
+
+enum rkcif_interface_pad_index {
+	RKCIF_IF_PAD_SINK,
+	RKCIF_IF_PAD_SRC,
+	RKCIF_IF_PAD_MAX
+};
+
+enum rkcif_interface_status {
+	RKCIF_IF_INACTIVE,
+	RKCIF_IF_ACTIVE,
+};
+
+enum rkcif_interface_type {
+	RKCIF_IF_INVALID,
+	RKCIF_IF_DVP,
+	RKCIF_IF_MIPI,
+};
+
+enum rkcif_plane_index {
+	RKCIF_PLANE_Y,
+	RKCIF_PLANE_UV,
+	RKCIF_PLANE_MAX
+};
+
+struct rkcif_input_fmt {
+	u32 mbus_code;
+
+	enum rkcif_format_type fmt_type;
+	enum v4l2_field field;
+
+	union {
+		u32 dvp_fmt_val;
+	};
+};
+
+struct rkcif_output_fmt {
+	u32 fourcc;
+	u32 mbus_code;
+	u8 cplanes;
+
+	union {
+		u32 dvp_fmt_val;
+	};
+};
+
+struct rkcif_buffer {
+	struct vb2_v4l2_buffer vb;
+	struct list_head queue;
+	dma_addr_t buff_addr[VIDEO_MAX_PLANES];
+	bool is_dummy;
+};
+
+struct rkcif_dummy_buffer {
+	struct rkcif_buffer buffer;
+	void *vaddr;
+	u32 size;
+};
+
+struct rkcif_interface;
+
+struct rkcif_remote {
+	struct v4l2_async_connection async_conn;
+	struct v4l2_subdev *sd;
+
+	struct rkcif_interface *interface;
+};
+
+struct rkcif_stream {
+	enum rkcif_id_index id;
+	struct rkcif_device *rkcif;
+	struct rkcif_interface *interface;
+	const struct rkcif_output_fmt *out_fmts;
+	unsigned int out_fmts_num;
+
+	/* in ping-pong mode, two buffers can be provided to the HW */
+	struct rkcif_buffer *buffers[2];
+	int frame_idx;
+	int frame_phase;
+
+	/* in case of no available buffer, HW can write to the dummy buffer */
+	struct rkcif_dummy_buffer dummy;
+
+	bool stopping;
+	wait_queue_head_t wq_stopped;
+
+	/* queue of available buffers plus spinlock that protects it */
+	spinlock_t driver_queue_lock;
+	struct list_head driver_queue;
+
+	/* lock used by the V4L2 core */
+	struct mutex vlock;
+
+	struct media_pad pad;
+	struct media_pipeline pipeline;
+	struct v4l2_pix_format_mplane pix;
+	struct vb2_queue buf_queue;
+	struct video_device vdev;
+
+	void (*queue_buffer)(struct rkcif_stream *stream, unsigned int index);
+	int (*start_streaming)(struct rkcif_stream *stream);
+	void (*stop_streaming)(struct rkcif_stream *stream);
+};
+
+struct rkcif_dvp {
+	u32 dvp_clk_delay;
+};
+
+struct rkcif_interface {
+	enum rkcif_interface_type type;
+	enum rkcif_interface_status status;
+	enum rkcif_interface_index index;
+	struct rkcif_device *rkcif;
+	struct rkcif_remote *remote;
+	struct rkcif_stream streams[RKCIF_ID_MAX];
+	unsigned int streams_num;
+	const struct rkcif_input_fmt *in_fmts;
+	unsigned int in_fmts_num;
+
+	struct media_pad pads[RKCIF_IF_PAD_MAX];
+	struct v4l2_fwnode_endpoint vep;
+	struct v4l2_subdev sd;
+
+	union {
+		struct rkcif_dvp dvp;
+	};
+
+	void (*set_crop)(struct rkcif_stream *stream, u16 left, u16 top);
+};
+
+struct rkcif_dvp_match_data {
+	const struct rkcif_input_fmt *in_fmts;
+	unsigned int in_fmts_num;
+	const struct rkcif_output_fmt *out_fmts;
+	unsigned int out_fmts_num;
+	void (*setup)(struct rkcif_device *rkcif);
+	bool has_scaler;
+	bool has_ids;
+	unsigned int regs[RKCIF_DVP_REGISTER_MAX];
+};
+
+struct rkcif_match_data {
+	const char *const *clks;
+	unsigned int clks_num;
+	const struct rkcif_dvp_match_data *dvp;
+};
+
+struct rkcif_device {
+	struct device *dev;
+
+	const struct rkcif_match_data *match_data;
+	struct clk_bulk_data clks[RKCIF_CLK_MAX];
+	unsigned int clks_num;
+	struct regmap *grf;
+	struct reset_control *reset;
+	void __iomem *base_addr;
+
+	struct rkcif_interface interfaces[RKCIF_IF_MAX];
+
+	struct media_device media_dev;
+	struct v4l2_device v4l2_dev;
+	struct v4l2_async_notifier notifier;
+};
+
+#endif
diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-dev.c b/drivers/media/platform/rockchip/rkcif/rkcif-dev.c
new file mode 100644
index 000000000000..2dcd35771fc9
--- /dev/null
+++ b/drivers/media/platform/rockchip/rkcif/rkcif-dev.c
@@ -0,0 +1,299 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Rockchip Camera Interface (CIF) Driver
+ *
+ * Copyright (C) 2018 Rockchip Electronics Co., Ltd.
+ * Copyright (C) 2020 Maxime Chevallier <maxime.chevallier@bootlin.com>
+ * Copyright (C) 2023 Mehdi Djait <mehdi.djait@bootlin.com>
+ * Copyright (C) 2025 Michael Riesch <michael.riesch@wolfvision.net>
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_graph.h>
+#include <linux/of_platform.h>
+#include <linux/pm_runtime.h>
+#include <linux/reset.h>
+
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-mc.h>
+
+#include "rkcif-capture-dvp.h"
+#include "rkcif-capture-mipi.h"
+#include "rkcif-common.h"
+
+static const char *const px30_vip_clks[] = {
+	"aclk",
+	"hclk",
+	"pclk",
+};
+
+static const struct rkcif_match_data px30_vip_match_data = {
+	.clks = px30_vip_clks,
+	.clks_num = ARRAY_SIZE(px30_vip_clks),
+	.dvp = &rkcif_px30_vip_dvp_match_data,
+};
+
+static const char *const rk3568_vicap_clks[] = {
+	"aclk",
+	"hclk",
+	"dclk",
+	"iclk",
+};
+
+static const struct rkcif_match_data rk3568_vicap_match_data = {
+	.clks = rk3568_vicap_clks,
+	.clks_num = ARRAY_SIZE(rk3568_vicap_clks),
+	.dvp = &rkcif_rk3568_vicap_dvp_match_data,
+};
+
+static const struct of_device_id rkcif_plat_of_match[] = {
+	{
+		.compatible = "rockchip,px30-vip",
+		.data = &px30_vip_match_data,
+	},
+	{
+		.compatible = "rockchip,rk3568-vicap",
+		.data = &rk3568_vicap_match_data,
+	},
+	{}
+};
+MODULE_DEVICE_TABLE(of, rkcif_plat_of_match);
+
+static int rkcif_notifier_bound(struct v4l2_async_notifier *notifier,
+				struct v4l2_subdev *sd,
+				struct v4l2_async_connection *asd)
+{
+	struct rkcif_device *rkcif =
+		container_of(notifier, struct rkcif_device, notifier);
+	struct rkcif_remote *remote =
+		container_of(asd, struct rkcif_remote, async_conn);
+	struct media_pad *sink_pad =
+		&remote->interface->pads[RKCIF_IF_PAD_SINK];
+	int ret;
+
+	ret = v4l2_create_fwnode_links_to_pad(sd, sink_pad,
+					      MEDIA_LNK_FL_ENABLED);
+	if (ret) {
+		dev_err(rkcif->dev, "failed to link source pad of %s\n",
+			sd->name);
+		return ret;
+	}
+
+	remote->sd = sd;
+
+	return 0;
+}
+
+static int rkcif_notifier_complete(struct v4l2_async_notifier *notifier)
+{
+	struct rkcif_device *rkcif =
+		container_of(notifier, struct rkcif_device, notifier);
+
+	return v4l2_device_register_subdev_nodes(&rkcif->v4l2_dev);
+}
+
+static const struct v4l2_async_notifier_operations rkcif_notifier_ops = {
+	.bound = rkcif_notifier_bound,
+	.complete = rkcif_notifier_complete,
+};
+
+static int rkcif_register(struct rkcif_device *rkcif)
+{
+	struct v4l2_async_notifier *ntf = &rkcif->notifier;
+	int ret;
+
+	v4l2_async_nf_init(ntf, &rkcif->v4l2_dev);
+	ntf->ops = &rkcif_notifier_ops;
+
+	ret = rkcif_dvp_register(rkcif);
+	if (ret && ret != -ENODEV)
+		goto err_notifier_cleanup;
+
+	ret = rkcif_mipi_register(rkcif);
+	if (ret && ret != -ENODEV)
+		goto err_dvp_unregister;
+
+	ret = v4l2_async_nf_register(ntf);
+	if (ret)
+		goto err_mipi_unregister;
+
+	return 0;
+
+err_mipi_unregister:
+	rkcif_mipi_unregister(rkcif);
+err_dvp_unregister:
+	rkcif_dvp_unregister(rkcif);
+err_notifier_cleanup:
+	v4l2_async_nf_cleanup(&rkcif->notifier);
+	return ret;
+}
+
+static void rkcif_unregister(struct rkcif_device *rkcif)
+{
+	v4l2_async_nf_unregister(&rkcif->notifier);
+	rkcif_mipi_unregister(rkcif);
+	rkcif_dvp_unregister(rkcif);
+	v4l2_async_nf_cleanup(&rkcif->notifier);
+}
+
+static irqreturn_t rkcif_isr(int irq, void *ctx)
+{
+	irqreturn_t ret = IRQ_NONE;
+
+	if (rkcif_dvp_isr(irq, ctx) == IRQ_HANDLED)
+		ret = IRQ_HANDLED;
+
+	if (rkcif_mipi_isr(irq, ctx) == IRQ_HANDLED)
+		ret = IRQ_HANDLED;
+
+	return ret;
+}
+
+static int rkcif_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct rkcif_device *rkcif;
+	int ret, irq, i;
+
+	rkcif = devm_kzalloc(dev, sizeof(*rkcif), GFP_KERNEL);
+	if (!rkcif)
+		return -ENOMEM;
+
+	rkcif->match_data = of_device_get_match_data(dev);
+	if (!rkcif->match_data)
+		return -ENODEV;
+
+	dev_set_drvdata(dev, rkcif);
+	rkcif->dev = dev;
+
+	rkcif->base_addr = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(rkcif->base_addr))
+		return PTR_ERR(rkcif->base_addr);
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return irq;
+
+	ret = devm_request_irq(dev, irq, rkcif_isr, IRQF_SHARED,
+			       dev_driver_string(dev), dev);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to request irq\n");
+
+	rkcif->clks_num = rkcif->match_data->clks_num;
+	for (i = 0; (i < rkcif->clks_num) && (i < RKCIF_CLK_MAX); i++)
+		rkcif->clks[i].id = rkcif->match_data->clks[i];
+	ret = devm_clk_bulk_get(dev, rkcif->clks_num, rkcif->clks);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to get clocks\n");
+
+	rkcif->reset = devm_reset_control_array_get_exclusive(dev);
+	if (IS_ERR(rkcif->reset))
+		return PTR_ERR(rkcif->reset);
+
+	rkcif->grf =
+		syscon_regmap_lookup_by_phandle(dev->of_node, "rockchip,grf");
+	if (IS_ERR(rkcif->grf))
+		rkcif->grf = NULL;
+
+	pm_runtime_enable(&pdev->dev);
+
+	rkcif->media_dev.dev = dev;
+	strscpy(rkcif->media_dev.model, RKCIF_DRIVER_NAME,
+		sizeof(rkcif->media_dev.model));
+	media_device_init(&rkcif->media_dev);
+
+	rkcif->v4l2_dev.mdev = &rkcif->media_dev;
+	ret = v4l2_device_register(dev, &rkcif->v4l2_dev);
+	if (ret)
+		goto err_media_dev_cleanup;
+
+	ret = media_device_register(&rkcif->media_dev);
+	if (ret < 0) {
+		dev_err(dev, "failed to register media device: %d\n", ret);
+		goto err_v4l2_dev_unregister;
+	}
+
+	ret = rkcif_register(rkcif);
+	if (ret) {
+		dev_err(dev, "failed to register media entities: %d\n", ret);
+		goto err_media_dev_unregister;
+	}
+
+	return 0;
+
+err_media_dev_unregister:
+	media_device_unregister(&rkcif->media_dev);
+err_v4l2_dev_unregister:
+	v4l2_device_unregister(&rkcif->v4l2_dev);
+err_media_dev_cleanup:
+	media_device_cleanup(&rkcif->media_dev);
+	pm_runtime_disable(&pdev->dev);
+	return ret;
+}
+
+static void rkcif_remove(struct platform_device *pdev)
+{
+	struct rkcif_device *rkcif = platform_get_drvdata(pdev);
+
+	rkcif_unregister(rkcif);
+	media_device_unregister(&rkcif->media_dev);
+	v4l2_device_unregister(&rkcif->v4l2_dev);
+	media_device_cleanup(&rkcif->media_dev);
+	pm_runtime_disable(&pdev->dev);
+}
+
+static int rkcif_runtime_suspend(struct device *dev)
+{
+	struct rkcif_device *rkcif = dev_get_drvdata(dev);
+
+	/*
+	 * Reset CIF (CRU, DMA, FIFOs) to allow a clean resume.
+	 * Since this resets the IOMMU too, we cannot issue this reset when
+	 * resuming.
+	 */
+	reset_control_assert(rkcif->reset);
+	udelay(5);
+	reset_control_deassert(rkcif->reset);
+
+	clk_bulk_disable_unprepare(rkcif->clks_num, rkcif->clks);
+
+	return 0;
+}
+
+static int rkcif_runtime_resume(struct device *dev)
+{
+	struct rkcif_device *rkcif = dev_get_drvdata(dev);
+	int ret;
+
+	ret = clk_bulk_prepare_enable(rkcif->clks_num, rkcif->clks);
+	if (ret) {
+		dev_err(dev, "failed to enable clocks\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static const struct dev_pm_ops rkcif_plat_pm_ops = {
+	.runtime_suspend = rkcif_runtime_suspend,
+	.runtime_resume = rkcif_runtime_resume,
+};
+
+static struct platform_driver rkcif_plat_drv = {
+	.driver = {
+		   .name = RKCIF_DRIVER_NAME,
+		   .of_match_table = rkcif_plat_of_match,
+		   .pm = &rkcif_plat_pm_ops,
+	},
+	.probe = rkcif_probe,
+	.remove = rkcif_remove,
+};
+module_platform_driver(rkcif_plat_drv);
+
+MODULE_DESCRIPTION("Rockchip Camera Interface (CIF) platform driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-interface.c b/drivers/media/platform/rockchip/rkcif/rkcif-interface.c
new file mode 100644
index 000000000000..0ec524586594
--- /dev/null
+++ b/drivers/media/platform/rockchip/rkcif/rkcif-interface.c
@@ -0,0 +1,423 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Rockchip Camera Interface (CIF) Driver
+ *
+ * Copyright (C) 2025 Michael Riesch <michael.riesch@wolfvision.net>
+ */
+
+#include <media/v4l2-common.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-mc.h>
+#include <media/v4l2-subdev.h>
+
+#include "rkcif-common.h"
+#include "rkcif-interface.h"
+
+static inline struct rkcif_interface *to_rkcif_interface(struct v4l2_subdev *sd)
+{
+	return container_of(sd, struct rkcif_interface, sd);
+}
+
+static const struct media_entity_operations rkcif_interface_media_ops = {
+	.link_validate = v4l2_subdev_link_validate,
+	.has_pad_interdep = v4l2_subdev_has_pad_interdep,
+};
+
+static int rkcif_interface_set_fmt(struct v4l2_subdev *sd,
+				   struct v4l2_subdev_state *state,
+				   struct v4l2_subdev_format *format)
+{
+	struct rkcif_interface *interface = to_rkcif_interface(sd);
+	const struct rkcif_input_fmt *input;
+	struct v4l2_mbus_framefmt *sink, *src;
+
+	/* the format on the source pad always matches the sink pad */
+	if (format->pad == RKCIF_IF_PAD_SRC)
+		return v4l2_subdev_get_fmt(sd, state, format);
+
+	input = rkcif_interface_find_input_fmt(interface, true,
+					       format->format.code);
+	format->format.code = input->mbus_code;
+
+	sink = v4l2_subdev_state_get_format(state, format->pad, format->stream);
+	if (!sink)
+		return -EINVAL;
+
+	*sink = format->format;
+
+	/* propagate the format to the source pad */
+	src = v4l2_subdev_state_get_opposite_stream_format(state, format->pad,
+							   format->stream);
+	if (!src)
+		return -EINVAL;
+
+	*src = *sink;
+
+	return 0;
+}
+
+static int rkcif_interface_get_sel(struct v4l2_subdev *sd,
+				   struct v4l2_subdev_state *state,
+				   struct v4l2_subdev_selection *sel)
+{
+	struct v4l2_mbus_framefmt *sink;
+	struct v4l2_rect *crop;
+	int ret = 0;
+
+	if (sel->pad != RKCIF_IF_PAD_SRC)
+		return -EINVAL;
+
+	sink = v4l2_subdev_state_get_opposite_stream_format(state, sel->pad,
+							    sel->stream);
+	if (!sink)
+		return -EINVAL;
+
+	crop = v4l2_subdev_state_get_crop(state, sel->pad, sel->stream);
+	if (!crop)
+		return -EINVAL;
+
+	switch (sel->target) {
+	case V4L2_SEL_TGT_CROP_DEFAULT:
+	case V4L2_SEL_TGT_CROP_BOUNDS:
+		sel->r.left = 0;
+		sel->r.top = 0;
+		sel->r.width = sink->width;
+		sel->r.height = sink->height;
+		break;
+	case V4L2_SEL_TGT_CROP:
+		sel->r = *crop;
+		break;
+	default:
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static int rkcif_interface_set_sel(struct v4l2_subdev *sd,
+				   struct v4l2_subdev_state *state,
+				   struct v4l2_subdev_selection *sel)
+{
+	struct v4l2_mbus_framefmt *sink, *src;
+	struct v4l2_rect *crop;
+
+	if (sel->pad != RKCIF_IF_PAD_SRC || sel->target != V4L2_SEL_TGT_CROP)
+		return -EINVAL;
+
+	sink = v4l2_subdev_state_get_opposite_stream_format(state, sel->pad,
+							    sel->stream);
+	if (!sink)
+		return -EINVAL;
+
+	src = v4l2_subdev_state_get_format(state, sel->pad, sel->stream);
+	if (!src)
+		return -EINVAL;
+
+	crop = v4l2_subdev_state_get_crop(state, sel->pad, sel->stream);
+	if (!crop)
+		return -EINVAL;
+
+	/* only starting point of crop can be specified */
+	sel->r.height = sink->height - sel->r.top;
+	sel->r.width = sink->width - sel->r.left;
+	*crop = sel->r;
+
+	src->height = sel->r.height;
+	src->width = sel->r.width;
+
+	return 0;
+}
+
+static int rkcif_interface_set_routing(struct v4l2_subdev *sd,
+				       struct v4l2_subdev_state *state,
+				       enum v4l2_subdev_format_whence which,
+				       struct v4l2_subdev_krouting *routing)
+{
+	int ret;
+
+	ret = v4l2_subdev_routing_validate(sd, routing,
+					   V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
+	if (ret)
+		return ret;
+
+	for (unsigned int i = 0; i < routing->num_routes; i++) {
+		const struct v4l2_subdev_route *route = &routing->routes[i];
+
+		if (route->source_stream >= RKCIF_ID_MAX)
+			return -EINVAL;
+	}
+
+	ret = v4l2_subdev_set_routing(sd, state, routing);
+
+	return ret;
+}
+
+static int rkcif_interface_apply_crop(struct rkcif_stream *stream,
+				      struct v4l2_subdev_state *state)
+{
+	struct rkcif_interface *interface = stream->interface;
+	struct v4l2_rect *crop;
+
+	crop = v4l2_subdev_state_get_crop(state, RKCIF_IF_PAD_SRC, stream->id);
+	if (!crop)
+		return -EINVAL;
+
+	if (interface->set_crop)
+		interface->set_crop(stream, crop->left, crop->top);
+
+	return 0;
+}
+
+static int rkcif_interface_enable_streams(struct v4l2_subdev *sd,
+					  struct v4l2_subdev_state *state,
+					  u32 pad, u64 streams_mask)
+{
+	struct rkcif_interface *interface = to_rkcif_interface(sd);
+	struct rkcif_stream *stream;
+	struct v4l2_subdev_route *route;
+	struct v4l2_subdev *remote_sd;
+	struct media_pad *remote_pad;
+	u64 mask;
+
+	remote_pad =
+		media_pad_remote_pad_first(&sd->entity.pads[RKCIF_IF_PAD_SINK]);
+	remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity);
+
+	/* DVP has one crop setting for all IDs */
+	if (interface->type == RKCIF_IF_DVP) {
+		stream = &interface->streams[RKCIF_ID0];
+		rkcif_interface_apply_crop(stream, state);
+	} else {
+		/* TODO implement for MIPI */
+	}
+
+	mask = v4l2_subdev_state_xlate_streams(state, RKCIF_IF_PAD_SINK,
+					       RKCIF_IF_PAD_SRC, &streams_mask);
+
+	return v4l2_subdev_enable_streams(remote_sd, remote_pad->index, mask);
+}
+
+static int rkcif_interface_disable_streams(struct v4l2_subdev *sd,
+					   struct v4l2_subdev_state *state,
+					   u32 pad, u64 streams_mask)
+{
+	struct v4l2_subdev *remote_sd;
+	struct media_pad *remote_pad;
+	u64 mask;
+
+	remote_pad =
+		media_pad_remote_pad_first(&sd->entity.pads[RKCIF_IF_PAD_SINK]);
+	remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity);
+
+	mask = v4l2_subdev_state_xlate_streams(state, RKCIF_IF_PAD_SINK,
+					       RKCIF_IF_PAD_SRC, &streams_mask);
+
+	return v4l2_subdev_disable_streams(remote_sd, remote_pad->index, mask);
+}
+
+static const struct v4l2_subdev_pad_ops rkcif_interface_pad_ops = {
+	.get_fmt = v4l2_subdev_get_fmt,
+	.set_fmt = rkcif_interface_set_fmt,
+	.get_selection = rkcif_interface_get_sel,
+	.set_selection = rkcif_interface_set_sel,
+	.set_routing = rkcif_interface_set_routing,
+	.enable_streams = rkcif_interface_enable_streams,
+	.disable_streams = rkcif_interface_disable_streams,
+};
+
+static const struct v4l2_subdev_ops rkcif_interface_ops = {
+	.pad = &rkcif_interface_pad_ops,
+};
+
+static int rkcif_interface_init_state(struct v4l2_subdev *sd,
+				      struct v4l2_subdev_state *state)
+{
+	struct rkcif_interface *interface = to_rkcif_interface(sd);
+	struct v4l2_subdev_route routes[] = {
+		{
+			.sink_pad = RKCIF_IF_PAD_SINK,
+			.sink_stream = 0,
+			.source_pad = RKCIF_IF_PAD_SRC,
+			.source_stream = 0,
+			.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
+		},
+	};
+	struct v4l2_subdev_krouting routing = {
+		.len_routes = ARRAY_SIZE(routes),
+		.num_routes = ARRAY_SIZE(routes),
+		.routes = routes,
+	};
+	const struct v4l2_mbus_framefmt dvp_default_format = {
+		.width = 3840,
+		.height = 2160,
+		.code = MEDIA_BUS_FMT_YUYV8_1X16,
+		.field = V4L2_FIELD_NONE,
+		.colorspace = V4L2_COLORSPACE_REC709,
+		.ycbcr_enc = V4L2_YCBCR_ENC_709,
+		.quantization = V4L2_QUANTIZATION_LIM_RANGE,
+		.xfer_func = V4L2_XFER_FUNC_NONE,
+	};
+	const struct v4l2_mbus_framefmt mipi_default_format = {
+		.width = 3840,
+		.height = 2160,
+		.code = MEDIA_BUS_FMT_SRGGB10_1X10,
+		.field = V4L2_FIELD_NONE,
+		.colorspace = V4L2_COLORSPACE_RAW,
+		.ycbcr_enc = V4L2_YCBCR_ENC_601,
+		.quantization = V4L2_QUANTIZATION_FULL_RANGE,
+		.xfer_func = V4L2_XFER_FUNC_NONE,
+	};
+	const struct v4l2_mbus_framefmt *default_format;
+	int ret;
+
+	default_format = (interface->type == RKCIF_IF_DVP) ?
+				 &dvp_default_format :
+				 &mipi_default_format;
+
+	ret = v4l2_subdev_set_routing_with_fmt(sd, state, &routing,
+					       default_format);
+
+	return ret;
+}
+
+static const struct v4l2_subdev_internal_ops rkcif_interface_internal_ops = {
+	.init_state = rkcif_interface_init_state,
+};
+
+static int rkcif_interface_add(struct rkcif_interface *interface)
+{
+	struct rkcif_device *rkcif = interface->rkcif;
+	struct rkcif_remote *remote;
+	struct v4l2_async_notifier *ntf = &rkcif->notifier;
+	struct v4l2_fwnode_endpoint *vep = &interface->vep;
+	struct device *dev = rkcif->dev;
+	struct fwnode_handle *ep;
+	u32 dvp_clk_delay = 0;
+	int ret;
+
+	ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), interface->index,
+					     0, 0);
+	if (!ep)
+		return -ENODEV;
+
+	vep->bus_type = V4L2_MBUS_UNKNOWN;
+	ret = v4l2_fwnode_endpoint_parse(ep, vep);
+	if (ret)
+		goto complete;
+
+	if (interface->type == RKCIF_IF_DVP) {
+		if (vep->bus_type != V4L2_MBUS_BT656 &&
+		    vep->bus_type != V4L2_MBUS_PARALLEL) {
+			ret = dev_err_probe(dev, -EINVAL,
+					    "unsupported bus type\n");
+			goto complete;
+		}
+
+		fwnode_property_read_u32(ep, "rockchip,dvp-clk-delay",
+					 &dvp_clk_delay);
+		interface->dvp.dvp_clk_delay = dvp_clk_delay;
+	}
+
+	remote = v4l2_async_nf_add_fwnode_remote(ntf, ep, struct rkcif_remote);
+	if (IS_ERR(remote)) {
+		ret = PTR_ERR(remote);
+		goto complete;
+	}
+
+	remote->interface = interface;
+	interface->remote = remote;
+	interface->status = RKCIF_IF_ACTIVE;
+	ret = 0;
+
+complete:
+	fwnode_handle_put(ep);
+
+	return ret;
+}
+
+int rkcif_interface_register(struct rkcif_device *rkcif,
+			     struct rkcif_interface *interface)
+{
+	struct media_pad *pads = interface->pads;
+	struct v4l2_subdev *sd = &interface->sd;
+	int ret;
+
+	interface->rkcif = rkcif;
+
+	v4l2_subdev_init(sd, &rkcif_interface_ops);
+	sd->dev = rkcif->dev;
+	sd->entity.ops = &rkcif_interface_media_ops;
+	sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
+	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS;
+	sd->internal_ops = &rkcif_interface_internal_ops;
+	sd->owner = THIS_MODULE;
+
+	if (interface->type == RKCIF_IF_DVP)
+		snprintf(sd->name, sizeof(sd->name), "rkcif-dvp0");
+	else if (interface->type == RKCIF_IF_MIPI)
+		snprintf(sd->name, sizeof(sd->name), "rkcif-mipi%d",
+			 interface->index - RKCIF_MIPI_BASE);
+
+	pads[RKCIF_IF_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+	pads[RKCIF_IF_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE;
+	ret = media_entity_pads_init(&sd->entity, RKCIF_IF_PAD_MAX, pads);
+	if (ret)
+		goto err;
+
+	ret = v4l2_subdev_init_finalize(sd);
+	if (ret)
+		goto err_entity_cleanup;
+
+	ret = v4l2_device_register_subdev(&rkcif->v4l2_dev, sd);
+	if (ret) {
+		dev_err(sd->dev, "failed to register subdev\n");
+		goto err_subdev_cleanup;
+	}
+
+	ret = rkcif_interface_add(interface);
+	if (ret)
+		goto err_subdev_unregister;
+
+	return 0;
+
+err_subdev_unregister:
+	v4l2_device_unregister_subdev(sd);
+err_subdev_cleanup:
+	v4l2_subdev_cleanup(sd);
+err_entity_cleanup:
+	media_entity_cleanup(&sd->entity);
+err:
+	return ret;
+}
+
+void rkcif_interface_unregister(struct rkcif_interface *interface)
+{
+	struct v4l2_subdev *sd = &interface->sd;
+
+	if (interface->status != RKCIF_IF_ACTIVE)
+		return;
+
+	v4l2_device_unregister_subdev(sd);
+	v4l2_subdev_cleanup(sd);
+	media_entity_cleanup(&sd->entity);
+}
+
+const struct rkcif_input_fmt *
+rkcif_interface_find_input_fmt(struct rkcif_interface *interface, bool ret_def,
+			       u32 mbus_code)
+{
+	const struct rkcif_input_fmt *fmt;
+	unsigned int i;
+
+	WARN_ON(interface->in_fmts_num == 0);
+
+	for (i = 0; i < interface->in_fmts_num; i++) {
+		fmt = &interface->in_fmts[i];
+		if (fmt->mbus_code == mbus_code)
+			return fmt;
+	}
+	if (ret_def)
+		return &interface->in_fmts[0];
+	else
+		return NULL;
+}
diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-interface.h b/drivers/media/platform/rockchip/rkcif/rkcif-interface.h
new file mode 100644
index 000000000000..f37fde4cc6a2
--- /dev/null
+++ b/drivers/media/platform/rockchip/rkcif/rkcif-interface.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Rockchip Camera Interface (CIF) Driver
+ *
+ * Abstraction for the INTERFACE and CROP parts of the different CIF variants.
+ * They shall be represented as V4L2 subdevice with one sink pad and one
+ * source pad. The sink pad is connected to a subdevice: either the subdevice
+ * provided by the driver of the companion chip connected to the DVP, or the
+ * subdevice provided by the MIPI CSI Receiver driver in
+ * rkcif-mipi-csi-receiver.c. The source pad is connected to an instance of the
+ * DMA abstraction in rkcif-stream.c.
+ *
+ * Copyright (C) 2025 Michael Riesch <michael.riesch@wolfvision.net>
+ */
+
+#ifndef _RKCIF_INTERFACE_H
+#define _RKCIF_INTERFACE_H
+
+#include "rkcif-common.h"
+
+int rkcif_interface_register(struct rkcif_device *rkcif,
+			     struct rkcif_interface *interface);
+
+void rkcif_interface_unregister(struct rkcif_interface *interface);
+
+const struct rkcif_input_fmt *
+rkcif_interface_find_input_fmt(struct rkcif_interface *interface, bool ret_def,
+			       u32 mbus_code);
+
+#endif
diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-regs.h b/drivers/media/platform/rockchip/rkcif/rkcif-regs.h
new file mode 100644
index 000000000000..07fd64174e80
--- /dev/null
+++ b/drivers/media/platform/rockchip/rkcif/rkcif-regs.h
@@ -0,0 +1,132 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Rockchip Camera Interface (CIF) Driver
+ *
+ * Copyright (C) 2018 Rockchip Electronics Co., Ltd.
+ * Copyright (C) 2023 Mehdi Djait <mehdi.djait@bootlin.com>
+ * Copyright (C) 2025 Michael Riesch <michael.riesch@wolfvision.net>
+ */
+
+#ifndef _RKCIF_REGS_H
+#define _RKCIF_REGS_H
+
+enum rkcif_dvp_register_index {
+	RKCIF_DVP_CTRL,
+	RKCIF_DVP_INTEN,
+	RKCIF_DVP_INTSTAT,
+	RKCIF_DVP_FOR,
+	RKCIF_DVP_LINE_NUM_ADDR,
+	RKCIF_DVP_FRM0_ADDR_Y,
+	RKCIF_DVP_FRM0_ADDR_UV,
+	RKCIF_DVP_FRM1_ADDR_Y,
+	RKCIF_DVP_FRM1_ADDR_UV,
+	RKCIF_DVP_VIR_LINE_WIDTH,
+	RKCIF_DVP_SET_SIZE,
+	RKCIF_DVP_SCL_CTRL,
+	RKCIF_DVP_CROP,
+	RKCIF_DVP_FRAME_STATUS,
+	RKCIF_DVP_LAST_LINE,
+	RKCIF_DVP_LAST_PIX,
+	RKCIF_DVP_REGISTER_MAX
+};
+
+#define RKCIF_REGISTER_NOTSUPPORTED           0x420000
+
+#define RKCIF_FETCH_Y(VAL)		       ((VAL) & 0x1fff)
+
+#define RKCIF_CTRL_ENABLE_CAPTURE	       BIT(0)
+#define RKCIF_CTRL_MODE_PINGPONG	       BIT(1)
+#define RKCIF_CTRL_MODE_LINELOOP	       BIT(2)
+#define RKCIF_CTRL_AXI_BURST_16		       (0xf << 12)
+
+#define RKCIF_INTEN_FRAME_END_EN	       BIT(0)
+#define RKCIF_INTEN_LINE_ERR_EN		       BIT(2)
+#define RKCIF_INTEN_BUS_ERR_EN		       BIT(6)
+#define RKCIF_INTEN_SCL_ERR_EN		       BIT(7)
+#define RKCIF_INTEN_PST_INF_FRAME_END_EN       BIT(9)
+
+#define RKCIF_INTSTAT_CLS		       0x3ff
+#define RKCIF_INTSTAT_FRAME_END		       BIT(0)
+#define RKCIF_INTSTAT_LINE_END		       BIT(1)
+#define RKCIF_INTSTAT_LINE_ERR		       BIT(2)
+#define RKCIF_INTSTAT_PIX_ERR		       BIT(3)
+#define RKCIF_INTSTAT_DFIFO_OF		       BIT(5)
+#define RKCIF_INTSTAT_BUS_ERR		       BIT(6)
+#define RKCIF_INTSTAT_PRE_INF_FRAME_END	       BIT(8)
+#define RKCIF_INTSTAT_PST_INF_FRAME_END	       BIT(9)
+#define RKCIF_INTSTAT_FRAME_END_CLR	       BIT(0)
+#define RKCIF_INTSTAT_LINE_END_CLR	       BIT(1)
+#define RKCIF_INTSTAT_LINE_ERR_CLR	       BIT(2)
+#define RKCIF_INTSTAT_PST_INF_FRAME_END_CLR    BIT(9)
+#define RKCIF_INTSTAT_ERR		       0xfc
+
+#define RKCIF_FRAME_STAT_CLS		       0x00
+#define RKCIF_FRAME_FRM0_STAT_CLS	       0x20
+
+#define RKCIF_FORMAT_VSY_HIGH_ACTIVE	       BIT(0)
+#define RKCIF_FORMAT_HSY_LOW_ACTIVE	       BIT(1)
+
+#define RKCIF_FORMAT_INPUT_MODE_YUV	       (0x00 << 2)
+#define RKCIF_FORMAT_INPUT_MODE_PAL	       (0x02 << 2)
+#define RKCIF_FORMAT_INPUT_MODE_NTSC	       (0x03 << 2)
+#define RKCIF_FORMAT_INPUT_MODE_BT1120	       (0x07 << 2)
+#define RKCIF_FORMAT_INPUT_MODE_RAW	       (0x04 << 2)
+#define RKCIF_FORMAT_INPUT_MODE_JPEG	       (0x05 << 2)
+#define RKCIF_FORMAT_INPUT_MODE_MIPI	       (0x06 << 2)
+
+#define RKCIF_FORMAT_YUV_INPUT_ORDER_UYVY      (0x00 << 5)
+#define RKCIF_FORMAT_YUV_INPUT_ORDER_YVYU      (0x01 << 5)
+#define RKCIF_FORMAT_YUV_INPUT_ORDER_VYUY      (0x02 << 5)
+#define RKCIF_FORMAT_YUV_INPUT_ORDER_YUYV      (0x03 << 5)
+#define RKCIF_FORMAT_YUV_INPUT_422	       (0x00 << 7)
+#define RKCIF_FORMAT_YUV_INPUT_420	       BIT(7)
+
+#define RKCIF_FORMAT_INPUT_420_ORDER_ODD       BIT(8)
+
+#define RKCIF_FORMAT_CCIR_INPUT_ORDER_EVEN     BIT(9)
+
+#define RKCIF_FORMAT_RAW_DATA_WIDTH_8	       (0x00 << 11)
+#define RKCIF_FORMAT_RAW_DATA_WIDTH_10	       (0x01 << 11)
+#define RKCIF_FORMAT_RAW_DATA_WIDTH_12	       (0x02 << 11)
+
+#define RKCIF_FORMAT_YUV_OUTPUT_422	       (0x00 << 16)
+#define RKCIF_FORMAT_YUV_OUTPUT_420	       BIT(16)
+
+#define RKCIF_FORMAT_OUTPUT_420_ORDER_EVEN     (0x00 << 17)
+#define RKCIF_FORMAT_OUTPUT_420_ORDER_ODD      BIT(17)
+
+#define RKCIF_FORMAT_RAWD_DATA_LITTLE_ENDIAN   (0x00 << 18)
+#define RKCIF_FORMAT_RAWD_DATA_BIG_ENDIAN      BIT(18)
+
+#define RKCIF_FORMAT_UV_STORAGE_ORDER_UVUV     (0x00 << 19)
+#define RKCIF_FORMAT_UV_STORAGE_ORDER_VUVU     BIT(19)
+
+#define RKCIF_FORMAT_BT1120_CLOCK_SINGLE_EDGES (0x00 << 24)
+#define RKCIF_FORMAT_BT1120_CLOCK_DOUBLE_EDGES BIT(24)
+#define RKCIF_FORMAT_BT1120_TRANSMIT_INTERFACE (0x00 << 25)
+#define RKCIF_FORMAT_BT1120_TRANSMIT_PROGRESS  BIT(25)
+#define RKCIF_FORMAT_BT1120_YC_SWAP	       BIT(26)
+
+#define RKCIF_SCL_CTRL_ENABLE_SCL_DOWN	       BIT(0)
+#define RKCIF_SCL_CTRL_ENABLE_SCL_UP	       BIT(1)
+#define RKCIF_SCL_CTRL_ENABLE_YUV_16BIT_BYPASS BIT(4)
+#define RKCIF_SCL_CTRL_ENABLE_RAW_16BIT_BYPASS BIT(5)
+#define RKCIF_SCL_CTRL_ENABLE_32BIT_BYPASS     BIT(6)
+#define RKCIF_SCL_CTRL_DISABLE_32BIT_BYPASS    (0x00 << 6)
+
+#define RKCIF_INTSTAT_F0_READY		       BIT(0)
+#define RKCIF_INTSTAT_F1_READY		       BIT(1)
+
+#define RKCIF_XY_COORD(x, y)		       (((y) << 16) | (x))
+
+/* GRF register offsets */
+#define RK3568_GRF_VI_CON0		       0x340
+#define RK3568_GRF_VI_CON1		       0x344
+#define RK3568_GRF_VI_STATUS0		       0x348
+
+#define RK3568_GRF_VI_CON1_CIF_DATAPATH	       BIT(9)
+#define RK3568_GRF_VI_CON1_CIF_CLK_DELAYNUM    GENMASK(6, 0)
+
+#define RK3568_GRF_WRITE_ENABLE(x)	       ((x) << 16)
+
+#endif
diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-stream.c b/drivers/media/platform/rockchip/rkcif/rkcif-stream.c
new file mode 100644
index 000000000000..bfafd76f4494
--- /dev/null
+++ b/drivers/media/platform/rockchip/rkcif/rkcif-stream.c
@@ -0,0 +1,622 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Rockchip Camera Interface (CIF) Driver
+ *
+ * Copyright (C) 2025 Michael Riesch <michael.riesch@wolfvision.net>
+ */
+
+#include <linux/pm_runtime.h>
+
+#include <media/v4l2-common.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-mc.h>
+#include <media/v4l2-subdev.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include "rkcif-common.h"
+#include "rkcif-stream.h"
+
+#define CIF_REQ_BUFS_MIN 8
+#define CIF_MIN_WIDTH	 64
+#define CIF_MIN_HEIGHT	 64
+#define CIF_MAX_WIDTH	 8192
+#define CIF_MAX_HEIGHT	 8192
+
+static inline struct rkcif_buffer *to_rkcif_buffer(struct vb2_v4l2_buffer *vb)
+{
+	return container_of(vb, struct rkcif_buffer, vb);
+}
+
+static inline struct rkcif_stream *to_rkcif_stream(struct video_device *vdev)
+{
+	return container_of(vdev, struct rkcif_stream, vdev);
+}
+
+static struct rkcif_buffer *rkcif_stream_pop_buffer(struct rkcif_stream *stream)
+{
+	struct rkcif_buffer *buffer = NULL;
+	unsigned long lock_flags;
+
+	spin_lock_irqsave(&stream->driver_queue_lock, lock_flags);
+
+	if (list_empty(&stream->driver_queue))
+		goto err_empty;
+
+	buffer = list_first_entry(&stream->driver_queue, struct rkcif_buffer,
+				  queue);
+	list_del(&buffer->queue);
+
+err_empty:
+	spin_unlock_irqrestore(&stream->driver_queue_lock, lock_flags);
+	return buffer;
+}
+
+static void rkcif_stream_push_buffer(struct rkcif_stream *stream,
+				     struct rkcif_buffer *buffer)
+{
+	unsigned long lock_flags;
+
+	spin_lock_irqsave(&stream->driver_queue_lock, lock_flags);
+	list_add_tail(&buffer->queue, &stream->driver_queue);
+	spin_unlock_irqrestore(&stream->driver_queue_lock, lock_flags);
+}
+
+static inline void rkcif_stream_return_buffer(struct rkcif_buffer *buffer,
+					      enum vb2_buffer_state state)
+{
+	struct vb2_v4l2_buffer *vb = &buffer->vb;
+
+	vb2_buffer_done(&vb->vb2_buf, state);
+}
+
+static void rkcif_stream_complete_buffer(struct rkcif_stream *stream,
+					 struct rkcif_buffer *buffer)
+{
+	struct vb2_v4l2_buffer *vb = &buffer->vb;
+
+	vb->vb2_buf.timestamp = ktime_get_ns();
+	vb->sequence = stream->frame_idx;
+	vb2_buffer_done(&vb->vb2_buf, VB2_BUF_STATE_DONE);
+	stream->frame_idx++;
+}
+
+void rkcif_stream_pingpong(struct rkcif_stream *stream)
+{
+	struct rkcif_buffer *buffer;
+
+	buffer = stream->buffers[stream->frame_phase];
+	if (!buffer->is_dummy)
+		rkcif_stream_complete_buffer(stream, buffer);
+
+	buffer = rkcif_stream_pop_buffer(stream);
+	if (buffer) {
+		stream->buffers[stream->frame_phase] = buffer;
+		stream->buffers[stream->frame_phase]->is_dummy = false;
+	} else {
+		stream->buffers[stream->frame_phase] = &stream->dummy.buffer;
+		stream->buffers[stream->frame_phase]->is_dummy = true;
+		dev_warn(stream->rkcif->dev,
+			 "no buffer available, frame will be dropped\n");
+	}
+
+	if (stream->queue_buffer)
+		stream->queue_buffer(stream, stream->frame_phase);
+
+	stream->frame_phase = 1 - stream->frame_phase;
+}
+
+static int rkcif_stream_init_buffers(struct rkcif_stream *stream)
+{
+	struct v4l2_pix_format_mplane *pix = &stream->pix;
+	int i;
+
+	stream->buffers[0] = rkcif_stream_pop_buffer(stream);
+	if (!stream->buffers[0])
+		goto err_buff_0;
+
+	stream->buffers[1] = rkcif_stream_pop_buffer(stream);
+	if (!stream->buffers[1])
+		goto err_buff_1;
+
+	if (stream->queue_buffer) {
+		stream->queue_buffer(stream, 0);
+		stream->queue_buffer(stream, 1);
+	}
+
+	stream->dummy.size = pix->num_planes * pix->plane_fmt[0].sizeimage;
+	stream->dummy.vaddr =
+		dma_alloc_attrs(stream->rkcif->dev, stream->dummy.size,
+				&stream->dummy.buffer.buff_addr[0], GFP_KERNEL,
+				DMA_ATTR_NO_KERNEL_MAPPING);
+	if (!stream->dummy.vaddr)
+		goto err_dummy;
+
+	for (i = 1; i < pix->num_planes; i++)
+		stream->dummy.buffer.buff_addr[i] =
+			stream->dummy.buffer.buff_addr[i - 1] +
+			pix->plane_fmt[i - 1].bytesperline * pix->height;
+
+	return 0;
+
+err_dummy:
+	rkcif_stream_return_buffer(stream->buffers[1], VB2_BUF_STATE_QUEUED);
+	stream->buffers[1] = NULL;
+
+err_buff_1:
+	rkcif_stream_return_buffer(stream->buffers[0], VB2_BUF_STATE_QUEUED);
+	stream->buffers[0] = NULL;
+err_buff_0:
+	return -EINVAL;
+}
+
+static void rkcif_stream_return_all_buffers(struct rkcif_stream *stream,
+					    enum vb2_buffer_state state)
+{
+	struct rkcif_buffer *buffer;
+
+	if (stream->buffers[0] && !stream->buffers[0]->is_dummy) {
+		rkcif_stream_return_buffer(stream->buffers[0], state);
+		stream->buffers[0] = NULL;
+	}
+
+	if (stream->buffers[1] && !stream->buffers[1]->is_dummy) {
+		rkcif_stream_return_buffer(stream->buffers[1], state);
+		stream->buffers[1] = NULL;
+	}
+
+	while ((buffer = rkcif_stream_pop_buffer(stream)))
+		rkcif_stream_return_buffer(buffer, state);
+
+	if (stream->dummy.vaddr) {
+		dma_free_attrs(stream->rkcif->dev, stream->dummy.size,
+			       stream->dummy.vaddr,
+			       stream->dummy.buffer.buff_addr[0],
+			       DMA_ATTR_NO_KERNEL_MAPPING);
+		stream->dummy.vaddr = NULL;
+	}
+}
+
+static int rkcif_stream_setup_queue(struct vb2_queue *queue,
+				    unsigned int *num_buffers,
+				    unsigned int *num_planes,
+				    unsigned int sizes[],
+				    struct device *alloc_devs[])
+{
+	struct rkcif_stream *stream = queue->drv_priv;
+	struct v4l2_pix_format_mplane *pix = &stream->pix;
+	unsigned int i;
+
+	if (*num_planes) {
+		if (*num_planes != pix->num_planes)
+			return -EINVAL;
+
+		for (i = 0; i < pix->num_planes; i++)
+			if (sizes[i] < pix->plane_fmt[i].sizeimage)
+				return -EINVAL;
+	} else {
+		*num_planes = pix->num_planes;
+		for (i = 0; i < pix->num_planes; i++)
+			sizes[i] = pix->plane_fmt[i].sizeimage;
+	}
+
+	return 0;
+}
+
+static int rkcif_stream_prepare_buffer(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct rkcif_buffer *buffer = to_rkcif_buffer(vbuf);
+	struct rkcif_stream *stream = vb->vb2_queue->drv_priv;
+	const struct rkcif_output_fmt *fmt;
+	struct v4l2_pix_format_mplane *pix = &stream->pix;
+	unsigned int i;
+
+	memset(buffer->buff_addr, 0, sizeof(buffer->buff_addr));
+	for (i = 0; i < pix->num_planes; i++)
+		buffer->buff_addr[i] = vb2_dma_contig_plane_dma_addr(vb, i);
+
+	/* apply fallback for non-mplane formats, if required */
+	if (pix->num_planes == 1) {
+		fmt = rkcif_stream_find_output_fmt(stream, true,
+						   pix->pixelformat);
+		for (i = 1; i < fmt->cplanes; i++)
+			buffer->buff_addr[i] =
+				buffer->buff_addr[i - 1] +
+				pix->plane_fmt[i - 1].bytesperline *
+					pix->height;
+	}
+
+	for (i = 0; i < pix->num_planes; i++) {
+		unsigned long size = pix->plane_fmt[i].sizeimage;
+
+		if (vb2_plane_size(vb, i) < size) {
+			dev_err(stream->rkcif->dev,
+				"user buffer too small (%ld < %ld)\n",
+				vb2_plane_size(vb, i), size);
+			return -EINVAL;
+		}
+
+		vb2_set_plane_payload(vb, i, size);
+	}
+
+	return 0;
+}
+
+static void rkcif_stream_queue_buffer(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct rkcif_buffer *buffer = to_rkcif_buffer(vbuf);
+	struct rkcif_stream *stream = vb->vb2_queue->drv_priv;
+
+	rkcif_stream_push_buffer(stream, buffer);
+}
+
+static int rkcif_stream_start_streaming(struct vb2_queue *queue,
+					unsigned int count)
+{
+	struct rkcif_stream *stream = queue->drv_priv;
+	struct rkcif_device *rkcif = stream->rkcif;
+	u64 mask;
+	int ret;
+
+	stream->frame_idx = 0;
+	stream->frame_phase = 0;
+
+	ret = video_device_pipeline_start(&stream->vdev, &stream->pipeline);
+	if (ret) {
+		dev_err(rkcif->dev, "failed to start pipeline %d\n", ret);
+		goto err_out;
+	}
+
+	ret = pm_runtime_resume_and_get(rkcif->dev);
+	if (ret < 0) {
+		dev_err(rkcif->dev, "failed to get runtime pm, %d\n", ret);
+		goto err_pipeline_stop;
+	}
+
+	ret = rkcif_stream_init_buffers(stream);
+	if (ret)
+		goto err_runtime_put;
+
+	if (stream->start_streaming) {
+		ret = stream->start_streaming(stream);
+		if (ret < 0)
+			goto err_runtime_put;
+	}
+
+	mask = BIT_ULL(stream->id);
+	ret = v4l2_subdev_enable_streams(&stream->interface->sd,
+					 RKCIF_IF_PAD_SRC, mask);
+	if (ret < 0)
+		goto err_stop_stream;
+
+	return 0;
+
+err_stop_stream:
+	if (stream->stop_streaming)
+		stream->stop_streaming(stream);
+err_runtime_put:
+	pm_runtime_put(rkcif->dev);
+err_pipeline_stop:
+	video_device_pipeline_stop(&stream->vdev);
+err_out:
+	rkcif_stream_return_all_buffers(stream, VB2_BUF_STATE_QUEUED);
+	return ret;
+}
+
+static void rkcif_stream_stop_streaming(struct vb2_queue *queue)
+{
+	struct rkcif_stream *stream = queue->drv_priv;
+	struct rkcif_device *rkcif = stream->rkcif;
+	u64 mask;
+	int ret;
+
+	mask = BIT_ULL(stream->id);
+	v4l2_subdev_disable_streams(&stream->interface->sd, RKCIF_IF_PAD_SRC,
+				    mask);
+
+	stream->stopping = true;
+	ret = wait_event_timeout(stream->wq_stopped, !stream->stopping,
+				 msecs_to_jiffies(1000));
+
+	if (!ret && stream->stop_streaming)
+		stream->stop_streaming(stream);
+
+	pm_runtime_put(rkcif->dev);
+
+	rkcif_stream_return_all_buffers(stream, VB2_BUF_STATE_ERROR);
+
+	video_device_pipeline_stop(&stream->vdev);
+}
+
+static const struct vb2_ops rkcif_stream_vb2_ops = {
+	.queue_setup = rkcif_stream_setup_queue,
+	.buf_prepare = rkcif_stream_prepare_buffer,
+	.buf_queue = rkcif_stream_queue_buffer,
+	.wait_prepare = vb2_ops_wait_prepare,
+	.wait_finish = vb2_ops_wait_finish,
+	.start_streaming = rkcif_stream_start_streaming,
+	.stop_streaming = rkcif_stream_stop_streaming,
+};
+
+static int rkcif_stream_try_format(struct file *file, void *fh,
+				   struct v4l2_format *f)
+{
+	struct rkcif_stream *stream = video_drvdata(file);
+	struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;
+	const struct rkcif_output_fmt *fmt;
+	u32 height, width;
+
+	fmt = rkcif_stream_find_output_fmt(stream, true, pix->pixelformat);
+	height = clamp_t(u32, pix->height, CIF_MIN_HEIGHT, CIF_MAX_HEIGHT);
+	width = clamp_t(u32, pix->width, CIF_MIN_WIDTH, CIF_MAX_WIDTH);
+	v4l2_fill_pixfmt_mp(pix, fmt->fourcc, width, height);
+
+	return 0;
+}
+
+static int rkcif_stream_set_format(struct file *file, void *priv,
+				   struct v4l2_format *f)
+{
+	struct rkcif_stream *stream = video_drvdata(file);
+	struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;
+	int ret;
+
+	if (vb2_is_busy(&stream->buf_queue))
+		return -EBUSY;
+
+	ret = rkcif_stream_try_format(file, priv, f);
+	if (ret)
+		return ret;
+
+	stream->pix = *pix;
+
+	return 0;
+}
+
+static int rkcif_stream_get_format(struct file *file, void *fh,
+				   struct v4l2_format *f)
+{
+	struct rkcif_stream *stream = video_drvdata(file);
+
+	f->fmt.pix_mp = stream->pix;
+
+	return 0;
+}
+
+static int rkcif_stream_enum_formats(struct file *file, void *priv,
+				     struct v4l2_fmtdesc *f)
+{
+	struct rkcif_stream *stream = video_drvdata(file);
+
+	if (f->index >= stream->out_fmts_num)
+		return -EINVAL;
+
+	f->pixelformat = stream->out_fmts[f->index].fourcc;
+
+	return 0;
+}
+
+static int rkcif_stream_enum_framesizes(struct file *file, void *fh,
+					struct v4l2_frmsizeenum *fsize)
+{
+	if (fsize->index > 0)
+		return -EINVAL;
+
+	fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
+	fsize->stepwise.min_width = CIF_MIN_WIDTH;
+	fsize->stepwise.max_width = CIF_MAX_WIDTH;
+	fsize->stepwise.step_width = 8;
+	fsize->stepwise.min_height = CIF_MIN_HEIGHT;
+	fsize->stepwise.max_height = CIF_MAX_HEIGHT;
+	fsize->stepwise.step_height = 8;
+
+	return 0;
+}
+
+static int rkcif_stream_querycap(struct file *file, void *priv,
+				 struct v4l2_capability *cap)
+{
+	struct rkcif_stream *stream = video_drvdata(file);
+	struct device *dev = stream->rkcif->dev;
+
+	strscpy(cap->driver, dev->driver->name, sizeof(cap->driver));
+	strscpy(cap->card, dev->driver->name, sizeof(cap->card));
+
+	return 0;
+}
+
+static const struct v4l2_ioctl_ops rkcif_stream_ioctl_ops = {
+	.vidioc_reqbufs = vb2_ioctl_reqbufs,
+	.vidioc_querybuf = vb2_ioctl_querybuf,
+	.vidioc_create_bufs = vb2_ioctl_create_bufs,
+	.vidioc_qbuf = vb2_ioctl_qbuf,
+	.vidioc_expbuf = vb2_ioctl_expbuf,
+	.vidioc_dqbuf = vb2_ioctl_dqbuf,
+	.vidioc_prepare_buf = vb2_ioctl_prepare_buf,
+	.vidioc_streamon = vb2_ioctl_streamon,
+	.vidioc_streamoff = vb2_ioctl_streamoff,
+	.vidioc_try_fmt_vid_cap_mplane = rkcif_stream_try_format,
+	.vidioc_s_fmt_vid_cap_mplane = rkcif_stream_set_format,
+	.vidioc_g_fmt_vid_cap_mplane = rkcif_stream_get_format,
+	.vidioc_enum_fmt_vid_cap = rkcif_stream_enum_formats,
+	.vidioc_enum_framesizes = rkcif_stream_enum_framesizes,
+	.vidioc_querycap = rkcif_stream_querycap,
+};
+
+static int rkcif_stream_link_validate(struct media_link *link)
+{
+	struct video_device *vdev =
+		media_entity_to_video_device(link->sink->entity);
+	struct v4l2_mbus_framefmt *source_fmt;
+	struct v4l2_subdev *sd;
+	struct v4l2_subdev_state *state;
+	struct rkcif_stream *stream = to_rkcif_stream(vdev);
+	int ret = -EINVAL;
+
+	if (!media_entity_remote_source_pad_unique(link->sink->entity))
+		return -ENOTCONN;
+
+	sd = media_entity_to_v4l2_subdev(link->source->entity);
+
+	state = v4l2_subdev_lock_and_get_active_state(sd);
+
+	source_fmt = v4l2_subdev_state_get_format(state, link->source->index,
+						  stream->id);
+	if (!source_fmt)
+		goto out;
+
+	if (source_fmt->height != stream->pix.height ||
+	    source_fmt->width != stream->pix.width) {
+		dev_dbg(stream->rkcif->dev,
+			"link '%s':%u -> '%s':%u not valid: %ux%u != %ux%u\n",
+			link->source->entity->name, link->source->index,
+			link->sink->entity->name, link->sink->index,
+			source_fmt->width, source_fmt->height,
+			stream->pix.width, stream->pix.height);
+		goto out;
+	}
+
+	ret = 0;
+
+out:
+	v4l2_subdev_unlock_state(state);
+	return ret;
+}
+
+static const struct media_entity_operations rkcif_stream_media_ops = {
+	.link_validate = rkcif_stream_link_validate,
+};
+
+static const struct v4l2_file_operations rkcif_stream_file_ops = {
+	.open = v4l2_fh_open,
+	.release = vb2_fop_release,
+	.unlocked_ioctl = video_ioctl2,
+	.poll = vb2_fop_poll,
+	.mmap = vb2_fop_mmap,
+};
+
+static int rkcif_stream_init_vb2_queue(struct vb2_queue *q,
+				       struct rkcif_stream *stream)
+{
+	q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
+	q->io_modes = VB2_MMAP | VB2_DMABUF;
+	q->drv_priv = stream;
+	q->ops = &rkcif_stream_vb2_ops;
+	q->mem_ops = &vb2_dma_contig_memops;
+	q->buf_struct_size = sizeof(struct rkcif_buffer);
+	q->min_queued_buffers = CIF_REQ_BUFS_MIN;
+	q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+	q->lock = &stream->vlock;
+	q->dev = stream->rkcif->dev;
+
+	return vb2_queue_init(q);
+}
+
+int rkcif_stream_register(struct rkcif_device *rkcif,
+			  struct rkcif_stream *stream)
+{
+	struct rkcif_interface *interface = stream->interface;
+	struct v4l2_device *v4l2_dev = &rkcif->v4l2_dev;
+	struct video_device *vdev = &stream->vdev;
+	u32 link_flags = 0;
+	int ret;
+
+	stream->rkcif = rkcif;
+
+	INIT_LIST_HEAD(&stream->driver_queue);
+	spin_lock_init(&stream->driver_queue_lock);
+
+	init_waitqueue_head(&stream->wq_stopped);
+
+	mutex_init(&stream->vlock);
+
+	vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_STREAMING |
+			    V4L2_CAP_IO_MC;
+	vdev->entity.ops = &rkcif_stream_media_ops;
+	vdev->fops = &rkcif_stream_file_ops;
+	vdev->ioctl_ops = &rkcif_stream_ioctl_ops;
+	vdev->lock = &stream->vlock;
+	vdev->minor = -1;
+	vdev->release = video_device_release_empty;
+	vdev->v4l2_dev = v4l2_dev;
+	vdev->vfl_dir = VFL_DIR_RX;
+	video_set_drvdata(vdev, stream);
+
+	stream->pad.flags = MEDIA_PAD_FL_SINK;
+
+	rkcif_stream_init_vb2_queue(&stream->buf_queue, stream);
+
+	vdev->queue = &stream->buf_queue;
+	if (interface->type == RKCIF_IF_DVP)
+		snprintf(vdev->name, sizeof(vdev->name), "rkcif-dvp0-id%d",
+			 stream->id);
+	else if (interface->type == RKCIF_IF_MIPI)
+		snprintf(vdev->name, sizeof(vdev->name), "rkcif-mipi%d-id%d",
+			 interface->index - RKCIF_MIPI_BASE, stream->id);
+
+	ret = media_entity_pads_init(&vdev->entity, 1, &stream->pad);
+	if (ret < 0) {
+		dev_err(rkcif->dev,
+			"failed to initialize stream media pad: %d\n", ret);
+		return ret;
+	}
+
+	ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
+	if (ret < 0) {
+		dev_err(rkcif->dev, "failed to register video device: %d\n",
+			ret);
+		goto err_media_entity_cleanup;
+	}
+
+	/* enable only stream ID0 by default */
+	if (stream->id == RKCIF_ID0)
+		link_flags |= MEDIA_LNK_FL_ENABLED;
+
+	ret = media_create_pad_link(&interface->sd.entity, RKCIF_IF_PAD_SRC,
+				    &stream->vdev.entity, 0, link_flags);
+	if (ret) {
+		dev_err(rkcif->dev, "failed to link stream media pad: %d\n",
+			ret);
+		goto err_video_unregister;
+	}
+
+	v4l2_info(v4l2_dev, "registered %s as /dev/video%d\n", vdev->name,
+		  vdev->num);
+
+	return 0;
+
+err_video_unregister:
+	video_unregister_device(&stream->vdev);
+err_media_entity_cleanup:
+	media_entity_cleanup(&stream->vdev.entity);
+	return ret;
+}
+
+void rkcif_stream_unregister(struct rkcif_stream *stream)
+{
+	video_unregister_device(&stream->vdev);
+	media_entity_cleanup(&stream->vdev.entity);
+}
+
+const struct rkcif_output_fmt *
+rkcif_stream_find_output_fmt(struct rkcif_stream *stream, bool ret_def,
+			     u32 pixelfmt)
+{
+	const struct rkcif_output_fmt *fmt;
+	unsigned int i;
+
+	WARN_ON(stream->out_fmts_num == 0);
+
+	for (i = 0; i < stream->out_fmts_num; i++) {
+		fmt = &stream->out_fmts[i];
+		if (fmt->fourcc == pixelfmt)
+			return fmt;
+	}
+
+	if (ret_def)
+		return &stream->out_fmts[0];
+	else
+		return NULL;
+}
diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-stream.h b/drivers/media/platform/rockchip/rkcif/rkcif-stream.h
new file mode 100644
index 000000000000..e50c9771f1b0
--- /dev/null
+++ b/drivers/media/platform/rockchip/rkcif/rkcif-stream.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Rockchip Camera Interface (CIF) Driver
+ *
+ * Abstraction for the DMA part and the ping-pong scheme (a double-buffering
+ * mechanism) of the different CIF variants.
+ * Each stream is represented as V4L2 device whose corresponding media entity
+ * has one sink pad.
+ * The sink pad is connected to an instance of the INTERFACE/CROP abstraction
+ * in rkcif-interface.c.
+ *
+ * Copyright (C) 2025 Michael Riesch <michael.riesch@wolfvision.net>
+ */
+
+#ifndef _RKCIF_STREAM_H
+#define _RKCIF_STREAM_H
+
+#include "rkcif-common.h"
+
+void rkcif_stream_pingpong(struct rkcif_stream *stream);
+
+int rkcif_stream_register(struct rkcif_device *rkcif,
+			  struct rkcif_stream *stream);
+
+void rkcif_stream_unregister(struct rkcif_stream *stream);
+
+const struct rkcif_output_fmt *
+rkcif_stream_find_output_fmt(struct rkcif_stream *stream, bool ret_def,
+			     u32 pixelfmt);
+
+#endif

-- 
2.39.5



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

* [PATCH v6 07/13] media: rockchip: rkcif: add driver for mipi csi-2 receiver
  2025-04-30  9:15 [PATCH v6 00/13] media: rockchip: add a driver for the rockchip camera interface Michael Riesch via B4 Relay
                   ` (5 preceding siblings ...)
  2025-04-30  9:15 ` [PATCH v6 06/13] media: rockchip: add a driver for the rockchip camera interface Michael Riesch via B4 Relay
@ 2025-04-30  9:15 ` Michael Riesch via B4 Relay
  2025-05-01  0:29   ` Bryan O'Donoghue
                     ` (2 more replies)
  2025-04-30  9:15 ` [PATCH v6 08/13] media: rockchip: rkcif: add support for mipi csi-2 capture Michael Riesch via B4 Relay
                   ` (5 subsequent siblings)
  12 siblings, 3 replies; 34+ messages in thread
From: Michael Riesch via B4 Relay @ 2025-04-30  9:15 UTC (permalink / raw)
  To: Mehdi Djait, Maxime Chevallier, Théo Lebrun, Gerald Loacker,
	Thomas Petazzoni, Laurent Pinchart, Mauro Carvalho Chehab,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner,
	Kever Yang, Nicolas Dufresne, Sebastian Reichel,
	Collabora Kernel Team, Paul Kocialkowski, Alexander Shiyan,
	Val Packett, Rob Herring, Philipp Zabel, Sakari Ailus
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-rockchip, Michael Riesch, Michael Riesch

From: Michael Riesch <michael.riesch@wolfvision.net>

The Rockchip RK3568 MIPI CSI-2 Receiver is a CSI-2 bridge with one
input port and one output port. It receives the data with the help
of an external MIPI PHY (C-PHY or D-PHY) and passes it to the
Rockchip RK3568 Video Capture (VICAP) block.

Add a V4L2 subdevice driver for this unit.

Signed-off-by: Michael Riesch <michael.riesch@wolfvision.net>
Signed-off-by: Michael Riesch <michael.riesch@collabora.com>
---
 drivers/media/platform/rockchip/rkcif/Makefile     |   3 +
 .../rockchip/rkcif/rkcif-mipi-csi-receiver.c       | 731 +++++++++++++++++++++
 2 files changed, 734 insertions(+)

diff --git a/drivers/media/platform/rockchip/rkcif/Makefile b/drivers/media/platform/rockchip/rkcif/Makefile
index 818424972c7b..a5c18a45c213 100644
--- a/drivers/media/platform/rockchip/rkcif/Makefile
+++ b/drivers/media/platform/rockchip/rkcif/Makefile
@@ -5,3 +5,6 @@ rockchip-cif-objs += rkcif-dev.o \
 	rkcif-capture-mipi.o \
 	rkcif-interface.o \
 	rkcif-stream.o
+
+obj-$(CONFIG_VIDEO_ROCKCHIP_CIF) += rockchip-mipi-csi.o
+rockchip-mipi-csi-objs += rkcif-mipi-csi-receiver.o
diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-mipi-csi-receiver.c b/drivers/media/platform/rockchip/rkcif/rkcif-mipi-csi-receiver.c
new file mode 100644
index 000000000000..81489f70490f
--- /dev/null
+++ b/drivers/media/platform/rockchip/rkcif/rkcif-mipi-csi-receiver.c
@@ -0,0 +1,731 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Rockchip MIPI CSI-2 Receiver Driver
+ *
+ * Copyright (C) 2019 Rockchip Electronics Co., Ltd.
+ * Copyright (C) 2025 Michael Riesch <michael.riesch@wolfvision.net>
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_graph.h>
+#include <linux/of_platform.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/reset.h>
+
+#include <media/mipi-csi2.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-subdev.h>
+
+#define CSI2HOST_N_LANES     0x04
+#define CSI2HOST_CSI2_RESETN 0x10
+#define CSI2HOST_PHY_STATE   0x14
+#define CSI2HOST_ERR1	     0x20
+#define CSI2HOST_ERR2	     0x24
+#define CSI2HOST_MSK1	     0x28
+#define CSI2HOST_MSK2	     0x2c
+#define CSI2HOST_CONTROL     0x40
+
+#define SW_CPHY_EN(x)	     ((x) << 0)
+#define SW_DSI_EN(x)	     ((x) << 4)
+#define SW_DATATYPE_FS(x)    ((x) << 8)
+#define SW_DATATYPE_FE(x)    ((x) << 14)
+#define SW_DATATYPE_LS(x)    ((x) << 20)
+#define SW_DATATYPE_LE(x)    ((x) << 26)
+
+#define RKCIF_CSI_CLKS_MAX   1
+
+enum {
+	RKCIF_CSI_PAD_SINK,
+	RKCIF_CSI_PAD_SRC,
+	RKCIF_CSI_PAD_MAX,
+};
+
+struct rkcif_csi_format {
+	u32 code;
+	u8 depth;
+	u8 csi_dt;
+};
+
+struct rkcif_csi_device {
+	struct device *dev;
+
+	void __iomem *base_addr;
+	struct clk_bulk_data *clks;
+	unsigned int clks_num;
+	struct phy *phy;
+	struct reset_control *reset;
+
+	const struct rkcif_csi_format *formats;
+	unsigned int formats_num;
+
+	struct media_pad pads[RKCIF_CSI_PAD_MAX];
+	struct v4l2_async_notifier notifier;
+	struct v4l2_fwnode_endpoint vep;
+	struct v4l2_subdev sd;
+
+	struct v4l2_subdev *source_sd;
+	u32 source_pad;
+};
+
+static const struct v4l2_mbus_framefmt default_format = {
+	.width = 3840,
+	.height = 2160,
+	.code = MEDIA_BUS_FMT_SRGGB10_1X10,
+	.field = V4L2_FIELD_NONE,
+	.colorspace = V4L2_COLORSPACE_RAW,
+	.ycbcr_enc = V4L2_YCBCR_ENC_601,
+	.quantization = V4L2_QUANTIZATION_FULL_RANGE,
+	.xfer_func = V4L2_XFER_FUNC_NONE,
+};
+
+static const struct rkcif_csi_format formats[] = {
+	/* YUV formats */
+	{
+		.code = MEDIA_BUS_FMT_YUYV8_1X16,
+		.depth = 16,
+		.csi_dt = MIPI_CSI2_DT_YUV422_8B,
+	},
+	{
+		.code = MEDIA_BUS_FMT_UYVY8_1X16,
+		.depth = 16,
+		.csi_dt = MIPI_CSI2_DT_YUV422_8B,
+	},
+	{
+		.code = MEDIA_BUS_FMT_YVYU8_1X16,
+		.depth = 16,
+		.csi_dt = MIPI_CSI2_DT_YUV422_8B,
+	},
+	{
+		.code = MEDIA_BUS_FMT_VYUY8_1X16,
+		.depth = 16,
+		.csi_dt = MIPI_CSI2_DT_YUV422_8B,
+	},
+	/* RGB formats */
+	{
+		.code = MEDIA_BUS_FMT_RGB888_1X24,
+		.depth = 24,
+		.csi_dt = MIPI_CSI2_DT_RGB888,
+	},
+	{
+		.code = MEDIA_BUS_FMT_BGR888_1X24,
+		.depth = 24,
+		.csi_dt = MIPI_CSI2_DT_RGB888,
+	},
+	/* Bayer formats */
+	{
+		.code = MEDIA_BUS_FMT_SBGGR8_1X8,
+		.depth = 8,
+		.csi_dt = MIPI_CSI2_DT_RAW8,
+	},
+	{
+		.code = MEDIA_BUS_FMT_SGBRG8_1X8,
+		.depth = 8,
+		.csi_dt = MIPI_CSI2_DT_RAW8,
+	},
+	{
+		.code = MEDIA_BUS_FMT_SGRBG8_1X8,
+		.depth = 8,
+		.csi_dt = MIPI_CSI2_DT_RAW8,
+	},
+	{
+		.code = MEDIA_BUS_FMT_SRGGB8_1X8,
+		.depth = 8,
+		.csi_dt = MIPI_CSI2_DT_RAW8,
+	},
+	{
+		.code = MEDIA_BUS_FMT_SBGGR10_1X10,
+		.depth = 10,
+		.csi_dt = MIPI_CSI2_DT_RAW10,
+	},
+	{
+		.code = MEDIA_BUS_FMT_SGBRG10_1X10,
+		.depth = 10,
+		.csi_dt = MIPI_CSI2_DT_RAW10,
+	},
+	{
+		.code = MEDIA_BUS_FMT_SGRBG10_1X10,
+		.depth = 10,
+		.csi_dt = MIPI_CSI2_DT_RAW10,
+	},
+	{
+		.code = MEDIA_BUS_FMT_SRGGB10_1X10,
+		.depth = 10,
+		.csi_dt = MIPI_CSI2_DT_RAW10,
+	},
+	{
+		.code = MEDIA_BUS_FMT_SBGGR12_1X12,
+		.depth = 12,
+		.csi_dt = MIPI_CSI2_DT_RAW12,
+	},
+	{
+		.code = MEDIA_BUS_FMT_SGBRG12_1X12,
+		.depth = 12,
+		.csi_dt = MIPI_CSI2_DT_RAW12,
+	},
+	{
+		.code = MEDIA_BUS_FMT_SGRBG12_1X12,
+		.depth = 12,
+		.csi_dt = MIPI_CSI2_DT_RAW12,
+	},
+	{
+		.code = MEDIA_BUS_FMT_SRGGB12_1X12,
+		.depth = 12,
+		.csi_dt = MIPI_CSI2_DT_RAW12,
+	},
+};
+
+static inline struct rkcif_csi_device *to_rkcif_csi(struct v4l2_subdev *sd)
+{
+	return container_of(sd, struct rkcif_csi_device, sd);
+}
+
+static inline __maybe_unused void
+rkcif_csi_write(struct rkcif_csi_device *csi_dev, unsigned int addr, u32 val)
+{
+	writel(val, csi_dev->base_addr + addr);
+}
+
+static inline __maybe_unused u32
+rkcif_csi_read(struct rkcif_csi_device *csi_dev, unsigned int addr)
+{
+	return readl(csi_dev->base_addr + addr);
+}
+
+static const struct rkcif_csi_format *
+rkcif_csi_find_format(struct rkcif_csi_device *csi_dev, u32 mbus_code)
+{
+	const struct rkcif_csi_format *format;
+
+	WARN_ON(csi_dev->formats_num == 0);
+
+	for (int i = 0; i < csi_dev->formats_num; i++) {
+		format = &csi_dev->formats[i];
+		if (format->code == mbus_code)
+			return format;
+	}
+
+	return NULL;
+}
+
+static int rkcif_csi_start(struct rkcif_csi_device *csi_dev)
+{
+	enum v4l2_mbus_type bus_type = csi_dev->vep.bus_type;
+	union phy_configure_opts opts;
+	s64 link_freq;
+	u32 lanes = csi_dev->vep.bus.mipi_csi2.num_data_lanes;
+	u32 control = 0;
+
+	if (lanes < 1 || lanes > 4)
+		return -EINVAL;
+
+	/* set mult and div to 0, thus completely rely on V4L2_CID_LINK_FREQ */
+	link_freq = v4l2_get_link_freq(csi_dev->source_sd->ctrl_handler, 0, 0);
+	if (link_freq <= 0)
+		return -EINVAL;
+
+	if (bus_type == V4L2_MBUS_CSI2_DPHY) {
+		struct phy_configure_opts_mipi_dphy *cfg = &opts.mipi_dphy;
+
+		phy_mipi_dphy_get_default_config_for_hsclk(link_freq * 2, lanes,
+							   cfg);
+		phy_set_mode(csi_dev->phy, PHY_MODE_MIPI_DPHY);
+		phy_configure(csi_dev->phy, &opts);
+
+		control |= SW_CPHY_EN(0);
+
+	} else if (bus_type == V4L2_MBUS_CSI2_CPHY) {
+		control |= SW_CPHY_EN(1);
+
+		/* TODO: implement CPHY configuration */
+	} else {
+		return -EINVAL;
+	}
+
+	control |= SW_DATATYPE_FS(0x00) | SW_DATATYPE_FE(0x01) |
+		   SW_DATATYPE_LS(0x02) | SW_DATATYPE_LE(0x03);
+
+	rkcif_csi_write(csi_dev, CSI2HOST_N_LANES, lanes - 1);
+	rkcif_csi_write(csi_dev, CSI2HOST_CONTROL, control);
+	rkcif_csi_write(csi_dev, CSI2HOST_CSI2_RESETN, 1);
+
+	phy_power_on(csi_dev->phy);
+
+	return 0;
+}
+
+static void rkcif_csi_stop(struct rkcif_csi_device *csi_dev)
+{
+	phy_power_off(csi_dev->phy);
+
+	rkcif_csi_write(csi_dev, CSI2HOST_CSI2_RESETN, 0);
+	rkcif_csi_write(csi_dev, CSI2HOST_MSK1, ~0);
+	rkcif_csi_write(csi_dev, CSI2HOST_MSK2, ~0);
+}
+
+static const struct media_entity_operations rkcif_csi_media_ops = {
+	.link_validate = v4l2_subdev_link_validate,
+};
+
+static int rkcif_csi_enum_mbus_code(struct v4l2_subdev *sd,
+				    struct v4l2_subdev_state *sd_state,
+				    struct v4l2_subdev_mbus_code_enum *code)
+{
+	struct rkcif_csi_device *csi_dev = to_rkcif_csi(sd);
+
+	if (code->pad == RKCIF_CSI_PAD_SRC) {
+		const struct v4l2_mbus_framefmt *sink_fmt;
+
+		if (code->index)
+			return -EINVAL;
+
+		sink_fmt = v4l2_subdev_state_get_format(sd_state,
+							RKCIF_CSI_PAD_SINK);
+		code->code = sink_fmt->code;
+
+		return 0;
+	} else if (code->pad == RKCIF_CSI_PAD_SINK) {
+		if (code->index > csi_dev->formats_num)
+			return -EINVAL;
+
+		code->code = csi_dev->formats[code->index].code;
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+static int rkcif_csi_set_fmt(struct v4l2_subdev *sd,
+			     struct v4l2_subdev_state *state,
+			     struct v4l2_subdev_format *format)
+{
+	struct rkcif_csi_device *csi_dev = to_rkcif_csi(sd);
+	const struct rkcif_csi_format *fmt;
+	struct v4l2_mbus_framefmt *sink, *src;
+
+	/* the format on the source pad always matches the sink pad */
+	if (format->pad == RKCIF_CSI_PAD_SRC)
+		return v4l2_subdev_get_fmt(sd, state, format);
+
+	sink = v4l2_subdev_state_get_format(state, format->pad, format->stream);
+	if (!sink)
+		return -EINVAL;
+
+	fmt = rkcif_csi_find_format(csi_dev, format->format.code);
+	if (fmt)
+		*sink = format->format;
+	else
+		*sink = default_format;
+
+	/* propagate the format to the source pad */
+	src = v4l2_subdev_state_get_opposite_stream_format(state, format->pad,
+							   format->stream);
+	if (!src)
+		return -EINVAL;
+
+	*src = *sink;
+
+	return 0;
+}
+
+static int rkcif_csi_set_routing(struct v4l2_subdev *sd,
+				 struct v4l2_subdev_state *state,
+				 enum v4l2_subdev_format_whence which,
+				 struct v4l2_subdev_krouting *routing)
+{
+	int ret;
+
+	ret = v4l2_subdev_routing_validate(sd, routing,
+					   V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
+	if (ret)
+		return ret;
+
+	ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing,
+					       &default_format);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int rkcif_csi_enable_streams(struct v4l2_subdev *sd,
+				    struct v4l2_subdev_state *state, u32 pad,
+				    u64 streams_mask)
+{
+	struct rkcif_csi_device *csi_dev = to_rkcif_csi(sd);
+	struct v4l2_subdev *remote_sd;
+	struct media_pad *sink_pad, *remote_pad;
+	struct device *dev = csi_dev->dev;
+	u64 mask;
+	int ret;
+
+	sink_pad = &sd->entity.pads[RKCIF_CSI_PAD_SINK];
+	remote_pad = media_pad_remote_pad_first(sink_pad);
+	remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity);
+
+	mask = v4l2_subdev_state_xlate_streams(state, RKCIF_CSI_PAD_SINK,
+					       RKCIF_CSI_PAD_SRC,
+					       &streams_mask);
+
+	ret = pm_runtime_resume_and_get(dev);
+	if (ret)
+		goto err;
+
+	ret = rkcif_csi_start(csi_dev);
+	if (ret) {
+		dev_err(dev, "failed to enable CSI hardware\n");
+		goto err_pm_runtime_put;
+	}
+
+	ret = v4l2_subdev_enable_streams(remote_sd, remote_pad->index, mask);
+	if (ret)
+		goto err_csi_stop;
+
+	return 0;
+
+err_csi_stop:
+	rkcif_csi_stop(csi_dev);
+err_pm_runtime_put:
+	pm_runtime_put_sync(dev);
+err:
+	return ret;
+}
+
+static int rkcif_csi_disable_streams(struct v4l2_subdev *sd,
+				     struct v4l2_subdev_state *state, u32 pad,
+				     u64 streams_mask)
+{
+	struct rkcif_csi_device *csi_dev = to_rkcif_csi(sd);
+	struct v4l2_subdev *remote_sd;
+	struct media_pad *sink_pad, *remote_pad;
+	struct device *dev = csi_dev->dev;
+	u64 mask;
+	int ret;
+
+	sink_pad = &sd->entity.pads[RKCIF_CSI_PAD_SINK];
+	remote_pad = media_pad_remote_pad_first(sink_pad);
+	remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity);
+
+	mask = v4l2_subdev_state_xlate_streams(state, RKCIF_CSI_PAD_SINK,
+					       RKCIF_CSI_PAD_SRC,
+					       &streams_mask);
+
+	ret = v4l2_subdev_disable_streams(remote_sd, remote_pad->index, mask);
+
+	rkcif_csi_stop(csi_dev);
+
+	pm_runtime_mark_last_busy(dev);
+	pm_runtime_put_autosuspend(dev);
+
+	return ret;
+}
+
+static const struct v4l2_subdev_pad_ops rkcif_csi_pad_ops = {
+	.enum_mbus_code = rkcif_csi_enum_mbus_code,
+	.get_fmt = v4l2_subdev_get_fmt,
+	.set_fmt = rkcif_csi_set_fmt,
+	.set_routing = rkcif_csi_set_routing,
+	.enable_streams = rkcif_csi_enable_streams,
+	.disable_streams = rkcif_csi_disable_streams,
+};
+
+static const struct v4l2_subdev_ops rkcif_csi_ops = {
+	.pad = &rkcif_csi_pad_ops,
+};
+
+static int rkcif_csi_init_state(struct v4l2_subdev *sd,
+				struct v4l2_subdev_state *state)
+{
+	struct v4l2_subdev_route routes[] = {
+		{
+			.sink_pad = RKCIF_CSI_PAD_SINK,
+			.sink_stream = 0,
+			.source_pad = RKCIF_CSI_PAD_SRC,
+			.source_stream = 0,
+			.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
+		},
+	};
+	struct v4l2_subdev_krouting routing = {
+		.len_routes = ARRAY_SIZE(routes),
+		.num_routes = ARRAY_SIZE(routes),
+		.routes = routes,
+	};
+	int ret;
+
+	ret = v4l2_subdev_set_routing_with_fmt(sd, state, &routing,
+					       &default_format);
+
+	return ret;
+}
+
+static const struct v4l2_subdev_internal_ops rkcif_csi_internal_ops = {
+	.init_state = rkcif_csi_init_state,
+};
+
+static int rkcif_csi_notifier_bound(struct v4l2_async_notifier *notifier,
+				    struct v4l2_subdev *sd,
+				    struct v4l2_async_connection *asd)
+{
+	struct rkcif_csi_device *csi_dev =
+		container_of(notifier, struct rkcif_csi_device, notifier);
+	int source_pad;
+
+	source_pad = media_entity_get_fwnode_pad(&sd->entity, sd->fwnode,
+						 MEDIA_PAD_FL_SOURCE);
+	if (source_pad < 0) {
+		dev_err(csi_dev->dev, "failed to find source pad for %s\n",
+			sd->name);
+		return source_pad;
+	}
+
+	csi_dev->source_sd = sd;
+	csi_dev->source_pad = source_pad;
+
+	return media_create_pad_link(&sd->entity, source_pad,
+				     &csi_dev->sd.entity, RKCIF_CSI_PAD_SINK,
+				     MEDIA_LNK_FL_ENABLED);
+}
+
+static const struct v4l2_async_notifier_operations rkcif_csi_notifier_ops = {
+	.bound = rkcif_csi_notifier_bound,
+};
+
+static int rkcif_csi_register_notifier(struct rkcif_csi_device *csi_dev)
+{
+	struct v4l2_async_connection *asd;
+	struct v4l2_async_notifier *ntf = &csi_dev->notifier;
+	struct v4l2_fwnode_endpoint *vep = &csi_dev->vep;
+	struct v4l2_subdev *sd = &csi_dev->sd;
+	struct device *dev = csi_dev->dev;
+	struct fwnode_handle *ep;
+	int ret = 0;
+
+	ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, 0);
+	if (!ep)
+		return dev_err_probe(dev, -ENODEV, "failed to get endpoint\n");
+
+	vep->bus_type = V4L2_MBUS_UNKNOWN;
+	ret = v4l2_fwnode_endpoint_parse(ep, vep);
+	if (ret) {
+		ret = dev_err_probe(dev, ret, "failed to parse endpoint\n");
+		goto out;
+	}
+
+	if (vep->bus_type != V4L2_MBUS_CSI2_DPHY &&
+	    vep->bus_type != V4L2_MBUS_CSI2_CPHY) {
+		ret = dev_err_probe(dev, -EINVAL,
+				    "invalid bus type of endpoint\n");
+		goto out;
+	}
+
+	v4l2_async_subdev_nf_init(ntf, sd);
+	ntf->ops = &rkcif_csi_notifier_ops;
+
+	asd = v4l2_async_nf_add_fwnode_remote(ntf, ep,
+					      struct v4l2_async_connection);
+	if (IS_ERR(asd)) {
+		ret = PTR_ERR(asd);
+		goto err_nf_cleanup;
+	}
+
+	ret = v4l2_async_nf_register(ntf);
+	if (ret) {
+		ret = dev_err_probe(dev, ret, "failed to register notifier\n");
+		goto err_nf_cleanup;
+	}
+
+	goto out;
+
+err_nf_cleanup:
+	v4l2_async_nf_cleanup(ntf);
+out:
+	fwnode_handle_put(ep);
+	return ret;
+}
+
+static int rkcif_csi_register(struct rkcif_csi_device *csi_dev)
+{
+	struct media_pad *pads = csi_dev->pads;
+	struct v4l2_subdev *sd = &csi_dev->sd;
+	int ret;
+
+	ret = rkcif_csi_register_notifier(csi_dev);
+	if (ret)
+		goto err;
+
+	v4l2_subdev_init(sd, &rkcif_csi_ops);
+	sd->dev = csi_dev->dev;
+	sd->entity.ops = &rkcif_csi_media_ops;
+	sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
+	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS;
+	sd->internal_ops = &rkcif_csi_internal_ops;
+	sd->owner = THIS_MODULE;
+	snprintf(sd->name, sizeof(sd->name), "rockchip-mipi-csi %s",
+		 dev_name(csi_dev->dev));
+
+	pads[RKCIF_CSI_PAD_SINK].flags = MEDIA_PAD_FL_SINK |
+					 MEDIA_PAD_FL_MUST_CONNECT;
+	pads[RKCIF_CSI_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE;
+	ret = media_entity_pads_init(&sd->entity, RKCIF_CSI_PAD_MAX, pads);
+	if (ret)
+		goto err_notifier_unregister;
+
+	ret = v4l2_subdev_init_finalize(sd);
+	if (ret)
+		goto err_entity_cleanup;
+
+	ret = v4l2_async_register_subdev(sd);
+	if (ret) {
+		dev_err(sd->dev, "failed to register CSI subdev\n");
+		goto err_subdev_cleanup;
+	}
+
+	return 0;
+
+err_subdev_cleanup:
+	v4l2_subdev_cleanup(sd);
+err_entity_cleanup:
+	media_entity_cleanup(&sd->entity);
+err_notifier_unregister:
+	v4l2_async_nf_unregister(&csi_dev->notifier);
+	v4l2_async_nf_cleanup(&csi_dev->notifier);
+err:
+	return ret;
+}
+
+static void rkcif_csi_unregister(struct rkcif_csi_device *csi_dev)
+{
+	struct v4l2_subdev *sd = &csi_dev->sd;
+
+	v4l2_async_unregister_subdev(sd);
+	v4l2_subdev_cleanup(sd);
+	media_entity_cleanup(&sd->entity);
+	v4l2_async_nf_unregister(&csi_dev->notifier);
+	v4l2_async_nf_cleanup(&csi_dev->notifier);
+}
+
+static const struct of_device_id rkcif_csi_of_match[] = {
+	{
+		.compatible = "rockchip,rk3568-mipi-csi",
+	},
+	{}
+};
+MODULE_DEVICE_TABLE(of, rkcif_csi_of_match);
+
+static int rkcif_csi_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct rkcif_csi_device *csi_dev;
+	int ret;
+
+	csi_dev = devm_kzalloc(dev, sizeof(*csi_dev), GFP_KERNEL);
+	if (!csi_dev)
+		return -ENOMEM;
+	csi_dev->dev = dev;
+	dev_set_drvdata(dev, csi_dev);
+
+	csi_dev->base_addr = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(csi_dev->base_addr))
+		return PTR_ERR(csi_dev->base_addr);
+
+	ret = devm_clk_bulk_get_all(dev, &csi_dev->clks);
+	if (ret != RKCIF_CSI_CLKS_MAX)
+		return dev_err_probe(dev, -ENODEV, "failed to get clocks\n");
+	csi_dev->clks_num = ret;
+
+	csi_dev->phy = devm_phy_get(dev, NULL);
+	if (IS_ERR(csi_dev->phy))
+		return dev_err_probe(dev, PTR_ERR(csi_dev->phy),
+				     "failed to get MIPI CSI PHY\n");
+
+	csi_dev->reset = devm_reset_control_array_get_exclusive(dev);
+	if (IS_ERR(csi_dev->reset))
+		return dev_err_probe(dev, PTR_ERR(csi_dev->reset),
+				     "failed to get reset\n");
+
+	csi_dev->formats = formats;
+	csi_dev->formats_num = ARRAY_SIZE(formats);
+
+	pm_runtime_enable(dev);
+
+	ret = phy_init(csi_dev->phy);
+	if (ret) {
+		ret = dev_err_probe(dev, ret,
+				    "failed to initialize MIPI CSI PHY\n");
+		goto err_pm_runtime_disable;
+	}
+
+	ret = rkcif_csi_register(csi_dev);
+	if (ret)
+		goto err_phy_exit;
+
+	return 0;
+
+err_phy_exit:
+	phy_exit(csi_dev->phy);
+err_pm_runtime_disable:
+	pm_runtime_disable(dev);
+	return ret;
+}
+
+static void rkcif_csi_remove(struct platform_device *pdev)
+{
+	struct rkcif_csi_device *csi_dev = platform_get_drvdata(pdev);
+	struct device *dev = &pdev->dev;
+
+	rkcif_csi_unregister(csi_dev);
+	phy_exit(csi_dev->phy);
+	pm_runtime_disable(dev);
+}
+
+static int rkcif_csi_runtime_suspend(struct device *dev)
+{
+	struct rkcif_csi_device *csi_dev = dev_get_drvdata(dev);
+
+	clk_bulk_disable_unprepare(csi_dev->clks_num, csi_dev->clks);
+
+	return 0;
+}
+
+static int rkcif_csi_runtime_resume(struct device *dev)
+{
+	struct rkcif_csi_device *csi_dev = dev_get_drvdata(dev);
+	int ret;
+
+	reset_control_assert(csi_dev->reset);
+	udelay(5);
+	reset_control_deassert(csi_dev->reset);
+
+	ret = clk_bulk_prepare_enable(csi_dev->clks_num, csi_dev->clks);
+	if (ret) {
+		dev_err(dev, "failed to enable clocks\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static const struct dev_pm_ops rkcif_csi_pm_ops = {
+	.runtime_suspend = rkcif_csi_runtime_suspend,
+	.runtime_resume = rkcif_csi_runtime_resume,
+};
+
+static struct platform_driver rkcif_csi_drv = {
+	.driver = {
+		   .name = "rockchip-mipi-csi",
+		   .of_match_table = rkcif_csi_of_match,
+		   .pm = &rkcif_csi_pm_ops,
+	},
+	.probe = rkcif_csi_probe,
+	.remove = rkcif_csi_remove,
+};
+module_platform_driver(rkcif_csi_drv);
+
+MODULE_DESCRIPTION("Rockchip MIPI CSI-2 Receiver platform driver");
+MODULE_LICENSE("GPL");

-- 
2.39.5



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

* [PATCH v6 08/13] media: rockchip: rkcif: add support for mipi csi-2 capture
  2025-04-30  9:15 [PATCH v6 00/13] media: rockchip: add a driver for the rockchip camera interface Michael Riesch via B4 Relay
                   ` (6 preceding siblings ...)
  2025-04-30  9:15 ` [PATCH v6 07/13] media: rockchip: rkcif: add driver for mipi csi-2 receiver Michael Riesch via B4 Relay
@ 2025-04-30  9:15 ` Michael Riesch via B4 Relay
  2025-05-01  0:48   ` Bryan O'Donoghue
  2025-04-30  9:15 ` [PATCH v6 09/13] arm64: defconfig: enable rockchip camera interface Michael Riesch via B4 Relay
                   ` (4 subsequent siblings)
  12 siblings, 1 reply; 34+ messages in thread
From: Michael Riesch via B4 Relay @ 2025-04-30  9:15 UTC (permalink / raw)
  To: Mehdi Djait, Maxime Chevallier, Théo Lebrun, Gerald Loacker,
	Thomas Petazzoni, Laurent Pinchart, Mauro Carvalho Chehab,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner,
	Kever Yang, Nicolas Dufresne, Sebastian Reichel,
	Collabora Kernel Team, Paul Kocialkowski, Alexander Shiyan,
	Val Packett, Rob Herring, Philipp Zabel, Sakari Ailus
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-rockchip, Michael Riesch, Michael Riesch

From: Michael Riesch <michael.riesch@wolfvision.net>

The RK3568 Video Capture (VICAP) unit features a MIPI CSI-2 capture
interface that can receive video data and write it into system memory
using the ping-pong scheme. Add support for it.

Signed-off-by: Michael Riesch <michael.riesch@wolfvision.net>
Signed-off-by: Michael Riesch <michael.riesch@collabora.com>
---
 .../platform/rockchip/rkcif/rkcif-capture-mipi.c   | 695 +++++++++++++++++++++
 .../platform/rockchip/rkcif/rkcif-capture-mipi.h   |   2 +
 .../media/platform/rockchip/rkcif/rkcif-common.h   |  16 +
 drivers/media/platform/rockchip/rkcif/rkcif-dev.c  |   1 +
 .../platform/rockchip/rkcif/rkcif-interface.c      |   5 +-
 drivers/media/platform/rockchip/rkcif/rkcif-regs.h |  24 +-
 6 files changed, 741 insertions(+), 2 deletions(-)

diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.c b/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.c
index 0c3f7b8cfa18..5e1c624e17c6 100644
--- a/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.c
+++ b/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.c
@@ -6,22 +6,717 @@
  * Copyright (C) 2025 Michael Riesch <michael.riesch@wolfvision.net>
  */
 
+#include <linux/interrupt.h>
+
+#include <media/mipi-csi2.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-fh.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-mc.h>
+#include <media/v4l2-subdev.h>
+
 #include "rkcif-capture-mipi.h"
 #include "rkcif-common.h"
+#include "rkcif-interface.h"
+#include "rkcif-regs.h"
 #include "rkcif-stream.h"
 
+#define RKCIF_MIPI_CTRL0_COMPACT_EN    BIT(6)
+#define RKCIF_MIPI_CTRL0_CROP_EN       BIT(5)
+#define RKCIF_MIPI_CTRL0_TYPE(type)    ((type) << 1)
+#define RKCIF_MIPI_CTRL0_TYPE_RAW8     RKCIF_MIPI_CTRL0_TYPE(0x0)
+#define RKCIF_MIPI_CTRL0_TYPE_RAW10    RKCIF_MIPI_CTRL0_TYPE(0x1)
+#define RKCIF_MIPI_CTRL0_TYPE_RAW12    RKCIF_MIPI_CTRL0_TYPE(0x2)
+#define RKCIF_MIPI_CTRL0_TYPE_RGB888   RKCIF_MIPI_CTRL0_TYPE(0x3)
+#define RKCIF_MIPI_CTRL0_TYPE_YUV422SP RKCIF_MIPI_CTRL0_TYPE(0x4)
+#define RKCIF_MIPI_CTRL0_TYPE_YUV420SP RKCIF_MIPI_CTRL0_TYPE(0x5)
+#define RKCIF_MIPI_CTRL0_TYPE_YUV400   RKCIF_MIPI_CTRL0_TYPE(0x6)
+#define RKCIF_MIPI_CTRL0_CAP_EN	       BIT(0)
+
+#define RKCIF_MIPI_INT_FRAME0_END(id)  BIT(8 + (id) * 2 + 0)
+#define RKCIF_MIPI_INT_FRAME1_END(id)  BIT(8 + (id) * 2 + 1)
+
+static const struct rkcif_output_fmt mipi_out_fmts[] = {
+	/* YUV formats */
+	{
+		.fourcc = V4L2_PIX_FMT_YUYV,
+		.mbus_code = MEDIA_BUS_FMT_YUYV8_1X16,
+		.depth = 16,
+		.cplanes = 1,
+		.mipi = {
+			.dt = MIPI_CSI2_DT_YUV422_8B,
+			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW8,
+		},
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_UYVY,
+		.mbus_code = MEDIA_BUS_FMT_UYVY8_1X16,
+		.depth = 16,
+		.cplanes = 1,
+		.mipi = {
+			.dt = MIPI_CSI2_DT_YUV422_8B,
+			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW8,
+		},
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_YVYU,
+		.mbus_code = MEDIA_BUS_FMT_YVYU8_1X16,
+		.depth = 16,
+		.cplanes = 1,
+		.mipi = {
+			.dt = MIPI_CSI2_DT_YUV422_8B,
+			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW8,
+		},
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_VYUY,
+		.mbus_code = MEDIA_BUS_FMT_VYUY8_1X16,
+		.depth = 16,
+		.cplanes = 1,
+		.mipi = {
+			.dt = MIPI_CSI2_DT_YUV422_8B,
+			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW8,
+		},
+	},
+	/* RGB formats */
+	{
+		.fourcc = V4L2_PIX_FMT_RGB24,
+		.mbus_code = MEDIA_BUS_FMT_RGB888_1X24,
+		.depth = 24,
+		.cplanes = 1,
+		.mipi = {
+			.dt = MIPI_CSI2_DT_RGB888,
+			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RGB888,
+		},
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_BGR24,
+		.mbus_code = MEDIA_BUS_FMT_BGR888_1X24,
+		.depth = 24,
+		.cplanes = 1,
+		.mipi = {
+			.dt = MIPI_CSI2_DT_RGB888,
+			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RGB888,
+		},
+	},
+	/* Bayer formats */
+	{
+		.fourcc = V4L2_PIX_FMT_SBGGR8,
+		.mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8,
+		.depth = 8,
+		.cplanes = 1,
+		.mipi = {
+			.dt = MIPI_CSI2_DT_RAW8,
+			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW8,
+		},
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SGBRG8,
+		.mbus_code = MEDIA_BUS_FMT_SGBRG8_1X8,
+		.depth = 8,
+		.cplanes = 1,
+		.mipi = {
+			.dt = MIPI_CSI2_DT_RAW8,
+			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW8,
+		},
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SGRBG8,
+		.mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8,
+		.depth = 8,
+		.cplanes = 1,
+		.mipi = {
+			.dt = MIPI_CSI2_DT_RAW8,
+			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW8,
+		},
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SRGGB8,
+		.mbus_code = MEDIA_BUS_FMT_SRGGB8_1X8,
+		.depth = 8,
+		.cplanes = 1,
+		.mipi = {
+			.dt = MIPI_CSI2_DT_RAW8,
+			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW8,
+		},
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SBGGR10,
+		.mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10,
+		.depth = 10,
+		.cplanes = 1,
+		.mipi = {
+			.dt = MIPI_CSI2_DT_RAW10,
+			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW10,
+		},
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SBGGR10P,
+		.mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10,
+		.depth = 10,
+		.cplanes = 1,
+		.mipi = {
+			.dt = MIPI_CSI2_DT_RAW10,
+			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW10 | RKCIF_MIPI_CTRL0_COMPACT_EN,
+		},
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SGBRG10,
+		.mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10,
+		.depth = 10,
+		.cplanes = 1,
+		.mipi = {
+			.dt = MIPI_CSI2_DT_RAW10,
+			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW10,
+		},
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SGBRG10P,
+		.mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10,
+		.depth = 10,
+		.cplanes = 1,
+		.mipi = {
+			.dt = MIPI_CSI2_DT_RAW10,
+			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW10 | RKCIF_MIPI_CTRL0_COMPACT_EN,
+		},
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SGRBG10,
+		.mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10,
+		.depth = 10,
+		.cplanes = 1,
+		.mipi = {
+			.dt = MIPI_CSI2_DT_RAW10,
+			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW10,
+		},
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SGRBG10P,
+		.mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10,
+		.depth = 10,
+		.cplanes = 1,
+		.mipi = {
+			.dt = MIPI_CSI2_DT_RAW10,
+			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW10 | RKCIF_MIPI_CTRL0_COMPACT_EN,
+		},
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SRGGB10,
+		.mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10,
+		.depth = 10,
+		.cplanes = 1,
+		.mipi = {
+			.dt = MIPI_CSI2_DT_RAW10,
+			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW10,
+		},
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SRGGB10P,
+		.mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10,
+		.depth = 10,
+		.cplanes = 1,
+		.mipi = {
+			.dt = MIPI_CSI2_DT_RAW10,
+			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW10 | RKCIF_MIPI_CTRL0_COMPACT_EN,
+		},
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SBGGR12,
+		.mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12,
+		.depth = 12,
+		.cplanes = 1,
+		.mipi = {
+			.dt = MIPI_CSI2_DT_RAW12,
+			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW12,
+		},
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SBGGR12P,
+		.mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12,
+		.depth = 12,
+		.cplanes = 1,
+		.mipi = {
+			.dt = MIPI_CSI2_DT_RAW12,
+			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW12 | RKCIF_MIPI_CTRL0_COMPACT_EN,
+		},
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SGBRG12,
+		.mbus_code = MEDIA_BUS_FMT_SGBRG12_1X12,
+		.depth = 12,
+		.cplanes = 1,
+		.mipi = {
+			.dt = MIPI_CSI2_DT_RAW12,
+			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW12,
+		},
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SGBRG12P,
+		.mbus_code = MEDIA_BUS_FMT_SGBRG12_1X12,
+		.depth = 12,
+		.cplanes = 1,
+		.mipi = {
+			.dt = MIPI_CSI2_DT_RAW12,
+			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW12 | RKCIF_MIPI_CTRL0_COMPACT_EN,
+		},
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SGRBG12,
+		.mbus_code = MEDIA_BUS_FMT_SGRBG12_1X12,
+		.depth = 12,
+		.cplanes = 1,
+		.mipi = {
+			.dt = MIPI_CSI2_DT_RAW12,
+			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW12,
+		},
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SGRBG12P,
+		.mbus_code = MEDIA_BUS_FMT_SGRBG12_1X12,
+		.depth = 12,
+		.cplanes = 1,
+		.mipi = {
+			.dt = MIPI_CSI2_DT_RAW12,
+			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW12 | RKCIF_MIPI_CTRL0_COMPACT_EN,
+		},
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SRGGB12,
+		.mbus_code = MEDIA_BUS_FMT_SRGGB12_1X12,
+		.depth = 12,
+		.cplanes = 1,
+		.mipi = {
+			.dt = MIPI_CSI2_DT_RAW12,
+			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW12,
+		},
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SRGGB12P,
+		.mbus_code = MEDIA_BUS_FMT_SRGGB12_1X12,
+		.depth = 12,
+		.cplanes = 1,
+		.mipi = {
+			.dt = MIPI_CSI2_DT_RAW12,
+			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW12 | RKCIF_MIPI_CTRL0_COMPACT_EN,
+		},
+	},
+};
+
+static const struct rkcif_input_fmt mipi_in_fmts[] = {
+	/* YUV formats */
+	{
+		.mbus_code = MEDIA_BUS_FMT_YUYV8_1X16,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_UYVY8_1X16,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_YVYU8_1X16,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_VYUY8_1X16,
+	},
+	/* RGB formats */
+	{
+		.mbus_code = MEDIA_BUS_FMT_RGB888_1X24,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_BGR888_1X24,
+	},
+	/* Bayer formats */
+	{
+		.mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_SGBRG8_1X8,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_SRGGB8_1X8,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_SGBRG12_1X12,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_SGRBG12_1X12,
+	},
+	{
+		.mbus_code = MEDIA_BUS_FMT_SRGGB12_1X12,
+	},
+};
+
+const struct rkcif_mipi_match_data rkcif_rk3568_vicap_mipi_match_data = {
+	.mipi_num = 1,
+	.regs = {
+		[RKCIF_MIPI_CTRL] = 0x20,
+		[RKCIF_MIPI_INTEN] = 0xa4,
+		[RKCIF_MIPI_INTSTAT] = 0xa8,
+	},
+	.regs_id = {
+		[RKCIF_ID0] = {
+			[RKCIF_MIPI_CTRL0] = 0x00,
+			[RKCIF_MIPI_CTRL1] = 0x04,
+			[RKCIF_MIPI_FRAME0_ADDR_Y] = 0x24,
+			[RKCIF_MIPI_FRAME0_ADDR_UV] = 0x2c,
+			[RKCIF_MIPI_FRAME0_VLW_Y] = 0x34,
+			[RKCIF_MIPI_FRAME0_VLW_UV] = 0x3c,
+			[RKCIF_MIPI_FRAME1_ADDR_Y] = 0x28,
+			[RKCIF_MIPI_FRAME1_ADDR_UV] = 0x30,
+			[RKCIF_MIPI_FRAME1_VLW_Y] = 0x38,
+			[RKCIF_MIPI_FRAME1_VLW_UV] = 0x40,
+			[RKCIF_MIPI_CROP_START] = 0xbc,
+		},
+		[RKCIF_ID1] = {
+			[RKCIF_MIPI_CTRL0] = 0x08,
+			[RKCIF_MIPI_CTRL1] = 0x0c,
+			[RKCIF_MIPI_FRAME0_ADDR_Y] = 0x44,
+			[RKCIF_MIPI_FRAME0_ADDR_UV] = 0x4c,
+			[RKCIF_MIPI_FRAME0_VLW_Y] = 0x54,
+			[RKCIF_MIPI_FRAME0_VLW_UV] = 0x5c,
+			[RKCIF_MIPI_FRAME1_ADDR_Y] = 0x48,
+			[RKCIF_MIPI_FRAME1_ADDR_UV] = 0x50,
+			[RKCIF_MIPI_FRAME1_VLW_Y] = 0x58,
+			[RKCIF_MIPI_FRAME1_VLW_UV] = 0x60,
+			[RKCIF_MIPI_CROP_START] = 0xc0,
+		},
+		[RKCIF_ID2] = {
+			[RKCIF_MIPI_CTRL0] = 0x10,
+			[RKCIF_MIPI_CTRL1] = 0x14,
+			[RKCIF_MIPI_FRAME0_ADDR_Y] = 0x64,
+			[RKCIF_MIPI_FRAME0_ADDR_UV] = 0x6c,
+			[RKCIF_MIPI_FRAME0_VLW_Y] = 0x74,
+			[RKCIF_MIPI_FRAME0_VLW_UV] = 0x7c,
+			[RKCIF_MIPI_FRAME1_ADDR_Y] = 0x68,
+			[RKCIF_MIPI_FRAME1_ADDR_UV] = 0x70,
+			[RKCIF_MIPI_FRAME1_VLW_Y] = 0x78,
+			[RKCIF_MIPI_FRAME1_VLW_UV] = 0x80,
+			[RKCIF_MIPI_CROP_START] = 0xc4,
+		},
+		[RKCIF_ID3] = {
+			[RKCIF_MIPI_CTRL0] = 0x18,
+			[RKCIF_MIPI_CTRL1] = 0x1c,
+			[RKCIF_MIPI_FRAME0_ADDR_Y] = 0x84,
+			[RKCIF_MIPI_FRAME0_ADDR_UV] = 0x8c,
+			[RKCIF_MIPI_FRAME0_VLW_Y] = 0x94,
+			[RKCIF_MIPI_FRAME0_VLW_UV] = 0x9c,
+			[RKCIF_MIPI_FRAME1_ADDR_Y] = 0x88,
+			[RKCIF_MIPI_FRAME1_ADDR_UV] = 0x90,
+			[RKCIF_MIPI_FRAME1_VLW_Y] = 0x98,
+			[RKCIF_MIPI_FRAME1_VLW_UV] = 0xa0,
+			[RKCIF_MIPI_CROP_START] = 0xc8,
+		},
+	},
+	.blocks = {
+		{
+			.offset = 0x80,
+		},
+	},
+};
+
+static inline unsigned int rkcif_mipi_get_reg(struct rkcif_interface *interface,
+					      unsigned int index)
+{
+	struct rkcif_device *rkcif = interface->rkcif;
+	unsigned int block, offset, reg;
+
+	block = interface->index - RKCIF_MIPI_BASE;
+
+	if (WARN_ON_ONCE(block > RKCIF_MIPI_MAX - RKCIF_MIPI_BASE) ||
+	    WARN_ON_ONCE(index > RKCIF_MIPI_REGISTER_MAX))
+		return RKCIF_REGISTER_NOTSUPPORTED;
+
+	offset = rkcif->match_data->mipi->blocks[block].offset;
+	reg = rkcif->match_data->mipi->regs[index];
+	if (reg == RKCIF_REGISTER_NOTSUPPORTED)
+		return reg;
+
+	return offset + reg;
+}
+
+static inline unsigned int rkcif_mipi_id_get_reg(struct rkcif_stream *stream,
+						 unsigned int index)
+{
+	struct rkcif_device *rkcif = stream->rkcif;
+	unsigned int block, id, offset, reg;
+
+	block = stream->interface->index - RKCIF_MIPI_BASE;
+	id = stream->id;
+
+	if (WARN_ON_ONCE(block > RKCIF_MIPI_MAX - RKCIF_MIPI_BASE) ||
+	    WARN_ON_ONCE(id > RKCIF_ID_MAX) ||
+	    WARN_ON_ONCE(index > RKCIF_MIPI_ID_REGISTER_MAX))
+		return RKCIF_REGISTER_NOTSUPPORTED;
+
+	offset = rkcif->match_data->mipi->blocks[block].offset;
+	reg = rkcif->match_data->mipi->regs_id[id][index];
+	if (reg == RKCIF_REGISTER_NOTSUPPORTED)
+		return reg;
+
+	return offset + reg;
+}
+
+static inline __maybe_unused void
+rkcif_mipi_write(struct rkcif_interface *interface, unsigned int index, u32 val)
+{
+	unsigned int addr = rkcif_mipi_get_reg(interface, index);
+
+	if (addr == RKCIF_REGISTER_NOTSUPPORTED)
+		return;
+
+	writel(val, interface->rkcif->base_addr + addr);
+}
+
+static inline __maybe_unused void
+rkcif_mipi_stream_write(struct rkcif_stream *stream, unsigned int index,
+			u32 val)
+{
+	unsigned int addr = rkcif_mipi_id_get_reg(stream, index);
+
+	if (addr == RKCIF_REGISTER_NOTSUPPORTED)
+		return;
+
+	writel(val, stream->rkcif->base_addr + addr);
+}
+
+static inline __maybe_unused u32
+rkcif_mipi_read(struct rkcif_interface *interface, unsigned int index)
+{
+	unsigned int addr = rkcif_mipi_get_reg(interface, index);
+
+	if (addr == RKCIF_REGISTER_NOTSUPPORTED)
+		return 0;
+
+	return readl(interface->rkcif->base_addr + addr);
+}
+
+static inline __maybe_unused u32
+rkcif_mipi_stream_read(struct rkcif_stream *stream, unsigned int index)
+{
+	unsigned int addr = rkcif_mipi_id_get_reg(stream, index);
+
+	if (addr == RKCIF_REGISTER_NOTSUPPORTED)
+		return 0;
+
+	return readl(stream->rkcif->base_addr + addr);
+}
+
+static void rkcif_mipi_queue_buffer(struct rkcif_stream *stream,
+				    unsigned int index)
+{
+	struct rkcif_buffer *buffer = stream->buffers[index];
+	u32 frm_addr_y, frm_addr_uv;
+
+	frm_addr_y = index ? RKCIF_MIPI_FRAME1_ADDR_Y :
+			     RKCIF_MIPI_FRAME0_ADDR_Y;
+	frm_addr_uv = index ? RKCIF_MIPI_FRAME1_ADDR_UV :
+			      RKCIF_MIPI_FRAME0_ADDR_UV;
+
+	rkcif_mipi_stream_write(stream, frm_addr_y,
+				buffer->buff_addr[RKCIF_PLANE_Y]);
+	rkcif_mipi_stream_write(stream, frm_addr_uv,
+				buffer->buff_addr[RKCIF_PLANE_UV]);
+}
+
+static int rkcif_mipi_start_streaming(struct rkcif_stream *stream)
+{
+	struct rkcif_interface *interface = stream->interface;
+	const struct rkcif_output_fmt *active_out_fmt;
+	struct v4l2_subdev_state *state;
+	u32 ctrl0 = 0, ctrl1 = 0, int_temp = 0, int_mask = 0, vlw = 0;
+	u16 height, width;
+	int ret = -EINVAL;
+
+	state = v4l2_subdev_lock_and_get_active_state(&interface->sd);
+
+	active_out_fmt = rkcif_stream_find_output_fmt(stream, false,
+						      stream->pix.pixelformat);
+	if (!active_out_fmt)
+		goto out;
+
+	height = stream->pix.height;
+	width = stream->pix.width;
+	/* TODO there may be different factors and/or alignment constraints */
+	vlw = ALIGN(width * 2, 8);
+
+	ctrl0 |= active_out_fmt->mipi.dt << 10;
+	ctrl0 |= active_out_fmt->mipi.ctrl0_val;
+	ctrl0 |= RKCIF_MIPI_CTRL0_CROP_EN;
+	ctrl0 |= RKCIF_MIPI_CTRL0_CAP_EN;
+
+	ctrl1 = RKCIF_XY_COORD(width, height);
+
+	int_mask |= RKCIF_MIPI_INT_FRAME0_END(stream->id);
+	int_mask |= RKCIF_MIPI_INT_FRAME1_END(stream->id);
+
+	int_temp = rkcif_mipi_read(interface, RKCIF_MIPI_INTEN);
+	int_temp |= int_mask;
+	rkcif_mipi_write(interface, RKCIF_MIPI_INTEN, int_temp);
+
+	int_temp = rkcif_mipi_read(interface, RKCIF_MIPI_INTSTAT);
+	int_temp &= ~int_mask;
+	rkcif_mipi_write(interface, RKCIF_MIPI_INTSTAT, int_temp);
+
+	rkcif_mipi_stream_write(stream, RKCIF_MIPI_FRAME0_VLW_Y, vlw);
+	rkcif_mipi_stream_write(stream, RKCIF_MIPI_FRAME1_VLW_Y, vlw);
+	rkcif_mipi_stream_write(stream, RKCIF_MIPI_FRAME0_VLW_UV, vlw);
+	rkcif_mipi_stream_write(stream, RKCIF_MIPI_FRAME1_VLW_UV, vlw);
+	rkcif_mipi_stream_write(stream, RKCIF_MIPI_CROP_START, 0x0);
+	rkcif_mipi_stream_write(stream, RKCIF_MIPI_CTRL1, ctrl1);
+	rkcif_mipi_stream_write(stream, RKCIF_MIPI_CTRL0, ctrl0);
+
+	ret = 0;
+
+out:
+	v4l2_subdev_unlock_state(state);
+	return ret;
+}
+
+static void rkcif_mipi_stop_streaming(struct rkcif_stream *stream)
+{
+	struct rkcif_interface *interface = stream->interface;
+	struct v4l2_subdev_state *state;
+	u32 int_temp = 0, int_mask = 0;
+
+	state = v4l2_subdev_lock_and_get_active_state(&interface->sd);
+
+	rkcif_mipi_stream_write(stream, RKCIF_MIPI_CTRL0, 0);
+
+	int_mask |= RKCIF_MIPI_INT_FRAME0_END(stream->id);
+	int_mask |= RKCIF_MIPI_INT_FRAME1_END(stream->id);
+
+	int_temp = rkcif_mipi_read(interface, RKCIF_MIPI_INTEN);
+	int_temp &= ~int_mask;
+	rkcif_mipi_write(interface, RKCIF_MIPI_INTEN, int_temp);
+
+	int_temp = rkcif_mipi_read(interface, RKCIF_MIPI_INTSTAT);
+	int_temp &= ~int_mask;
+	rkcif_mipi_write(interface, RKCIF_MIPI_INTSTAT, int_temp);
+
+	stream->stopping = false;
+
+	v4l2_subdev_unlock_state(state);
+}
+
+static void rkcif_mipi_set_crop(struct rkcif_stream *stream, u16 left, u16 top)
+{
+	u32 val;
+
+	val = RKCIF_XY_COORD(left, top);
+	rkcif_mipi_stream_write(stream, RKCIF_MIPI_CROP_START, val);
+}
+
 irqreturn_t rkcif_mipi_isr(int irq, void *ctx)
 {
+	struct device *dev = ctx;
+	struct rkcif_device *rkcif = dev_get_drvdata(dev);
 	irqreturn_t ret = IRQ_NONE;
+	u32 intstat;
+
+	for (int i = 0; i < rkcif->match_data->mipi->mipi_num; i++) {
+		enum rkcif_interface_index index = RKCIF_MIPI_BASE + i;
+		struct rkcif_interface *interface = &rkcif->interfaces[index];
+
+		intstat = rkcif_mipi_read(interface, RKCIF_MIPI_INTSTAT);
+		rkcif_mipi_write(interface, RKCIF_MIPI_INTSTAT, intstat);
+
+		for (int j = 0; j < interface->streams_num; j++) {
+			struct rkcif_stream *stream = &interface->streams[j];
+
+			if (intstat & RKCIF_MIPI_INT_FRAME0_END(stream->id) ||
+			    intstat & RKCIF_MIPI_INT_FRAME1_END(stream->id)) {
+				rkcif_stream_pingpong(stream);
+				ret = IRQ_HANDLED;
+			}
+		}
+	}
 
 	return ret;
 }
 
 int rkcif_mipi_register(struct rkcif_device *rkcif)
 {
+	int ret, i;
+
+	if (!rkcif->match_data->mipi)
+		return 0;
+
+	for (i = 0; i < rkcif->match_data->mipi->mipi_num; i++) {
+		enum rkcif_interface_index index = RKCIF_MIPI_BASE + i;
+		struct rkcif_interface *interface = &rkcif->interfaces[index];
+
+		interface->index = index;
+		interface->type = RKCIF_IF_MIPI;
+		interface->in_fmts = mipi_in_fmts;
+		interface->in_fmts_num = ARRAY_SIZE(mipi_in_fmts);
+		interface->set_crop = rkcif_mipi_set_crop;
+		interface->streams_num = 0;
+		ret = rkcif_interface_register(rkcif, interface);
+		if (ret)
+			continue;
+
+		for (int j = 0; j < RKCIF_ID_MAX; j++) {
+			struct rkcif_stream *stream = &interface->streams[j];
+
+			stream->id = j;
+			stream->interface = interface;
+			stream->out_fmts = mipi_out_fmts;
+			stream->out_fmts_num = ARRAY_SIZE(mipi_out_fmts);
+			stream->queue_buffer = rkcif_mipi_queue_buffer;
+			stream->start_streaming = rkcif_mipi_start_streaming;
+			stream->stop_streaming = rkcif_mipi_stop_streaming;
+			ret = rkcif_stream_register(rkcif, stream);
+			if (ret)
+				goto err;
+			interface->streams_num++;
+		}
+	}
+
 	return 0;
+
+err:
+	for (; i >= 0; i--) {
+		enum rkcif_interface_index index = RKCIF_MIPI_BASE + i;
+		struct rkcif_interface *interface = &rkcif->interfaces[index];
+
+		for (int j = 0; j < interface->streams_num; j++)
+			rkcif_stream_unregister(&interface->streams[j]);
+
+		rkcif_interface_unregister(interface);
+	}
+	return ret;
 }
 
 void rkcif_mipi_unregister(struct rkcif_device *rkcif)
 {
+	if (!rkcif->match_data->mipi)
+		return;
+
+	for (int i = 0; i < rkcif->match_data->mipi->mipi_num; i++) {
+		enum rkcif_interface_index index = RKCIF_MIPI_BASE + i;
+		struct rkcif_interface *interface = &rkcif->interfaces[index];
+
+		for (int j = 0; j < interface->streams_num; j++)
+			rkcif_stream_unregister(&interface->streams[j]);
+
+		rkcif_interface_unregister(interface);
+	}
 }
diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.h b/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.h
index ee1a50a59505..1248af70bdab 100644
--- a/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.h
+++ b/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.h
@@ -11,6 +11,8 @@
 
 #include "rkcif-common.h"
 
+extern const struct rkcif_mipi_match_data rkcif_rk3568_vicap_mipi_match_data;
+
 int rkcif_mipi_register(struct rkcif_device *rkcif);
 
 void rkcif_mipi_unregister(struct rkcif_device *rkcif);
diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-common.h b/drivers/media/platform/rockchip/rkcif/rkcif-common.h
index 62fb3580eec5..d0d929648f21 100644
--- a/drivers/media/platform/rockchip/rkcif/rkcif-common.h
+++ b/drivers/media/platform/rockchip/rkcif/rkcif-common.h
@@ -93,9 +93,14 @@ struct rkcif_output_fmt {
 	u32 fourcc;
 	u32 mbus_code;
 	u8 cplanes;
+	u8 depth;
 
 	union {
 		u32 dvp_fmt_val;
+		struct {
+			u8 dt;
+			u32 ctrl0_val;
+		} mipi;
 	};
 };
 
@@ -183,6 +188,16 @@ struct rkcif_interface {
 	void (*set_crop)(struct rkcif_stream *stream, u16 left, u16 top);
 };
 
+struct rkcif_mipi_match_data {
+	unsigned int mipi_num;
+	unsigned int regs[RKCIF_MIPI_REGISTER_MAX];
+	unsigned int regs_id[RKCIF_ID_MAX][RKCIF_MIPI_ID_REGISTER_MAX];
+
+	struct {
+		unsigned int offset;
+	} blocks[RKCIF_MIPI_MAX - RKCIF_MIPI_BASE];
+};
+
 struct rkcif_dvp_match_data {
 	const struct rkcif_input_fmt *in_fmts;
 	unsigned int in_fmts_num;
@@ -198,6 +213,7 @@ struct rkcif_match_data {
 	const char *const *clks;
 	unsigned int clks_num;
 	const struct rkcif_dvp_match_data *dvp;
+	const struct rkcif_mipi_match_data *mipi;
 };
 
 struct rkcif_device {
diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-dev.c b/drivers/media/platform/rockchip/rkcif/rkcif-dev.c
index 2dcd35771fc9..1d8815156c46 100644
--- a/drivers/media/platform/rockchip/rkcif/rkcif-dev.c
+++ b/drivers/media/platform/rockchip/rkcif/rkcif-dev.c
@@ -49,6 +49,7 @@ static const struct rkcif_match_data rk3568_vicap_match_data = {
 	.clks = rk3568_vicap_clks,
 	.clks_num = ARRAY_SIZE(rk3568_vicap_clks),
 	.dvp = &rkcif_rk3568_vicap_dvp_match_data,
+	.mipi = &rkcif_rk3568_vicap_mipi_match_data,
 };
 
 static const struct of_device_id rkcif_plat_of_match[] = {
diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-interface.c b/drivers/media/platform/rockchip/rkcif/rkcif-interface.c
index 0ec524586594..f598a62f9fbd 100644
--- a/drivers/media/platform/rockchip/rkcif/rkcif-interface.c
+++ b/drivers/media/platform/rockchip/rkcif/rkcif-interface.c
@@ -188,7 +188,10 @@ static int rkcif_interface_enable_streams(struct v4l2_subdev *sd,
 		stream = &interface->streams[RKCIF_ID0];
 		rkcif_interface_apply_crop(stream, state);
 	} else {
-		/* TODO implement for MIPI */
+		for_each_active_route(&state->routing, route) {
+			stream = &interface->streams[route->sink_stream];
+			rkcif_interface_apply_crop(stream, state);
+		}
 	}
 
 	mask = v4l2_subdev_state_xlate_streams(state, RKCIF_IF_PAD_SINK,
diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-regs.h b/drivers/media/platform/rockchip/rkcif/rkcif-regs.h
index 07fd64174e80..3d1f0c45c638 100644
--- a/drivers/media/platform/rockchip/rkcif/rkcif-regs.h
+++ b/drivers/media/platform/rockchip/rkcif/rkcif-regs.h
@@ -30,7 +30,29 @@ enum rkcif_dvp_register_index {
 	RKCIF_DVP_REGISTER_MAX
 };
 
-#define RKCIF_REGISTER_NOTSUPPORTED           0x420000
+enum rkcif_mipi_register_index {
+	RKCIF_MIPI_CTRL,
+	RKCIF_MIPI_INTEN,
+	RKCIF_MIPI_INTSTAT,
+	RKCIF_MIPI_REGISTER_MAX
+};
+
+enum rkcif_mipi_id_register_index {
+	RKCIF_MIPI_CTRL0,
+	RKCIF_MIPI_CTRL1,
+	RKCIF_MIPI_FRAME0_ADDR_Y,
+	RKCIF_MIPI_FRAME0_ADDR_UV,
+	RKCIF_MIPI_FRAME0_VLW_Y,
+	RKCIF_MIPI_FRAME0_VLW_UV,
+	RKCIF_MIPI_FRAME1_ADDR_Y,
+	RKCIF_MIPI_FRAME1_ADDR_UV,
+	RKCIF_MIPI_FRAME1_VLW_Y,
+	RKCIF_MIPI_FRAME1_VLW_UV,
+	RKCIF_MIPI_CROP_START,
+	RKCIF_MIPI_ID_REGISTER_MAX
+};
+
+#define RKCIF_REGISTER_NOTSUPPORTED	       0x420000
 
 #define RKCIF_FETCH_Y(VAL)		       ((VAL) & 0x1fff)
 

-- 
2.39.5



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

* [PATCH v6 09/13] arm64: defconfig: enable rockchip camera interface
  2025-04-30  9:15 [PATCH v6 00/13] media: rockchip: add a driver for the rockchip camera interface Michael Riesch via B4 Relay
                   ` (7 preceding siblings ...)
  2025-04-30  9:15 ` [PATCH v6 08/13] media: rockchip: rkcif: add support for mipi csi-2 capture Michael Riesch via B4 Relay
@ 2025-04-30  9:15 ` Michael Riesch via B4 Relay
  2025-05-01  0:47   ` Bryan O'Donoghue
  2025-04-30  9:15 ` [PATCH v6 10/13] arm64: dts: rockchip: add the vip node to px30 Michael Riesch via B4 Relay
                   ` (3 subsequent siblings)
  12 siblings, 1 reply; 34+ messages in thread
From: Michael Riesch via B4 Relay @ 2025-04-30  9:15 UTC (permalink / raw)
  To: Mehdi Djait, Maxime Chevallier, Théo Lebrun, Gerald Loacker,
	Thomas Petazzoni, Laurent Pinchart, Mauro Carvalho Chehab,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner,
	Kever Yang, Nicolas Dufresne, Sebastian Reichel,
	Collabora Kernel Team, Paul Kocialkowski, Alexander Shiyan,
	Val Packett, Rob Herring, Philipp Zabel, Sakari Ailus
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-rockchip, Michael Riesch, Michael Riesch

From: Michael Riesch <michael.riesch@collabora.com>

The Rockchip Camera Interface (CIF) is featured in many Rockchip SoCs
in different variations. Enable the driver for it in the default
configuration.

Signed-off-by: Michael Riesch <michael.riesch@collabora.com>
---
 arch/arm64/configs/defconfig | 1 +
 1 file changed, 1 insertion(+)

diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig
index 5bb8f09422a2..110ff52195a6 100644
--- a/arch/arm64/configs/defconfig
+++ b/arch/arm64/configs/defconfig
@@ -858,6 +858,7 @@ CONFIG_VIDEO_RENESAS_FCP=m
 CONFIG_VIDEO_RENESAS_FDP1=m
 CONFIG_VIDEO_RENESAS_VSP1=m
 CONFIG_VIDEO_RCAR_DRIF=m
+CONFIG_VIDEO_ROCKCHIP_CIF=m
 CONFIG_VIDEO_SAMSUNG_EXYNOS_GSC=m
 CONFIG_VIDEO_SAMSUNG_S5P_JPEG=m
 CONFIG_VIDEO_SAMSUNG_S5P_MFC=m

-- 
2.39.5



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

* [PATCH v6 10/13] arm64: dts: rockchip: add the vip node to px30
  2025-04-30  9:15 [PATCH v6 00/13] media: rockchip: add a driver for the rockchip camera interface Michael Riesch via B4 Relay
                   ` (8 preceding siblings ...)
  2025-04-30  9:15 ` [PATCH v6 09/13] arm64: defconfig: enable rockchip camera interface Michael Riesch via B4 Relay
@ 2025-04-30  9:15 ` Michael Riesch via B4 Relay
  2025-04-30  9:16 ` [PATCH v6 11/13] arm64: dts: rockchip: add vicap node to rk356x Michael Riesch via B4 Relay
                   ` (2 subsequent siblings)
  12 siblings, 0 replies; 34+ messages in thread
From: Michael Riesch via B4 Relay @ 2025-04-30  9:15 UTC (permalink / raw)
  To: Mehdi Djait, Maxime Chevallier, Théo Lebrun, Gerald Loacker,
	Thomas Petazzoni, Laurent Pinchart, Mauro Carvalho Chehab,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner,
	Kever Yang, Nicolas Dufresne, Sebastian Reichel,
	Collabora Kernel Team, Paul Kocialkowski, Alexander Shiyan,
	Val Packett, Rob Herring, Philipp Zabel, Sakari Ailus
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-rockchip, Michael Riesch, Michael Riesch, Mehdi Djait

From: Mehdi Djait <mehdi.djait@bootlin.com>

Add the device tree node for the PX30 Video Input Processor (VIP).

Signed-off-by: Mehdi Djait <mehdi.djait@bootlin.com>
[added cosmetic changes]
Signed-off-by: Michael Riesch <michael.riesch@wolfvision.net>
Signed-off-by: Michael Riesch <michael.riesch@collabora.com>
---
 arch/arm64/boot/dts/rockchip/px30.dtsi | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/arch/arm64/boot/dts/rockchip/px30.dtsi b/arch/arm64/boot/dts/rockchip/px30.dtsi
index 9137dd76e72c..c2acf5952d08 100644
--- a/arch/arm64/boot/dts/rockchip/px30.dtsi
+++ b/arch/arm64/boot/dts/rockchip/px30.dtsi
@@ -1282,6 +1282,18 @@ isp_mmu: iommu@ff4a8000 {
 		#iommu-cells = <0>;
 	};
 
+	cif: video-capture@ff490000 {
+		compatible = "rockchip,px30-vip";
+		reg = <0x0 0xff490000 0x0 0x200>;
+		interrupts = <GIC_SPI 69 IRQ_TYPE_LEVEL_HIGH>;
+		clocks = <&cru ACLK_CIF>, <&cru HCLK_CIF>, <&cru PCLK_CIF>;
+		clock-names = "aclk", "hclk", "pclk";
+		power-domains = <&power PX30_PD_VI>;
+		resets = <&cru SRST_CIF_A>, <&cru SRST_CIF_H>, <&cru SRST_CIF_PCLKIN>;
+		reset-names = "axi", "ahb", "pclkin";
+		status = "disabled";
+	};
+
 	qos_gmac: qos@ff518000 {
 		compatible = "rockchip,px30-qos", "syscon";
 		reg = <0x0 0xff518000 0x0 0x20>;

-- 
2.39.5



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

* [PATCH v6 11/13] arm64: dts: rockchip: add vicap node to rk356x
  2025-04-30  9:15 [PATCH v6 00/13] media: rockchip: add a driver for the rockchip camera interface Michael Riesch via B4 Relay
                   ` (9 preceding siblings ...)
  2025-04-30  9:15 ` [PATCH v6 10/13] arm64: dts: rockchip: add the vip node to px30 Michael Riesch via B4 Relay
@ 2025-04-30  9:16 ` Michael Riesch via B4 Relay
  2025-04-30  9:16 ` [PATCH v6 12/13] arm64: dts: rockchip: add mipi csi receiver " Michael Riesch via B4 Relay
  2025-04-30  9:16 ` [PATCH v6 13/13] arm64: dts: rockchip: enable vicap dvp on wolfvision pf5 io expander Michael Riesch via B4 Relay
  12 siblings, 0 replies; 34+ messages in thread
From: Michael Riesch via B4 Relay @ 2025-04-30  9:16 UTC (permalink / raw)
  To: Mehdi Djait, Maxime Chevallier, Théo Lebrun, Gerald Loacker,
	Thomas Petazzoni, Laurent Pinchart, Mauro Carvalho Chehab,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner,
	Kever Yang, Nicolas Dufresne, Sebastian Reichel,
	Collabora Kernel Team, Paul Kocialkowski, Alexander Shiyan,
	Val Packett, Rob Herring, Philipp Zabel, Sakari Ailus
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-rockchip, Michael Riesch, Michael Riesch

From: Michael Riesch <michael.riesch@wolfvision.net>

Add the device tree node for the RK356x Video Capture (VICAP) unit.

Signed-off-by: Michael Riesch <michael.riesch@wolfvision.net>
Signed-off-by: Michael Riesch <michael.riesch@collabora.com>
---
 arch/arm64/boot/dts/rockchip/rk356x-base.dtsi | 44 +++++++++++++++++++++++++++
 1 file changed, 44 insertions(+)

diff --git a/arch/arm64/boot/dts/rockchip/rk356x-base.dtsi b/arch/arm64/boot/dts/rockchip/rk356x-base.dtsi
index fd2214b6fad4..e0e4dc85a3a9 100644
--- a/arch/arm64/boot/dts/rockchip/rk356x-base.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk356x-base.dtsi
@@ -564,6 +564,50 @@ gpu: gpu@fde60000 {
 		status = "disabled";
 	};
 
+	vicap: video-capture@fdfe0000 {
+		compatible = "rockchip,rk3568-vicap";
+		reg = <0x0 0xfdfe0000 0x0 0x200>;
+		interrupts = <GIC_SPI 146 IRQ_TYPE_LEVEL_HIGH>;
+		assigned-clocks = <&cru DCLK_VICAP>;
+		assigned-clock-rates = <300000000>;
+		clocks = <&cru ACLK_VICAP>, <&cru HCLK_VICAP>,
+			 <&cru DCLK_VICAP>, <&cru ICLK_VICAP_G>;
+		clock-names = "aclk", "hclk", "dclk", "iclk";
+		iommus = <&vicap_mmu>;
+		power-domains = <&power RK3568_PD_VI>;
+		resets = <&cru SRST_A_VICAP>, <&cru SRST_H_VICAP>,
+			 <&cru SRST_D_VICAP>, <&cru SRST_P_VICAP>,
+			 <&cru SRST_I_VICAP>;
+		reset-names = "arst", "hrst", "drst", "prst", "irst";
+		rockchip,grf = <&grf>;
+		status = "disabled";
+
+		ports {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			vicap_dvp: port@0 {
+				reg = <0>;
+			};
+
+			vicap_mipi: port@1 {
+				reg = <1>;
+			};
+		};
+	};
+
+	vicap_mmu: iommu@fdfe0800 {
+		compatible = "rockchip,rk3568-iommu";
+		reg = <0x0 0xfdfe0800 0x0 0x100>;
+		interrupts = <GIC_SPI 146 IRQ_TYPE_LEVEL_HIGH>;
+		clocks = <&cru ACLK_VICAP>, <&cru HCLK_VICAP>;
+		clock-names = "aclk", "iface";
+		#iommu-cells = <0>;
+		power-domains = <&power RK3568_PD_VI>;
+		rockchip,disable-mmu-reset;
+		status = "disabled";
+	};
+
 	vpu: video-codec@fdea0400 {
 		compatible = "rockchip,rk3568-vpu";
 		reg = <0x0 0xfdea0000 0x0 0x800>;

-- 
2.39.5



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

* [PATCH v6 12/13] arm64: dts: rockchip: add mipi csi receiver node to rk356x
  2025-04-30  9:15 [PATCH v6 00/13] media: rockchip: add a driver for the rockchip camera interface Michael Riesch via B4 Relay
                   ` (10 preceding siblings ...)
  2025-04-30  9:16 ` [PATCH v6 11/13] arm64: dts: rockchip: add vicap node to rk356x Michael Riesch via B4 Relay
@ 2025-04-30  9:16 ` Michael Riesch via B4 Relay
  2025-04-30  9:16 ` [PATCH v6 13/13] arm64: dts: rockchip: enable vicap dvp on wolfvision pf5 io expander Michael Riesch via B4 Relay
  12 siblings, 0 replies; 34+ messages in thread
From: Michael Riesch via B4 Relay @ 2025-04-30  9:16 UTC (permalink / raw)
  To: Mehdi Djait, Maxime Chevallier, Théo Lebrun, Gerald Loacker,
	Thomas Petazzoni, Laurent Pinchart, Mauro Carvalho Chehab,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner,
	Kever Yang, Nicolas Dufresne, Sebastian Reichel,
	Collabora Kernel Team, Paul Kocialkowski, Alexander Shiyan,
	Val Packett, Rob Herring, Philipp Zabel, Sakari Ailus
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-rockchip, Michael Riesch, Michael Riesch

From: Michael Riesch <michael.riesch@wolfvision.net>

Add the device tree node for the RK356x MIPI CSI-2 Receiver.

Signed-off-by: Michael Riesch <michael.riesch@wolfvision.net>
Signed-off-by: Michael Riesch <michael.riesch@collabora.com>
---
 arch/arm64/boot/dts/rockchip/rk356x-base.dtsi | 31 +++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)

diff --git a/arch/arm64/boot/dts/rockchip/rk356x-base.dtsi b/arch/arm64/boot/dts/rockchip/rk356x-base.dtsi
index e0e4dc85a3a9..a1eab57003f6 100644
--- a/arch/arm64/boot/dts/rockchip/rk356x-base.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk356x-base.dtsi
@@ -564,6 +564,33 @@ gpu: gpu@fde60000 {
 		status = "disabled";
 	};
 
+	csi: csi@fdfb0000 {
+		compatible = "rockchip,rk3568-mipi-csi";
+		reg = <0x0 0xfdfb0000 0x0 0x10000>;
+		clocks = <&cru PCLK_CSI2HOST1>;
+		phys = <&csi_dphy>;
+		power-domains = <&power RK3568_PD_VI>;
+		resets = <&cru SRST_P_CSI2HOST1>;
+		status = "disabled";
+
+		ports {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			csi_in: port@0 {
+				reg = <0>;
+			};
+
+			csi_out: port@1 {
+				reg = <1>;
+
+				csi_output: endpoint {
+					remote-endpoint = <&vicap_mipi_input>;
+				};
+			};
+		};
+	};
+
 	vicap: video-capture@fdfe0000 {
 		compatible = "rockchip,rk3568-vicap";
 		reg = <0x0 0xfdfe0000 0x0 0x200>;
@@ -592,6 +619,10 @@ vicap_dvp: port@0 {
 
 			vicap_mipi: port@1 {
 				reg = <1>;
+
+				vicap_mipi_input: endpoint {
+					remote-endpoint = <&csi_output>;
+				};
 			};
 		};
 	};

-- 
2.39.5



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

* [PATCH v6 13/13] arm64: dts: rockchip: enable vicap dvp on wolfvision pf5 io expander
  2025-04-30  9:15 [PATCH v6 00/13] media: rockchip: add a driver for the rockchip camera interface Michael Riesch via B4 Relay
                   ` (11 preceding siblings ...)
  2025-04-30  9:16 ` [PATCH v6 12/13] arm64: dts: rockchip: add mipi csi receiver " Michael Riesch via B4 Relay
@ 2025-04-30  9:16 ` Michael Riesch via B4 Relay
  12 siblings, 0 replies; 34+ messages in thread
From: Michael Riesch via B4 Relay @ 2025-04-30  9:16 UTC (permalink / raw)
  To: Mehdi Djait, Maxime Chevallier, Théo Lebrun, Gerald Loacker,
	Thomas Petazzoni, Laurent Pinchart, Mauro Carvalho Chehab,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner,
	Kever Yang, Nicolas Dufresne, Sebastian Reichel,
	Collabora Kernel Team, Paul Kocialkowski, Alexander Shiyan,
	Val Packett, Rob Herring, Philipp Zabel, Sakari Ailus
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-rockchip, Michael Riesch, Michael Riesch

From: Michael Riesch <michael.riesch@wolfvision.net>

The Digital Video Port (DVP, the 16-bit variant) of the RK3568 VICAP
is broken out to the PF5 mainboard expansion header.
Enable it in the device tree overlay for the WolfVision PF5 IO
Expander board.

Signed-off-by: Michael Riesch <michael.riesch@wolfvision.net>
Signed-off-by: Michael Riesch <michael.riesch@collabora.com>
---
 .../rockchip/rk3568-wolfvision-pf5-io-expander.dtso  | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/arch/arm64/boot/dts/rockchip/rk3568-wolfvision-pf5-io-expander.dtso b/arch/arm64/boot/dts/rockchip/rk3568-wolfvision-pf5-io-expander.dtso
index 048933de2943..8cfce71dd318 100644
--- a/arch/arm64/boot/dts/rockchip/rk3568-wolfvision-pf5-io-expander.dtso
+++ b/arch/arm64/boot/dts/rockchip/rk3568-wolfvision-pf5-io-expander.dtso
@@ -11,6 +11,7 @@
 #include <dt-bindings/clock/rk3568-cru.h>
 #include <dt-bindings/gpio/gpio.h>
 #include <dt-bindings/interrupt-controller/irq.h>
+#include <dt-bindings/media/video-interfaces.h>
 #include <dt-bindings/pinctrl/rockchip.h>
 
 &{/} {
@@ -134,3 +135,22 @@ &usb2phy0_host {
 	phy-supply = <&usb_host_vbus>;
 	status = "okay";
 };
+
+&vicap {
+	pinctrl-names = "default";
+	pinctrl-0 = <&cif_clk &cif_dvp_clk &cif_dvp_bus16>;
+	status = "okay";
+};
+
+&vicap_dvp {
+	vicap_dvp_input: endpoint {
+		bus-type = <MEDIA_BUS_TYPE_BT656>;
+		bus-width = <16>;
+		pclk-sample = <MEDIA_PCLK_SAMPLE_DUAL_EDGE>;
+		rockchip,dvp-clk-delay = <10>;
+	};
+};
+
+&vicap_mmu {
+	status = "okay";
+};

-- 
2.39.5



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

* Re: [PATCH v6 01/13] Documentation: admin-guide: media: add rockchip camera interface
  2025-04-30  9:15 ` [PATCH v6 01/13] Documentation: admin-guide: media: add " Michael Riesch via B4 Relay
@ 2025-04-30  9:42   ` Heiko Stübner
  0 siblings, 0 replies; 34+ messages in thread
From: Heiko Stübner @ 2025-04-30  9:42 UTC (permalink / raw)
  To: Mehdi Djait, Maxime Chevallier, Théo Lebrun, Gerald Loacker,
	Thomas Petazzoni, Laurent Pinchart, Mauro Carvalho Chehab,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Kever Yang,
	Nicolas Dufresne, Sebastian Reichel, Collabora Kernel Team,
	Paul Kocialkowski, Alexander Shiyan, Val Packett, Rob Herring,
	Philipp Zabel, Sakari Ailus, michael.riesch
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-rockchip, Michael Riesch, Michael Riesch

Hey Michael,

Am Mittwoch, 30. April 2025, 11:15:50 Mitteleuropäische Sommerzeit schrieb Michael Riesch via B4 Relay:
> From: Michael Riesch <michael.riesch@collabora.com>
> 
> Add a document that describes the different variants of the Rockchip
> Camera Interface (CIF), their hardware layout, as well as their
> representation in the media controller centric rkcif device driver,
> which is located under drivers/media/platform/rockchip/rkcif.
> 
> Signed-off-by: Michael Riesch <michael.riesch@collabora.com>

> diff --git a/Documentation/admin-guide/media/rkcif.rst b/Documentation/admin-guide/media/rkcif.rst
> new file mode 100644
> index 000000000000..f35f644a54a0
> --- /dev/null
> +++ b/Documentation/admin-guide/media/rkcif.rst
> @@ -0,0 +1,83 @@
> +.. SPDX-License-Identifier: GPL-2.0
> +
> +=========================================
> +Rockchip Camera Interface (CIF)
> +=========================================
> +
> +Introduction
> +============
> +
> +The Rockchip Camera Interface (CIF) is featured in many Rockchip SoCs in
> +different variants.
> +The different variants are combinations of common building blocks, such as
> +
> +* INTERFACE blocks of different types, namely
> +
> +  * the Digital Video Port (DVP, a parallel data interface)
> +  * the interface block for the MIPI CSI-2 receiver
> +
> +* CROP units
> +
> +* MIPI CSI-2 receiver (not available on all variants): This unit is referred
> +  to as MIPI CSI HOST in the Rockchip documentation.
> +  Technically, it is a separate hardware block, but it is strongly coupled to
> +  the CIF and therefore included here.
> +
> +* MUX units (not available on all variants) that pass the video data to an
> +  image signal processor (ISP)
> +
> +* SCALE units (not available on all variants)
> +
> +* DMA engines that transfer video data into system memory using a
> +  double-buffering mechanism called ping-pong mode
> +
> +* Support for four streams per INTERFACE block (not available on all
> +  variants), e.g., for MIPI CSI-2 Virtual Channels (VCs)
> +
> +This document describes the different variants of the CIF, their hardware
> +layout, as well as their representation in the media controller centric rkcif
> +device driver, which is located under drivers/media/platform/rockchip/rkcif.
> +
> +Variants
> +========
> +
> +Rockchip PX30 Video Input Processor (VIP)
> +-----------------------------------------
> +
> +The PX30 Video Input Processor (VIP) features a digital video port that accepts
> +parallel video data or BT.656.
> +Since these protocols do not feature multiple streams, the VIP has one DMA
> +engine that transfers the input video data into system memory.
> +
> +The rkcif driver represents this hardware variant by exposing one V4L2 subdevice
> +(the DVP INTERFACE/CROP block) and one V4L2 device (the DVP DMA engine).
> +
> +Rockchip RK3568 Video Capture (VICAP)
> +-------------------------------------
> +
> +The RK3568 Video Capture (VICAP) unit features a digital video port and a MIPI
> +CSI-2 receiver that can receive video data independently.
> +The DVP accepts parallel video data, BT.656 and BT.1120.
> +Since the BT.1120 protocol may feature more than one stream, the RK3568 VICAP
> +DVP features four DMA engines that can capture different streams.
> +Similarly, the RK3568 VICAP MIPI CSI-2 receiver features four DMA engines to
> +handle different Virtual Channels (VCs).
> +
> +The rkcif driver represents this hardware variant by exposing up to three V4L2
> +subdevices:
> +
> +* rkcif-dvp0: INTERFACE/CROP block for the DVP
> +* rockchip-mipi-csi fdfb0000.csi: MIPI CSI-2 receiver
> +* rkcif-mipi0: INTERFACE/CROP block for the MIPI CSI-2 receiver
> +
> +and up to five V4L2 devices:
> +
> +* rkcif-dvp0-id0: The support for multiple streams on the DVP is not yet
> +  implemented, as it is hard to find test hardware. Thus, this video device
> +  represents the first DMA engine of the RK3568 DVP.
> +* rkcif-mipi0-id[0...3]: The four DMA engines of the RK3568 MIPI CSI-2
> +  receiver. Each DMA engine can capture a certain MIPI CSI-2 Virtual Channel.
> +
> +.. kernel-figure:: rkcif-rk3568-vicap.dot
> +    :alt:   Topology of the RK3568 Video Capture (VICAP) unit
> +    :align: center

in patch 6 you already have a block stating

	Finally, the RK3588 VICAP unit constitutes an essential piece of the
	camera interface with one DVP, six MIPI CSI-2 receivers, scale/crop
	units, and a data path multiplexer (to scaler units, to ISP, ...).

so the rk3588 seems to be on the table "soonish" ;-) .
So maybe also already include it here in the description already, if there
is a next version, and your investigation is already that far along?

Otherwise a really nice (and concise) read, and made me understand the
setup somewhat nicely.

Reviewed-by: Heiko Stuebner <heiko@sntech.de>



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

* Re: [PATCH v6 05/13] media: dt-bindings: add rockchip rk3568 mipi csi receiver
  2025-04-30  9:15 ` [PATCH v6 05/13] media: dt-bindings: add rockchip rk3568 mipi csi receiver Michael Riesch via B4 Relay
@ 2025-04-30 10:50   ` Rob Herring (Arm)
  2025-05-06 12:09   ` Mehdi Djait
  1 sibling, 0 replies; 34+ messages in thread
From: Rob Herring (Arm) @ 2025-04-30 10:50 UTC (permalink / raw)
  To: Michael Riesch
  Cc: Laurent Pinchart, linux-media, Krzysztof Kozlowski, devicetree,
	Nicolas Dufresne, Thomas Petazzoni, Alexander Shiyan,
	linux-rockchip, Collabora Kernel Team, Maxime Chevallier,
	Gerald Loacker, Sebastian Reichel, Kever Yang, Théo Lebrun,
	linux-arm-kernel, Conor Dooley, linux-kernel, Philipp Zabel,
	Sakari Ailus, Val Packett, Mauro Carvalho Chehab, Heiko Stuebner,
	Michael Riesch, Mehdi Djait, Paul Kocialkowski, Rob Herring


On Wed, 30 Apr 2025 11:15:54 +0200, Michael Riesch wrote:
> From: Michael Riesch <michael.riesch@wolfvision.net>
> 
> Add documentation for the Rockchip RK3568 MIPI CSI-2 Receiver.
> 
> Signed-off-by: Michael Riesch <michael.riesch@wolfvision.net>
> Signed-off-by: Michael Riesch <michael.riesch@collabora.com>
> ---
>  .../bindings/media/rockchip,rk3568-mipi-csi.yaml   | 113 +++++++++++++++++++++
>  MAINTAINERS                                        |   1 +
>  2 files changed, 114 insertions(+)
> 

My bot found errors running 'make dt_binding_check' on your patch:

yamllint warnings/errors:

dtschema/dtc warnings/errors:
/builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi.example.dtb: csi@fdfb0000 (rockchip,rk3568-mipi-csi): 'phy-names' is a required property
	from schema $id: http://devicetree.org/schemas/media/rockchip,rk3568-mipi-csi.yaml#

doc reference errors (make refcheckdocs):

See https://patchwork.ozlabs.org/project/devicetree-bindings/patch/20240220-rk3568-vicap-v6-5-d2f5fbee1551@collabora.com

The base for the series is generally the latest rc1. A different dependency
should be noted in *this* patch.

If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:

pip3 install dtschema --upgrade

Please check and re-submit after running the above command yourself. Note
that DT_SCHEMA_FILES can be set to your schema file to speed up checking
your schema. However, it must be unset to test all examples with your schema.


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

* Re: [PATCH v6 07/13] media: rockchip: rkcif: add driver for mipi csi-2 receiver
  2025-04-30  9:15 ` [PATCH v6 07/13] media: rockchip: rkcif: add driver for mipi csi-2 receiver Michael Riesch via B4 Relay
@ 2025-05-01  0:29   ` Bryan O'Donoghue
  2025-05-06 18:56     ` Michael Riesch
  2025-05-01 21:57   ` Sakari Ailus
  2025-05-02 13:31   ` Laurent Pinchart
  2 siblings, 1 reply; 34+ messages in thread
From: Bryan O'Donoghue @ 2025-05-01  0:29 UTC (permalink / raw)
  To: michael.riesch, Mehdi Djait, Maxime Chevallier, Théo Lebrun,
	Gerald Loacker, Thomas Petazzoni, Laurent Pinchart,
	Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Heiko Stuebner, Kever Yang, Nicolas Dufresne,
	Sebastian Reichel, Collabora Kernel Team, Paul Kocialkowski,
	Alexander Shiyan, Val Packett, Rob Herring, Philipp Zabel,
	Sakari Ailus
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-rockchip, Michael Riesch

On 30/04/2025 10:15, Michael Riesch via B4 Relay wrote:
> From: Michael Riesch <michael.riesch@wolfvision.net>
> 
> The Rockchip RK3568 MIPI CSI-2 Receiver is a CSI-2 bridge with one
> input port and one output port. It receives the data with the help
> of an external MIPI PHY (C-PHY or D-PHY) and passes it to the
> Rockchip RK3568 Video Capture (VICAP) block.
> 
> Add a V4L2 subdevice driver for this unit.
> 
> Signed-off-by: Michael Riesch <michael.riesch@wolfvision.net>
> Signed-off-by: Michael Riesch <michael.riesch@collabora.com>
> ---
>   drivers/media/platform/rockchip/rkcif/Makefile     |   3 +
>   .../rockchip/rkcif/rkcif-mipi-csi-receiver.c       | 731 +++++++++++++++++++++
>   2 files changed, 734 insertions(+)
> 
> diff --git a/drivers/media/platform/rockchip/rkcif/Makefile b/drivers/media/platform/rockchip/rkcif/Makefile
> index 818424972c7b..a5c18a45c213 100644
> --- a/drivers/media/platform/rockchip/rkcif/Makefile
> +++ b/drivers/media/platform/rockchip/rkcif/Makefile
> @@ -5,3 +5,6 @@ rockchip-cif-objs += rkcif-dev.o \
>   	rkcif-capture-mipi.o \
>   	rkcif-interface.o \
>   	rkcif-stream.o
> +
> +obj-$(CONFIG_VIDEO_ROCKCHIP_CIF) += rockchip-mipi-csi.o
> +rockchip-mipi-csi-objs += rkcif-mipi-csi-receiver.o
> diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-mipi-csi-receiver.c b/drivers/media/platform/rockchip/rkcif/rkcif-mipi-csi-receiver.c
> new file mode 100644
> index 000000000000..81489f70490f
> --- /dev/null
> +++ b/drivers/media/platform/rockchip/rkcif/rkcif-mipi-csi-receiver.c
> @@ -0,0 +1,731 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Rockchip MIPI CSI-2 Receiver Driver
> + *
> + * Copyright (C) 2019 Rockchip Electronics Co., Ltd.
> + * Copyright (C) 2025 Michael Riesch <michael.riesch@wolfvision.net>
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/io.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_graph.h>
> +#include <linux/of_platform.h>
> +#include <linux/phy/phy.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/reset.h>
> +
> +#include <media/mipi-csi2.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-fwnode.h>
> +#include <media/v4l2-subdev.h>
> +
> +#define CSI2HOST_N_LANES     0x04
> +#define CSI2HOST_CSI2_RESETN 0x10
> +#define CSI2HOST_PHY_STATE   0x14
> +#define CSI2HOST_ERR1	     0x20
> +#define CSI2HOST_ERR2	     0x24
> +#define CSI2HOST_MSK1	     0x28
> +#define CSI2HOST_MSK2	     0x2c
> +#define CSI2HOST_CONTROL     0x40
> +
> +#define SW_CPHY_EN(x)	     ((x) << 0)
> +#define SW_DSI_EN(x)	     ((x) << 4)
> +#define SW_DATATYPE_FS(x)    ((x) << 8)
> +#define SW_DATATYPE_FE(x)    ((x) << 14)
> +#define SW_DATATYPE_LS(x)    ((x) << 20)
> +#define SW_DATATYPE_LE(x)    ((x) << 26)
> +
> +#define RKCIF_CSI_CLKS_MAX   1
> +
> +enum {
> +	RKCIF_CSI_PAD_SINK,
> +	RKCIF_CSI_PAD_SRC,
> +	RKCIF_CSI_PAD_MAX,
> +};
> +
> +struct rkcif_csi_format {
> +	u32 code;
> +	u8 depth;
> +	u8 csi_dt;
> +};
> +
> +struct rkcif_csi_device {
> +	struct device *dev;
> +
> +	void __iomem *base_addr;
> +	struct clk_bulk_data *clks;
> +	unsigned int clks_num;
> +	struct phy *phy;
> +	struct reset_control *reset;
> +
> +	const struct rkcif_csi_format *formats;
> +	unsigned int formats_num;
> +
> +	struct media_pad pads[RKCIF_CSI_PAD_MAX];
> +	struct v4l2_async_notifier notifier;
> +	struct v4l2_fwnode_endpoint vep;
> +	struct v4l2_subdev sd;
> +
> +	struct v4l2_subdev *source_sd;
> +	u32 source_pad;
> +};
> +
> +static const struct v4l2_mbus_framefmt default_format = {
> +	.width = 3840,
> +	.height = 2160,
> +	.code = MEDIA_BUS_FMT_SRGGB10_1X10,
> +	.field = V4L2_FIELD_NONE,
> +	.colorspace = V4L2_COLORSPACE_RAW,
> +	.ycbcr_enc = V4L2_YCBCR_ENC_601,
> +	.quantization = V4L2_QUANTIZATION_FULL_RANGE,
> +	.xfer_func = V4L2_XFER_FUNC_NONE,
> +};
> +
> +static const struct rkcif_csi_format formats[] = {
> +	/* YUV formats */
> +	{
> +		.code = MEDIA_BUS_FMT_YUYV8_1X16,
> +		.depth = 16,
> +		.csi_dt = MIPI_CSI2_DT_YUV422_8B,
> +	},
> +	{
> +		.code = MEDIA_BUS_FMT_UYVY8_1X16,
> +		.depth = 16,
> +		.csi_dt = MIPI_CSI2_DT_YUV422_8B,
> +	},
> +	{
> +		.code = MEDIA_BUS_FMT_YVYU8_1X16,
> +		.depth = 16,
> +		.csi_dt = MIPI_CSI2_DT_YUV422_8B,
> +	},
> +	{
> +		.code = MEDIA_BUS_FMT_VYUY8_1X16,
> +		.depth = 16,
> +		.csi_dt = MIPI_CSI2_DT_YUV422_8B,
> +	},
> +	/* RGB formats */
> +	{
> +		.code = MEDIA_BUS_FMT_RGB888_1X24,
> +		.depth = 24,
> +		.csi_dt = MIPI_CSI2_DT_RGB888,
> +	},
> +	{
> +		.code = MEDIA_BUS_FMT_BGR888_1X24,
> +		.depth = 24,
> +		.csi_dt = MIPI_CSI2_DT_RGB888,
> +	},
> +	/* Bayer formats */
> +	{
> +		.code = MEDIA_BUS_FMT_SBGGR8_1X8,
> +		.depth = 8,
> +		.csi_dt = MIPI_CSI2_DT_RAW8,
> +	},
> +	{
> +		.code = MEDIA_BUS_FMT_SGBRG8_1X8,
> +		.depth = 8,
> +		.csi_dt = MIPI_CSI2_DT_RAW8,
> +	},
> +	{
> +		.code = MEDIA_BUS_FMT_SGRBG8_1X8,
> +		.depth = 8,
> +		.csi_dt = MIPI_CSI2_DT_RAW8,
> +	},
> +	{
> +		.code = MEDIA_BUS_FMT_SRGGB8_1X8,
> +		.depth = 8,
> +		.csi_dt = MIPI_CSI2_DT_RAW8,
> +	},
> +	{
> +		.code = MEDIA_BUS_FMT_SBGGR10_1X10,
> +		.depth = 10,
> +		.csi_dt = MIPI_CSI2_DT_RAW10,
> +	},
> +	{
> +		.code = MEDIA_BUS_FMT_SGBRG10_1X10,
> +		.depth = 10,
> +		.csi_dt = MIPI_CSI2_DT_RAW10,
> +	},
> +	{
> +		.code = MEDIA_BUS_FMT_SGRBG10_1X10,
> +		.depth = 10,
> +		.csi_dt = MIPI_CSI2_DT_RAW10,
> +	},
> +	{
> +		.code = MEDIA_BUS_FMT_SRGGB10_1X10,
> +		.depth = 10,
> +		.csi_dt = MIPI_CSI2_DT_RAW10,
> +	},
> +	{
> +		.code = MEDIA_BUS_FMT_SBGGR12_1X12,
> +		.depth = 12,
> +		.csi_dt = MIPI_CSI2_DT_RAW12,
> +	},
> +	{
> +		.code = MEDIA_BUS_FMT_SGBRG12_1X12,
> +		.depth = 12,
> +		.csi_dt = MIPI_CSI2_DT_RAW12,
> +	},
> +	{
> +		.code = MEDIA_BUS_FMT_SGRBG12_1X12,
> +		.depth = 12,
> +		.csi_dt = MIPI_CSI2_DT_RAW12,
> +	},
> +	{
> +		.code = MEDIA_BUS_FMT_SRGGB12_1X12,
> +		.depth = 12,
> +		.csi_dt = MIPI_CSI2_DT_RAW12,
> +	},
> +};
> +
> +static inline struct rkcif_csi_device *to_rkcif_csi(struct v4l2_subdev *sd)
> +{
> +	return container_of(sd, struct rkcif_csi_device, sd);
> +}
> +
> +static inline __maybe_unused void
> +rkcif_csi_write(struct rkcif_csi_device *csi_dev, unsigned int addr, u32 val)
> +{
> +	writel(val, csi_dev->base_addr + addr);
> +}
> +
> +static inline __maybe_unused u32
> +rkcif_csi_read(struct rkcif_csi_device *csi_dev, unsigned int addr)
> +{
> +	return readl(csi_dev->base_addr + addr);
> +}
> +
> +static const struct rkcif_csi_format *
> +rkcif_csi_find_format(struct rkcif_csi_device *csi_dev, u32 mbus_code)
> +{
> +	const struct rkcif_csi_format *format;
> +
> +	WARN_ON(csi_dev->formats_num == 0);
> +
> +	for (int i = 0; i < csi_dev->formats_num; i++) {
> +		format = &csi_dev->formats[i];
> +		if (format->code == mbus_code)
> +			return format;
> +	}
> +
> +	return NULL;
> +}
> +
> +static int rkcif_csi_start(struct rkcif_csi_device *csi_dev)
> +{
> +	enum v4l2_mbus_type bus_type = csi_dev->vep.bus_type;
> +	union phy_configure_opts opts;
> +	s64 link_freq;
> +	u32 lanes = csi_dev->vep.bus.mipi_csi2.num_data_lanes;
> +	u32 control = 0;
> +
> +	if (lanes < 1 || lanes > 4)
> +		return -EINVAL;
> +
> +	/* set mult and div to 0, thus completely rely on V4L2_CID_LINK_FREQ */
> +	link_freq = v4l2_get_link_freq(csi_dev->source_sd->ctrl_handler, 0, 0);
> +	if (link_freq <= 0)
> +		return -EINVAL;
> +
> +	if (bus_type == V4L2_MBUS_CSI2_DPHY) {
> +		struct phy_configure_opts_mipi_dphy *cfg = &opts.mipi_dphy;
> +
> +		phy_mipi_dphy_get_default_config_for_hsclk(link_freq * 2, lanes,
> +							   cfg);
> +		phy_set_mode(csi_dev->phy, PHY_MODE_MIPI_DPHY);
> +		phy_configure(csi_dev->phy, &opts);
> +
> +		control |= SW_CPHY_EN(0);
> +
> +	} else if (bus_type == V4L2_MBUS_CSI2_CPHY) {
> +		control |= SW_CPHY_EN(1);

return -ENOTSUPP;

> +		/* TODO: implement CPHY configuration */
> +	} else {
> +		return -EINVAL;
> +	}
> +
> +	control |= SW_DATATYPE_FS(0x00) | SW_DATATYPE_FE(0x01) |
> +		   SW_DATATYPE_LS(0x02) | SW_DATATYPE_LE(0x03);
> +
> +	rkcif_csi_write(csi_dev, CSI2HOST_N_LANES, lanes - 1);
> +	rkcif_csi_write(csi_dev, CSI2HOST_CONTROL, control);
> +	rkcif_csi_write(csi_dev, CSI2HOST_CSI2_RESETN, 1);
> +
> +	phy_power_on(csi_dev->phy);

this can fail

ret = phy_power_on();
if (ret)
	return ret;

> +
> +	return 0;
> +}
> +
> +static void rkcif_csi_stop(struct rkcif_csi_device *csi_dev)
> +{
> +	phy_power_off(csi_dev->phy);
> +
> +	rkcif_csi_write(csi_dev, CSI2HOST_CSI2_RESETN, 0);
> +	rkcif_csi_write(csi_dev, CSI2HOST_MSK1, ~0);
> +	rkcif_csi_write(csi_dev, CSI2HOST_MSK2, ~0);
> +}
> +
> +static const struct media_entity_operations rkcif_csi_media_ops = {
> +	.link_validate = v4l2_subdev_link_validate,
> +};
> +
> +static int rkcif_csi_enum_mbus_code(struct v4l2_subdev *sd,
> +				    struct v4l2_subdev_state *sd_state,
> +				    struct v4l2_subdev_mbus_code_enum *code)
> +{
> +	struct rkcif_csi_device *csi_dev = to_rkcif_csi(sd);
> +
> +	if (code->pad == RKCIF_CSI_PAD_SRC) {
> +		const struct v4l2_mbus_framefmt *sink_fmt;
> +
> +		if (code->index)
> +			return -EINVAL;
> +
> +		sink_fmt = v4l2_subdev_state_get_format(sd_state,
> +							RKCIF_CSI_PAD_SINK);
> +		code->code = sink_fmt->code;
> +
> +		return 0;
> +	} else if (code->pad == RKCIF_CSI_PAD_SINK) {
> +		if (code->index > csi_dev->formats_num)
> +			return -EINVAL;
> +
> +		code->code = csi_dev->formats[code->index].code;
> +		return 0;
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static int rkcif_csi_set_fmt(struct v4l2_subdev *sd,
> +			     struct v4l2_subdev_state *state,
> +			     struct v4l2_subdev_format *format)
> +{
> +	struct rkcif_csi_device *csi_dev = to_rkcif_csi(sd);
> +	const struct rkcif_csi_format *fmt;
> +	struct v4l2_mbus_framefmt *sink, *src;
> +
> +	/* the format on the source pad always matches the sink pad */
> +	if (format->pad == RKCIF_CSI_PAD_SRC)
> +		return v4l2_subdev_get_fmt(sd, state, format);
> +
> +	sink = v4l2_subdev_state_get_format(state, format->pad, format->stream);
> +	if (!sink)
> +		return -EINVAL;
> +
> +	fmt = rkcif_csi_find_format(csi_dev, format->format.code);
> +	if (fmt)
> +		*sink = format->format;
> +	else
> +		*sink = default_format;
> +
> +	/* propagate the format to the source pad */
> +	src = v4l2_subdev_state_get_opposite_stream_format(state, format->pad,
> +							   format->stream);
> +	if (!src)
> +		return -EINVAL;
> +
> +	*src = *sink;
> +
> +	return 0;
> +}
> +
> +static int rkcif_csi_set_routing(struct v4l2_subdev *sd,
> +				 struct v4l2_subdev_state *state,
> +				 enum v4l2_subdev_format_whence which,
> +				 struct v4l2_subdev_krouting *routing)
> +{
> +	int ret;
> +
> +	ret = v4l2_subdev_routing_validate(sd, routing,
> +					   V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
> +	if (ret)
> +		return ret;
> +
> +	ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing,
> +					       &default_format);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int rkcif_csi_enable_streams(struct v4l2_subdev *sd,
> +				    struct v4l2_subdev_state *state, u32 pad,
> +				    u64 streams_mask)
> +{
> +	struct rkcif_csi_device *csi_dev = to_rkcif_csi(sd);
> +	struct v4l2_subdev *remote_sd;
> +	struct media_pad *sink_pad, *remote_pad;
> +	struct device *dev = csi_dev->dev;
> +	u64 mask;
> +	int ret;
> +
> +	sink_pad = &sd->entity.pads[RKCIF_CSI_PAD_SINK];
> +	remote_pad = media_pad_remote_pad_first(sink_pad);
> +	remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity);
> +
> +	mask = v4l2_subdev_state_xlate_streams(state, RKCIF_CSI_PAD_SINK,
> +					       RKCIF_CSI_PAD_SRC,
> +					       &streams_mask);
> +
> +	ret = pm_runtime_resume_and_get(dev);
> +	if (ret)
> +		goto err;
> +
> +	ret = rkcif_csi_start(csi_dev);
> +	if (ret) {
> +		dev_err(dev, "failed to enable CSI hardware\n");
> +		goto err_pm_runtime_put;
> +	}
> +
> +	ret = v4l2_subdev_enable_streams(remote_sd, remote_pad->index, mask);
> +	if (ret)
> +		goto err_csi_stop;
> +
> +	return 0;
> +
> +err_csi_stop:
> +	rkcif_csi_stop(csi_dev);
> +err_pm_runtime_put:
> +	pm_runtime_put_sync(dev);
> +err:
> +	return ret;
> +}
> +
> +static int rkcif_csi_disable_streams(struct v4l2_subdev *sd,
> +				     struct v4l2_subdev_state *state, u32 pad,
> +				     u64 streams_mask)
> +{
> +	struct rkcif_csi_device *csi_dev = to_rkcif_csi(sd);
> +	struct v4l2_subdev *remote_sd;
> +	struct media_pad *sink_pad, *remote_pad;
> +	struct device *dev = csi_dev->dev;
> +	u64 mask;
> +	int ret;
> +
> +	sink_pad = &sd->entity.pads[RKCIF_CSI_PAD_SINK];
> +	remote_pad = media_pad_remote_pad_first(sink_pad);
> +	remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity);
> +
> +	mask = v4l2_subdev_state_xlate_streams(state, RKCIF_CSI_PAD_SINK,
> +					       RKCIF_CSI_PAD_SRC,
> +					       &streams_mask);
> +
> +	ret = v4l2_subdev_disable_streams(remote_sd, remote_pad->index, mask);
> +
> +	rkcif_csi_stop(csi_dev);
> +
> +	pm_runtime_mark_last_busy(dev);
> +	pm_runtime_put_autosuspend(dev);
> +
> +	return ret;
> +}
> +
> +static const struct v4l2_subdev_pad_ops rkcif_csi_pad_ops = {
> +	.enum_mbus_code = rkcif_csi_enum_mbus_code,
> +	.get_fmt = v4l2_subdev_get_fmt,
> +	.set_fmt = rkcif_csi_set_fmt,
> +	.set_routing = rkcif_csi_set_routing,
> +	.enable_streams = rkcif_csi_enable_streams,
> +	.disable_streams = rkcif_csi_disable_streams,
> +};
> +
> +static const struct v4l2_subdev_ops rkcif_csi_ops = {
> +	.pad = &rkcif_csi_pad_ops,
> +};
> +
> +static int rkcif_csi_init_state(struct v4l2_subdev *sd,
> +				struct v4l2_subdev_state *state)
> +{
> +	struct v4l2_subdev_route routes[] = {
> +		{
> +			.sink_pad = RKCIF_CSI_PAD_SINK,
> +			.sink_stream = 0,
> +			.source_pad = RKCIF_CSI_PAD_SRC,
> +			.source_stream = 0,
> +			.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
> +		},
> +	};
> +	struct v4l2_subdev_krouting routing = {
> +		.len_routes = ARRAY_SIZE(routes),
> +		.num_routes = ARRAY_SIZE(routes),
> +		.routes = routes,
> +	};
> +	int ret;
> +
> +	ret = v4l2_subdev_set_routing_with_fmt(sd, state, &routing,
> +					       &default_format);
> +
> +	return ret;
> +}
> +
> +static const struct v4l2_subdev_internal_ops rkcif_csi_internal_ops = {
> +	.init_state = rkcif_csi_init_state,
> +};
> +
> +static int rkcif_csi_notifier_bound(struct v4l2_async_notifier *notifier,
> +				    struct v4l2_subdev *sd,
> +				    struct v4l2_async_connection *asd)
> +{
> +	struct rkcif_csi_device *csi_dev =
> +		container_of(notifier, struct rkcif_csi_device, notifier);
> +	int source_pad;
> +
> +	source_pad = media_entity_get_fwnode_pad(&sd->entity, sd->fwnode,
> +						 MEDIA_PAD_FL_SOURCE);
> +	if (source_pad < 0) {
> +		dev_err(csi_dev->dev, "failed to find source pad for %s\n",
> +			sd->name);
> +		return source_pad;
> +	}
> +
> +	csi_dev->source_sd = sd;
> +	csi_dev->source_pad = source_pad;
> +
> +	return media_create_pad_link(&sd->entity, source_pad,
> +				     &csi_dev->sd.entity, RKCIF_CSI_PAD_SINK,
> +				     MEDIA_LNK_FL_ENABLED);
> +}
> +
> +static const struct v4l2_async_notifier_operations rkcif_csi_notifier_ops = {
> +	.bound = rkcif_csi_notifier_bound,
> +};
> +
> +static int rkcif_csi_register_notifier(struct rkcif_csi_device *csi_dev)
> +{
> +	struct v4l2_async_connection *asd;
> +	struct v4l2_async_notifier *ntf = &csi_dev->notifier;
> +	struct v4l2_fwnode_endpoint *vep = &csi_dev->vep;
> +	struct v4l2_subdev *sd = &csi_dev->sd;
> +	struct device *dev = csi_dev->dev;
> +	struct fwnode_handle *ep;
> +	int ret = 0;
> +
> +	ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, 0);
> +	if (!ep)
> +		return dev_err_probe(dev, -ENODEV, "failed to get endpoint\n");
> +
> +	vep->bus_type = V4L2_MBUS_UNKNOWN;
> +	ret = v4l2_fwnode_endpoint_parse(ep, vep);
> +	if (ret) {
> +		ret = dev_err_probe(dev, ret, "failed to parse endpoint\n");
> +		goto out;
> +	}
> +
> +	if (vep->bus_type != V4L2_MBUS_CSI2_DPHY &&
> +	    vep->bus_type != V4L2_MBUS_CSI2_CPHY) {
> +		ret = dev_err_probe(dev, -EINVAL,
> +				    "invalid bus type of endpoint\n");
> +		goto out;
> +	}
> +
> +	v4l2_async_subdev_nf_init(ntf, sd);
> +	ntf->ops = &rkcif_csi_notifier_ops;
> +
> +	asd = v4l2_async_nf_add_fwnode_remote(ntf, ep,
> +					      struct v4l2_async_connection);
> +	if (IS_ERR(asd)) {
> +		ret = PTR_ERR(asd);
> +		goto err_nf_cleanup;
> +	}
> +
> +	ret = v4l2_async_nf_register(ntf);
> +	if (ret) {
> +		ret = dev_err_probe(dev, ret, "failed to register notifier\n");
> +		goto err_nf_cleanup;
> +	}
> +
> +	goto out;
> +
> +err_nf_cleanup:
> +	v4l2_async_nf_cleanup(ntf);
> +out:
> +	fwnode_handle_put(ep);
> +	return ret;
> +}
> +
> +static int rkcif_csi_register(struct rkcif_csi_device *csi_dev)
> +{
> +	struct media_pad *pads = csi_dev->pads;
> +	struct v4l2_subdev *sd = &csi_dev->sd;
> +	int ret;
> +
> +	ret = rkcif_csi_register_notifier(csi_dev);
> +	if (ret)
> +		goto err;
> +
> +	v4l2_subdev_init(sd, &rkcif_csi_ops);
> +	sd->dev = csi_dev->dev;
> +	sd->entity.ops = &rkcif_csi_media_ops;
> +	sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
> +	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS;
> +	sd->internal_ops = &rkcif_csi_internal_ops;
> +	sd->owner = THIS_MODULE;
> +	snprintf(sd->name, sizeof(sd->name), "rockchip-mipi-csi %s",
> +		 dev_name(csi_dev->dev));
> +
> +	pads[RKCIF_CSI_PAD_SINK].flags = MEDIA_PAD_FL_SINK |
> +					 MEDIA_PAD_FL_MUST_CONNECT;
> +	pads[RKCIF_CSI_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE;
> +	ret = media_entity_pads_init(&sd->entity, RKCIF_CSI_PAD_MAX, pads);
> +	if (ret)
> +		goto err_notifier_unregister;
> +
> +	ret = v4l2_subdev_init_finalize(sd);
> +	if (ret)
> +		goto err_entity_cleanup;
> +
> +	ret = v4l2_async_register_subdev(sd);
> +	if (ret) {
> +		dev_err(sd->dev, "failed to register CSI subdev\n");
> +		goto err_subdev_cleanup;
> +	}
> +
> +	return 0;
> +
> +err_subdev_cleanup:
> +	v4l2_subdev_cleanup(sd);
> +err_entity_cleanup:
> +	media_entity_cleanup(&sd->entity);
> +err_notifier_unregister:
> +	v4l2_async_nf_unregister(&csi_dev->notifier);
> +	v4l2_async_nf_cleanup(&csi_dev->notifier);
> +err:
> +	return ret;
> +}
> +
> +static void rkcif_csi_unregister(struct rkcif_csi_device *csi_dev)
> +{
> +	struct v4l2_subdev *sd = &csi_dev->sd;
> +
> +	v4l2_async_unregister_subdev(sd);
> +	v4l2_subdev_cleanup(sd);
> +	media_entity_cleanup(&sd->entity);
> +	v4l2_async_nf_unregister(&csi_dev->notifier);
> +	v4l2_async_nf_cleanup(&csi_dev->notifier);
> +}
> +
> +static const struct of_device_id rkcif_csi_of_match[] = {
> +	{
> +		.compatible = "rockchip,rk3568-mipi-csi",
> +	},
> +	{}
> +};
> +MODULE_DEVICE_TABLE(of, rkcif_csi_of_match);
> +
> +static int rkcif_csi_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct rkcif_csi_device *csi_dev;
> +	int ret;
> +
> +	csi_dev = devm_kzalloc(dev, sizeof(*csi_dev), GFP_KERNEL);
> +	if (!csi_dev)
> +		return -ENOMEM;
> +	csi_dev->dev = dev;
> +	dev_set_drvdata(dev, csi_dev);
> +
> +	csi_dev->base_addr = devm_platform_ioremap_resource(pdev, 0);
> +	if (IS_ERR(csi_dev->base_addr))
> +		return PTR_ERR(csi_dev->base_addr);
> +
> +	ret = devm_clk_bulk_get_all(dev, &csi_dev->clks);
> +	if (ret != RKCIF_CSI_CLKS_MAX)
> +		return dev_err_probe(dev, -ENODEV, "failed to get clocks\n");
> +	csi_dev->clks_num = ret;
> +
> +	csi_dev->phy = devm_phy_get(dev, NULL);
> +	if (IS_ERR(csi_dev->phy))
> +		return dev_err_probe(dev, PTR_ERR(csi_dev->phy),
> +				     "failed to get MIPI CSI PHY\n");
> +
> +	csi_dev->reset = devm_reset_control_array_get_exclusive(dev);
> +	if (IS_ERR(csi_dev->reset))
> +		return dev_err_probe(dev, PTR_ERR(csi_dev->reset),
> +				     "failed to get reset\n");
> +
> +	csi_dev->formats = formats;
> +	csi_dev->formats_num = ARRAY_SIZE(formats);
> +
> +	pm_runtime_enable(dev);
> +
> +	ret = phy_init(csi_dev->phy);
> +	if (ret) {
> +		ret = dev_err_probe(dev, ret,
> +				    "failed to initialize MIPI CSI PHY\n");
> +		goto err_pm_runtime_disable;
> +	}
> +
> +	ret = rkcif_csi_register(csi_dev);
> +	if (ret)
> +		goto err_phy_exit;
> +
> +	return 0;
> +
> +err_phy_exit:
> +	phy_exit(csi_dev->phy);
> +err_pm_runtime_disable:
> +	pm_runtime_disable(dev);
> +	return ret;
> +}
> +
> +static void rkcif_csi_remove(struct platform_device *pdev)
> +{
> +	struct rkcif_csi_device *csi_dev = platform_get_drvdata(pdev);
> +	struct device *dev = &pdev->dev;
> +
> +	rkcif_csi_unregister(csi_dev);
> +	phy_exit(csi_dev->phy);
> +	pm_runtime_disable(dev);
> +}
> +
> +static int rkcif_csi_runtime_suspend(struct device *dev)
> +{
> +	struct rkcif_csi_device *csi_dev = dev_get_drvdata(dev);
> +
> +	clk_bulk_disable_unprepare(csi_dev->clks_num, csi_dev->clks);
> +
> +	return 0;
> +}
> +
> +static int rkcif_csi_runtime_resume(struct device *dev)
> +{
> +	struct rkcif_csi_device *csi_dev = dev_get_drvdata(dev);
> +	int ret;
> +
> +	reset_control_assert(csi_dev->reset);
> +	udelay(5);
> +	reset_control_deassert(csi_dev->reset);
> +
> +	ret = clk_bulk_prepare_enable(csi_dev->clks_num, csi_dev->clks);
> +	if (ret) {
> +		dev_err(dev, "failed to enable clocks\n");
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct dev_pm_ops rkcif_csi_pm_ops = {
> +	.runtime_suspend = rkcif_csi_runtime_suspend,
> +	.runtime_resume = rkcif_csi_runtime_resume,
> +};
> +
> +static struct platform_driver rkcif_csi_drv = {
> +	.driver = {
> +		   .name = "rockchip-mipi-csi",
> +		   .of_match_table = rkcif_csi_of_match,
> +		   .pm = &rkcif_csi_pm_ops,
> +	},
> +	.probe = rkcif_csi_probe,
> +	.remove = rkcif_csi_remove,
> +};
> +module_platform_driver(rkcif_csi_drv);
> +
> +MODULE_DESCRIPTION("Rockchip MIPI CSI-2 Receiver platform driver");
> +MODULE_LICENSE("GPL");
> 
> --
> 2.39.5
> 
> 
> 


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

* Re: [PATCH v6 06/13] media: rockchip: add a driver for the rockchip camera interface
  2025-04-30  9:15 ` [PATCH v6 06/13] media: rockchip: add a driver for the rockchip camera interface Michael Riesch via B4 Relay
@ 2025-05-01  0:43   ` Bryan O'Donoghue
  2025-05-06 10:37   ` Mehdi Djait
  1 sibling, 0 replies; 34+ messages in thread
From: Bryan O'Donoghue @ 2025-05-01  0:43 UTC (permalink / raw)
  To: michael.riesch, Mehdi Djait, Maxime Chevallier, Théo Lebrun,
	Gerald Loacker, Thomas Petazzoni, Laurent Pinchart,
	Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Heiko Stuebner, Kever Yang, Nicolas Dufresne,
	Sebastian Reichel, Collabora Kernel Team, Paul Kocialkowski,
	Alexander Shiyan, Val Packett, Rob Herring, Philipp Zabel,
	Sakari Ailus
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-rockchip, Michael Riesch, Mehdi Djait

On 30/04/2025 10:15, Michael Riesch via B4 Relay wrote:
> From: Michael Riesch <michael.riesch@wolfvision.net>
> 
> The Rockchip Camera Interface (CIF) is featured in many Rockchip SoCs
> in different variations. For example, the PX30 Video Input Processor (VIP)
> is able to receive video data via the Digital Video Port (DVP, a parallel
> data interface) and transfer it into system memory using a
> double-buffering mechanism called ping-pong mode.
> The RK3568 Video Capture (VICAP) unit, on the other hand, features a DVP
> and a MIPI CSI-2 receiver that can receive video data independently
> (both using the ping-pong scheme).
> The different variants may have additional features, such as scaling
> and/or cropping.
> Finally, the RK3588 VICAP unit constitutes an essential piece of the
> camera interface with one DVP, six MIPI CSI-2 receivers, scale/crop
> units, and a data path multiplexer (to scaler units, to ISP, ...).
> 
> Add a basic media controller centric V4L2 driver for the Rockchip CIF with
>   - support for the PX30 VIP
>   - support for the RK3568 VICAP DVP
>   - abstraction for the ping-pong scheme to allow for future extensions
>   - abstraction for the INTERFACE and CROP parts to allow for future
>     extensions
> 
> [PX30 VIP support v1-v5]
> Co-developed-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
> Signed-off-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
> [PX30 VIP support v6-v13]
> Co-developed-by: Mehdi Djait <mehdi.djait@bootlin.com>
> Signed-off-by: Mehdi Djait <mehdi.djait@bootlin.com>
> [added RK3568 VICAP DVP support]
> [refactored to media controller centric driver, added mplane support]
> Co-developed-by: Gerald Loacker <gerald.loacker@wolfvision.net>
> Signed-off-by: Gerald Loacker <gerald.loacker@wolfvision.net>
> Signed-off-by: Michael Riesch <michael.riesch@wolfvision.net>
> Signed-off-by: Michael Riesch <michael.riesch@collabora.com>
> ---
>   MAINTAINERS                                        |   1 +
>   drivers/media/platform/rockchip/Kconfig            |   1 +
>   drivers/media/platform/rockchip/Makefile           |   1 +
>   drivers/media/platform/rockchip/rkcif/Kconfig      |  15 +
>   drivers/media/platform/rockchip/rkcif/Makefile     |   7 +
>   .../platform/rockchip/rkcif/rkcif-capture-dvp.c    | 858 +++++++++++++++++++++
>   .../platform/rockchip/rkcif/rkcif-capture-dvp.h    |  24 +
>   .../platform/rockchip/rkcif/rkcif-capture-mipi.c   |  27 +
>   .../platform/rockchip/rkcif/rkcif-capture-mipi.h   |  20 +
>   .../media/platform/rockchip/rkcif/rkcif-common.h   | 220 ++++++
>   drivers/media/platform/rockchip/rkcif/rkcif-dev.c  | 299 +++++++
>   .../platform/rockchip/rkcif/rkcif-interface.c      | 423 ++++++++++
>   .../platform/rockchip/rkcif/rkcif-interface.h      |  30 +
>   drivers/media/platform/rockchip/rkcif/rkcif-regs.h | 132 ++++
>   .../media/platform/rockchip/rkcif/rkcif-stream.c   | 622 +++++++++++++++
>   .../media/platform/rockchip/rkcif/rkcif-stream.h   |  31 +
>   16 files changed, 2711 insertions(+)
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index e993ef6f8771..1f67709c2184 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -20868,6 +20868,7 @@ F:	Documentation/admin-guide/media/rkcif*
>   F:	Documentation/devicetree/bindings/media/rockchip,px30-vip.yaml
>   F:	Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi.yaml
>   F:	Documentation/devicetree/bindings/media/rockchip,rk3568-vicap.yaml
> +F:	drivers/media/platform/rockchip/rkcif/
> 
>   ROCKCHIP CRYPTO DRIVERS
>   M:	Corentin Labbe <clabbe@baylibre.com>
> diff --git a/drivers/media/platform/rockchip/Kconfig b/drivers/media/platform/rockchip/Kconfig
> index b41d3960c1b4..549f4e9f443e 100644
> --- a/drivers/media/platform/rockchip/Kconfig
> +++ b/drivers/media/platform/rockchip/Kconfig
> @@ -3,4 +3,5 @@
>   comment "Rockchip media platform drivers"
> 
>   source "drivers/media/platform/rockchip/rga/Kconfig"
> +source "drivers/media/platform/rockchip/rkcif/Kconfig"
>   source "drivers/media/platform/rockchip/rkisp1/Kconfig"
> diff --git a/drivers/media/platform/rockchip/Makefile b/drivers/media/platform/rockchip/Makefile
> index 4f782b876ac9..6aba32c8830c 100644
> --- a/drivers/media/platform/rockchip/Makefile
> +++ b/drivers/media/platform/rockchip/Makefile
> @@ -1,3 +1,4 @@
>   # SPDX-License-Identifier: GPL-2.0-only
>   obj-y += rga/
> +obj-y += rkcif/
>   obj-y += rkisp1/
> diff --git a/drivers/media/platform/rockchip/rkcif/Kconfig b/drivers/media/platform/rockchip/rkcif/Kconfig
> new file mode 100644
> index 000000000000..f53e79a4b42d
> --- /dev/null
> +++ b/drivers/media/platform/rockchip/rkcif/Kconfig
> @@ -0,0 +1,15 @@
> +config VIDEO_ROCKCHIP_CIF
> +	tristate "Rockchip Camera Interface (CIF)"
> +	depends on VIDEO_DEV
> +	depends on ARCH_ROCKCHIP || COMPILE_TEST
> +	depends on V4L_PLATFORM_DRIVERS
> +	depends on PM && COMMON_CLK
> +	select MEDIA_CONTROLLER
> +	select VIDEOBUF2_DMA_CONTIG
> +	select V4L2_FWNODE
> +	select VIDEO_V4L2_SUBDEV_API
> +	help
> +	  This is a driver for Rockchip Camera Interface (CIF). It is featured
> +	  in many Rockchips SoCs in different variations, such as the PX30
> +	  Video Input Processor (VIP, one Digital Video Port (DVP)) or the
> +	  RK3568 Video Capture (VICAP, one DVP, one MIPI CSI-2 receiver) unit.
> diff --git a/drivers/media/platform/rockchip/rkcif/Makefile b/drivers/media/platform/rockchip/rkcif/Makefile
> new file mode 100644
> index 000000000000..818424972c7b
> --- /dev/null
> +++ b/drivers/media/platform/rockchip/rkcif/Makefile
> @@ -0,0 +1,7 @@
> +# SPDX-License-Identifier: GPL-2.0
> +obj-$(CONFIG_VIDEO_ROCKCHIP_CIF) += rockchip-cif.o
> +rockchip-cif-objs += rkcif-dev.o \
> +	rkcif-capture-dvp.o \
> +	rkcif-capture-mipi.o \
> +	rkcif-interface.o \
> +	rkcif-stream.o
> diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-capture-dvp.c b/drivers/media/platform/rockchip/rkcif/rkcif-capture-dvp.c
> new file mode 100644
> index 000000000000..b1370ee4d900
> --- /dev/null
> +++ b/drivers/media/platform/rockchip/rkcif/rkcif-capture-dvp.c
> @@ -0,0 +1,858 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Rockchip Camera Interface (CIF) Driver
> + *
> + * Copyright (C) 2018 Rockchip Electronics Co., Ltd.
> + * Copyright (C) 2020 Maxime Chevallier <maxime.chevallier@bootlin.com>
> + * Copyright (C) 2023 Mehdi Djait <mehdi.djait@bootlin.com>
> + * Copyright (C) 2025 Michael Riesch <michael.riesch@wolfvision.net>
> + */
> +
> +#include <media/v4l2-common.h>
> +#include <media/v4l2-fwnode.h>
> +#include <media/v4l2-mc.h>
> +#include <media/v4l2-subdev.h>
> +
> +#include "rkcif-capture-dvp.h"
> +#include "rkcif-common.h"
> +#include "rkcif-interface.h"
> +#include "rkcif-regs.h"
> +#include "rkcif-stream.h"
> +
> +static const struct rkcif_output_fmt dvp_out_fmts[] = {
> +	{
> +		.fourcc = V4L2_PIX_FMT_NV16,
> +		.dvp_fmt_val = RKCIF_FORMAT_YUV_OUTPUT_422 |
> +			       RKCIF_FORMAT_UV_STORAGE_ORDER_UVUV,
> +		.cplanes = 2,
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_NV16M,
> +		.dvp_fmt_val = RKCIF_FORMAT_YUV_OUTPUT_422 |
> +			       RKCIF_FORMAT_UV_STORAGE_ORDER_UVUV,
> +		.cplanes = 2,
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_NV61,
> +		.dvp_fmt_val = RKCIF_FORMAT_YUV_OUTPUT_422 |
> +			       RKCIF_FORMAT_UV_STORAGE_ORDER_VUVU,
> +		.cplanes = 2,
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_NV61M,
> +		.dvp_fmt_val = RKCIF_FORMAT_YUV_OUTPUT_422 |
> +			       RKCIF_FORMAT_UV_STORAGE_ORDER_VUVU,
> +		.cplanes = 2,
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_NV12,
> +		.dvp_fmt_val = RKCIF_FORMAT_YUV_OUTPUT_420 |
> +			       RKCIF_FORMAT_UV_STORAGE_ORDER_UVUV,
> +		.cplanes = 2,
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_NV12M,
> +		.dvp_fmt_val = RKCIF_FORMAT_YUV_OUTPUT_420 |
> +			       RKCIF_FORMAT_UV_STORAGE_ORDER_UVUV,
> +		.cplanes = 2,
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_NV21,
> +		.dvp_fmt_val = RKCIF_FORMAT_YUV_OUTPUT_420 |
> +			       RKCIF_FORMAT_UV_STORAGE_ORDER_VUVU,
> +		.cplanes = 2,
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_NV21M,
> +		.dvp_fmt_val = RKCIF_FORMAT_YUV_OUTPUT_420 |
> +			       RKCIF_FORMAT_UV_STORAGE_ORDER_VUVU,
> +		.cplanes = 2,
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_RGB24,
> +		.cplanes = 1,
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_RGB565,
> +		.cplanes = 1,
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_BGR666,
> +		.cplanes = 1,
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_SRGGB8,
> +		.cplanes = 1,
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_SGRBG8,
> +		.cplanes = 1,
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_SGBRG8,
> +		.cplanes = 1,
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_SBGGR8,
> +		.cplanes = 1,
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_SRGGB10,
> +		.cplanes = 1,
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_SGRBG10,
> +		.cplanes = 1,
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_SGBRG10,
> +		.cplanes = 1,
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_SBGGR10,
> +		.cplanes = 1,
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_SRGGB12,
> +		.cplanes = 1,
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_SGRBG12,
> +		.cplanes = 1,
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_SGBRG12,
> +		.cplanes = 1,
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_SBGGR12,
> +		.cplanes = 1,
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_SBGGR16,
> +		.cplanes = 1,
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_Y16,
> +		.cplanes = 1,
> +	},
> +};
> +
> +static const struct rkcif_input_fmt px30_dvp_in_fmts[] = {
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
> +		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
> +			       RKCIF_FORMAT_YUV_INPUT_ORDER_YUYV,
> +		.fmt_type = RKCIF_FMT_TYPE_YUV,
> +		.field = V4L2_FIELD_NONE,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
> +		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
> +			       RKCIF_FORMAT_YUV_INPUT_ORDER_YUYV,
> +		.fmt_type = RKCIF_FMT_TYPE_YUV,
> +		.field = V4L2_FIELD_INTERLACED,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_YVYU8_2X8,
> +		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
> +			       RKCIF_FORMAT_YUV_INPUT_ORDER_YVYU,
> +		.fmt_type = RKCIF_FMT_TYPE_YUV,
> +		.field = V4L2_FIELD_NONE,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_YVYU8_2X8,
> +		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
> +			       RKCIF_FORMAT_YUV_INPUT_ORDER_YVYU,
> +		.fmt_type = RKCIF_FMT_TYPE_YUV,
> +		.field = V4L2_FIELD_INTERLACED,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_UYVY8_2X8,
> +		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
> +			       RKCIF_FORMAT_YUV_INPUT_ORDER_UYVY,
> +		.fmt_type = RKCIF_FMT_TYPE_YUV,
> +		.field = V4L2_FIELD_NONE,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_UYVY8_2X8,
> +		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
> +			       RKCIF_FORMAT_YUV_INPUT_ORDER_UYVY,
> +		.fmt_type = RKCIF_FMT_TYPE_YUV,
> +		.field = V4L2_FIELD_INTERLACED,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_VYUY8_2X8,
> +		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
> +			       RKCIF_FORMAT_YUV_INPUT_ORDER_VYUY,
> +		.fmt_type = RKCIF_FMT_TYPE_YUV,
> +		.field = V4L2_FIELD_NONE,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_VYUY8_2X8,
> +		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
> +			       RKCIF_FORMAT_YUV_INPUT_ORDER_VYUY,
> +		.fmt_type = RKCIF_FMT_TYPE_YUV,
> +		.field = V4L2_FIELD_INTERLACED,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8,
> +		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
> +			       RKCIF_FORMAT_RAW_DATA_WIDTH_8,
> +		.fmt_type = RKCIF_FMT_TYPE_RAW,
> +		.field = V4L2_FIELD_NONE,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_SGBRG8_1X8,
> +		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
> +			       RKCIF_FORMAT_RAW_DATA_WIDTH_8,
> +		.fmt_type = RKCIF_FMT_TYPE_RAW,
> +		.field = V4L2_FIELD_NONE,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8,
> +		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
> +			       RKCIF_FORMAT_RAW_DATA_WIDTH_8,
> +		.fmt_type = RKCIF_FMT_TYPE_RAW,
> +		.field = V4L2_FIELD_NONE,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_SRGGB8_1X8,
> +		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
> +			       RKCIF_FORMAT_RAW_DATA_WIDTH_8,
> +		.fmt_type = RKCIF_FMT_TYPE_RAW,
> +		.field = V4L2_FIELD_NONE,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10,
> +		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
> +			       RKCIF_FORMAT_RAW_DATA_WIDTH_10,
> +		.fmt_type = RKCIF_FMT_TYPE_RAW,
> +		.field = V4L2_FIELD_NONE,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10,
> +		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
> +			       RKCIF_FORMAT_RAW_DATA_WIDTH_10,
> +		.fmt_type = RKCIF_FMT_TYPE_RAW,
> +		.field = V4L2_FIELD_NONE,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10,
> +		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
> +			       RKCIF_FORMAT_RAW_DATA_WIDTH_10,
> +		.fmt_type = RKCIF_FMT_TYPE_RAW,
> +		.field = V4L2_FIELD_NONE,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10,
> +		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
> +			       RKCIF_FORMAT_RAW_DATA_WIDTH_10,
> +		.fmt_type = RKCIF_FMT_TYPE_RAW,
> +		.field = V4L2_FIELD_NONE,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12,
> +		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
> +			       RKCIF_FORMAT_RAW_DATA_WIDTH_12,
> +		.fmt_type = RKCIF_FMT_TYPE_RAW,
> +		.field = V4L2_FIELD_NONE,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_SGBRG12_1X12,
> +		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
> +			       RKCIF_FORMAT_RAW_DATA_WIDTH_12,
> +		.fmt_type = RKCIF_FMT_TYPE_RAW,
> +		.field = V4L2_FIELD_NONE,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_SGRBG12_1X12,
> +		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
> +			       RKCIF_FORMAT_RAW_DATA_WIDTH_12,
> +		.fmt_type = RKCIF_FMT_TYPE_RAW,
> +		.field = V4L2_FIELD_NONE,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_SRGGB12_1X12,
> +		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
> +			       RKCIF_FORMAT_RAW_DATA_WIDTH_12,
> +		.fmt_type = RKCIF_FMT_TYPE_RAW,
> +		.field = V4L2_FIELD_NONE,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_RGB888_1X24,
> +		.field = V4L2_FIELD_NONE,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_Y8_1X8,
> +		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
> +			       RKCIF_FORMAT_RAW_DATA_WIDTH_8,
> +		.fmt_type = RKCIF_FMT_TYPE_RAW,
> +		.field = V4L2_FIELD_NONE,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_Y10_1X10,
> +		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
> +			       RKCIF_FORMAT_RAW_DATA_WIDTH_10,
> +		.fmt_type = RKCIF_FMT_TYPE_RAW,
> +		.field = V4L2_FIELD_NONE,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_Y12_1X12,
> +		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
> +			       RKCIF_FORMAT_RAW_DATA_WIDTH_12,
> +		.fmt_type = RKCIF_FMT_TYPE_RAW,
> +		.field = V4L2_FIELD_NONE,
> +	}
> +};
> +
> +const struct rkcif_dvp_match_data rkcif_px30_vip_dvp_match_data = {
> +	.in_fmts = px30_dvp_in_fmts,
> +	.in_fmts_num = ARRAY_SIZE(px30_dvp_in_fmts),
> +	.out_fmts = dvp_out_fmts,
> +	.out_fmts_num = ARRAY_SIZE(dvp_out_fmts),
> +	.has_scaler = true,
> +	.regs = {
> +		[RKCIF_DVP_CTRL] = 0x00,
> +		[RKCIF_DVP_INTEN] = 0x04,
> +		[RKCIF_DVP_INTSTAT] = 0x08,
> +		[RKCIF_DVP_FOR] = 0x0c,
> +		[RKCIF_DVP_LINE_NUM_ADDR] = 0x10,
> +		[RKCIF_DVP_FRM0_ADDR_Y] = 0x14,
> +		[RKCIF_DVP_FRM0_ADDR_UV] = 0x18,
> +		[RKCIF_DVP_FRM1_ADDR_Y] = 0x1c,
> +		[RKCIF_DVP_FRM1_ADDR_UV] = 0x20,
> +		[RKCIF_DVP_VIR_LINE_WIDTH] = 0x24,
> +		[RKCIF_DVP_SET_SIZE] = 0x28,
> +		[RKCIF_DVP_SCL_CTRL] = 0x48,
> +		[RKCIF_DVP_FRAME_STATUS] = 0x60,
> +		[RKCIF_DVP_LAST_LINE] = 0x68,
> +		[RKCIF_DVP_LAST_PIX] = 0x6c,
> +	},
> +};
> +
> +static const struct rkcif_input_fmt rk3568_dvp_in_fmts[] = {
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
> +		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
> +			       RKCIF_FORMAT_YUV_INPUT_ORDER_YUYV,
> +		.fmt_type = RKCIF_FMT_TYPE_YUV,
> +		.field = V4L2_FIELD_NONE,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
> +		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
> +			       RKCIF_FORMAT_YUV_INPUT_ORDER_YUYV,
> +		.fmt_type = RKCIF_FMT_TYPE_YUV,
> +		.field = V4L2_FIELD_INTERLACED,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_YVYU8_2X8,
> +		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
> +			       RKCIF_FORMAT_YUV_INPUT_ORDER_YVYU,
> +		.fmt_type = RKCIF_FMT_TYPE_YUV,
> +		.field = V4L2_FIELD_NONE,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_YVYU8_2X8,
> +		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
> +			       RKCIF_FORMAT_YUV_INPUT_ORDER_YVYU,
> +		.fmt_type = RKCIF_FMT_TYPE_YUV,
> +		.field = V4L2_FIELD_INTERLACED,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_UYVY8_2X8,
> +		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
> +			       RKCIF_FORMAT_YUV_INPUT_ORDER_UYVY,
> +		.fmt_type = RKCIF_FMT_TYPE_YUV,
> +		.field = V4L2_FIELD_NONE,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_UYVY8_2X8,
> +		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
> +			       RKCIF_FORMAT_YUV_INPUT_ORDER_UYVY,
> +		.fmt_type = RKCIF_FMT_TYPE_YUV,
> +		.field = V4L2_FIELD_INTERLACED,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_VYUY8_2X8,
> +		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
> +			       RKCIF_FORMAT_YUV_INPUT_ORDER_VYUY,
> +		.fmt_type = RKCIF_FMT_TYPE_YUV,
> +		.field = V4L2_FIELD_NONE,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_VYUY8_2X8,
> +		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
> +			       RKCIF_FORMAT_YUV_INPUT_ORDER_VYUY,
> +		.fmt_type = RKCIF_FMT_TYPE_YUV,
> +		.field = V4L2_FIELD_INTERLACED,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_YUYV8_1X16,
> +		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
> +			       RKCIF_FORMAT_YUV_INPUT_ORDER_YUYV |
> +			       RKCIF_FORMAT_INPUT_MODE_BT1120 |
> +			       RKCIF_FORMAT_BT1120_TRANSMIT_PROGRESS,
> +		.field = V4L2_FIELD_NONE,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_YUYV8_1X16,
> +		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
> +			       RKCIF_FORMAT_YUV_INPUT_ORDER_YUYV |
> +			       RKCIF_FORMAT_INPUT_MODE_BT1120,
> +		.field = V4L2_FIELD_INTERLACED,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_YVYU8_1X16,
> +		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
> +			       RKCIF_FORMAT_YUV_INPUT_ORDER_YVYU |
> +			       RKCIF_FORMAT_INPUT_MODE_BT1120 |
> +			       RKCIF_FORMAT_BT1120_TRANSMIT_PROGRESS,
> +		.field = V4L2_FIELD_NONE,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_YVYU8_1X16,
> +		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
> +			       RKCIF_FORMAT_YUV_INPUT_ORDER_YVYU |
> +			       RKCIF_FORMAT_INPUT_MODE_BT1120,
> +		.field = V4L2_FIELD_INTERLACED,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_UYVY8_1X16,
> +		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
> +			       RKCIF_FORMAT_YUV_INPUT_ORDER_YUYV |
> +			       RKCIF_FORMAT_INPUT_MODE_BT1120 |
> +			       RKCIF_FORMAT_BT1120_YC_SWAP |
> +			       RKCIF_FORMAT_BT1120_TRANSMIT_PROGRESS,
> +		.field = V4L2_FIELD_NONE,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_UYVY8_1X16,
> +		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
> +			       RKCIF_FORMAT_YUV_INPUT_ORDER_YUYV |
> +			       RKCIF_FORMAT_BT1120_YC_SWAP |
> +			       RKCIF_FORMAT_INPUT_MODE_BT1120,
> +		.field = V4L2_FIELD_INTERLACED,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_VYUY8_1X16,
> +		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
> +			       RKCIF_FORMAT_YUV_INPUT_ORDER_YVYU |
> +			       RKCIF_FORMAT_INPUT_MODE_BT1120 |
> +			       RKCIF_FORMAT_BT1120_YC_SWAP |
> +			       RKCIF_FORMAT_BT1120_TRANSMIT_PROGRESS,
> +		.field = V4L2_FIELD_NONE,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_VYUY8_1X16,
> +		.dvp_fmt_val = RKCIF_FORMAT_YUV_INPUT_422 |
> +			       RKCIF_FORMAT_YUV_INPUT_ORDER_YVYU |
> +			       RKCIF_FORMAT_BT1120_YC_SWAP |
> +			       RKCIF_FORMAT_INPUT_MODE_BT1120,
> +		.field = V4L2_FIELD_INTERLACED,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8,
> +		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
> +			       RKCIF_FORMAT_RAW_DATA_WIDTH_8,
> +		.fmt_type = RKCIF_FMT_TYPE_RAW,
> +		.field = V4L2_FIELD_NONE,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_SGBRG8_1X8,
> +		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
> +			       RKCIF_FORMAT_RAW_DATA_WIDTH_8,
> +		.fmt_type = RKCIF_FMT_TYPE_RAW,
> +		.field = V4L2_FIELD_NONE,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8,
> +		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
> +			       RKCIF_FORMAT_RAW_DATA_WIDTH_8,
> +		.fmt_type = RKCIF_FMT_TYPE_RAW,
> +		.field = V4L2_FIELD_NONE,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_SRGGB8_1X8,
> +		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
> +			       RKCIF_FORMAT_RAW_DATA_WIDTH_8,
> +		.fmt_type = RKCIF_FMT_TYPE_RAW,
> +		.field = V4L2_FIELD_NONE,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10,
> +		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
> +			       RKCIF_FORMAT_RAW_DATA_WIDTH_10,
> +		.fmt_type = RKCIF_FMT_TYPE_RAW,
> +		.field = V4L2_FIELD_NONE,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10,
> +		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
> +			       RKCIF_FORMAT_RAW_DATA_WIDTH_10,
> +		.fmt_type = RKCIF_FMT_TYPE_RAW,
> +		.field = V4L2_FIELD_NONE,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10,
> +		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
> +			       RKCIF_FORMAT_RAW_DATA_WIDTH_10,
> +		.fmt_type = RKCIF_FMT_TYPE_RAW,
> +		.field = V4L2_FIELD_NONE,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10,
> +		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
> +			       RKCIF_FORMAT_RAW_DATA_WIDTH_10,
> +		.fmt_type = RKCIF_FMT_TYPE_RAW,
> +		.field = V4L2_FIELD_NONE,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12,
> +		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
> +			       RKCIF_FORMAT_RAW_DATA_WIDTH_12,
> +		.fmt_type = RKCIF_FMT_TYPE_RAW,
> +		.field = V4L2_FIELD_NONE,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_SGBRG12_1X12,
> +		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
> +			       RKCIF_FORMAT_RAW_DATA_WIDTH_12,
> +		.fmt_type = RKCIF_FMT_TYPE_RAW,
> +		.field = V4L2_FIELD_NONE,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_SGRBG12_1X12,
> +		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
> +			       RKCIF_FORMAT_RAW_DATA_WIDTH_12,
> +		.fmt_type = RKCIF_FMT_TYPE_RAW,
> +		.field = V4L2_FIELD_NONE,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_SRGGB12_1X12,
> +		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
> +			       RKCIF_FORMAT_RAW_DATA_WIDTH_12,
> +		.fmt_type = RKCIF_FMT_TYPE_RAW,
> +		.field = V4L2_FIELD_NONE,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_RGB888_1X24,
> +		.field = V4L2_FIELD_NONE,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_Y8_1X8,
> +		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
> +			       RKCIF_FORMAT_RAW_DATA_WIDTH_8,
> +		.fmt_type = RKCIF_FMT_TYPE_RAW,
> +		.field = V4L2_FIELD_NONE,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_Y10_1X10,
> +		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
> +			       RKCIF_FORMAT_RAW_DATA_WIDTH_10,
> +		.fmt_type = RKCIF_FMT_TYPE_RAW,
> +		.field = V4L2_FIELD_NONE,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_Y12_1X12,
> +		.dvp_fmt_val = RKCIF_FORMAT_INPUT_MODE_RAW |
> +			       RKCIF_FORMAT_RAW_DATA_WIDTH_12,
> +		.fmt_type = RKCIF_FMT_TYPE_RAW,
> +		.field = V4L2_FIELD_NONE,
> +	},
> +};
> +
> +static void rk3568_dvp_grf_setup(struct rkcif_device *rkcif)
> +{
> +	u32 con1 = RK3568_GRF_WRITE_ENABLE(RK3568_GRF_VI_CON1_CIF_DATAPATH |
> +					   RK3568_GRF_VI_CON1_CIF_CLK_DELAYNUM);
> +
> +	if (!rkcif->grf)
> +		return;
> +
> +	con1 |= rkcif->interfaces[RKCIF_DVP].dvp.dvp_clk_delay &
> +		RK3568_GRF_VI_CON1_CIF_CLK_DELAYNUM;
> +
> +	if (rkcif->interfaces[RKCIF_DVP].vep.bus.parallel.flags &
> +	    V4L2_MBUS_PCLK_SAMPLE_DUALEDGE)
> +		con1 |= RK3568_GRF_VI_CON1_CIF_DATAPATH;
> +
> +	regmap_write(rkcif->grf, RK3568_GRF_VI_CON1, con1);
> +}
> +
> +const struct rkcif_dvp_match_data rkcif_rk3568_vicap_dvp_match_data = {
> +	.in_fmts = rk3568_dvp_in_fmts,
> +	.in_fmts_num = ARRAY_SIZE(rk3568_dvp_in_fmts),
> +	.out_fmts = dvp_out_fmts,
> +	.out_fmts_num = ARRAY_SIZE(dvp_out_fmts),
> +	.setup = rk3568_dvp_grf_setup,
> +	.has_scaler = false,
> +	.regs = {
> +		[RKCIF_DVP_CTRL] = 0x00,
> +		[RKCIF_DVP_INTEN] = 0x04,
> +		[RKCIF_DVP_INTSTAT] = 0x08,
> +		[RKCIF_DVP_FOR] = 0x0c,
> +		[RKCIF_DVP_LINE_NUM_ADDR] = 0x2c,
> +		[RKCIF_DVP_FRM0_ADDR_Y] = 0x14,
> +		[RKCIF_DVP_FRM0_ADDR_UV] = 0x18,
> +		[RKCIF_DVP_FRM1_ADDR_Y] = 0x1c,
> +		[RKCIF_DVP_FRM1_ADDR_UV] = 0x20,
> +		[RKCIF_DVP_VIR_LINE_WIDTH] = 0x24,
> +		[RKCIF_DVP_SET_SIZE] = 0x28,
> +		[RKCIF_DVP_CROP] = 0x34,
> +		[RKCIF_DVP_FRAME_STATUS] = 0x3c,
> +		[RKCIF_DVP_LAST_LINE] = 0x44,
> +		[RKCIF_DVP_LAST_PIX] = 0x48,
> +	},
> +};
> +
> +static inline unsigned int cif_dvp_get_addr(struct rkcif_device *rkcif,
> +					    unsigned int index)
> +{
> +	if (WARN_ON_ONCE(index >= RKCIF_DVP_REGISTER_MAX))
> +		return RKCIF_REGISTER_NOTSUPPORTED;
> +
> +	return rkcif->match_data->dvp->regs[index];
> +}
> +
> +static inline __maybe_unused void cif_dvp_write(struct rkcif_device *rkcif,
> +						unsigned int index, u32 val)
> +{
> +	unsigned int addr = cif_dvp_get_addr(rkcif, index);
> +
> +	if (addr == RKCIF_REGISTER_NOTSUPPORTED)
> +		return;
> +
> +	writel(val, rkcif->base_addr + addr);
> +}
> +
> +static inline __maybe_unused u32 cif_dvp_read(struct rkcif_device *rkcif,
> +					      unsigned int index)
> +{
> +	unsigned int addr = cif_dvp_get_addr(rkcif, index);
> +
> +	if (addr == RKCIF_REGISTER_NOTSUPPORTED)
> +		return 0;
> +
> +	return readl(rkcif->base_addr + addr);
> +}
> +
> +static void cif_dvp_queue_buffer(struct rkcif_stream *stream,
> +				 unsigned int index)
> +{
> +	struct rkcif_device *rkcif = stream->rkcif;
> +	struct rkcif_buffer *buffer = stream->buffers[index];
> +	u32 frm_addr_y, frm_addr_uv;
> +
> +	frm_addr_y = index ? RKCIF_DVP_FRM1_ADDR_Y : RKCIF_DVP_FRM0_ADDR_Y;
> +	frm_addr_uv = index ? RKCIF_DVP_FRM1_ADDR_UV : RKCIF_DVP_FRM0_ADDR_UV;
> +
> +	cif_dvp_write(rkcif, frm_addr_y, buffer->buff_addr[RKCIF_PLANE_Y]);
> +	cif_dvp_write(rkcif, frm_addr_uv, buffer->buff_addr[RKCIF_PLANE_UV]);
> +}
> +
> +static int cif_dvp_start_streaming(struct rkcif_stream *stream)
> +{
> +	struct rkcif_device *rkcif = stream->rkcif;
> +	struct rkcif_interface *interface = stream->interface;
> +	struct v4l2_mbus_config_parallel *parallel;
> +	struct v4l2_mbus_framefmt *source_fmt;
> +	struct v4l2_subdev_state *state;
> +	const struct rkcif_input_fmt *active_in_fmt;
> +	const struct rkcif_output_fmt *active_out_fmt;
> +	u32 val = 0;
> +	int ret = -EINVAL;
> +
> +	state = v4l2_subdev_lock_and_get_active_state(&interface->sd);
> +	source_fmt = v4l2_subdev_state_get_format(state, RKCIF_IF_PAD_SRC,
> +						  stream->id);
> +	if (!source_fmt)
> +		goto out;
> +
> +	active_in_fmt = rkcif_interface_find_input_fmt(interface, false,
> +						       source_fmt->code);
> +	active_out_fmt = rkcif_stream_find_output_fmt(stream, false,
> +						      stream->pix.pixelformat);
> +	if (!active_in_fmt || !active_out_fmt)
> +		goto out;
> +
> +	parallel = &interface->vep.bus.parallel;
> +	if (parallel->bus_width == 16 &&
> +	    (parallel->flags & V4L2_MBUS_PCLK_SAMPLE_DUALEDGE))
> +		val |= RKCIF_FORMAT_BT1120_CLOCK_DOUBLE_EDGES;
> +	val |= active_in_fmt->dvp_fmt_val;
> +	val |= active_out_fmt->dvp_fmt_val;
> +	cif_dvp_write(rkcif, RKCIF_DVP_FOR, val);
> +
> +	val = stream->pix.width;
> +	if (active_in_fmt->fmt_type == RKCIF_FMT_TYPE_RAW)
> +		val = stream->pix.width * 2;
> +	cif_dvp_write(rkcif, RKCIF_DVP_VIR_LINE_WIDTH, val);
> +
> +	val = RKCIF_XY_COORD(stream->pix.width, stream->pix.height);
> +	cif_dvp_write(rkcif, RKCIF_DVP_SET_SIZE, val);
> +
> +	cif_dvp_write(rkcif, RKCIF_DVP_FRAME_STATUS, RKCIF_FRAME_STAT_CLS);
> +	cif_dvp_write(rkcif, RKCIF_DVP_INTSTAT, RKCIF_INTSTAT_CLS);
> +	if (rkcif->match_data->dvp->has_scaler) {
> +		val = active_in_fmt->fmt_type == RKCIF_FMT_TYPE_YUV ?
> +			      RKCIF_SCL_CTRL_ENABLE_YUV_16BIT_BYPASS :
> +			      RKCIF_SCL_CTRL_ENABLE_RAW_16BIT_BYPASS;
> +		cif_dvp_write(rkcif, RKCIF_DVP_SCL_CTRL, val);
> +	}
> +
> +	cif_dvp_write(rkcif, RKCIF_DVP_INTEN,
> +		      RKCIF_INTEN_FRAME_END_EN |
> +			      RKCIF_INTEN_PST_INF_FRAME_END_EN);
> +
> +	cif_dvp_write(rkcif, RKCIF_DVP_CTRL,
> +		      RKCIF_CTRL_AXI_BURST_16 | RKCIF_CTRL_MODE_PINGPONG |
> +			      RKCIF_CTRL_ENABLE_CAPTURE);

This indentation looks misaligned.

> +
> +	ret = 0;
> +
> +out:
> +	v4l2_subdev_unlock_state(state);
> +	return ret;
> +}
> +
> +static void cif_dvp_stop_streaming(struct rkcif_stream *stream)
> +{
> +	struct rkcif_device *rkcif = stream->rkcif;
> +	u32 val;
> +
> +	val = cif_dvp_read(rkcif, RKCIF_DVP_CTRL);
> +	cif_dvp_write(rkcif, RKCIF_DVP_CTRL,
> +		      val & (~RKCIF_CTRL_ENABLE_CAPTURE));
> +	cif_dvp_write(rkcif, RKCIF_DVP_INTEN, 0x0);
> +	cif_dvp_write(rkcif, RKCIF_DVP_INTSTAT, 0x3ff);
> +	cif_dvp_write(rkcif, RKCIF_DVP_FRAME_STATUS, 0x0);
> +
> +	stream->stopping = false;
> +}
> +
> +static void cif_dvp_reset_stream(struct rkcif_device *rkcif)
> +{
> +	u32 ctl = cif_dvp_read(rkcif, RKCIF_DVP_CTRL);
> +
> +	cif_dvp_write(rkcif, RKCIF_DVP_CTRL,
> +		      ctl & (~RKCIF_CTRL_ENABLE_CAPTURE));
> +	cif_dvp_write(rkcif, RKCIF_DVP_CTRL, ctl | RKCIF_CTRL_ENABLE_CAPTURE);
> +}
> +
> +static void rkcif_dvp_set_crop(struct rkcif_stream *stream, u16 left, u16 top)
> +{
> +	struct rkcif_device *rkcif = stream->rkcif;
> +	u32 val;
> +
> +	val = RKCIF_XY_COORD(left, top);
> +	cif_dvp_write(rkcif, RKCIF_DVP_CROP, val);
> +}
> +
> +irqreturn_t rkcif_dvp_isr(int irq, void *ctx)
> +{
> +	struct device *dev = ctx;
> +	struct rkcif_device *rkcif = dev_get_drvdata(dev);
> +	struct rkcif_stream *stream;
> +	u32 intstat, lastline, lastpix, cif_frmst;
> +	irqreturn_t ret = IRQ_NONE;
> +
> +	if (!rkcif->match_data->dvp)
> +		return ret;
> +
> +	intstat = cif_dvp_read(rkcif, RKCIF_DVP_INTSTAT);
> +	cif_frmst = cif_dvp_read(rkcif, RKCIF_DVP_FRAME_STATUS);
> +	lastline = RKCIF_FETCH_Y(cif_dvp_read(rkcif, RKCIF_DVP_LAST_LINE));
> +	lastpix = RKCIF_FETCH_Y(cif_dvp_read(rkcif, RKCIF_DVP_LAST_PIX));
> +
> +	if (intstat & RKCIF_INTSTAT_FRAME_END) {
> +		cif_dvp_write(rkcif, RKCIF_DVP_INTSTAT,
> +			      RKCIF_INTSTAT_FRAME_END_CLR |
> +				      RKCIF_INTSTAT_LINE_END_CLR);

again looks misaligned - perhaps just my mailer but, please check.

> +
> +		stream = &rkcif->interfaces[RKCIF_DVP].streams[RKCIF_ID0];
> +
> +		if (stream->stopping) {
> +			cif_dvp_stop_streaming(stream);
> +			wake_up(&stream->wq_stopped);

since you have a ret would it not be more consistent to ret = 
IRQ_HANDLED and then have a goto > +			return IRQ_HANDLED;
> +		}
> +
> +		if (lastline != stream->pix.height) {
> +			v4l2_err(&rkcif->v4l2_dev,
> +				 "bad frame, irq:%#x frmst:%#x size:%dx%d\n",
> +				 intstat, cif_frmst, lastpix, lastline);
> +
> +			cif_dvp_reset_stream(rkcif);
> +		}
> +
> +		rkcif_stream_pingpong(stream);
> +
> +		ret = IRQ_HANDLED;
> +	}

down here.

> +	return ret;
> +}
> +
> +int rkcif_dvp_register(struct rkcif_device *rkcif)
> +{
> +	struct rkcif_interface *interface;
> +	int ret, i;
> +
> +	if (!rkcif->match_data->dvp)
> +		return 0;
> +
> +	interface = &rkcif->interfaces[RKCIF_DVP];
> +	interface->index = RKCIF_DVP;
> +	interface->type = RKCIF_IF_DVP;
> +	interface->in_fmts = rkcif->match_data->dvp->in_fmts;
> +	interface->in_fmts_num = rkcif->match_data->dvp->in_fmts_num;
> +	interface->set_crop = rkcif_dvp_set_crop;
> +	ret = rkcif_interface_register(rkcif, interface);
> +	if (ret)
> +		return 0;
> +
> +	if (rkcif->match_data->dvp->setup)
> +		rkcif->match_data->dvp->setup(rkcif);
> +
> +	interface->streams_num = rkcif->match_data->dvp->has_ids ? 4 : 1;
> +	for (i = 0; i < interface->streams_num; i++) {
> +		struct rkcif_stream *stream = &interface->streams[i];
> +
> +		stream->id = i;
> +		stream->interface = interface;
> +		stream->out_fmts = rkcif->match_data->dvp->out_fmts;
> +		stream->out_fmts_num = rkcif->match_data->dvp->out_fmts_num;
> +		stream->queue_buffer = cif_dvp_queue_buffer;
> +		stream->start_streaming = cif_dvp_start_streaming;
> +		stream->stop_streaming = cif_dvp_stop_streaming;
> +
> +		ret = rkcif_stream_register(rkcif, stream);
> +		if (ret)
> +			goto err_streams_unregister;
> +	}
\n> +	return 0;
> +
> +err_streams_unregister:
> +	for (; i >= 0; i--)
> +		rkcif_stream_unregister(&interface->streams[i]);
> +	rkcif_interface_unregister(interface);
> +
> +	return ret;
> +}
> +
> +void rkcif_dvp_unregister(struct rkcif_device *rkcif)
> +{
> +	struct rkcif_interface *interface;
> +	int i;
> +
> +	if (!rkcif->match_data->dvp)
> +		return;
> +
> +	interface = &rkcif->interfaces[RKCIF_DVP];
> +
> +	for (i = 0; i < interface->streams_num; i++)
> +		rkcif_stream_unregister(&interface->streams[i]);
> +	rkcif_interface_unregister(interface);
> +}
> diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-capture-dvp.h b/drivers/media/platform/rockchip/rkcif/rkcif-capture-dvp.h
> new file mode 100644
> index 000000000000..4bd72d41ec2f
> --- /dev/null
> +++ b/drivers/media/platform/rockchip/rkcif/rkcif-capture-dvp.h
> @@ -0,0 +1,24 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Rockchip Camera Interface (CIF) Driver
> + *
> + * Copyright (C) 2018 Rockchip Electronics Co., Ltd.
> + * Copyright (C) 2023 Mehdi Djait <mehdi.djait@bootlin.com>
> + * Copyright (C) 2025 Michael Riesch <michael.riesch@wolfvision.net>
> + */
> +
> +#ifndef _RKCIF_CAPTURE_DVP_H
> +#define _RKCIF_CAPTURE_DVP_H
> +
> +#include "rkcif-common.h"
> +
> +extern const struct rkcif_dvp_match_data rkcif_px30_vip_dvp_match_data;
> +extern const struct rkcif_dvp_match_data rkcif_rk3568_vicap_dvp_match_data;
> +
> +int rkcif_dvp_register(struct rkcif_device *rkcif);
> +
> +void rkcif_dvp_unregister(struct rkcif_device *rkcif);
> +
> +irqreturn_t rkcif_dvp_isr(int irq, void *ctx);
> +
> +#endif
> diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.c b/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.c
> new file mode 100644
> index 000000000000..0c3f7b8cfa18
> --- /dev/null
> +++ b/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.c
> @@ -0,0 +1,27 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Rockchip Camera Interface (CIF) Driver
> + *
> + * Copyright (C) 2018 Rockchip Electronics Co., Ltd.
> + * Copyright (C) 2025 Michael Riesch <michael.riesch@wolfvision.net>
> + */
> +
> +#include "rkcif-capture-mipi.h"
> +#include "rkcif-common.h"
> +#include "rkcif-stream.h"
> +
> +irqreturn_t rkcif_mipi_isr(int irq, void *ctx)
> +{
> +	irqreturn_t ret = IRQ_NONE;
> +
> +	return ret;
> +}
> +
> +int rkcif_mipi_register(struct rkcif_device *rkcif)
> +{
> +	return 0;
> +}
> +
> +void rkcif_mipi_unregister(struct rkcif_device *rkcif)
> +{
> +}
> diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.h b/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.h
> new file mode 100644
> index 000000000000..ee1a50a59505
> --- /dev/null
> +++ b/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.h
> @@ -0,0 +1,20 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Rockchip Camera Interface (CIF) Driver
> + *
> + * Copyright (C) 2018 Rockchip Electronics Co., Ltd.
> + * Copyright (C) 2025 Michael Riesch <michael.riesch@wolfvision.net>
> + */
> +
> +#ifndef _RKCIF_CAPTURE_MIPI_H
> +#define _RKCIF_CAPTURE_MIPI_H
> +
> +#include "rkcif-common.h"
> +
> +int rkcif_mipi_register(struct rkcif_device *rkcif);
> +
> +void rkcif_mipi_unregister(struct rkcif_device *rkcif);
> +
> +irqreturn_t rkcif_mipi_isr(int irq, void *ctx);
> +
> +#endif
> diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-common.h b/drivers/media/platform/rockchip/rkcif/rkcif-common.h
> new file mode 100644
> index 000000000000..62fb3580eec5
> --- /dev/null
> +++ b/drivers/media/platform/rockchip/rkcif/rkcif-common.h
> @@ -0,0 +1,220 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Rockchip Camera Interface (CIF) Driver
> + *
> + * Copyright (C) 2018 Rockchip Electronics Co., Ltd.
> + * Copyright (C) 2023 Mehdi Djait <mehdi.djait@bootlin.com>
> + * Copyright (C) 2025 Michael Riesch <michael.riesch@wolfvision.net>
> + */
> +
> +#ifndef _RKCIF_COMMON_H
> +#define _RKCIF_COMMON_H
> +
> +#include <linux/clk.h>
> +#include <linux/mutex.h>
> +#include <linux/regmap.h>
> +
> +#include <media/media-device.h>
> +#include <media/media-entity.h>
> +#include <media/v4l2-common.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-fwnode.h>
> +#include <media/v4l2-mc.h>
> +#include <media/v4l2-subdev.h>
> +#include <media/videobuf2-v4l2.h>
> +
> +#include "rkcif-regs.h"
> +
> +#define RKCIF_DRIVER_NAME "rockchip-cif"
> +#define RKCIF_CLK_MAX	  4
> +
> +enum rkcif_format_type {
> +	RKCIF_FMT_TYPE_INVALID,
> +	RKCIF_FMT_TYPE_YUV,
> +	RKCIF_FMT_TYPE_RAW,
> +};
> +
> +enum rkcif_id_index {
> +	RKCIF_ID0,
> +	RKCIF_ID1,
> +	RKCIF_ID2,
> +	RKCIF_ID3,
> +	RKCIF_ID_MAX
> +};
> +
> +enum rkcif_interface_index {
> +	RKCIF_DVP,
> +	RKCIF_MIPI_BASE,
> +	RKCIF_MIPI1 = RKCIF_MIPI_BASE,
> +	RKCIF_MIPI2,
> +	RKCIF_MIPI3,
> +	RKCIF_MIPI4,
> +	RKCIF_MIPI5,
> +	RKCIF_MIPI6,
> +	RKCIF_MIPI_MAX = RKCIF_MIPI6,
> +	RKCIF_IF_MAX = RKCIF_MIPI_MAX
> +};
> +
> +enum rkcif_interface_pad_index {
> +	RKCIF_IF_PAD_SINK,
> +	RKCIF_IF_PAD_SRC,
> +	RKCIF_IF_PAD_MAX
> +};
> +
> +enum rkcif_interface_status {
> +	RKCIF_IF_INACTIVE,
> +	RKCIF_IF_ACTIVE,
> +};
> +
> +enum rkcif_interface_type {
> +	RKCIF_IF_INVALID,
> +	RKCIF_IF_DVP,
> +	RKCIF_IF_MIPI,
> +};
> +
> +enum rkcif_plane_index {
> +	RKCIF_PLANE_Y,
> +	RKCIF_PLANE_UV,
> +	RKCIF_PLANE_MAX
> +};
> +
> +struct rkcif_input_fmt {
> +	u32 mbus_code;
> +
> +	enum rkcif_format_type fmt_type;
> +	enum v4l2_field field;
> +
> +	union {
> +		u32 dvp_fmt_val;
> +	};
> +};
> +
> +struct rkcif_output_fmt {
> +	u32 fourcc;
> +	u32 mbus_code;
> +	u8 cplanes;
> +
> +	union {
> +		u32 dvp_fmt_val;
> +	};
> +};
> +
> +struct rkcif_buffer {
> +	struct vb2_v4l2_buffer vb;
> +	struct list_head queue;
> +	dma_addr_t buff_addr[VIDEO_MAX_PLANES];
> +	bool is_dummy;
> +};
> +
> +struct rkcif_dummy_buffer {
> +	struct rkcif_buffer buffer;
> +	void *vaddr;
> +	u32 size;
> +};
> +
> +struct rkcif_interface;
> +
> +struct rkcif_remote {
> +	struct v4l2_async_connection async_conn;
> +	struct v4l2_subdev *sd;
> +
> +	struct rkcif_interface *interface;
> +};
> +
> +struct rkcif_stream {
> +	enum rkcif_id_index id;
> +	struct rkcif_device *rkcif;
> +	struct rkcif_interface *interface;
> +	const struct rkcif_output_fmt *out_fmts;
> +	unsigned int out_fmts_num;
> +
> +	/* in ping-pong mode, two buffers can be provided to the HW */
> +	struct rkcif_buffer *buffers[2];
> +	int frame_idx;
> +	int frame_phase;
> +
> +	/* in case of no available buffer, HW can write to the dummy buffer */
> +	struct rkcif_dummy_buffer dummy;
> +
> +	bool stopping;
> +	wait_queue_head_t wq_stopped;
> +
> +	/* queue of available buffers plus spinlock that protects it */
> +	spinlock_t driver_queue_lock;
> +	struct list_head driver_queue;
> +
> +	/* lock used by the V4L2 core */
> +	struct mutex vlock;
> +
> +	struct media_pad pad;
> +	struct media_pipeline pipeline;
> +	struct v4l2_pix_format_mplane pix;
> +	struct vb2_queue buf_queue;
> +	struct video_device vdev;
> +
> +	void (*queue_buffer)(struct rkcif_stream *stream, unsigned int index);
> +	int (*start_streaming)(struct rkcif_stream *stream);
> +	void (*stop_streaming)(struct rkcif_stream *stream);
> +};
> +
> +struct rkcif_dvp {
> +	u32 dvp_clk_delay;
> +};
> +
> +struct rkcif_interface {
> +	enum rkcif_interface_type type;
> +	enum rkcif_interface_status status;
> +	enum rkcif_interface_index index;
> +	struct rkcif_device *rkcif;
> +	struct rkcif_remote *remote;
> +	struct rkcif_stream streams[RKCIF_ID_MAX];
> +	unsigned int streams_num;
> +	const struct rkcif_input_fmt *in_fmts;
> +	unsigned int in_fmts_num;
> +
> +	struct media_pad pads[RKCIF_IF_PAD_MAX];
> +	struct v4l2_fwnode_endpoint vep;
> +	struct v4l2_subdev sd;
> +
> +	union {
> +		struct rkcif_dvp dvp;
> +	};
> +
> +	void (*set_crop)(struct rkcif_stream *stream, u16 left, u16 top);
> +};
> +
> +struct rkcif_dvp_match_data {
> +	const struct rkcif_input_fmt *in_fmts;
> +	unsigned int in_fmts_num;
> +	const struct rkcif_output_fmt *out_fmts;
> +	unsigned int out_fmts_num;
> +	void (*setup)(struct rkcif_device *rkcif);
> +	bool has_scaler;
> +	bool has_ids;
> +	unsigned int regs[RKCIF_DVP_REGISTER_MAX];
> +};
> +
> +struct rkcif_match_data {
> +	const char *const *clks;
> +	unsigned int clks_num;
> +	const struct rkcif_dvp_match_data *dvp;
> +};
> +
> +struct rkcif_device {
> +	struct device *dev;
> +
> +	const struct rkcif_match_data *match_data;
> +	struct clk_bulk_data clks[RKCIF_CLK_MAX];
> +	unsigned int clks_num;
> +	struct regmap *grf;
> +	struct reset_control *reset;
> +	void __iomem *base_addr;
> +
> +	struct rkcif_interface interfaces[RKCIF_IF_MAX];
> +
> +	struct media_device media_dev;
> +	struct v4l2_device v4l2_dev;
> +	struct v4l2_async_notifier notifier;
> +};
> +
> +#endif
> diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-dev.c b/drivers/media/platform/rockchip/rkcif/rkcif-dev.c
> new file mode 100644
> index 000000000000..2dcd35771fc9
> --- /dev/null
> +++ b/drivers/media/platform/rockchip/rkcif/rkcif-dev.c
> @@ -0,0 +1,299 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Rockchip Camera Interface (CIF) Driver
> + *
> + * Copyright (C) 2018 Rockchip Electronics Co., Ltd.
> + * Copyright (C) 2020 Maxime Chevallier <maxime.chevallier@bootlin.com>
> + * Copyright (C) 2023 Mehdi Djait <mehdi.djait@bootlin.com>
> + * Copyright (C) 2025 Michael Riesch <michael.riesch@wolfvision.net>
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/interrupt.h>
> +#include <linux/mfd/syscon.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_graph.h>
> +#include <linux/of_platform.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/reset.h>
> +
> +#include <media/v4l2-fwnode.h>
> +#include <media/v4l2-mc.h>
> +
> +#include "rkcif-capture-dvp.h"
> +#include "rkcif-capture-mipi.h"
> +#include "rkcif-common.h"
> +
> +static const char *const px30_vip_clks[] = {
> +	"aclk",
> +	"hclk",
> +	"pclk",
> +};
> +
> +static const struct rkcif_match_data px30_vip_match_data = {
> +	.clks = px30_vip_clks,
> +	.clks_num = ARRAY_SIZE(px30_vip_clks),
> +	.dvp = &rkcif_px30_vip_dvp_match_data,
> +};
> +
> +static const char *const rk3568_vicap_clks[] = {
> +	"aclk",
> +	"hclk",
> +	"dclk",
> +	"iclk",
> +};
> +
> +static const struct rkcif_match_data rk3568_vicap_match_data = {
> +	.clks = rk3568_vicap_clks,
> +	.clks_num = ARRAY_SIZE(rk3568_vicap_clks),
> +	.dvp = &rkcif_rk3568_vicap_dvp_match_data,
> +};
> +
> +static const struct of_device_id rkcif_plat_of_match[] = {
> +	{
> +		.compatible = "rockchip,px30-vip",
> +		.data = &px30_vip_match_data,
> +	},
> +	{
> +		.compatible = "rockchip,rk3568-vicap",
> +		.data = &rk3568_vicap_match_data,
> +	},
> +	{}
> +};
> +MODULE_DEVICE_TABLE(of, rkcif_plat_of_match);
> +
> +static int rkcif_notifier_bound(struct v4l2_async_notifier *notifier,
> +				struct v4l2_subdev *sd,
> +				struct v4l2_async_connection *asd)
> +{
> +	struct rkcif_device *rkcif =
> +		container_of(notifier, struct rkcif_device, notifier);
> +	struct rkcif_remote *remote =
> +		container_of(asd, struct rkcif_remote, async_conn);
> +	struct media_pad *sink_pad =
> +		&remote->interface->pads[RKCIF_IF_PAD_SINK];
> +	int ret;
> +
> +	ret = v4l2_create_fwnode_links_to_pad(sd, sink_pad,
> +					      MEDIA_LNK_FL_ENABLED);
> +	if (ret) {
> +		dev_err(rkcif->dev, "failed to link source pad of %s\n",
> +			sd->name);
> +		return ret;
> +	}
> +
> +	remote->sd = sd;
> +
> +	return 0;
> +}
> +
> +static int rkcif_notifier_complete(struct v4l2_async_notifier *notifier)
> +{
> +	struct rkcif_device *rkcif =
> +		container_of(notifier, struct rkcif_device, notifier);
> +
> +	return v4l2_device_register_subdev_nodes(&rkcif->v4l2_dev);
> +}
> +
> +static const struct v4l2_async_notifier_operations rkcif_notifier_ops = {
> +	.bound = rkcif_notifier_bound,
> +	.complete = rkcif_notifier_complete,
> +};
> +
> +static int rkcif_register(struct rkcif_device *rkcif)
> +{
> +	struct v4l2_async_notifier *ntf = &rkcif->notifier;
> +	int ret;
> +
> +	v4l2_async_nf_init(ntf, &rkcif->v4l2_dev);
> +	ntf->ops = &rkcif_notifier_ops;
> +
> +	ret = rkcif_dvp_register(rkcif);
> +	if (ret && ret != -ENODEV)
> +		goto err_notifier_cleanup;
> +
> +	ret = rkcif_mipi_register(rkcif);
> +	if (ret && ret != -ENODEV)
> +		goto err_dvp_unregister;
> +
> +	ret = v4l2_async_nf_register(ntf);
> +	if (ret)
> +		goto err_mipi_unregister;
> +
> +	return 0;
> +
> +err_mipi_unregister:
> +	rkcif_mipi_unregister(rkcif);
> +err_dvp_unregister:
> +	rkcif_dvp_unregister(rkcif);
> +err_notifier_cleanup:
> +	v4l2_async_nf_cleanup(&rkcif->notifier);
> +	return ret;
> +}
> +
> +static void rkcif_unregister(struct rkcif_device *rkcif)
> +{
> +	v4l2_async_nf_unregister(&rkcif->notifier);
> +	rkcif_mipi_unregister(rkcif);
> +	rkcif_dvp_unregister(rkcif);
> +	v4l2_async_nf_cleanup(&rkcif->notifier);
> +}
> +
> +static irqreturn_t rkcif_isr(int irq, void *ctx)
> +{
> +	irqreturn_t ret = IRQ_NONE;
> +
> +	if (rkcif_dvp_isr(irq, ctx) == IRQ_HANDLED)
> +		ret = IRQ_HANDLED;
> +
> +	if (rkcif_mipi_isr(irq, ctx) == IRQ_HANDLED)
> +		ret = IRQ_HANDLED;
> +
> +	return ret;
> +}
> +
> +static int rkcif_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct rkcif_device *rkcif;
> +	int ret, irq, i;
> +
> +	rkcif = devm_kzalloc(dev, sizeof(*rkcif), GFP_KERNEL);
> +	if (!rkcif)
> +		return -ENOMEM;
> +
> +	rkcif->match_data = of_device_get_match_data(dev);
> +	if (!rkcif->match_data)
> +		return -ENODEV;
> +
> +	dev_set_drvdata(dev, rkcif);
> +	rkcif->dev = dev;
> +
> +	rkcif->base_addr = devm_platform_ioremap_resource(pdev, 0);
> +	if (IS_ERR(rkcif->base_addr))
> +		return PTR_ERR(rkcif->base_addr);
> +
> +	irq = platform_get_irq(pdev, 0);
> +	if (irq < 0)
> +		return irq;
> +
> +	ret = devm_request_irq(dev, irq, rkcif_isr, IRQF_SHARED,
> +			       dev_driver_string(dev), dev);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "failed to request irq\n");
> +
> +	rkcif->clks_num = rkcif->match_data->clks_num;
> +	for (i = 0; (i < rkcif->clks_num) && (i < RKCIF_CLK_MAX); i++)
> +		rkcif->clks[i].id = rkcif->match_data->clks[i];

why the additional check against the define ?

would it not be better to reject clks_num > RKCIF_CLK_MAX ?

once you commit to this pattern of double checks you'll need to 
proliferate it throughout your clks array lookup.

> +	ret = devm_clk_bulk_get(dev, rkcif->clks_num, rkcif->clks);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "failed to get clocks\n");
> +
> +	rkcif->reset = devm_reset_control_array_get_exclusive(dev);
> +	if (IS_ERR(rkcif->reset))
> +		return PTR_ERR(rkcif->reset);
> +
> +	rkcif->grf =
> +		syscon_regmap_lookup_by_phandle(dev->of_node, "rockchip,grf");
> +	if (IS_ERR(rkcif->grf))
> +		rkcif->grf = NULL;
> +
> +	pm_runtime_enable(&pdev->dev);
> +
> +	rkcif->media_dev.dev = dev;
> +	strscpy(rkcif->media_dev.model, RKCIF_DRIVER_NAME,
> +		sizeof(rkcif->media_dev.model));
> +	media_device_init(&rkcif->media_dev);
> +
> +	rkcif->v4l2_dev.mdev = &rkcif->media_dev;
> +	ret = v4l2_device_register(dev, &rkcif->v4l2_dev);
> +	if (ret)
> +		goto err_media_dev_cleanup;
> +
> +	ret = media_device_register(&rkcif->media_dev);
> +	if (ret < 0) {
> +		dev_err(dev, "failed to register media device: %d\n", ret);
> +		goto err_v4l2_dev_unregister;
> +	}
> +
> +	ret = rkcif_register(rkcif);
> +	if (ret) {
> +		dev_err(dev, "failed to register media entities: %d\n", ret);
> +		goto err_media_dev_unregister;
> +	}
> +
> +	return 0;
> +
> +err_media_dev_unregister:
> +	media_device_unregister(&rkcif->media_dev);
> +err_v4l2_dev_unregister:
> +	v4l2_device_unregister(&rkcif->v4l2_dev);
> +err_media_dev_cleanup:
> +	media_device_cleanup(&rkcif->media_dev);
> +	pm_runtime_disable(&pdev->dev);
> +	return ret;
> +}
> +
> +static void rkcif_remove(struct platform_device *pdev)
> +{
> +	struct rkcif_device *rkcif = platform_get_drvdata(pdev);
> +
> +	rkcif_unregister(rkcif);
> +	media_device_unregister(&rkcif->media_dev);
> +	v4l2_device_unregister(&rkcif->v4l2_dev);
> +	media_device_cleanup(&rkcif->media_dev);
> +	pm_runtime_disable(&pdev->dev);
> +}
> +
> +static int rkcif_runtime_suspend(struct device *dev)
> +{
> +	struct rkcif_device *rkcif = dev_get_drvdata(dev);
> +
> +	/*
> +	 * Reset CIF (CRU, DMA, FIFOs) to allow a clean resume.
> +	 * Since this resets the IOMMU too, we cannot issue this reset when
> +	 * resuming.
> +	 */
> +	reset_control_assert(rkcif->reset);
> +	udelay(5);
> +	reset_control_deassert(rkcif->reset);
> +
> +	clk_bulk_disable_unprepare(rkcif->clks_num, rkcif->clks);
> +
> +	return 0;
> +}
> +
> +static int rkcif_runtime_resume(struct device *dev)
> +{
> +	struct rkcif_device *rkcif = dev_get_drvdata(dev);
> +	int ret;
> +
> +	ret = clk_bulk_prepare_enable(rkcif->clks_num, rkcif->clks);
> +	if (ret) {
> +		dev_err(dev, "failed to enable clocks\n");
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct dev_pm_ops rkcif_plat_pm_ops = {
> +	.runtime_suspend = rkcif_runtime_suspend,
> +	.runtime_resume = rkcif_runtime_resume,
> +};
> +
> +static struct platform_driver rkcif_plat_drv = {
> +	.driver = {
> +		   .name = RKCIF_DRIVER_NAME,
> +		   .of_match_table = rkcif_plat_of_match,
> +		   .pm = &rkcif_plat_pm_ops,
> +	},
> +	.probe = rkcif_probe,
> +	.remove = rkcif_remove,
> +};
> +module_platform_driver(rkcif_plat_drv);
> +
> +MODULE_DESCRIPTION("Rockchip Camera Interface (CIF) platform driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-interface.c b/drivers/media/platform/rockchip/rkcif/rkcif-interface.c
> new file mode 100644
> index 000000000000..0ec524586594
> --- /dev/null
> +++ b/drivers/media/platform/rockchip/rkcif/rkcif-interface.c
> @@ -0,0 +1,423 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Rockchip Camera Interface (CIF) Driver
> + *
> + * Copyright (C) 2025 Michael Riesch <michael.riesch@wolfvision.net>
> + */
> +
> +#include <media/v4l2-common.h>
> +#include <media/v4l2-fwnode.h>
> +#include <media/v4l2-mc.h>
> +#include <media/v4l2-subdev.h>
> +
> +#include "rkcif-common.h"
> +#include "rkcif-interface.h"
> +
> +static inline struct rkcif_interface *to_rkcif_interface(struct v4l2_subdev *sd)
> +{
> +	return container_of(sd, struct rkcif_interface, sd);
> +}
> +
> +static const struct media_entity_operations rkcif_interface_media_ops = {
> +	.link_validate = v4l2_subdev_link_validate,
> +	.has_pad_interdep = v4l2_subdev_has_pad_interdep,
> +};
> +
> +static int rkcif_interface_set_fmt(struct v4l2_subdev *sd,
> +				   struct v4l2_subdev_state *state,
> +				   struct v4l2_subdev_format *format)
> +{
> +	struct rkcif_interface *interface = to_rkcif_interface(sd);
> +	const struct rkcif_input_fmt *input;
> +	struct v4l2_mbus_framefmt *sink, *src;
> +
> +	/* the format on the source pad always matches the sink pad */
> +	if (format->pad == RKCIF_IF_PAD_SRC)
> +		return v4l2_subdev_get_fmt(sd, state, format);
> +
> +	input = rkcif_interface_find_input_fmt(interface, true,
> +					       format->format.code);
> +	format->format.code = input->mbus_code;
> +
> +	sink = v4l2_subdev_state_get_format(state, format->pad, format->stream);
> +	if (!sink)
> +		return -EINVAL;
> +
> +	*sink = format->format;
> +
> +	/* propagate the format to the source pad */
> +	src = v4l2_subdev_state_get_opposite_stream_format(state, format->pad,
> +							   format->stream);
> +	if (!src)
> +		return -EINVAL;
> +
> +	*src = *sink;
> +
> +	return 0;
> +}
> +
> +static int rkcif_interface_get_sel(struct v4l2_subdev *sd,
> +				   struct v4l2_subdev_state *state,
> +				   struct v4l2_subdev_selection *sel)
> +{
> +	struct v4l2_mbus_framefmt *sink;
> +	struct v4l2_rect *crop;
> +	int ret = 0;
> +
> +	if (sel->pad != RKCIF_IF_PAD_SRC)
> +		return -EINVAL;
> +
> +	sink = v4l2_subdev_state_get_opposite_stream_format(state, sel->pad,
> +							    sel->stream);
> +	if (!sink)
> +		return -EINVAL;
> +
> +	crop = v4l2_subdev_state_get_crop(state, sel->pad, sel->stream);
> +	if (!crop)
> +		return -EINVAL;
> +
> +	switch (sel->target) {
> +	case V4L2_SEL_TGT_CROP_DEFAULT:
> +	case V4L2_SEL_TGT_CROP_BOUNDS:
> +		sel->r.left = 0;
> +		sel->r.top = 0;
> +		sel->r.width = sink->width;
> +		sel->r.height = sink->height;
> +		break;
> +	case V4L2_SEL_TGT_CROP:
> +		sel->r = *crop;
> +		break;
> +	default:
> +		ret = -EINVAL;
> +	}
> +
> +	return ret;
> +}
> +
> +static int rkcif_interface_set_sel(struct v4l2_subdev *sd,
> +				   struct v4l2_subdev_state *state,
> +				   struct v4l2_subdev_selection *sel)
> +{
> +	struct v4l2_mbus_framefmt *sink, *src;
> +	struct v4l2_rect *crop;
> +
> +	if (sel->pad != RKCIF_IF_PAD_SRC || sel->target != V4L2_SEL_TGT_CROP)
> +		return -EINVAL;
> +
> +	sink = v4l2_subdev_state_get_opposite_stream_format(state, sel->pad,
> +							    sel->stream);
> +	if (!sink)
> +		return -EINVAL;
> +
> +	src = v4l2_subdev_state_get_format(state, sel->pad, sel->stream);
> +	if (!src)
> +		return -EINVAL;
> +
> +	crop = v4l2_subdev_state_get_crop(state, sel->pad, sel->stream);
> +	if (!crop)
> +		return -EINVAL;
> +
> +	/* only starting point of crop can be specified */
> +	sel->r.height = sink->height - sel->r.top;
> +	sel->r.width = sink->width - sel->r.left;
> +	*crop = sel->r;
> +
> +	src->height = sel->r.height;
> +	src->width = sel->r.width;
> +
> +	return 0;
> +}
> +
> +static int rkcif_interface_set_routing(struct v4l2_subdev *sd,
> +				       struct v4l2_subdev_state *state,
> +				       enum v4l2_subdev_format_whence which,
> +				       struct v4l2_subdev_krouting *routing)
> +{
> +	int ret;
> +
> +	ret = v4l2_subdev_routing_validate(sd, routing,
> +					   V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
> +	if (ret)
> +		return ret;
> +
> +	for (unsigned int i = 0; i < routing->num_routes; i++) {
> +		const struct v4l2_subdev_route *route = &routing->routes[i];
> +
> +		if (route->source_stream >= RKCIF_ID_MAX)
> +			return -EINVAL;
> +	}
> +
> +	ret = v4l2_subdev_set_routing(sd, state, routing);
> +
> +	return ret;
> +}
> +
> +static int rkcif_interface_apply_crop(struct rkcif_stream *stream,
> +				      struct v4l2_subdev_state *state)
> +{
> +	struct rkcif_interface *interface = stream->interface;
> +	struct v4l2_rect *crop;
> +
> +	crop = v4l2_subdev_state_get_crop(state, RKCIF_IF_PAD_SRC, stream->id);
> +	if (!crop)
> +		return -EINVAL;
> +
> +	if (interface->set_crop)
> +		interface->set_crop(stream, crop->left, crop->top);
> +
> +	return 0;
> +}
> +
> +static int rkcif_interface_enable_streams(struct v4l2_subdev *sd,
> +					  struct v4l2_subdev_state *state,
> +					  u32 pad, u64 streams_mask)
> +{
> +	struct rkcif_interface *interface = to_rkcif_interface(sd);
> +	struct rkcif_stream *stream;
> +	struct v4l2_subdev_route *route;
> +	struct v4l2_subdev *remote_sd;
> +	struct media_pad *remote_pad;
> +	u64 mask;
> +
> +	remote_pad =
> +		media_pad_remote_pad_first(&sd->entity.pads[RKCIF_IF_PAD_SINK]);
> +	remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity);
> +
> +	/* DVP has one crop setting for all IDs */
> +	if (interface->type == RKCIF_IF_DVP) {
> +		stream = &interface->streams[RKCIF_ID0];
> +		rkcif_interface_apply_crop(stream, state);
> +	} else {
> +		/* TODO implement for MIPI */

-ENOTSUPP ?

> +	}
> +
> +	mask = v4l2_subdev_state_xlate_streams(state, RKCIF_IF_PAD_SINK,
> +					       RKCIF_IF_PAD_SRC, &streams_mask);
> +
> +	return v4l2_subdev_enable_streams(remote_sd, remote_pad->index, mask);
> +}
> +
> +static int rkcif_interface_disable_streams(struct v4l2_subdev *sd,
> +					   struct v4l2_subdev_state *state,
> +					   u32 pad, u64 streams_mask)
> +{
> +	struct v4l2_subdev *remote_sd;
> +	struct media_pad *remote_pad;
> +	u64 mask;
> +
> +	remote_pad =
> +		media_pad_remote_pad_first(&sd->entity.pads[RKCIF_IF_PAD_SINK]);
> +	remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity);
> +
> +	mask = v4l2_subdev_state_xlate_streams(state, RKCIF_IF_PAD_SINK,
> +					       RKCIF_IF_PAD_SRC, &streams_mask);
> +
> +	return v4l2_subdev_disable_streams(remote_sd, remote_pad->index, mask);
> +}
> +
> +static const struct v4l2_subdev_pad_ops rkcif_interface_pad_ops = {
> +	.get_fmt = v4l2_subdev_get_fmt,
> +	.set_fmt = rkcif_interface_set_fmt,
> +	.get_selection = rkcif_interface_get_sel,
> +	.set_selection = rkcif_interface_set_sel,
> +	.set_routing = rkcif_interface_set_routing,
> +	.enable_streams = rkcif_interface_enable_streams,
> +	.disable_streams = rkcif_interface_disable_streams,
> +};
> +
> +static const struct v4l2_subdev_ops rkcif_interface_ops = {
> +	.pad = &rkcif_interface_pad_ops,
> +};
> +
> +static int rkcif_interface_init_state(struct v4l2_subdev *sd,
> +				      struct v4l2_subdev_state *state)
> +{
> +	struct rkcif_interface *interface = to_rkcif_interface(sd);
> +	struct v4l2_subdev_route routes[] = {
> +		{
> +			.sink_pad = RKCIF_IF_PAD_SINK,
> +			.sink_stream = 0,
> +			.source_pad = RKCIF_IF_PAD_SRC,
> +			.source_stream = 0,
> +			.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
> +		},
> +	};
> +	struct v4l2_subdev_krouting routing = {
> +		.len_routes = ARRAY_SIZE(routes),
> +		.num_routes = ARRAY_SIZE(routes),
> +		.routes = routes,
> +	};
> +	const struct v4l2_mbus_framefmt dvp_default_format = {
> +		.width = 3840,
> +		.height = 2160,
> +		.code = MEDIA_BUS_FMT_YUYV8_1X16,
> +		.field = V4L2_FIELD_NONE,
> +		.colorspace = V4L2_COLORSPACE_REC709,
> +		.ycbcr_enc = V4L2_YCBCR_ENC_709,
> +		.quantization = V4L2_QUANTIZATION_LIM_RANGE,
> +		.xfer_func = V4L2_XFER_FUNC_NONE,
> +	};
> +	const struct v4l2_mbus_framefmt mipi_default_format = {
> +		.width = 3840,
> +		.height = 2160,
> +		.code = MEDIA_BUS_FMT_SRGGB10_1X10,
> +		.field = V4L2_FIELD_NONE,
> +		.colorspace = V4L2_COLORSPACE_RAW,
> +		.ycbcr_enc = V4L2_YCBCR_ENC_601,
> +		.quantization = V4L2_QUANTIZATION_FULL_RANGE,
> +		.xfer_func = V4L2_XFER_FUNC_NONE,
> +	};
> +	const struct v4l2_mbus_framefmt *default_format;
> +	int ret;
> +
> +	default_format = (interface->type == RKCIF_IF_DVP) ?
> +				 &dvp_default_format :
> +				 &mipi_default_format;
> +
> +	ret = v4l2_subdev_set_routing_with_fmt(sd, state, &routing,
> +					       default_format);
> +
> +	return ret;
> +}
> +
> +static const struct v4l2_subdev_internal_ops rkcif_interface_internal_ops = {
> +	.init_state = rkcif_interface_init_state,
> +};
> +
> +static int rkcif_interface_add(struct rkcif_interface *interface)
> +{
> +	struct rkcif_device *rkcif = interface->rkcif;
> +	struct rkcif_remote *remote;
> +	struct v4l2_async_notifier *ntf = &rkcif->notifier;
> +	struct v4l2_fwnode_endpoint *vep = &interface->vep;
> +	struct device *dev = rkcif->dev;
> +	struct fwnode_handle *ep;
> +	u32 dvp_clk_delay = 0;
> +	int ret;
> +
> +	ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), interface->index,
> +					     0, 0);
> +	if (!ep)
> +		return -ENODEV;
> +
> +	vep->bus_type = V4L2_MBUS_UNKNOWN;
> +	ret = v4l2_fwnode_endpoint_parse(ep, vep);
> +	if (ret)
> +		goto complete;
> +
> +	if (interface->type == RKCIF_IF_DVP) {
> +		if (vep->bus_type != V4L2_MBUS_BT656 &&
> +		    vep->bus_type != V4L2_MBUS_PARALLEL) {
> +			ret = dev_err_probe(dev, -EINVAL,
> +					    "unsupported bus type\n");
> +			goto complete;
> +		}
> +
> +		fwnode_property_read_u32(ep, "rockchip,dvp-clk-delay",
> +					 &dvp_clk_delay);
> +		interface->dvp.dvp_clk_delay = dvp_clk_delay;
> +	}
> +
> +	remote = v4l2_async_nf_add_fwnode_remote(ntf, ep, struct rkcif_remote);
> +	if (IS_ERR(remote)) {
> +		ret = PTR_ERR(remote);
> +		goto complete;
> +	}
> +
> +	remote->interface = interface;
> +	interface->remote = remote;
> +	interface->status = RKCIF_IF_ACTIVE;
> +	ret = 0;
> +
> +complete:
> +	fwnode_handle_put(ep);
> +
> +	return ret;
> +}
> +
> +int rkcif_interface_register(struct rkcif_device *rkcif,
> +			     struct rkcif_interface *interface)
> +{
> +	struct media_pad *pads = interface->pads;
> +	struct v4l2_subdev *sd = &interface->sd;
> +	int ret;
> +
> +	interface->rkcif = rkcif;
> +
> +	v4l2_subdev_init(sd, &rkcif_interface_ops);
> +	sd->dev = rkcif->dev;
> +	sd->entity.ops = &rkcif_interface_media_ops;
> +	sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
> +	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS;
> +	sd->internal_ops = &rkcif_interface_internal_ops;
> +	sd->owner = THIS_MODULE;
> +
> +	if (interface->type == RKCIF_IF_DVP)
> +		snprintf(sd->name, sizeof(sd->name), "rkcif-dvp0");
> +	else if (interface->type == RKCIF_IF_MIPI)
> +		snprintf(sd->name, sizeof(sd->name), "rkcif-mipi%d",
> +			 interface->index - RKCIF_MIPI_BASE);
> +
> +	pads[RKCIF_IF_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
> +	pads[RKCIF_IF_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE;
> +	ret = media_entity_pads_init(&sd->entity, RKCIF_IF_PAD_MAX, pads);
> +	if (ret)
> +		goto err;
> +
> +	ret = v4l2_subdev_init_finalize(sd);
> +	if (ret)
> +		goto err_entity_cleanup;
> +
> +	ret = v4l2_device_register_subdev(&rkcif->v4l2_dev, sd);
> +	if (ret) {
> +		dev_err(sd->dev, "failed to register subdev\n");
> +		goto err_subdev_cleanup;
> +	}
> +
> +	ret = rkcif_interface_add(interface);
> +	if (ret)
> +		goto err_subdev_unregister;
> +
> +	return 0;
> +
> +err_subdev_unregister:
> +	v4l2_device_unregister_subdev(sd);
> +err_subdev_cleanup:
> +	v4l2_subdev_cleanup(sd);
> +err_entity_cleanup:
> +	media_entity_cleanup(&sd->entity);
> +err:
> +	return ret;
> +}
> +
> +void rkcif_interface_unregister(struct rkcif_interface *interface)
> +{
> +	struct v4l2_subdev *sd = &interface->sd;
> +
> +	if (interface->status != RKCIF_IF_ACTIVE)
> +		return;
> +
> +	v4l2_device_unregister_subdev(sd);
> +	v4l2_subdev_cleanup(sd);
> +	media_entity_cleanup(&sd->entity);
> +}
> +
> +const struct rkcif_input_fmt *
> +rkcif_interface_find_input_fmt(struct rkcif_interface *interface, bool ret_def,
> +			       u32 mbus_code)
> +{
> +	const struct rkcif_input_fmt *fmt;
> +	unsigned int i;
> +
> +	WARN_ON(interface->in_fmts_num == 0);
> +
> +	for (i = 0; i < interface->in_fmts_num; i++) {
> +		fmt = &interface->in_fmts[i];
> +		if (fmt->mbus_code == mbus_code)
> +			return fmt;
> +	}
> +	if (ret_def)
> +		return &interface->in_fmts[0];
> +	else
> +		return NULL;
> +}
> diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-interface.h b/drivers/media/platform/rockchip/rkcif/rkcif-interface.h
> new file mode 100644
> index 000000000000..f37fde4cc6a2
> --- /dev/null
> +++ b/drivers/media/platform/rockchip/rkcif/rkcif-interface.h
> @@ -0,0 +1,30 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Rockchip Camera Interface (CIF) Driver
> + *
> + * Abstraction for the INTERFACE and CROP parts of the different CIF variants.
> + * They shall be represented as V4L2 subdevice with one sink pad and one
> + * source pad. The sink pad is connected to a subdevice: either the subdevice
> + * provided by the driver of the companion chip connected to the DVP, or the
> + * subdevice provided by the MIPI CSI Receiver driver in
> + * rkcif-mipi-csi-receiver.c. The source pad is connected to an instance of the
> + * DMA abstraction in rkcif-stream.c.
> + *
> + * Copyright (C) 2025 Michael Riesch <michael.riesch@wolfvision.net>
> + */
> +
> +#ifndef _RKCIF_INTERFACE_H
> +#define _RKCIF_INTERFACE_H
> +
> +#include "rkcif-common.h"
> +
> +int rkcif_interface_register(struct rkcif_device *rkcif,
> +			     struct rkcif_interface *interface);
> +
> +void rkcif_interface_unregister(struct rkcif_interface *interface);
> +
> +const struct rkcif_input_fmt *
> +rkcif_interface_find_input_fmt(struct rkcif_interface *interface, bool ret_def,
> +			       u32 mbus_code);
> +
> +#endif
> diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-regs.h b/drivers/media/platform/rockchip/rkcif/rkcif-regs.h
> new file mode 100644
> index 000000000000..07fd64174e80
> --- /dev/null
> +++ b/drivers/media/platform/rockchip/rkcif/rkcif-regs.h
> @@ -0,0 +1,132 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Rockchip Camera Interface (CIF) Driver
> + *
> + * Copyright (C) 2018 Rockchip Electronics Co., Ltd.
> + * Copyright (C) 2023 Mehdi Djait <mehdi.djait@bootlin.com>
> + * Copyright (C) 2025 Michael Riesch <michael.riesch@wolfvision.net>
> + */
> +
> +#ifndef _RKCIF_REGS_H
> +#define _RKCIF_REGS_H
> +
> +enum rkcif_dvp_register_index {
> +	RKCIF_DVP_CTRL,
> +	RKCIF_DVP_INTEN,
> +	RKCIF_DVP_INTSTAT,
> +	RKCIF_DVP_FOR,
> +	RKCIF_DVP_LINE_NUM_ADDR,
> +	RKCIF_DVP_FRM0_ADDR_Y,
> +	RKCIF_DVP_FRM0_ADDR_UV,
> +	RKCIF_DVP_FRM1_ADDR_Y,
> +	RKCIF_DVP_FRM1_ADDR_UV,
> +	RKCIF_DVP_VIR_LINE_WIDTH,
> +	RKCIF_DVP_SET_SIZE,
> +	RKCIF_DVP_SCL_CTRL,
> +	RKCIF_DVP_CROP,
> +	RKCIF_DVP_FRAME_STATUS,
> +	RKCIF_DVP_LAST_LINE,
> +	RKCIF_DVP_LAST_PIX,
> +	RKCIF_DVP_REGISTER_MAX
> +};
> +
> +#define RKCIF_REGISTER_NOTSUPPORTED           0x420000
> +
> +#define RKCIF_FETCH_Y(VAL)		       ((VAL) & 0x1fff)
> +
> +#define RKCIF_CTRL_ENABLE_CAPTURE	       BIT(0)
> +#define RKCIF_CTRL_MODE_PINGPONG	       BIT(1)
> +#define RKCIF_CTRL_MODE_LINELOOP	       BIT(2)
> +#define RKCIF_CTRL_AXI_BURST_16		       (0xf << 12)
> +
> +#define RKCIF_INTEN_FRAME_END_EN	       BIT(0)
> +#define RKCIF_INTEN_LINE_ERR_EN		       BIT(2)
> +#define RKCIF_INTEN_BUS_ERR_EN		       BIT(6)
> +#define RKCIF_INTEN_SCL_ERR_EN		       BIT(7)
> +#define RKCIF_INTEN_PST_INF_FRAME_END_EN       BIT(9)
> +
> +#define RKCIF_INTSTAT_CLS		       0x3ff
> +#define RKCIF_INTSTAT_FRAME_END		       BIT(0)
> +#define RKCIF_INTSTAT_LINE_END		       BIT(1)
> +#define RKCIF_INTSTAT_LINE_ERR		       BIT(2)
> +#define RKCIF_INTSTAT_PIX_ERR		       BIT(3)
> +#define RKCIF_INTSTAT_DFIFO_OF		       BIT(5)
> +#define RKCIF_INTSTAT_BUS_ERR		       BIT(6)
> +#define RKCIF_INTSTAT_PRE_INF_FRAME_END	       BIT(8)
> +#define RKCIF_INTSTAT_PST_INF_FRAME_END	       BIT(9)
> +#define RKCIF_INTSTAT_FRAME_END_CLR	       BIT(0)
> +#define RKCIF_INTSTAT_LINE_END_CLR	       BIT(1)
> +#define RKCIF_INTSTAT_LINE_ERR_CLR	       BIT(2)
> +#define RKCIF_INTSTAT_PST_INF_FRAME_END_CLR    BIT(9)
> +#define RKCIF_INTSTAT_ERR		       0xfc
> +
> +#define RKCIF_FRAME_STAT_CLS		       0x00
> +#define RKCIF_FRAME_FRM0_STAT_CLS	       0x20
> +
> +#define RKCIF_FORMAT_VSY_HIGH_ACTIVE	       BIT(0)
> +#define RKCIF_FORMAT_HSY_LOW_ACTIVE	       BIT(1)
> +
> +#define RKCIF_FORMAT_INPUT_MODE_YUV	       (0x00 << 2)
> +#define RKCIF_FORMAT_INPUT_MODE_PAL	       (0x02 << 2)
> +#define RKCIF_FORMAT_INPUT_MODE_NTSC	       (0x03 << 2)
> +#define RKCIF_FORMAT_INPUT_MODE_BT1120	       (0x07 << 2)
> +#define RKCIF_FORMAT_INPUT_MODE_RAW	       (0x04 << 2)
> +#define RKCIF_FORMAT_INPUT_MODE_JPEG	       (0x05 << 2)
> +#define RKCIF_FORMAT_INPUT_MODE_MIPI	       (0x06 << 2)
> +
> +#define RKCIF_FORMAT_YUV_INPUT_ORDER_UYVY      (0x00 << 5)
> +#define RKCIF_FORMAT_YUV_INPUT_ORDER_YVYU      (0x01 << 5)
> +#define RKCIF_FORMAT_YUV_INPUT_ORDER_VYUY      (0x02 << 5)
> +#define RKCIF_FORMAT_YUV_INPUT_ORDER_YUYV      (0x03 << 5)
> +#define RKCIF_FORMAT_YUV_INPUT_422	       (0x00 << 7)
> +#define RKCIF_FORMAT_YUV_INPUT_420	       BIT(7)
> +
> +#define RKCIF_FORMAT_INPUT_420_ORDER_ODD       BIT(8)
> +
> +#define RKCIF_FORMAT_CCIR_INPUT_ORDER_EVEN     BIT(9)
> +
> +#define RKCIF_FORMAT_RAW_DATA_WIDTH_8	       (0x00 << 11)
> +#define RKCIF_FORMAT_RAW_DATA_WIDTH_10	       (0x01 << 11)
> +#define RKCIF_FORMAT_RAW_DATA_WIDTH_12	       (0x02 << 11)
> +
> +#define RKCIF_FORMAT_YUV_OUTPUT_422	       (0x00 << 16)
> +#define RKCIF_FORMAT_YUV_OUTPUT_420	       BIT(16)
> +
> +#define RKCIF_FORMAT_OUTPUT_420_ORDER_EVEN     (0x00 << 17)
> +#define RKCIF_FORMAT_OUTPUT_420_ORDER_ODD      BIT(17)
> +
> +#define RKCIF_FORMAT_RAWD_DATA_LITTLE_ENDIAN   (0x00 << 18)
> +#define RKCIF_FORMAT_RAWD_DATA_BIG_ENDIAN      BIT(18)
> +
> +#define RKCIF_FORMAT_UV_STORAGE_ORDER_UVUV     (0x00 << 19)
> +#define RKCIF_FORMAT_UV_STORAGE_ORDER_VUVU     BIT(19)
> +
> +#define RKCIF_FORMAT_BT1120_CLOCK_SINGLE_EDGES (0x00 << 24)
> +#define RKCIF_FORMAT_BT1120_CLOCK_DOUBLE_EDGES BIT(24)
> +#define RKCIF_FORMAT_BT1120_TRANSMIT_INTERFACE (0x00 << 25)
> +#define RKCIF_FORMAT_BT1120_TRANSMIT_PROGRESS  BIT(25)
> +#define RKCIF_FORMAT_BT1120_YC_SWAP	       BIT(26)
> +
> +#define RKCIF_SCL_CTRL_ENABLE_SCL_DOWN	       BIT(0)
> +#define RKCIF_SCL_CTRL_ENABLE_SCL_UP	       BIT(1)
> +#define RKCIF_SCL_CTRL_ENABLE_YUV_16BIT_BYPASS BIT(4)
> +#define RKCIF_SCL_CTRL_ENABLE_RAW_16BIT_BYPASS BIT(5)
> +#define RKCIF_SCL_CTRL_ENABLE_32BIT_BYPASS     BIT(6)
> +#define RKCIF_SCL_CTRL_DISABLE_32BIT_BYPASS    (0x00 << 6)
> +
> +#define RKCIF_INTSTAT_F0_READY		       BIT(0)
> +#define RKCIF_INTSTAT_F1_READY		       BIT(1)
> +
> +#define RKCIF_XY_COORD(x, y)		       (((y) << 16) | (x))
> +
> +/* GRF register offsets */
> +#define RK3568_GRF_VI_CON0		       0x340
> +#define RK3568_GRF_VI_CON1		       0x344
> +#define RK3568_GRF_VI_STATUS0		       0x348
> +
> +#define RK3568_GRF_VI_CON1_CIF_DATAPATH	       BIT(9)
> +#define RK3568_GRF_VI_CON1_CIF_CLK_DELAYNUM    GENMASK(6, 0)
> +
> +#define RK3568_GRF_WRITE_ENABLE(x)	       ((x) << 16)
> +
> +#endif
> diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-stream.c b/drivers/media/platform/rockchip/rkcif/rkcif-stream.c
> new file mode 100644
> index 000000000000..bfafd76f4494
> --- /dev/null
> +++ b/drivers/media/platform/rockchip/rkcif/rkcif-stream.c
> @@ -0,0 +1,622 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Rockchip Camera Interface (CIF) Driver
> + *
> + * Copyright (C) 2025 Michael Riesch <michael.riesch@wolfvision.net>
> + */
> +
> +#include <linux/pm_runtime.h>
> +
> +#include <media/v4l2-common.h>
> +#include <media/v4l2-fwnode.h>
> +#include <media/v4l2-ioctl.h>
> +#include <media/v4l2-mc.h>
> +#include <media/v4l2-subdev.h>
> +#include <media/videobuf2-dma-contig.h>
> +
> +#include "rkcif-common.h"
> +#include "rkcif-stream.h"
> +
> +#define CIF_REQ_BUFS_MIN 8
> +#define CIF_MIN_WIDTH	 64
> +#define CIF_MIN_HEIGHT	 64
> +#define CIF_MAX_WIDTH	 8192
> +#define CIF_MAX_HEIGHT	 8192
> +
> +static inline struct rkcif_buffer *to_rkcif_buffer(struct vb2_v4l2_buffer *vb)
> +{
> +	return container_of(vb, struct rkcif_buffer, vb);
> +}
> +
> +static inline struct rkcif_stream *to_rkcif_stream(struct video_device *vdev)
> +{
> +	return container_of(vdev, struct rkcif_stream, vdev);
> +}
> +
> +static struct rkcif_buffer *rkcif_stream_pop_buffer(struct rkcif_stream *stream)
> +{
> +	struct rkcif_buffer *buffer = NULL;
> +	unsigned long lock_flags;
> +
> +	spin_lock_irqsave(&stream->driver_queue_lock, lock_flags);
> +
> +	if (list_empty(&stream->driver_queue))
> +		goto err_empty;
> +
> +	buffer = list_first_entry(&stream->driver_queue, struct rkcif_buffer,
> +				  queue);
> +	list_del(&buffer->queue);
> +
> +err_empty:
> +	spin_unlock_irqrestore(&stream->driver_queue_lock, lock_flags);
> +	return buffer;
> +}
> +
> +static void rkcif_stream_push_buffer(struct rkcif_stream *stream,
> +				     struct rkcif_buffer *buffer)
> +{
> +	unsigned long lock_flags;
> +
> +	spin_lock_irqsave(&stream->driver_queue_lock, lock_flags);
> +	list_add_tail(&buffer->queue, &stream->driver_queue);
> +	spin_unlock_irqrestore(&stream->driver_queue_lock, lock_flags);
> +}
> +
> +static inline void rkcif_stream_return_buffer(struct rkcif_buffer *buffer,
> +					      enum vb2_buffer_state state)
> +{
> +	struct vb2_v4l2_buffer *vb = &buffer->vb;
> +
> +	vb2_buffer_done(&vb->vb2_buf, state);
> +}
> +
> +static void rkcif_stream_complete_buffer(struct rkcif_stream *stream,
> +					 struct rkcif_buffer *buffer)
> +{
> +	struct vb2_v4l2_buffer *vb = &buffer->vb;
> +
> +	vb->vb2_buf.timestamp = ktime_get_ns();
> +	vb->sequence = stream->frame_idx;
> +	vb2_buffer_done(&vb->vb2_buf, VB2_BUF_STATE_DONE);
> +	stream->frame_idx++;
> +}
> +
> +void rkcif_stream_pingpong(struct rkcif_stream *stream)
> +{
> +	struct rkcif_buffer *buffer;
> +
> +	buffer = stream->buffers[stream->frame_phase];
> +	if (!buffer->is_dummy)
> +		rkcif_stream_complete_buffer(stream, buffer);
> +
> +	buffer = rkcif_stream_pop_buffer(stream);
> +	if (buffer) {
> +		stream->buffers[stream->frame_phase] = buffer;
> +		stream->buffers[stream->frame_phase]->is_dummy = false;
> +	} else {
> +		stream->buffers[stream->frame_phase] = &stream->dummy.buffer;
> +		stream->buffers[stream->frame_phase]->is_dummy = true;
> +		dev_warn(stream->rkcif->dev,
> +			 "no buffer available, frame will be dropped\n");
> +	}
> +
> +	if (stream->queue_buffer)
> +		stream->queue_buffer(stream, stream->frame_phase);
> +
> +	stream->frame_phase = 1 - stream->frame_phase;
> +}
> +
> +static int rkcif_stream_init_buffers(struct rkcif_stream *stream)
> +{
> +	struct v4l2_pix_format_mplane *pix = &stream->pix;
> +	int i;
> +
> +	stream->buffers[0] = rkcif_stream_pop_buffer(stream);
> +	if (!stream->buffers[0])
> +		goto err_buff_0;
> +
> +	stream->buffers[1] = rkcif_stream_pop_buffer(stream);
> +	if (!stream->buffers[1])
> +		goto err_buff_1;
> +
> +	if (stream->queue_buffer) {
> +		stream->queue_buffer(stream, 0);
> +		stream->queue_buffer(stream, 1);
> +	}
> +
> +	stream->dummy.size = pix->num_planes * pix->plane_fmt[0].sizeimage;
> +	stream->dummy.vaddr =
> +		dma_alloc_attrs(stream->rkcif->dev, stream->dummy.size,
> +				&stream->dummy.buffer.buff_addr[0], GFP_KERNEL,
> +				DMA_ATTR_NO_KERNEL_MAPPING);
> +	if (!stream->dummy.vaddr)
> +		goto err_dummy;
> +
> +	for (i = 1; i < pix->num_planes; i++)
> +		stream->dummy.buffer.buff_addr[i] =
> +			stream->dummy.buffer.buff_addr[i - 1] +
> +			pix->plane_fmt[i - 1].bytesperline * pix->height;
> +
> +	return 0;
> +
> +err_dummy:
> +	rkcif_stream_return_buffer(stream->buffers[1], VB2_BUF_STATE_QUEUED);
> +	stream->buffers[1] = NULL;
> +
> +err_buff_1:
> +	rkcif_stream_return_buffer(stream->buffers[0], VB2_BUF_STATE_QUEUED);
> +	stream->buffers[0] = NULL;
> +err_buff_0:
> +	return -EINVAL;
> +}
> +
> +static void rkcif_stream_return_all_buffers(struct rkcif_stream *stream,
> +					    enum vb2_buffer_state state)
> +{
> +	struct rkcif_buffer *buffer;
> +
> +	if (stream->buffers[0] && !stream->buffers[0]->is_dummy) {
> +		rkcif_stream_return_buffer(stream->buffers[0], state);
> +		stream->buffers[0] = NULL;
> +	}
> +
> +	if (stream->buffers[1] && !stream->buffers[1]->is_dummy) {
> +		rkcif_stream_return_buffer(stream->buffers[1], state);
> +		stream->buffers[1] = NULL;
> +	}
> +
> +	while ((buffer = rkcif_stream_pop_buffer(stream)))
> +		rkcif_stream_return_buffer(buffer, state);
> +
> +	if (stream->dummy.vaddr) {
> +		dma_free_attrs(stream->rkcif->dev, stream->dummy.size,
> +			       stream->dummy.vaddr,
> +			       stream->dummy.buffer.buff_addr[0],
> +			       DMA_ATTR_NO_KERNEL_MAPPING);
> +		stream->dummy.vaddr = NULL;
> +	}
> +}
> +
> +static int rkcif_stream_setup_queue(struct vb2_queue *queue,
> +				    unsigned int *num_buffers,
> +				    unsigned int *num_planes,
> +				    unsigned int sizes[],
> +				    struct device *alloc_devs[])
> +{
> +	struct rkcif_stream *stream = queue->drv_priv;
> +	struct v4l2_pix_format_mplane *pix = &stream->pix;
> +	unsigned int i;
> +
> +	if (*num_planes) {
> +		if (*num_planes != pix->num_planes)
> +			return -EINVAL;
> +
> +		for (i = 0; i < pix->num_planes; i++)
> +			if (sizes[i] < pix->plane_fmt[i].sizeimage)
> +				return -EINVAL;
> +	} else {
> +		*num_planes = pix->num_planes;
> +		for (i = 0; i < pix->num_planes; i++)
> +			sizes[i] = pix->plane_fmt[i].sizeimage;
> +	}
> +
> +	return 0;
> +}
> +
> +static int rkcif_stream_prepare_buffer(struct vb2_buffer *vb)
> +{
> +	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
> +	struct rkcif_buffer *buffer = to_rkcif_buffer(vbuf);
> +	struct rkcif_stream *stream = vb->vb2_queue->drv_priv;
> +	const struct rkcif_output_fmt *fmt;
> +	struct v4l2_pix_format_mplane *pix = &stream->pix;
> +	unsigned int i;
> +
> +	memset(buffer->buff_addr, 0, sizeof(buffer->buff_addr));
> +	for (i = 0; i < pix->num_planes; i++)
> +		buffer->buff_addr[i] = vb2_dma_contig_plane_dma_addr(vb, i);
> +
> +	/* apply fallback for non-mplane formats, if required */
> +	if (pix->num_planes == 1) {
> +		fmt = rkcif_stream_find_output_fmt(stream, true,
> +						   pix->pixelformat);
> +		for (i = 1; i < fmt->cplanes; i++)
> +			buffer->buff_addr[i] =
> +				buffer->buff_addr[i - 1] +
> +				pix->plane_fmt[i - 1].bytesperline *
> +					pix->height;
> +	}
> +
> +	for (i = 0; i < pix->num_planes; i++) {
> +		unsigned long size = pix->plane_fmt[i].sizeimage;
> +
> +		if (vb2_plane_size(vb, i) < size) {
> +			dev_err(stream->rkcif->dev,
> +				"user buffer too small (%ld < %ld)\n",
> +				vb2_plane_size(vb, i), size);
> +			return -EINVAL;
> +		}
> +
> +		vb2_set_plane_payload(vb, i, size);
> +	}
> +
> +	return 0;
> +}
> +
> +static void rkcif_stream_queue_buffer(struct vb2_buffer *vb)
> +{
> +	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
> +	struct rkcif_buffer *buffer = to_rkcif_buffer(vbuf);
> +	struct rkcif_stream *stream = vb->vb2_queue->drv_priv;
> +
> +	rkcif_stream_push_buffer(stream, buffer);
> +}
> +
> +static int rkcif_stream_start_streaming(struct vb2_queue *queue,
> +					unsigned int count)
> +{
> +	struct rkcif_stream *stream = queue->drv_priv;
> +	struct rkcif_device *rkcif = stream->rkcif;
> +	u64 mask;
> +	int ret;
> +
> +	stream->frame_idx = 0;
> +	stream->frame_phase = 0;
> +
> +	ret = video_device_pipeline_start(&stream->vdev, &stream->pipeline);
> +	if (ret) {
> +		dev_err(rkcif->dev, "failed to start pipeline %d\n", ret);
> +		goto err_out;
> +	}
> +
> +	ret = pm_runtime_resume_and_get(rkcif->dev);
> +	if (ret < 0) {
> +		dev_err(rkcif->dev, "failed to get runtime pm, %d\n", ret);
> +		goto err_pipeline_stop;
> +	}
> +
> +	ret = rkcif_stream_init_buffers(stream);
> +	if (ret)
> +		goto err_runtime_put;
> +
> +	if (stream->start_streaming) {
> +		ret = stream->start_streaming(stream);
> +		if (ret < 0)
> +			goto err_runtime_put;
> +	}
> +
> +	mask = BIT_ULL(stream->id);
> +	ret = v4l2_subdev_enable_streams(&stream->interface->sd,
> +					 RKCIF_IF_PAD_SRC, mask);
> +	if (ret < 0)
> +		goto err_stop_stream;
> +
> +	return 0;
> +
> +err_stop_stream:
> +	if (stream->stop_streaming)
> +		stream->stop_streaming(stream);
> +err_runtime_put:
> +	pm_runtime_put(rkcif->dev);
> +err_pipeline_stop:
> +	video_device_pipeline_stop(&stream->vdev);
> +err_out:
> +	rkcif_stream_return_all_buffers(stream, VB2_BUF_STATE_QUEUED);
> +	return ret;
> +}
> +
> +static void rkcif_stream_stop_streaming(struct vb2_queue *queue)
> +{
> +	struct rkcif_stream *stream = queue->drv_priv;
> +	struct rkcif_device *rkcif = stream->rkcif;
> +	u64 mask;
> +	int ret;
> +
> +	mask = BIT_ULL(stream->id);
> +	v4l2_subdev_disable_streams(&stream->interface->sd, RKCIF_IF_PAD_SRC,
> +				    mask);
> +
> +	stream->stopping = true;
> +	ret = wait_event_timeout(stream->wq_stopped, !stream->stopping,
> +				 msecs_to_jiffies(1000));
> +
> +	if (!ret && stream->stop_streaming)
> +		stream->stop_streaming(stream);
> +
> +	pm_runtime_put(rkcif->dev);
> +
> +	rkcif_stream_return_all_buffers(stream, VB2_BUF_STATE_ERROR);
> +
> +	video_device_pipeline_stop(&stream->vdev);
> +}
> +
> +static const struct vb2_ops rkcif_stream_vb2_ops = {
> +	.queue_setup = rkcif_stream_setup_queue,
> +	.buf_prepare = rkcif_stream_prepare_buffer,
> +	.buf_queue = rkcif_stream_queue_buffer,
> +	.wait_prepare = vb2_ops_wait_prepare,
> +	.wait_finish = vb2_ops_wait_finish,
> +	.start_streaming = rkcif_stream_start_streaming,
> +	.stop_streaming = rkcif_stream_stop_streaming,
> +};
> +
> +static int rkcif_stream_try_format(struct file *file, void *fh,
> +				   struct v4l2_format *f)
> +{
> +	struct rkcif_stream *stream = video_drvdata(file);
> +	struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;
> +	const struct rkcif_output_fmt *fmt;
> +	u32 height, width;
> +
> +	fmt = rkcif_stream_find_output_fmt(stream, true, pix->pixelformat);
> +	height = clamp_t(u32, pix->height, CIF_MIN_HEIGHT, CIF_MAX_HEIGHT);
> +	width = clamp_t(u32, pix->width, CIF_MIN_WIDTH, CIF_MAX_WIDTH);
> +	v4l2_fill_pixfmt_mp(pix, fmt->fourcc, width, height);
> +
> +	return 0;
> +}
> +
> +static int rkcif_stream_set_format(struct file *file, void *priv,
> +				   struct v4l2_format *f)
> +{
> +	struct rkcif_stream *stream = video_drvdata(file);
> +	struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;
> +	int ret;
> +
> +	if (vb2_is_busy(&stream->buf_queue))
> +		return -EBUSY;
> +
> +	ret = rkcif_stream_try_format(file, priv, f);
> +	if (ret)
> +		return ret;
> +
> +	stream->pix = *pix;
> +
> +	return 0;
> +}
> +
> +static int rkcif_stream_get_format(struct file *file, void *fh,
> +				   struct v4l2_format *f)
> +{
> +	struct rkcif_stream *stream = video_drvdata(file);
> +
> +	f->fmt.pix_mp = stream->pix;
> +
> +	return 0;
> +}
> +
> +static int rkcif_stream_enum_formats(struct file *file, void *priv,
> +				     struct v4l2_fmtdesc *f)
> +{
> +	struct rkcif_stream *stream = video_drvdata(file);
> +
> +	if (f->index >= stream->out_fmts_num)
> +		return -EINVAL;
> +
> +	f->pixelformat = stream->out_fmts[f->index].fourcc;
> +
> +	return 0;
> +}
> +
> +static int rkcif_stream_enum_framesizes(struct file *file, void *fh,
> +					struct v4l2_frmsizeenum *fsize)
> +{
> +	if (fsize->index > 0)
> +		return -EINVAL;
> +
> +	fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
> +	fsize->stepwise.min_width = CIF_MIN_WIDTH;
> +	fsize->stepwise.max_width = CIF_MAX_WIDTH;
> +	fsize->stepwise.step_width = 8;
> +	fsize->stepwise.min_height = CIF_MIN_HEIGHT;
> +	fsize->stepwise.max_height = CIF_MAX_HEIGHT;
> +	fsize->stepwise.step_height = 8;
> +
> +	return 0;
> +}
> +
> +static int rkcif_stream_querycap(struct file *file, void *priv,
> +				 struct v4l2_capability *cap)
> +{
> +	struct rkcif_stream *stream = video_drvdata(file);
> +	struct device *dev = stream->rkcif->dev;
> +
> +	strscpy(cap->driver, dev->driver->name, sizeof(cap->driver));
> +	strscpy(cap->card, dev->driver->name, sizeof(cap->card));
> +
> +	return 0;
> +}
> +
> +static const struct v4l2_ioctl_ops rkcif_stream_ioctl_ops = {
> +	.vidioc_reqbufs = vb2_ioctl_reqbufs,
> +	.vidioc_querybuf = vb2_ioctl_querybuf,
> +	.vidioc_create_bufs = vb2_ioctl_create_bufs,
> +	.vidioc_qbuf = vb2_ioctl_qbuf,
> +	.vidioc_expbuf = vb2_ioctl_expbuf,
> +	.vidioc_dqbuf = vb2_ioctl_dqbuf,
> +	.vidioc_prepare_buf = vb2_ioctl_prepare_buf,
> +	.vidioc_streamon = vb2_ioctl_streamon,
> +	.vidioc_streamoff = vb2_ioctl_streamoff,
> +	.vidioc_try_fmt_vid_cap_mplane = rkcif_stream_try_format,
> +	.vidioc_s_fmt_vid_cap_mplane = rkcif_stream_set_format,
> +	.vidioc_g_fmt_vid_cap_mplane = rkcif_stream_get_format,
> +	.vidioc_enum_fmt_vid_cap = rkcif_stream_enum_formats,
> +	.vidioc_enum_framesizes = rkcif_stream_enum_framesizes,
> +	.vidioc_querycap = rkcif_stream_querycap,
> +};
> +
> +static int rkcif_stream_link_validate(struct media_link *link)
> +{
> +	struct video_device *vdev =
> +		media_entity_to_video_device(link->sink->entity);
> +	struct v4l2_mbus_framefmt *source_fmt;
> +	struct v4l2_subdev *sd;
> +	struct v4l2_subdev_state *state;
> +	struct rkcif_stream *stream = to_rkcif_stream(vdev);
> +	int ret = -EINVAL;
> +
> +	if (!media_entity_remote_source_pad_unique(link->sink->entity))
> +		return -ENOTCONN;
> +
> +	sd = media_entity_to_v4l2_subdev(link->source->entity);
> +
> +	state = v4l2_subdev_lock_and_get_active_state(sd);
> +
> +	source_fmt = v4l2_subdev_state_get_format(state, link->source->index,
> +						  stream->id);
> +	if (!source_fmt)
> +		goto out;
> +
> +	if (source_fmt->height != stream->pix.height ||
> +	    source_fmt->width != stream->pix.width) {
> +		dev_dbg(stream->rkcif->dev,
> +			"link '%s':%u -> '%s':%u not valid: %ux%u != %ux%u\n",
> +			link->source->entity->name, link->source->index,
> +			link->sink->entity->name, link->sink->index,
> +			source_fmt->width, source_fmt->height,
> +			stream->pix.width, stream->pix.height);
> +		goto out;
> +	}
> +
> +	ret = 0;
> +
> +out:
> +	v4l2_subdev_unlock_state(state);
> +	return ret;
> +}
> +
> +static const struct media_entity_operations rkcif_stream_media_ops = {
> +	.link_validate = rkcif_stream_link_validate,
> +};
> +
> +static const struct v4l2_file_operations rkcif_stream_file_ops = {
> +	.open = v4l2_fh_open,
> +	.release = vb2_fop_release,
> +	.unlocked_ioctl = video_ioctl2,
> +	.poll = vb2_fop_poll,
> +	.mmap = vb2_fop_mmap,
> +};
> +
> +static int rkcif_stream_init_vb2_queue(struct vb2_queue *q,
> +				       struct rkcif_stream *stream)
> +{
> +	q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
> +	q->io_modes = VB2_MMAP | VB2_DMABUF;
> +	q->drv_priv = stream;
> +	q->ops = &rkcif_stream_vb2_ops;
> +	q->mem_ops = &vb2_dma_contig_memops;
> +	q->buf_struct_size = sizeof(struct rkcif_buffer);
> +	q->min_queued_buffers = CIF_REQ_BUFS_MIN;
> +	q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
> +	q->lock = &stream->vlock;
> +	q->dev = stream->rkcif->dev;
> +
> +	return vb2_queue_init(q);
> +}
> +
> +int rkcif_stream_register(struct rkcif_device *rkcif,
> +			  struct rkcif_stream *stream)
> +{
> +	struct rkcif_interface *interface = stream->interface;
> +	struct v4l2_device *v4l2_dev = &rkcif->v4l2_dev;
> +	struct video_device *vdev = &stream->vdev;
> +	u32 link_flags = 0;
> +	int ret;
> +
> +	stream->rkcif = rkcif;
> +
> +	INIT_LIST_HEAD(&stream->driver_queue);
> +	spin_lock_init(&stream->driver_queue_lock);
> +
> +	init_waitqueue_head(&stream->wq_stopped);
> +
> +	mutex_init(&stream->vlock);
> +
> +	vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_STREAMING |
> +			    V4L2_CAP_IO_MC;
> +	vdev->entity.ops = &rkcif_stream_media_ops;
> +	vdev->fops = &rkcif_stream_file_ops;
> +	vdev->ioctl_ops = &rkcif_stream_ioctl_ops;
> +	vdev->lock = &stream->vlock;
> +	vdev->minor = -1;
> +	vdev->release = video_device_release_empty;
> +	vdev->v4l2_dev = v4l2_dev;
> +	vdev->vfl_dir = VFL_DIR_RX;
> +	video_set_drvdata(vdev, stream);
> +
> +	stream->pad.flags = MEDIA_PAD_FL_SINK;
> +
> +	rkcif_stream_init_vb2_queue(&stream->buf_queue, stream);
> +
> +	vdev->queue = &stream->buf_queue;
> +	if (interface->type == RKCIF_IF_DVP)
> +		snprintf(vdev->name, sizeof(vdev->name), "rkcif-dvp0-id%d",
> +			 stream->id);
> +	else if (interface->type == RKCIF_IF_MIPI)
> +		snprintf(vdev->name, sizeof(vdev->name), "rkcif-mipi%d-id%d",
> +			 interface->index - RKCIF_MIPI_BASE, stream->id);
> +
> +	ret = media_entity_pads_init(&vdev->entity, 1, &stream->pad);
> +	if (ret < 0) {
> +		dev_err(rkcif->dev,
> +			"failed to initialize stream media pad: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
> +	if (ret < 0) {
> +		dev_err(rkcif->dev, "failed to register video device: %d\n",
> +			ret);
> +		goto err_media_entity_cleanup;
> +	}
> +
> +	/* enable only stream ID0 by default */
> +	if (stream->id == RKCIF_ID0)
> +		link_flags |= MEDIA_LNK_FL_ENABLED;
> +
> +	ret = media_create_pad_link(&interface->sd.entity, RKCIF_IF_PAD_SRC,
> +				    &stream->vdev.entity, 0, link_flags);
> +	if (ret) {
> +		dev_err(rkcif->dev, "failed to link stream media pad: %d\n",
> +			ret);
> +		goto err_video_unregister;
> +	}
> +
> +	v4l2_info(v4l2_dev, "registered %s as /dev/video%d\n", vdev->name,
> +		  vdev->num);
> +
> +	return 0;
> +
> +err_video_unregister:
> +	video_unregister_device(&stream->vdev);
> +err_media_entity_cleanup:
> +	media_entity_cleanup(&stream->vdev.entity);
> +	return ret;
> +}
> +
> +void rkcif_stream_unregister(struct rkcif_stream *stream)
> +{
> +	video_unregister_device(&stream->vdev);
> +	media_entity_cleanup(&stream->vdev.entity);
> +}
> +
> +const struct rkcif_output_fmt *
> +rkcif_stream_find_output_fmt(struct rkcif_stream *stream, bool ret_def,
> +			     u32 pixelfmt)
> +{
> +	const struct rkcif_output_fmt *fmt;
> +	unsigned int i;
> +
> +	WARN_ON(stream->out_fmts_num == 0);
> +
> +	for (i = 0; i < stream->out_fmts_num; i++) {
> +		fmt = &stream->out_fmts[i];
> +		if (fmt->fourcc == pixelfmt)
> +			return fmt;
> +	}
> +
> +	if (ret_def)
> +		return &stream->out_fmts[0];
> +	else
> +		return NULL;
> +}
> diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-stream.h b/drivers/media/platform/rockchip/rkcif/rkcif-stream.h
> new file mode 100644
> index 000000000000..e50c9771f1b0
> --- /dev/null
> +++ b/drivers/media/platform/rockchip/rkcif/rkcif-stream.h
> @@ -0,0 +1,31 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Rockchip Camera Interface (CIF) Driver
> + *
> + * Abstraction for the DMA part and the ping-pong scheme (a double-buffering
> + * mechanism) of the different CIF variants.
> + * Each stream is represented as V4L2 device whose corresponding media entity
> + * has one sink pad.
> + * The sink pad is connected to an instance of the INTERFACE/CROP abstraction
> + * in rkcif-interface.c.
> + *
> + * Copyright (C) 2025 Michael Riesch <michael.riesch@wolfvision.net>
> + */
> +
> +#ifndef _RKCIF_STREAM_H
> +#define _RKCIF_STREAM_H
> +
> +#include "rkcif-common.h"
> +
> +void rkcif_stream_pingpong(struct rkcif_stream *stream);
> +
> +int rkcif_stream_register(struct rkcif_device *rkcif,
> +			  struct rkcif_stream *stream);
> +
> +void rkcif_stream_unregister(struct rkcif_stream *stream);
> +
> +const struct rkcif_output_fmt *
> +rkcif_stream_find_output_fmt(struct rkcif_stream *stream, bool ret_def,
> +			     u32 pixelfmt);
> +
> +#endif
> 
> --
> 2.39.5
> 
> 
> 

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

* Re: [PATCH v6 09/13] arm64: defconfig: enable rockchip camera interface
  2025-04-30  9:15 ` [PATCH v6 09/13] arm64: defconfig: enable rockchip camera interface Michael Riesch via B4 Relay
@ 2025-05-01  0:47   ` Bryan O'Donoghue
  0 siblings, 0 replies; 34+ messages in thread
From: Bryan O'Donoghue @ 2025-05-01  0:47 UTC (permalink / raw)
  To: michael.riesch, Mehdi Djait, Maxime Chevallier, Théo Lebrun,
	Gerald Loacker, Thomas Petazzoni, Laurent Pinchart,
	Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Heiko Stuebner, Kever Yang, Nicolas Dufresne,
	Sebastian Reichel, Collabora Kernel Team, Paul Kocialkowski,
	Alexander Shiyan, Val Packett, Rob Herring, Philipp Zabel,
	Sakari Ailus
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-rockchip, Michael Riesch

On 30/04/2025 10:15, Michael Riesch via B4 Relay wrote:
> From: Michael Riesch <michael.riesch@collabora.com>
> 
> The Rockchip Camera Interface (CIF) is featured in many Rockchip SoCs
> in different variations. Enable the driver for it in the default
> configuration.
> 
> Signed-off-by: Michael Riesch <michael.riesch@collabora.com>
> ---
>   arch/arm64/configs/defconfig | 1 +
>   1 file changed, 1 insertion(+)
> 
> diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig
> index 5bb8f09422a2..110ff52195a6 100644
> --- a/arch/arm64/configs/defconfig
> +++ b/arch/arm64/configs/defconfig
> @@ -858,6 +858,7 @@ CONFIG_VIDEO_RENESAS_FCP=m
>   CONFIG_VIDEO_RENESAS_FDP1=m
>   CONFIG_VIDEO_RENESAS_VSP1=m
>   CONFIG_VIDEO_RCAR_DRIF=m
> +CONFIG_VIDEO_ROCKCHIP_CIF=m
>   CONFIG_VIDEO_SAMSUNG_EXYNOS_GSC=m
>   CONFIG_VIDEO_SAMSUNG_S5P_JPEG=m
>   CONFIG_VIDEO_SAMSUNG_S5P_MFC=m
> 
> --
> 2.39.5
> 
> 
> 
Reviewed-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org>

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

* Re: [PATCH v6 08/13] media: rockchip: rkcif: add support for mipi csi-2 capture
  2025-04-30  9:15 ` [PATCH v6 08/13] media: rockchip: rkcif: add support for mipi csi-2 capture Michael Riesch via B4 Relay
@ 2025-05-01  0:48   ` Bryan O'Donoghue
  0 siblings, 0 replies; 34+ messages in thread
From: Bryan O'Donoghue @ 2025-05-01  0:48 UTC (permalink / raw)
  To: michael.riesch, Mehdi Djait, Maxime Chevallier, Théo Lebrun,
	Gerald Loacker, Thomas Petazzoni, Laurent Pinchart,
	Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Heiko Stuebner, Kever Yang, Nicolas Dufresne,
	Sebastian Reichel, Collabora Kernel Team, Paul Kocialkowski,
	Alexander Shiyan, Val Packett, Rob Herring, Philipp Zabel,
	Sakari Ailus
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-rockchip, Michael Riesch

On 30/04/2025 10:15, Michael Riesch via B4 Relay wrote:
> From: Michael Riesch <michael.riesch@wolfvision.net>
> 
> The RK3568 Video Capture (VICAP) unit features a MIPI CSI-2 capture
> interface that can receive video data and write it into system memory
> using the ping-pong scheme. Add support for it.
> 
> Signed-off-by: Michael Riesch <michael.riesch@wolfvision.net>
> Signed-off-by: Michael Riesch <michael.riesch@collabora.com>
> ---
>   .../platform/rockchip/rkcif/rkcif-capture-mipi.c   | 695 +++++++++++++++++++++
>   .../platform/rockchip/rkcif/rkcif-capture-mipi.h   |   2 +
>   .../media/platform/rockchip/rkcif/rkcif-common.h   |  16 +
>   drivers/media/platform/rockchip/rkcif/rkcif-dev.c  |   1 +
>   .../platform/rockchip/rkcif/rkcif-interface.c      |   5 +-
>   drivers/media/platform/rockchip/rkcif/rkcif-regs.h |  24 +-
>   6 files changed, 741 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.c b/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.c
> index 0c3f7b8cfa18..5e1c624e17c6 100644
> --- a/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.c
> +++ b/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.c
> @@ -6,22 +6,717 @@
>    * Copyright (C) 2025 Michael Riesch <michael.riesch@wolfvision.net>
>    */
> 
> +#include <linux/interrupt.h>
> +
> +#include <media/mipi-csi2.h>
> +#include <media/v4l2-common.h>
> +#include <media/v4l2-event.h>
> +#include <media/v4l2-fh.h>
> +#include <media/v4l2-fwnode.h>
> +#include <media/v4l2-ioctl.h>
> +#include <media/v4l2-mc.h>
> +#include <media/v4l2-subdev.h>
> +
>   #include "rkcif-capture-mipi.h"
>   #include "rkcif-common.h"
> +#include "rkcif-interface.h"
> +#include "rkcif-regs.h"
>   #include "rkcif-stream.h"
> 
> +#define RKCIF_MIPI_CTRL0_COMPACT_EN    BIT(6)
> +#define RKCIF_MIPI_CTRL0_CROP_EN       BIT(5)
> +#define RKCIF_MIPI_CTRL0_TYPE(type)    ((type) << 1)
> +#define RKCIF_MIPI_CTRL0_TYPE_RAW8     RKCIF_MIPI_CTRL0_TYPE(0x0)
> +#define RKCIF_MIPI_CTRL0_TYPE_RAW10    RKCIF_MIPI_CTRL0_TYPE(0x1)
> +#define RKCIF_MIPI_CTRL0_TYPE_RAW12    RKCIF_MIPI_CTRL0_TYPE(0x2)
> +#define RKCIF_MIPI_CTRL0_TYPE_RGB888   RKCIF_MIPI_CTRL0_TYPE(0x3)
> +#define RKCIF_MIPI_CTRL0_TYPE_YUV422SP RKCIF_MIPI_CTRL0_TYPE(0x4)
> +#define RKCIF_MIPI_CTRL0_TYPE_YUV420SP RKCIF_MIPI_CTRL0_TYPE(0x5)
> +#define RKCIF_MIPI_CTRL0_TYPE_YUV400   RKCIF_MIPI_CTRL0_TYPE(0x6)
> +#define RKCIF_MIPI_CTRL0_CAP_EN	       BIT(0)
> +
> +#define RKCIF_MIPI_INT_FRAME0_END(id)  BIT(8 + (id) * 2 + 0)
> +#define RKCIF_MIPI_INT_FRAME1_END(id)  BIT(8 + (id) * 2 + 1)
> +
> +static const struct rkcif_output_fmt mipi_out_fmts[] = {
> +	/* YUV formats */
> +	{
> +		.fourcc = V4L2_PIX_FMT_YUYV,
> +		.mbus_code = MEDIA_BUS_FMT_YUYV8_1X16,
> +		.depth = 16,
> +		.cplanes = 1,
> +		.mipi = {
> +			.dt = MIPI_CSI2_DT_YUV422_8B,
> +			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW8,
> +		},
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_UYVY,
> +		.mbus_code = MEDIA_BUS_FMT_UYVY8_1X16,
> +		.depth = 16,
> +		.cplanes = 1,
> +		.mipi = {
> +			.dt = MIPI_CSI2_DT_YUV422_8B,
> +			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW8,
> +		},
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_YVYU,
> +		.mbus_code = MEDIA_BUS_FMT_YVYU8_1X16,
> +		.depth = 16,
> +		.cplanes = 1,
> +		.mipi = {
> +			.dt = MIPI_CSI2_DT_YUV422_8B,
> +			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW8,
> +		},
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_VYUY,
> +		.mbus_code = MEDIA_BUS_FMT_VYUY8_1X16,
> +		.depth = 16,
> +		.cplanes = 1,
> +		.mipi = {
> +			.dt = MIPI_CSI2_DT_YUV422_8B,
> +			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW8,
> +		},
> +	},
> +	/* RGB formats */
> +	{
> +		.fourcc = V4L2_PIX_FMT_RGB24,
> +		.mbus_code = MEDIA_BUS_FMT_RGB888_1X24,
> +		.depth = 24,
> +		.cplanes = 1,
> +		.mipi = {
> +			.dt = MIPI_CSI2_DT_RGB888,
> +			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RGB888,
> +		},
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_BGR24,
> +		.mbus_code = MEDIA_BUS_FMT_BGR888_1X24,
> +		.depth = 24,
> +		.cplanes = 1,
> +		.mipi = {
> +			.dt = MIPI_CSI2_DT_RGB888,
> +			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RGB888,
> +		},
> +	},
> +	/* Bayer formats */
> +	{
> +		.fourcc = V4L2_PIX_FMT_SBGGR8,
> +		.mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8,
> +		.depth = 8,
> +		.cplanes = 1,
> +		.mipi = {
> +			.dt = MIPI_CSI2_DT_RAW8,
> +			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW8,
> +		},
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_SGBRG8,
> +		.mbus_code = MEDIA_BUS_FMT_SGBRG8_1X8,
> +		.depth = 8,
> +		.cplanes = 1,
> +		.mipi = {
> +			.dt = MIPI_CSI2_DT_RAW8,
> +			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW8,
> +		},
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_SGRBG8,
> +		.mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8,
> +		.depth = 8,
> +		.cplanes = 1,
> +		.mipi = {
> +			.dt = MIPI_CSI2_DT_RAW8,
> +			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW8,
> +		},
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_SRGGB8,
> +		.mbus_code = MEDIA_BUS_FMT_SRGGB8_1X8,
> +		.depth = 8,
> +		.cplanes = 1,
> +		.mipi = {
> +			.dt = MIPI_CSI2_DT_RAW8,
> +			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW8,
> +		},
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_SBGGR10,
> +		.mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10,
> +		.depth = 10,
> +		.cplanes = 1,
> +		.mipi = {
> +			.dt = MIPI_CSI2_DT_RAW10,
> +			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW10,
> +		},
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_SBGGR10P,
> +		.mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10,
> +		.depth = 10,
> +		.cplanes = 1,
> +		.mipi = {
> +			.dt = MIPI_CSI2_DT_RAW10,
> +			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW10 | RKCIF_MIPI_CTRL0_COMPACT_EN,
> +		},
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_SGBRG10,
> +		.mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10,
> +		.depth = 10,
> +		.cplanes = 1,
> +		.mipi = {
> +			.dt = MIPI_CSI2_DT_RAW10,
> +			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW10,
> +		},
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_SGBRG10P,
> +		.mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10,
> +		.depth = 10,
> +		.cplanes = 1,
> +		.mipi = {
> +			.dt = MIPI_CSI2_DT_RAW10,
> +			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW10 | RKCIF_MIPI_CTRL0_COMPACT_EN,
> +		},
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_SGRBG10,
> +		.mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10,
> +		.depth = 10,
> +		.cplanes = 1,
> +		.mipi = {
> +			.dt = MIPI_CSI2_DT_RAW10,
> +			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW10,
> +		},
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_SGRBG10P,
> +		.mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10,
> +		.depth = 10,
> +		.cplanes = 1,
> +		.mipi = {
> +			.dt = MIPI_CSI2_DT_RAW10,
> +			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW10 | RKCIF_MIPI_CTRL0_COMPACT_EN,
> +		},
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_SRGGB10,
> +		.mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10,
> +		.depth = 10,
> +		.cplanes = 1,
> +		.mipi = {
> +			.dt = MIPI_CSI2_DT_RAW10,
> +			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW10,
> +		},
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_SRGGB10P,
> +		.mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10,
> +		.depth = 10,
> +		.cplanes = 1,
> +		.mipi = {
> +			.dt = MIPI_CSI2_DT_RAW10,
> +			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW10 | RKCIF_MIPI_CTRL0_COMPACT_EN,
> +		},
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_SBGGR12,
> +		.mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12,
> +		.depth = 12,
> +		.cplanes = 1,
> +		.mipi = {
> +			.dt = MIPI_CSI2_DT_RAW12,
> +			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW12,
> +		},
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_SBGGR12P,
> +		.mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12,
> +		.depth = 12,
> +		.cplanes = 1,
> +		.mipi = {
> +			.dt = MIPI_CSI2_DT_RAW12,
> +			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW12 | RKCIF_MIPI_CTRL0_COMPACT_EN,
> +		},
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_SGBRG12,
> +		.mbus_code = MEDIA_BUS_FMT_SGBRG12_1X12,
> +		.depth = 12,
> +		.cplanes = 1,
> +		.mipi = {
> +			.dt = MIPI_CSI2_DT_RAW12,
> +			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW12,
> +		},
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_SGBRG12P,
> +		.mbus_code = MEDIA_BUS_FMT_SGBRG12_1X12,
> +		.depth = 12,
> +		.cplanes = 1,
> +		.mipi = {
> +			.dt = MIPI_CSI2_DT_RAW12,
> +			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW12 | RKCIF_MIPI_CTRL0_COMPACT_EN,
> +		},
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_SGRBG12,
> +		.mbus_code = MEDIA_BUS_FMT_SGRBG12_1X12,
> +		.depth = 12,
> +		.cplanes = 1,
> +		.mipi = {
> +			.dt = MIPI_CSI2_DT_RAW12,
> +			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW12,
> +		},
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_SGRBG12P,
> +		.mbus_code = MEDIA_BUS_FMT_SGRBG12_1X12,
> +		.depth = 12,
> +		.cplanes = 1,
> +		.mipi = {
> +			.dt = MIPI_CSI2_DT_RAW12,
> +			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW12 | RKCIF_MIPI_CTRL0_COMPACT_EN,
> +		},
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_SRGGB12,
> +		.mbus_code = MEDIA_BUS_FMT_SRGGB12_1X12,
> +		.depth = 12,
> +		.cplanes = 1,
> +		.mipi = {
> +			.dt = MIPI_CSI2_DT_RAW12,
> +			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW12,
> +		},
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_SRGGB12P,
> +		.mbus_code = MEDIA_BUS_FMT_SRGGB12_1X12,
> +		.depth = 12,
> +		.cplanes = 1,
> +		.mipi = {
> +			.dt = MIPI_CSI2_DT_RAW12,
> +			.ctrl0_val = RKCIF_MIPI_CTRL0_TYPE_RAW12 | RKCIF_MIPI_CTRL0_COMPACT_EN,
> +		},
> +	},
> +};
> +
> +static const struct rkcif_input_fmt mipi_in_fmts[] = {
> +	/* YUV formats */
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_YUYV8_1X16,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_UYVY8_1X16,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_YVYU8_1X16,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_VYUY8_1X16,
> +	},
> +	/* RGB formats */
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_RGB888_1X24,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_BGR888_1X24,
> +	},
> +	/* Bayer formats */
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_SGBRG8_1X8,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_SRGGB8_1X8,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_SGBRG12_1X12,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_SGRBG12_1X12,
> +	},
> +	{
> +		.mbus_code = MEDIA_BUS_FMT_SRGGB12_1X12,
> +	},
> +};
> +
> +const struct rkcif_mipi_match_data rkcif_rk3568_vicap_mipi_match_data = {
> +	.mipi_num = 1,
> +	.regs = {
> +		[RKCIF_MIPI_CTRL] = 0x20,
> +		[RKCIF_MIPI_INTEN] = 0xa4,
> +		[RKCIF_MIPI_INTSTAT] = 0xa8,
> +	},
> +	.regs_id = {
> +		[RKCIF_ID0] = {
> +			[RKCIF_MIPI_CTRL0] = 0x00,
> +			[RKCIF_MIPI_CTRL1] = 0x04,
> +			[RKCIF_MIPI_FRAME0_ADDR_Y] = 0x24,
> +			[RKCIF_MIPI_FRAME0_ADDR_UV] = 0x2c,
> +			[RKCIF_MIPI_FRAME0_VLW_Y] = 0x34,
> +			[RKCIF_MIPI_FRAME0_VLW_UV] = 0x3c,
> +			[RKCIF_MIPI_FRAME1_ADDR_Y] = 0x28,
> +			[RKCIF_MIPI_FRAME1_ADDR_UV] = 0x30,
> +			[RKCIF_MIPI_FRAME1_VLW_Y] = 0x38,
> +			[RKCIF_MIPI_FRAME1_VLW_UV] = 0x40,
> +			[RKCIF_MIPI_CROP_START] = 0xbc,
> +		},
> +		[RKCIF_ID1] = {
> +			[RKCIF_MIPI_CTRL0] = 0x08,
> +			[RKCIF_MIPI_CTRL1] = 0x0c,
> +			[RKCIF_MIPI_FRAME0_ADDR_Y] = 0x44,
> +			[RKCIF_MIPI_FRAME0_ADDR_UV] = 0x4c,
> +			[RKCIF_MIPI_FRAME0_VLW_Y] = 0x54,
> +			[RKCIF_MIPI_FRAME0_VLW_UV] = 0x5c,
> +			[RKCIF_MIPI_FRAME1_ADDR_Y] = 0x48,
> +			[RKCIF_MIPI_FRAME1_ADDR_UV] = 0x50,
> +			[RKCIF_MIPI_FRAME1_VLW_Y] = 0x58,
> +			[RKCIF_MIPI_FRAME1_VLW_UV] = 0x60,
> +			[RKCIF_MIPI_CROP_START] = 0xc0,
> +		},
> +		[RKCIF_ID2] = {
> +			[RKCIF_MIPI_CTRL0] = 0x10,
> +			[RKCIF_MIPI_CTRL1] = 0x14,
> +			[RKCIF_MIPI_FRAME0_ADDR_Y] = 0x64,
> +			[RKCIF_MIPI_FRAME0_ADDR_UV] = 0x6c,
> +			[RKCIF_MIPI_FRAME0_VLW_Y] = 0x74,
> +			[RKCIF_MIPI_FRAME0_VLW_UV] = 0x7c,
> +			[RKCIF_MIPI_FRAME1_ADDR_Y] = 0x68,
> +			[RKCIF_MIPI_FRAME1_ADDR_UV] = 0x70,
> +			[RKCIF_MIPI_FRAME1_VLW_Y] = 0x78,
> +			[RKCIF_MIPI_FRAME1_VLW_UV] = 0x80,
> +			[RKCIF_MIPI_CROP_START] = 0xc4,
> +		},
> +		[RKCIF_ID3] = {
> +			[RKCIF_MIPI_CTRL0] = 0x18,
> +			[RKCIF_MIPI_CTRL1] = 0x1c,
> +			[RKCIF_MIPI_FRAME0_ADDR_Y] = 0x84,
> +			[RKCIF_MIPI_FRAME0_ADDR_UV] = 0x8c,
> +			[RKCIF_MIPI_FRAME0_VLW_Y] = 0x94,
> +			[RKCIF_MIPI_FRAME0_VLW_UV] = 0x9c,
> +			[RKCIF_MIPI_FRAME1_ADDR_Y] = 0x88,
> +			[RKCIF_MIPI_FRAME1_ADDR_UV] = 0x90,
> +			[RKCIF_MIPI_FRAME1_VLW_Y] = 0x98,
> +			[RKCIF_MIPI_FRAME1_VLW_UV] = 0xa0,
> +			[RKCIF_MIPI_CROP_START] = 0xc8,
> +		},
> +	},
> +	.blocks = {
> +		{
> +			.offset = 0x80,
> +		},
> +	},
> +};
> +
> +static inline unsigned int rkcif_mipi_get_reg(struct rkcif_interface *interface,
> +					      unsigned int index)
> +{
> +	struct rkcif_device *rkcif = interface->rkcif;
> +	unsigned int block, offset, reg;
> +
> +	block = interface->index - RKCIF_MIPI_BASE;
> +
> +	if (WARN_ON_ONCE(block > RKCIF_MIPI_MAX - RKCIF_MIPI_BASE) ||
> +	    WARN_ON_ONCE(index > RKCIF_MIPI_REGISTER_MAX))
> +		return RKCIF_REGISTER_NOTSUPPORTED;
> +
> +	offset = rkcif->match_data->mipi->blocks[block].offset;
> +	reg = rkcif->match_data->mipi->regs[index];
> +	if (reg == RKCIF_REGISTER_NOTSUPPORTED)
> +		return reg;
> +
> +	return offset + reg;
> +}
> +
> +static inline unsigned int rkcif_mipi_id_get_reg(struct rkcif_stream *stream,
> +						 unsigned int index)
> +{
> +	struct rkcif_device *rkcif = stream->rkcif;
> +	unsigned int block, id, offset, reg;
> +
> +	block = stream->interface->index - RKCIF_MIPI_BASE;
> +	id = stream->id;
> +
> +	if (WARN_ON_ONCE(block > RKCIF_MIPI_MAX - RKCIF_MIPI_BASE) ||
> +	    WARN_ON_ONCE(id > RKCIF_ID_MAX) ||
> +	    WARN_ON_ONCE(index > RKCIF_MIPI_ID_REGISTER_MAX))
> +		return RKCIF_REGISTER_NOTSUPPORTED;
> +
> +	offset = rkcif->match_data->mipi->blocks[block].offset;
> +	reg = rkcif->match_data->mipi->regs_id[id][index];
> +	if (reg == RKCIF_REGISTER_NOTSUPPORTED)
> +		return reg;
> +
> +	return offset + reg;
> +}
> +
> +static inline __maybe_unused void
> +rkcif_mipi_write(struct rkcif_interface *interface, unsigned int index, u32 val)
> +{
> +	unsigned int addr = rkcif_mipi_get_reg(interface, index);
> +
> +	if (addr == RKCIF_REGISTER_NOTSUPPORTED)
> +		return;
> +
> +	writel(val, interface->rkcif->base_addr + addr);
> +}
> +
> +static inline __maybe_unused void
> +rkcif_mipi_stream_write(struct rkcif_stream *stream, unsigned int index,
> +			u32 val)
> +{
> +	unsigned int addr = rkcif_mipi_id_get_reg(stream, index);
> +
> +	if (addr == RKCIF_REGISTER_NOTSUPPORTED)
> +		return;
> +
> +	writel(val, stream->rkcif->base_addr + addr);
> +}
> +
> +static inline __maybe_unused u32
> +rkcif_mipi_read(struct rkcif_interface *interface, unsigned int index)
> +{
> +	unsigned int addr = rkcif_mipi_get_reg(interface, index);
> +
> +	if (addr == RKCIF_REGISTER_NOTSUPPORTED)
> +		return 0;
> +
> +	return readl(interface->rkcif->base_addr + addr);
> +}
> +
> +static inline __maybe_unused u32
> +rkcif_mipi_stream_read(struct rkcif_stream *stream, unsigned int index)
> +{
> +	unsigned int addr = rkcif_mipi_id_get_reg(stream, index);
> +
> +	if (addr == RKCIF_REGISTER_NOTSUPPORTED)
> +		return 0;
> +
> +	return readl(stream->rkcif->base_addr + addr);
> +}
> +
> +static void rkcif_mipi_queue_buffer(struct rkcif_stream *stream,
> +				    unsigned int index)
> +{
> +	struct rkcif_buffer *buffer = stream->buffers[index];
> +	u32 frm_addr_y, frm_addr_uv;
> +
> +	frm_addr_y = index ? RKCIF_MIPI_FRAME1_ADDR_Y :
> +			     RKCIF_MIPI_FRAME0_ADDR_Y;
> +	frm_addr_uv = index ? RKCIF_MIPI_FRAME1_ADDR_UV :
> +			      RKCIF_MIPI_FRAME0_ADDR_UV;
> +
> +	rkcif_mipi_stream_write(stream, frm_addr_y,
> +				buffer->buff_addr[RKCIF_PLANE_Y]);
> +	rkcif_mipi_stream_write(stream, frm_addr_uv,
> +				buffer->buff_addr[RKCIF_PLANE_UV]);
> +}
> +
> +static int rkcif_mipi_start_streaming(struct rkcif_stream *stream)
> +{
> +	struct rkcif_interface *interface = stream->interface;
> +	const struct rkcif_output_fmt *active_out_fmt;
> +	struct v4l2_subdev_state *state;
> +	u32 ctrl0 = 0, ctrl1 = 0, int_temp = 0, int_mask = 0, vlw = 0;
> +	u16 height, width;
> +	int ret = -EINVAL;
> +
> +	state = v4l2_subdev_lock_and_get_active_state(&interface->sd);
> +
> +	active_out_fmt = rkcif_stream_find_output_fmt(stream, false,
> +						      stream->pix.pixelformat);
> +	if (!active_out_fmt)
> +		goto out;
> +
> +	height = stream->pix.height;
> +	width = stream->pix.width;
> +	/* TODO there may be different factors and/or alignment constraints */
> +	vlw = ALIGN(width * 2, 8);
> +
> +	ctrl0 |= active_out_fmt->mipi.dt << 10;
> +	ctrl0 |= active_out_fmt->mipi.ctrl0_val;
> +	ctrl0 |= RKCIF_MIPI_CTRL0_CROP_EN;
> +	ctrl0 |= RKCIF_MIPI_CTRL0_CAP_EN;
> +
> +	ctrl1 = RKCIF_XY_COORD(width, height);
> +
> +	int_mask |= RKCIF_MIPI_INT_FRAME0_END(stream->id);
> +	int_mask |= RKCIF_MIPI_INT_FRAME1_END(stream->id);
> +
> +	int_temp = rkcif_mipi_read(interface, RKCIF_MIPI_INTEN);
> +	int_temp |= int_mask;
> +	rkcif_mipi_write(interface, RKCIF_MIPI_INTEN, int_temp);
> +
> +	int_temp = rkcif_mipi_read(interface, RKCIF_MIPI_INTSTAT);
> +	int_temp &= ~int_mask;
> +	rkcif_mipi_write(interface, RKCIF_MIPI_INTSTAT, int_temp);
> +
> +	rkcif_mipi_stream_write(stream, RKCIF_MIPI_FRAME0_VLW_Y, vlw);
> +	rkcif_mipi_stream_write(stream, RKCIF_MIPI_FRAME1_VLW_Y, vlw);
> +	rkcif_mipi_stream_write(stream, RKCIF_MIPI_FRAME0_VLW_UV, vlw);
> +	rkcif_mipi_stream_write(stream, RKCIF_MIPI_FRAME1_VLW_UV, vlw);
> +	rkcif_mipi_stream_write(stream, RKCIF_MIPI_CROP_START, 0x0);
> +	rkcif_mipi_stream_write(stream, RKCIF_MIPI_CTRL1, ctrl1);
> +	rkcif_mipi_stream_write(stream, RKCIF_MIPI_CTRL0, ctrl0);
> +
> +	ret = 0;
> +
> +out:
> +	v4l2_subdev_unlock_state(state);
> +	return ret;
> +}
> +
> +static void rkcif_mipi_stop_streaming(struct rkcif_stream *stream)
> +{
> +	struct rkcif_interface *interface = stream->interface;
> +	struct v4l2_subdev_state *state;
> +	u32 int_temp = 0, int_mask = 0;
> +
> +	state = v4l2_subdev_lock_and_get_active_state(&interface->sd);
> +
> +	rkcif_mipi_stream_write(stream, RKCIF_MIPI_CTRL0, 0);
> +
> +	int_mask |= RKCIF_MIPI_INT_FRAME0_END(stream->id);
> +	int_mask |= RKCIF_MIPI_INT_FRAME1_END(stream->id);
> +
> +	int_temp = rkcif_mipi_read(interface, RKCIF_MIPI_INTEN);
> +	int_temp &= ~int_mask;
> +	rkcif_mipi_write(interface, RKCIF_MIPI_INTEN, int_temp);
> +
> +	int_temp = rkcif_mipi_read(interface, RKCIF_MIPI_INTSTAT);
> +	int_temp &= ~int_mask;
> +	rkcif_mipi_write(interface, RKCIF_MIPI_INTSTAT, int_temp);
> +
> +	stream->stopping = false;
> +
> +	v4l2_subdev_unlock_state(state);
> +}
> +
> +static void rkcif_mipi_set_crop(struct rkcif_stream *stream, u16 left, u16 top)
> +{
> +	u32 val;
> +
> +	val = RKCIF_XY_COORD(left, top);
> +	rkcif_mipi_stream_write(stream, RKCIF_MIPI_CROP_START, val);
> +}
> +
>   irqreturn_t rkcif_mipi_isr(int irq, void *ctx)
>   {
> +	struct device *dev = ctx;
> +	struct rkcif_device *rkcif = dev_get_drvdata(dev);
>   	irqreturn_t ret = IRQ_NONE;
> +	u32 intstat;
> +
> +	for (int i = 0; i < rkcif->match_data->mipi->mipi_num; i++) {
> +		enum rkcif_interface_index index = RKCIF_MIPI_BASE + i;
> +		struct rkcif_interface *interface = &rkcif->interfaces[index];
> +
> +		intstat = rkcif_mipi_read(interface, RKCIF_MIPI_INTSTAT);
> +		rkcif_mipi_write(interface, RKCIF_MIPI_INTSTAT, intstat);
> +
> +		for (int j = 0; j < interface->streams_num; j++) {
> +			struct rkcif_stream *stream = &interface->streams[j];
> +
> +			if (intstat & RKCIF_MIPI_INT_FRAME0_END(stream->id) ||
> +			    intstat & RKCIF_MIPI_INT_FRAME1_END(stream->id)) {
> +				rkcif_stream_pingpong(stream);
> +				ret = IRQ_HANDLED;
> +			}
> +		}
> +	}
> 
>   	return ret;
>   }
> 
>   int rkcif_mipi_register(struct rkcif_device *rkcif)
>   {
> +	int ret, i;
> +
> +	if (!rkcif->match_data->mipi)
> +		return 0;
> +
> +	for (i = 0; i < rkcif->match_data->mipi->mipi_num; i++) {
> +		enum rkcif_interface_index index = RKCIF_MIPI_BASE + i;
> +		struct rkcif_interface *interface = &rkcif->interfaces[index];
> +
> +		interface->index = index;
> +		interface->type = RKCIF_IF_MIPI;
> +		interface->in_fmts = mipi_in_fmts;
> +		interface->in_fmts_num = ARRAY_SIZE(mipi_in_fmts);
> +		interface->set_crop = rkcif_mipi_set_crop;
> +		interface->streams_num = 0;
> +		ret = rkcif_interface_register(rkcif, interface);
> +		if (ret)
> +			continue;
> +
> +		for (int j = 0; j < RKCIF_ID_MAX; j++) {
> +			struct rkcif_stream *stream = &interface->streams[j];
> +
> +			stream->id = j;
> +			stream->interface = interface;
> +			stream->out_fmts = mipi_out_fmts;
> +			stream->out_fmts_num = ARRAY_SIZE(mipi_out_fmts);
> +			stream->queue_buffer = rkcif_mipi_queue_buffer;
> +			stream->start_streaming = rkcif_mipi_start_streaming;
> +			stream->stop_streaming = rkcif_mipi_stop_streaming;
> +			ret = rkcif_stream_register(rkcif, stream);
> +			if (ret)
> +				goto err;
> +			interface->streams_num++;
> +		}
> +	}
> +
>   	return 0;
> +
> +err:
> +	for (; i >= 0; i--) {
> +		enum rkcif_interface_index index = RKCIF_MIPI_BASE + i;
> +		struct rkcif_interface *interface = &rkcif->interfaces[index];
> +
> +		for (int j = 0; j < interface->streams_num; j++)
> +			rkcif_stream_unregister(&interface->streams[j]);
> +
> +		rkcif_interface_unregister(interface);
> +	}
> +	return ret;
>   }
> 
>   void rkcif_mipi_unregister(struct rkcif_device *rkcif)
>   {
> +	if (!rkcif->match_data->mipi)
> +		return;
> +
> +	for (int i = 0; i < rkcif->match_data->mipi->mipi_num; i++) {
> +		enum rkcif_interface_index index = RKCIF_MIPI_BASE + i;
> +		struct rkcif_interface *interface = &rkcif->interfaces[index];
> +
> +		for (int j = 0; j < interface->streams_num; j++)
> +			rkcif_stream_unregister(&interface->streams[j]);
> +
> +		rkcif_interface_unregister(interface);
> +	}
>   }
> diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.h b/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.h
> index ee1a50a59505..1248af70bdab 100644
> --- a/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.h
> +++ b/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.h
> @@ -11,6 +11,8 @@
> 
>   #include "rkcif-common.h"
> 
> +extern const struct rkcif_mipi_match_data rkcif_rk3568_vicap_mipi_match_data;
> +
>   int rkcif_mipi_register(struct rkcif_device *rkcif);
> 
>   void rkcif_mipi_unregister(struct rkcif_device *rkcif);
> diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-common.h b/drivers/media/platform/rockchip/rkcif/rkcif-common.h
> index 62fb3580eec5..d0d929648f21 100644
> --- a/drivers/media/platform/rockchip/rkcif/rkcif-common.h
> +++ b/drivers/media/platform/rockchip/rkcif/rkcif-common.h
> @@ -93,9 +93,14 @@ struct rkcif_output_fmt {
>   	u32 fourcc;
>   	u32 mbus_code;
>   	u8 cplanes;
> +	u8 depth;
> 
>   	union {
>   		u32 dvp_fmt_val;
> +		struct {
> +			u8 dt;
> +			u32 ctrl0_val;
> +		} mipi;
>   	};
>   };
> 
> @@ -183,6 +188,16 @@ struct rkcif_interface {
>   	void (*set_crop)(struct rkcif_stream *stream, u16 left, u16 top);
>   };
> 
> +struct rkcif_mipi_match_data {
> +	unsigned int mipi_num;
> +	unsigned int regs[RKCIF_MIPI_REGISTER_MAX];
> +	unsigned int regs_id[RKCIF_ID_MAX][RKCIF_MIPI_ID_REGISTER_MAX];
> +
> +	struct {
> +		unsigned int offset;
> +	} blocks[RKCIF_MIPI_MAX - RKCIF_MIPI_BASE];
> +};
> +
>   struct rkcif_dvp_match_data {
>   	const struct rkcif_input_fmt *in_fmts;
>   	unsigned int in_fmts_num;
> @@ -198,6 +213,7 @@ struct rkcif_match_data {
>   	const char *const *clks;
>   	unsigned int clks_num;
>   	const struct rkcif_dvp_match_data *dvp;
> +	const struct rkcif_mipi_match_data *mipi;
>   };
> 
>   struct rkcif_device {
> diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-dev.c b/drivers/media/platform/rockchip/rkcif/rkcif-dev.c
> index 2dcd35771fc9..1d8815156c46 100644
> --- a/drivers/media/platform/rockchip/rkcif/rkcif-dev.c
> +++ b/drivers/media/platform/rockchip/rkcif/rkcif-dev.c
> @@ -49,6 +49,7 @@ static const struct rkcif_match_data rk3568_vicap_match_data = {
>   	.clks = rk3568_vicap_clks,
>   	.clks_num = ARRAY_SIZE(rk3568_vicap_clks),
>   	.dvp = &rkcif_rk3568_vicap_dvp_match_data,
> +	.mipi = &rkcif_rk3568_vicap_mipi_match_data,
>   };
> 
>   static const struct of_device_id rkcif_plat_of_match[] = {
> diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-interface.c b/drivers/media/platform/rockchip/rkcif/rkcif-interface.c
> index 0ec524586594..f598a62f9fbd 100644
> --- a/drivers/media/platform/rockchip/rkcif/rkcif-interface.c
> +++ b/drivers/media/platform/rockchip/rkcif/rkcif-interface.c
> @@ -188,7 +188,10 @@ static int rkcif_interface_enable_streams(struct v4l2_subdev *sd,
>   		stream = &interface->streams[RKCIF_ID0];
>   		rkcif_interface_apply_crop(stream, state);
>   	} else {
> -		/* TODO implement for MIPI */
> +		for_each_active_route(&state->routing, route) {
> +			stream = &interface->streams[route->sink_stream];
> +			rkcif_interface_apply_crop(stream, state);
> +		}
>   	}
> 
>   	mask = v4l2_subdev_state_xlate_streams(state, RKCIF_IF_PAD_SINK,
> diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-regs.h b/drivers/media/platform/rockchip/rkcif/rkcif-regs.h
> index 07fd64174e80..3d1f0c45c638 100644
> --- a/drivers/media/platform/rockchip/rkcif/rkcif-regs.h
> +++ b/drivers/media/platform/rockchip/rkcif/rkcif-regs.h
> @@ -30,7 +30,29 @@ enum rkcif_dvp_register_index {
>   	RKCIF_DVP_REGISTER_MAX
>   };
> 
> -#define RKCIF_REGISTER_NOTSUPPORTED           0x420000
> +enum rkcif_mipi_register_index {
> +	RKCIF_MIPI_CTRL,
> +	RKCIF_MIPI_INTEN,
> +	RKCIF_MIPI_INTSTAT,
> +	RKCIF_MIPI_REGISTER_MAX
> +};
> +
> +enum rkcif_mipi_id_register_index {
> +	RKCIF_MIPI_CTRL0,
> +	RKCIF_MIPI_CTRL1,
> +	RKCIF_MIPI_FRAME0_ADDR_Y,
> +	RKCIF_MIPI_FRAME0_ADDR_UV,
> +	RKCIF_MIPI_FRAME0_VLW_Y,
> +	RKCIF_MIPI_FRAME0_VLW_UV,
> +	RKCIF_MIPI_FRAME1_ADDR_Y,
> +	RKCIF_MIPI_FRAME1_ADDR_UV,
> +	RKCIF_MIPI_FRAME1_VLW_Y,
> +	RKCIF_MIPI_FRAME1_VLW_UV,
> +	RKCIF_MIPI_CROP_START,
> +	RKCIF_MIPI_ID_REGISTER_MAX
> +};
> +
> +#define RKCIF_REGISTER_NOTSUPPORTED	       0x420000
> 
>   #define RKCIF_FETCH_Y(VAL)		       ((VAL) & 0x1fff)
> 
> 
> --
> 2.39.5
> 
> 
> 

This looks pretty clean to me.

Reviewed-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org>

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

* Re: [PATCH v6 04/13] media: dt-bindings: add rockchip rk3568 vicap
  2025-04-30  9:15 ` [PATCH v6 04/13] media: dt-bindings: add rockchip rk3568 vicap Michael Riesch via B4 Relay
@ 2025-05-01 11:49   ` Krzysztof Kozlowski
  0 siblings, 0 replies; 34+ messages in thread
From: Krzysztof Kozlowski @ 2025-05-01 11:49 UTC (permalink / raw)
  To: Michael Riesch
  Cc: Mehdi Djait, Maxime Chevallier, Théo Lebrun, Gerald Loacker,
	Thomas Petazzoni, Laurent Pinchart, Mauro Carvalho Chehab,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner,
	Kever Yang, Nicolas Dufresne, Sebastian Reichel,
	Collabora Kernel Team, Paul Kocialkowski, Alexander Shiyan,
	Val Packett, Rob Herring, Philipp Zabel, Sakari Ailus,
	linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-rockchip, Michael Riesch

On Wed, Apr 30, 2025 at 11:15:53AM GMT, Michael Riesch wrote:
> From: Michael Riesch <michael.riesch@wolfvision.net>
> 
> Add documentation for the Rockchip RK3568 Video Capture (VICAP) unit.
> 
> Signed-off-by: Michael Riesch <michael.riesch@wolfvision.net>
> Signed-off-by: Michael Riesch <michael.riesch@collabora.com>
> ---
>  .../bindings/media/rockchip,rk3568-vicap.yaml      | 170 +++++++++++++++++++++
>  MAINTAINERS                                        |   1 +
>  2 files changed, 171 insertions(+)

Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>

Best regards,
Krzysztof


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

* Re: [PATCH v6 07/13] media: rockchip: rkcif: add driver for mipi csi-2 receiver
  2025-04-30  9:15 ` [PATCH v6 07/13] media: rockchip: rkcif: add driver for mipi csi-2 receiver Michael Riesch via B4 Relay
  2025-05-01  0:29   ` Bryan O'Donoghue
@ 2025-05-01 21:57   ` Sakari Ailus
  2025-05-02 13:31   ` Laurent Pinchart
  2 siblings, 0 replies; 34+ messages in thread
From: Sakari Ailus @ 2025-05-01 21:57 UTC (permalink / raw)
  To: michael.riesch
  Cc: Mehdi Djait, Maxime Chevallier, Théo Lebrun, Gerald Loacker,
	Thomas Petazzoni, Laurent Pinchart, Mauro Carvalho Chehab,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner,
	Kever Yang, Nicolas Dufresne, Sebastian Reichel,
	Collabora Kernel Team, Paul Kocialkowski, Alexander Shiyan,
	Val Packett, Rob Herring, Philipp Zabel, linux-media, devicetree,
	linux-kernel, linux-arm-kernel, linux-rockchip, Michael Riesch

Hi Michael,

On Wed, Apr 30, 2025 at 11:15:56AM +0200, Michael Riesch via B4 Relay wrote:
> +	link_freq = v4l2_get_link_freq(csi_dev->source_sd->ctrl_handler, 0, 0);

Use the source pad for this, the control handler is there just for
compatibility and will be removed soon.

-- 
Sakari Ailus

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

* Re: [PATCH v6 07/13] media: rockchip: rkcif: add driver for mipi csi-2 receiver
  2025-04-30  9:15 ` [PATCH v6 07/13] media: rockchip: rkcif: add driver for mipi csi-2 receiver Michael Riesch via B4 Relay
  2025-05-01  0:29   ` Bryan O'Donoghue
  2025-05-01 21:57   ` Sakari Ailus
@ 2025-05-02 13:31   ` Laurent Pinchart
  2025-05-02 14:19     ` Michael Riesch
  2 siblings, 1 reply; 34+ messages in thread
From: Laurent Pinchart @ 2025-05-02 13:31 UTC (permalink / raw)
  To: michael.riesch
  Cc: Mehdi Djait, Maxime Chevallier, Théo Lebrun, Gerald Loacker,
	Thomas Petazzoni, Mauro Carvalho Chehab, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner, Kever Yang,
	Nicolas Dufresne, Sebastian Reichel, Collabora Kernel Team,
	Paul Kocialkowski, Alexander Shiyan, Val Packett, Rob Herring,
	Philipp Zabel, Sakari Ailus, linux-media, devicetree,
	linux-kernel, linux-arm-kernel, linux-rockchip, Michael Riesch

Hi Michael,

Thank you for the patch.

On Wed, Apr 30, 2025 at 11:15:56AM +0200, Michael Riesch via B4 Relay wrote:
> From: Michael Riesch <michael.riesch@wolfvision.net>
> 
> The Rockchip RK3568 MIPI CSI-2 Receiver is a CSI-2 bridge with one
> input port and one output port. It receives the data with the help
> of an external MIPI PHY (C-PHY or D-PHY) and passes it to the
> Rockchip RK3568 Video Capture (VICAP) block.
> 
> Add a V4L2 subdevice driver for this unit.
> 
> Signed-off-by: Michael Riesch <michael.riesch@wolfvision.net>
> Signed-off-by: Michael Riesch <michael.riesch@collabora.com>
> ---
>  drivers/media/platform/rockchip/rkcif/Makefile     |   3 +
>  .../rockchip/rkcif/rkcif-mipi-csi-receiver.c       | 731 +++++++++++++++++++++
>  2 files changed, 734 insertions(+)
> 
> diff --git a/drivers/media/platform/rockchip/rkcif/Makefile b/drivers/media/platform/rockchip/rkcif/Makefile
> index 818424972c7b..a5c18a45c213 100644
> --- a/drivers/media/platform/rockchip/rkcif/Makefile
> +++ b/drivers/media/platform/rockchip/rkcif/Makefile
> @@ -5,3 +5,6 @@ rockchip-cif-objs += rkcif-dev.o \
>  	rkcif-capture-mipi.o \
>  	rkcif-interface.o \
>  	rkcif-stream.o
> +
> +obj-$(CONFIG_VIDEO_ROCKCHIP_CIF) += rockchip-mipi-csi.o
> +rockchip-mipi-csi-objs += rkcif-mipi-csi-receiver.o
> diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-mipi-csi-receiver.c b/drivers/media/platform/rockchip/rkcif/rkcif-mipi-csi-receiver.c
> new file mode 100644
> index 000000000000..81489f70490f
> --- /dev/null
> +++ b/drivers/media/platform/rockchip/rkcif/rkcif-mipi-csi-receiver.c
> @@ -0,0 +1,731 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Rockchip MIPI CSI-2 Receiver Driver
> + *
> + * Copyright (C) 2019 Rockchip Electronics Co., Ltd.
> + * Copyright (C) 2025 Michael Riesch <michael.riesch@wolfvision.net>
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/io.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_graph.h>
> +#include <linux/of_platform.h>
> +#include <linux/phy/phy.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/reset.h>
> +
> +#include <media/mipi-csi2.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-fwnode.h>
> +#include <media/v4l2-subdev.h>
> +
> +#define CSI2HOST_N_LANES     0x04
> +#define CSI2HOST_CSI2_RESETN 0x10
> +#define CSI2HOST_PHY_STATE   0x14
> +#define CSI2HOST_ERR1	     0x20
> +#define CSI2HOST_ERR2	     0x24
> +#define CSI2HOST_MSK1	     0x28
> +#define CSI2HOST_MSK2	     0x2c
> +#define CSI2HOST_CONTROL     0x40

I'm trying to get to the bottom of the CSI-2 RX integration questions
for the RK3568. Some of the registers here seem to the CSI2RX_1C00 block
as documented starting on page 1059 of the RK3568 TRM (revision 1.1),
but they're not an exact match. The control register, in particular,
doesn't match at all. Where is this CSI-2 receiver documented in the TRM
?

> +
> +#define SW_CPHY_EN(x)	     ((x) << 0)
> +#define SW_DSI_EN(x)	     ((x) << 4)
> +#define SW_DATATYPE_FS(x)    ((x) << 8)
> +#define SW_DATATYPE_FE(x)    ((x) << 14)
> +#define SW_DATATYPE_LS(x)    ((x) << 20)
> +#define SW_DATATYPE_LE(x)    ((x) << 26)
> +
> +#define RKCIF_CSI_CLKS_MAX   1
> +
> +enum {
> +	RKCIF_CSI_PAD_SINK,
> +	RKCIF_CSI_PAD_SRC,
> +	RKCIF_CSI_PAD_MAX,
> +};
> +
> +struct rkcif_csi_format {
> +	u32 code;
> +	u8 depth;
> +	u8 csi_dt;
> +};
> +
> +struct rkcif_csi_device {
> +	struct device *dev;
> +
> +	void __iomem *base_addr;
> +	struct clk_bulk_data *clks;
> +	unsigned int clks_num;
> +	struct phy *phy;
> +	struct reset_control *reset;
> +
> +	const struct rkcif_csi_format *formats;
> +	unsigned int formats_num;
> +
> +	struct media_pad pads[RKCIF_CSI_PAD_MAX];
> +	struct v4l2_async_notifier notifier;
> +	struct v4l2_fwnode_endpoint vep;
> +	struct v4l2_subdev sd;
> +
> +	struct v4l2_subdev *source_sd;
> +	u32 source_pad;
> +};
> +
> +static const struct v4l2_mbus_framefmt default_format = {
> +	.width = 3840,
> +	.height = 2160,
> +	.code = MEDIA_BUS_FMT_SRGGB10_1X10,
> +	.field = V4L2_FIELD_NONE,
> +	.colorspace = V4L2_COLORSPACE_RAW,
> +	.ycbcr_enc = V4L2_YCBCR_ENC_601,
> +	.quantization = V4L2_QUANTIZATION_FULL_RANGE,
> +	.xfer_func = V4L2_XFER_FUNC_NONE,
> +};
> +
> +static const struct rkcif_csi_format formats[] = {
> +	/* YUV formats */
> +	{
> +		.code = MEDIA_BUS_FMT_YUYV8_1X16,
> +		.depth = 16,
> +		.csi_dt = MIPI_CSI2_DT_YUV422_8B,
> +	},
> +	{
> +		.code = MEDIA_BUS_FMT_UYVY8_1X16,
> +		.depth = 16,
> +		.csi_dt = MIPI_CSI2_DT_YUV422_8B,
> +	},
> +	{
> +		.code = MEDIA_BUS_FMT_YVYU8_1X16,
> +		.depth = 16,
> +		.csi_dt = MIPI_CSI2_DT_YUV422_8B,
> +	},
> +	{
> +		.code = MEDIA_BUS_FMT_VYUY8_1X16,
> +		.depth = 16,
> +		.csi_dt = MIPI_CSI2_DT_YUV422_8B,
> +	},
> +	/* RGB formats */
> +	{
> +		.code = MEDIA_BUS_FMT_RGB888_1X24,
> +		.depth = 24,
> +		.csi_dt = MIPI_CSI2_DT_RGB888,
> +	},
> +	{
> +		.code = MEDIA_BUS_FMT_BGR888_1X24,
> +		.depth = 24,
> +		.csi_dt = MIPI_CSI2_DT_RGB888,
> +	},
> +	/* Bayer formats */
> +	{
> +		.code = MEDIA_BUS_FMT_SBGGR8_1X8,
> +		.depth = 8,
> +		.csi_dt = MIPI_CSI2_DT_RAW8,
> +	},
> +	{
> +		.code = MEDIA_BUS_FMT_SGBRG8_1X8,
> +		.depth = 8,
> +		.csi_dt = MIPI_CSI2_DT_RAW8,
> +	},
> +	{
> +		.code = MEDIA_BUS_FMT_SGRBG8_1X8,
> +		.depth = 8,
> +		.csi_dt = MIPI_CSI2_DT_RAW8,
> +	},
> +	{
> +		.code = MEDIA_BUS_FMT_SRGGB8_1X8,
> +		.depth = 8,
> +		.csi_dt = MIPI_CSI2_DT_RAW8,
> +	},
> +	{
> +		.code = MEDIA_BUS_FMT_SBGGR10_1X10,
> +		.depth = 10,
> +		.csi_dt = MIPI_CSI2_DT_RAW10,
> +	},
> +	{
> +		.code = MEDIA_BUS_FMT_SGBRG10_1X10,
> +		.depth = 10,
> +		.csi_dt = MIPI_CSI2_DT_RAW10,
> +	},
> +	{
> +		.code = MEDIA_BUS_FMT_SGRBG10_1X10,
> +		.depth = 10,
> +		.csi_dt = MIPI_CSI2_DT_RAW10,
> +	},
> +	{
> +		.code = MEDIA_BUS_FMT_SRGGB10_1X10,
> +		.depth = 10,
> +		.csi_dt = MIPI_CSI2_DT_RAW10,
> +	},
> +	{
> +		.code = MEDIA_BUS_FMT_SBGGR12_1X12,
> +		.depth = 12,
> +		.csi_dt = MIPI_CSI2_DT_RAW12,
> +	},
> +	{
> +		.code = MEDIA_BUS_FMT_SGBRG12_1X12,
> +		.depth = 12,
> +		.csi_dt = MIPI_CSI2_DT_RAW12,
> +	},
> +	{
> +		.code = MEDIA_BUS_FMT_SGRBG12_1X12,
> +		.depth = 12,
> +		.csi_dt = MIPI_CSI2_DT_RAW12,
> +	},
> +	{
> +		.code = MEDIA_BUS_FMT_SRGGB12_1X12,
> +		.depth = 12,
> +		.csi_dt = MIPI_CSI2_DT_RAW12,
> +	},
> +};
> +
> +static inline struct rkcif_csi_device *to_rkcif_csi(struct v4l2_subdev *sd)
> +{
> +	return container_of(sd, struct rkcif_csi_device, sd);
> +}
> +
> +static inline __maybe_unused void
> +rkcif_csi_write(struct rkcif_csi_device *csi_dev, unsigned int addr, u32 val)
> +{
> +	writel(val, csi_dev->base_addr + addr);
> +}
> +
> +static inline __maybe_unused u32
> +rkcif_csi_read(struct rkcif_csi_device *csi_dev, unsigned int addr)
> +{
> +	return readl(csi_dev->base_addr + addr);
> +}
> +
> +static const struct rkcif_csi_format *
> +rkcif_csi_find_format(struct rkcif_csi_device *csi_dev, u32 mbus_code)
> +{
> +	const struct rkcif_csi_format *format;
> +
> +	WARN_ON(csi_dev->formats_num == 0);
> +
> +	for (int i = 0; i < csi_dev->formats_num; i++) {
> +		format = &csi_dev->formats[i];
> +		if (format->code == mbus_code)
> +			return format;
> +	}
> +
> +	return NULL;
> +}
> +
> +static int rkcif_csi_start(struct rkcif_csi_device *csi_dev)
> +{
> +	enum v4l2_mbus_type bus_type = csi_dev->vep.bus_type;
> +	union phy_configure_opts opts;
> +	s64 link_freq;
> +	u32 lanes = csi_dev->vep.bus.mipi_csi2.num_data_lanes;
> +	u32 control = 0;
> +
> +	if (lanes < 1 || lanes > 4)
> +		return -EINVAL;
> +
> +	/* set mult and div to 0, thus completely rely on V4L2_CID_LINK_FREQ */
> +	link_freq = v4l2_get_link_freq(csi_dev->source_sd->ctrl_handler, 0, 0);
> +	if (link_freq <= 0)
> +		return -EINVAL;
> +
> +	if (bus_type == V4L2_MBUS_CSI2_DPHY) {
> +		struct phy_configure_opts_mipi_dphy *cfg = &opts.mipi_dphy;
> +
> +		phy_mipi_dphy_get_default_config_for_hsclk(link_freq * 2, lanes,
> +							   cfg);
> +		phy_set_mode(csi_dev->phy, PHY_MODE_MIPI_DPHY);
> +		phy_configure(csi_dev->phy, &opts);
> +
> +		control |= SW_CPHY_EN(0);
> +
> +	} else if (bus_type == V4L2_MBUS_CSI2_CPHY) {
> +		control |= SW_CPHY_EN(1);
> +
> +		/* TODO: implement CPHY configuration */
> +	} else {
> +		return -EINVAL;
> +	}
> +
> +	control |= SW_DATATYPE_FS(0x00) | SW_DATATYPE_FE(0x01) |
> +		   SW_DATATYPE_LS(0x02) | SW_DATATYPE_LE(0x03);
> +
> +	rkcif_csi_write(csi_dev, CSI2HOST_N_LANES, lanes - 1);
> +	rkcif_csi_write(csi_dev, CSI2HOST_CONTROL, control);
> +	rkcif_csi_write(csi_dev, CSI2HOST_CSI2_RESETN, 1);
> +
> +	phy_power_on(csi_dev->phy);
> +
> +	return 0;
> +}
> +
> +static void rkcif_csi_stop(struct rkcif_csi_device *csi_dev)
> +{
> +	phy_power_off(csi_dev->phy);
> +
> +	rkcif_csi_write(csi_dev, CSI2HOST_CSI2_RESETN, 0);
> +	rkcif_csi_write(csi_dev, CSI2HOST_MSK1, ~0);
> +	rkcif_csi_write(csi_dev, CSI2HOST_MSK2, ~0);
> +}
> +
> +static const struct media_entity_operations rkcif_csi_media_ops = {
> +	.link_validate = v4l2_subdev_link_validate,
> +};
> +
> +static int rkcif_csi_enum_mbus_code(struct v4l2_subdev *sd,
> +				    struct v4l2_subdev_state *sd_state,
> +				    struct v4l2_subdev_mbus_code_enum *code)
> +{
> +	struct rkcif_csi_device *csi_dev = to_rkcif_csi(sd);
> +
> +	if (code->pad == RKCIF_CSI_PAD_SRC) {
> +		const struct v4l2_mbus_framefmt *sink_fmt;
> +
> +		if (code->index)
> +			return -EINVAL;
> +
> +		sink_fmt = v4l2_subdev_state_get_format(sd_state,
> +							RKCIF_CSI_PAD_SINK);
> +		code->code = sink_fmt->code;
> +
> +		return 0;
> +	} else if (code->pad == RKCIF_CSI_PAD_SINK) {
> +		if (code->index > csi_dev->formats_num)
> +			return -EINVAL;
> +
> +		code->code = csi_dev->formats[code->index].code;
> +		return 0;
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static int rkcif_csi_set_fmt(struct v4l2_subdev *sd,
> +			     struct v4l2_subdev_state *state,
> +			     struct v4l2_subdev_format *format)
> +{
> +	struct rkcif_csi_device *csi_dev = to_rkcif_csi(sd);
> +	const struct rkcif_csi_format *fmt;
> +	struct v4l2_mbus_framefmt *sink, *src;
> +
> +	/* the format on the source pad always matches the sink pad */
> +	if (format->pad == RKCIF_CSI_PAD_SRC)
> +		return v4l2_subdev_get_fmt(sd, state, format);
> +
> +	sink = v4l2_subdev_state_get_format(state, format->pad, format->stream);
> +	if (!sink)
> +		return -EINVAL;
> +
> +	fmt = rkcif_csi_find_format(csi_dev, format->format.code);
> +	if (fmt)
> +		*sink = format->format;
> +	else
> +		*sink = default_format;
> +
> +	/* propagate the format to the source pad */
> +	src = v4l2_subdev_state_get_opposite_stream_format(state, format->pad,
> +							   format->stream);
> +	if (!src)
> +		return -EINVAL;
> +
> +	*src = *sink;
> +
> +	return 0;
> +}
> +
> +static int rkcif_csi_set_routing(struct v4l2_subdev *sd,
> +				 struct v4l2_subdev_state *state,
> +				 enum v4l2_subdev_format_whence which,
> +				 struct v4l2_subdev_krouting *routing)
> +{
> +	int ret;
> +
> +	ret = v4l2_subdev_routing_validate(sd, routing,
> +					   V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
> +	if (ret)
> +		return ret;
> +
> +	ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing,
> +					       &default_format);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int rkcif_csi_enable_streams(struct v4l2_subdev *sd,
> +				    struct v4l2_subdev_state *state, u32 pad,
> +				    u64 streams_mask)
> +{
> +	struct rkcif_csi_device *csi_dev = to_rkcif_csi(sd);
> +	struct v4l2_subdev *remote_sd;
> +	struct media_pad *sink_pad, *remote_pad;
> +	struct device *dev = csi_dev->dev;
> +	u64 mask;
> +	int ret;
> +
> +	sink_pad = &sd->entity.pads[RKCIF_CSI_PAD_SINK];
> +	remote_pad = media_pad_remote_pad_first(sink_pad);
> +	remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity);
> +
> +	mask = v4l2_subdev_state_xlate_streams(state, RKCIF_CSI_PAD_SINK,
> +					       RKCIF_CSI_PAD_SRC,
> +					       &streams_mask);
> +
> +	ret = pm_runtime_resume_and_get(dev);
> +	if (ret)
> +		goto err;
> +
> +	ret = rkcif_csi_start(csi_dev);
> +	if (ret) {
> +		dev_err(dev, "failed to enable CSI hardware\n");
> +		goto err_pm_runtime_put;
> +	}
> +
> +	ret = v4l2_subdev_enable_streams(remote_sd, remote_pad->index, mask);
> +	if (ret)
> +		goto err_csi_stop;
> +
> +	return 0;
> +
> +err_csi_stop:
> +	rkcif_csi_stop(csi_dev);
> +err_pm_runtime_put:
> +	pm_runtime_put_sync(dev);
> +err:
> +	return ret;
> +}
> +
> +static int rkcif_csi_disable_streams(struct v4l2_subdev *sd,
> +				     struct v4l2_subdev_state *state, u32 pad,
> +				     u64 streams_mask)
> +{
> +	struct rkcif_csi_device *csi_dev = to_rkcif_csi(sd);
> +	struct v4l2_subdev *remote_sd;
> +	struct media_pad *sink_pad, *remote_pad;
> +	struct device *dev = csi_dev->dev;
> +	u64 mask;
> +	int ret;
> +
> +	sink_pad = &sd->entity.pads[RKCIF_CSI_PAD_SINK];
> +	remote_pad = media_pad_remote_pad_first(sink_pad);
> +	remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity);
> +
> +	mask = v4l2_subdev_state_xlate_streams(state, RKCIF_CSI_PAD_SINK,
> +					       RKCIF_CSI_PAD_SRC,
> +					       &streams_mask);
> +
> +	ret = v4l2_subdev_disable_streams(remote_sd, remote_pad->index, mask);
> +
> +	rkcif_csi_stop(csi_dev);
> +
> +	pm_runtime_mark_last_busy(dev);
> +	pm_runtime_put_autosuspend(dev);
> +
> +	return ret;
> +}
> +
> +static const struct v4l2_subdev_pad_ops rkcif_csi_pad_ops = {
> +	.enum_mbus_code = rkcif_csi_enum_mbus_code,
> +	.get_fmt = v4l2_subdev_get_fmt,
> +	.set_fmt = rkcif_csi_set_fmt,
> +	.set_routing = rkcif_csi_set_routing,
> +	.enable_streams = rkcif_csi_enable_streams,
> +	.disable_streams = rkcif_csi_disable_streams,
> +};
> +
> +static const struct v4l2_subdev_ops rkcif_csi_ops = {
> +	.pad = &rkcif_csi_pad_ops,
> +};
> +
> +static int rkcif_csi_init_state(struct v4l2_subdev *sd,
> +				struct v4l2_subdev_state *state)
> +{
> +	struct v4l2_subdev_route routes[] = {
> +		{
> +			.sink_pad = RKCIF_CSI_PAD_SINK,
> +			.sink_stream = 0,
> +			.source_pad = RKCIF_CSI_PAD_SRC,
> +			.source_stream = 0,
> +			.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
> +		},
> +	};
> +	struct v4l2_subdev_krouting routing = {
> +		.len_routes = ARRAY_SIZE(routes),
> +		.num_routes = ARRAY_SIZE(routes),
> +		.routes = routes,
> +	};
> +	int ret;
> +
> +	ret = v4l2_subdev_set_routing_with_fmt(sd, state, &routing,
> +					       &default_format);
> +
> +	return ret;
> +}
> +
> +static const struct v4l2_subdev_internal_ops rkcif_csi_internal_ops = {
> +	.init_state = rkcif_csi_init_state,
> +};
> +
> +static int rkcif_csi_notifier_bound(struct v4l2_async_notifier *notifier,
> +				    struct v4l2_subdev *sd,
> +				    struct v4l2_async_connection *asd)
> +{
> +	struct rkcif_csi_device *csi_dev =
> +		container_of(notifier, struct rkcif_csi_device, notifier);
> +	int source_pad;
> +
> +	source_pad = media_entity_get_fwnode_pad(&sd->entity, sd->fwnode,
> +						 MEDIA_PAD_FL_SOURCE);
> +	if (source_pad < 0) {
> +		dev_err(csi_dev->dev, "failed to find source pad for %s\n",
> +			sd->name);
> +		return source_pad;
> +	}
> +
> +	csi_dev->source_sd = sd;
> +	csi_dev->source_pad = source_pad;
> +
> +	return media_create_pad_link(&sd->entity, source_pad,
> +				     &csi_dev->sd.entity, RKCIF_CSI_PAD_SINK,
> +				     MEDIA_LNK_FL_ENABLED);
> +}
> +
> +static const struct v4l2_async_notifier_operations rkcif_csi_notifier_ops = {
> +	.bound = rkcif_csi_notifier_bound,
> +};
> +
> +static int rkcif_csi_register_notifier(struct rkcif_csi_device *csi_dev)
> +{
> +	struct v4l2_async_connection *asd;
> +	struct v4l2_async_notifier *ntf = &csi_dev->notifier;
> +	struct v4l2_fwnode_endpoint *vep = &csi_dev->vep;
> +	struct v4l2_subdev *sd = &csi_dev->sd;
> +	struct device *dev = csi_dev->dev;
> +	struct fwnode_handle *ep;
> +	int ret = 0;
> +
> +	ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, 0);
> +	if (!ep)
> +		return dev_err_probe(dev, -ENODEV, "failed to get endpoint\n");
> +
> +	vep->bus_type = V4L2_MBUS_UNKNOWN;
> +	ret = v4l2_fwnode_endpoint_parse(ep, vep);
> +	if (ret) {
> +		ret = dev_err_probe(dev, ret, "failed to parse endpoint\n");
> +		goto out;
> +	}
> +
> +	if (vep->bus_type != V4L2_MBUS_CSI2_DPHY &&
> +	    vep->bus_type != V4L2_MBUS_CSI2_CPHY) {
> +		ret = dev_err_probe(dev, -EINVAL,
> +				    "invalid bus type of endpoint\n");
> +		goto out;
> +	}
> +
> +	v4l2_async_subdev_nf_init(ntf, sd);
> +	ntf->ops = &rkcif_csi_notifier_ops;
> +
> +	asd = v4l2_async_nf_add_fwnode_remote(ntf, ep,
> +					      struct v4l2_async_connection);
> +	if (IS_ERR(asd)) {
> +		ret = PTR_ERR(asd);
> +		goto err_nf_cleanup;
> +	}
> +
> +	ret = v4l2_async_nf_register(ntf);
> +	if (ret) {
> +		ret = dev_err_probe(dev, ret, "failed to register notifier\n");
> +		goto err_nf_cleanup;
> +	}
> +
> +	goto out;
> +
> +err_nf_cleanup:
> +	v4l2_async_nf_cleanup(ntf);
> +out:
> +	fwnode_handle_put(ep);
> +	return ret;
> +}
> +
> +static int rkcif_csi_register(struct rkcif_csi_device *csi_dev)
> +{
> +	struct media_pad *pads = csi_dev->pads;
> +	struct v4l2_subdev *sd = &csi_dev->sd;
> +	int ret;
> +
> +	ret = rkcif_csi_register_notifier(csi_dev);
> +	if (ret)
> +		goto err;
> +
> +	v4l2_subdev_init(sd, &rkcif_csi_ops);
> +	sd->dev = csi_dev->dev;
> +	sd->entity.ops = &rkcif_csi_media_ops;
> +	sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
> +	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS;
> +	sd->internal_ops = &rkcif_csi_internal_ops;
> +	sd->owner = THIS_MODULE;
> +	snprintf(sd->name, sizeof(sd->name), "rockchip-mipi-csi %s",
> +		 dev_name(csi_dev->dev));
> +
> +	pads[RKCIF_CSI_PAD_SINK].flags = MEDIA_PAD_FL_SINK |
> +					 MEDIA_PAD_FL_MUST_CONNECT;
> +	pads[RKCIF_CSI_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE;
> +	ret = media_entity_pads_init(&sd->entity, RKCIF_CSI_PAD_MAX, pads);
> +	if (ret)
> +		goto err_notifier_unregister;
> +
> +	ret = v4l2_subdev_init_finalize(sd);
> +	if (ret)
> +		goto err_entity_cleanup;
> +
> +	ret = v4l2_async_register_subdev(sd);
> +	if (ret) {
> +		dev_err(sd->dev, "failed to register CSI subdev\n");
> +		goto err_subdev_cleanup;
> +	}
> +
> +	return 0;
> +
> +err_subdev_cleanup:
> +	v4l2_subdev_cleanup(sd);
> +err_entity_cleanup:
> +	media_entity_cleanup(&sd->entity);
> +err_notifier_unregister:
> +	v4l2_async_nf_unregister(&csi_dev->notifier);
> +	v4l2_async_nf_cleanup(&csi_dev->notifier);
> +err:
> +	return ret;
> +}
> +
> +static void rkcif_csi_unregister(struct rkcif_csi_device *csi_dev)
> +{
> +	struct v4l2_subdev *sd = &csi_dev->sd;
> +
> +	v4l2_async_unregister_subdev(sd);
> +	v4l2_subdev_cleanup(sd);
> +	media_entity_cleanup(&sd->entity);
> +	v4l2_async_nf_unregister(&csi_dev->notifier);
> +	v4l2_async_nf_cleanup(&csi_dev->notifier);
> +}
> +
> +static const struct of_device_id rkcif_csi_of_match[] = {
> +	{
> +		.compatible = "rockchip,rk3568-mipi-csi",
> +	},
> +	{}
> +};
> +MODULE_DEVICE_TABLE(of, rkcif_csi_of_match);
> +
> +static int rkcif_csi_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct rkcif_csi_device *csi_dev;
> +	int ret;
> +
> +	csi_dev = devm_kzalloc(dev, sizeof(*csi_dev), GFP_KERNEL);
> +	if (!csi_dev)
> +		return -ENOMEM;
> +	csi_dev->dev = dev;
> +	dev_set_drvdata(dev, csi_dev);
> +
> +	csi_dev->base_addr = devm_platform_ioremap_resource(pdev, 0);
> +	if (IS_ERR(csi_dev->base_addr))
> +		return PTR_ERR(csi_dev->base_addr);
> +
> +	ret = devm_clk_bulk_get_all(dev, &csi_dev->clks);
> +	if (ret != RKCIF_CSI_CLKS_MAX)
> +		return dev_err_probe(dev, -ENODEV, "failed to get clocks\n");
> +	csi_dev->clks_num = ret;
> +
> +	csi_dev->phy = devm_phy_get(dev, NULL);
> +	if (IS_ERR(csi_dev->phy))
> +		return dev_err_probe(dev, PTR_ERR(csi_dev->phy),
> +				     "failed to get MIPI CSI PHY\n");
> +
> +	csi_dev->reset = devm_reset_control_array_get_exclusive(dev);
> +	if (IS_ERR(csi_dev->reset))
> +		return dev_err_probe(dev, PTR_ERR(csi_dev->reset),
> +				     "failed to get reset\n");
> +
> +	csi_dev->formats = formats;
> +	csi_dev->formats_num = ARRAY_SIZE(formats);
> +
> +	pm_runtime_enable(dev);
> +
> +	ret = phy_init(csi_dev->phy);
> +	if (ret) {
> +		ret = dev_err_probe(dev, ret,
> +				    "failed to initialize MIPI CSI PHY\n");
> +		goto err_pm_runtime_disable;
> +	}
> +
> +	ret = rkcif_csi_register(csi_dev);
> +	if (ret)
> +		goto err_phy_exit;
> +
> +	return 0;
> +
> +err_phy_exit:
> +	phy_exit(csi_dev->phy);
> +err_pm_runtime_disable:
> +	pm_runtime_disable(dev);
> +	return ret;
> +}
> +
> +static void rkcif_csi_remove(struct platform_device *pdev)
> +{
> +	struct rkcif_csi_device *csi_dev = platform_get_drvdata(pdev);
> +	struct device *dev = &pdev->dev;
> +
> +	rkcif_csi_unregister(csi_dev);
> +	phy_exit(csi_dev->phy);
> +	pm_runtime_disable(dev);
> +}
> +
> +static int rkcif_csi_runtime_suspend(struct device *dev)
> +{
> +	struct rkcif_csi_device *csi_dev = dev_get_drvdata(dev);
> +
> +	clk_bulk_disable_unprepare(csi_dev->clks_num, csi_dev->clks);
> +
> +	return 0;
> +}
> +
> +static int rkcif_csi_runtime_resume(struct device *dev)
> +{
> +	struct rkcif_csi_device *csi_dev = dev_get_drvdata(dev);
> +	int ret;
> +
> +	reset_control_assert(csi_dev->reset);
> +	udelay(5);
> +	reset_control_deassert(csi_dev->reset);
> +
> +	ret = clk_bulk_prepare_enable(csi_dev->clks_num, csi_dev->clks);
> +	if (ret) {
> +		dev_err(dev, "failed to enable clocks\n");
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct dev_pm_ops rkcif_csi_pm_ops = {
> +	.runtime_suspend = rkcif_csi_runtime_suspend,
> +	.runtime_resume = rkcif_csi_runtime_resume,
> +};
> +
> +static struct platform_driver rkcif_csi_drv = {
> +	.driver = {
> +		   .name = "rockchip-mipi-csi",
> +		   .of_match_table = rkcif_csi_of_match,
> +		   .pm = &rkcif_csi_pm_ops,
> +	},
> +	.probe = rkcif_csi_probe,
> +	.remove = rkcif_csi_remove,
> +};
> +module_platform_driver(rkcif_csi_drv);
> +
> +MODULE_DESCRIPTION("Rockchip MIPI CSI-2 Receiver platform driver");
> +MODULE_LICENSE("GPL");

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v6 07/13] media: rockchip: rkcif: add driver for mipi csi-2 receiver
  2025-05-02 13:31   ` Laurent Pinchart
@ 2025-05-02 14:19     ` Michael Riesch
  2025-05-02 14:35       ` Laurent Pinchart
  0 siblings, 1 reply; 34+ messages in thread
From: Michael Riesch @ 2025-05-02 14:19 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Mehdi Djait, Maxime Chevallier, Théo Lebrun, Gerald Loacker,
	Thomas Petazzoni, Mauro Carvalho Chehab, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner, Kever Yang,
	Nicolas Dufresne, Sebastian Reichel, Collabora Kernel Team,
	Paul Kocialkowski, Alexander Shiyan, Val Packett, Rob Herring,
	Philipp Zabel, Sakari Ailus, linux-media, devicetree,
	linux-kernel, linux-arm-kernel, linux-rockchip, Michael Riesch

Hi Laurent,

On 5/2/25 15:31, Laurent Pinchart wrote:
> Hi Michael,
> 
> Thank you for the patch.
> 
> On Wed, Apr 30, 2025 at 11:15:56AM +0200, Michael Riesch via B4 Relay wrote:
>> From: Michael Riesch <michael.riesch@wolfvision.net>
>>
>> The Rockchip RK3568 MIPI CSI-2 Receiver is a CSI-2 bridge with one
>> input port and one output port. It receives the data with the help
>> of an external MIPI PHY (C-PHY or D-PHY) and passes it to the
>> Rockchip RK3568 Video Capture (VICAP) block.
>>
>> Add a V4L2 subdevice driver for this unit.
>>
>> Signed-off-by: Michael Riesch <michael.riesch@wolfvision.net>
>> Signed-off-by: Michael Riesch <michael.riesch@collabora.com>
>> ---
>>  drivers/media/platform/rockchip/rkcif/Makefile     |   3 +
>>  .../rockchip/rkcif/rkcif-mipi-csi-receiver.c       | 731 +++++++++++++++++++++
>>  2 files changed, 734 insertions(+)
>>
>> diff --git a/drivers/media/platform/rockchip/rkcif/Makefile b/drivers/media/platform/rockchip/rkcif/Makefile
>> index 818424972c7b..a5c18a45c213 100644
>> --- a/drivers/media/platform/rockchip/rkcif/Makefile
>> +++ b/drivers/media/platform/rockchip/rkcif/Makefile
>> @@ -5,3 +5,6 @@ rockchip-cif-objs += rkcif-dev.o \
>>  	rkcif-capture-mipi.o \
>>  	rkcif-interface.o \
>>  	rkcif-stream.o
>> +
>> +obj-$(CONFIG_VIDEO_ROCKCHIP_CIF) += rockchip-mipi-csi.o
>> +rockchip-mipi-csi-objs += rkcif-mipi-csi-receiver.o
>> diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-mipi-csi-receiver.c b/drivers/media/platform/rockchip/rkcif/rkcif-mipi-csi-receiver.c
>> new file mode 100644
>> index 000000000000..81489f70490f
>> --- /dev/null
>> +++ b/drivers/media/platform/rockchip/rkcif/rkcif-mipi-csi-receiver.c
>> @@ -0,0 +1,731 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Rockchip MIPI CSI-2 Receiver Driver
>> + *
>> + * Copyright (C) 2019 Rockchip Electronics Co., Ltd.
>> + * Copyright (C) 2025 Michael Riesch <michael.riesch@wolfvision.net>
>> + */
>> +
>> +#include <linux/clk.h>
>> +#include <linux/delay.h>
>> +#include <linux/io.h>
>> +#include <linux/module.h>
>> +#include <linux/of.h>
>> +#include <linux/of_graph.h>
>> +#include <linux/of_platform.h>
>> +#include <linux/phy/phy.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/pm_runtime.h>
>> +#include <linux/reset.h>
>> +
>> +#include <media/mipi-csi2.h>
>> +#include <media/v4l2-ctrls.h>
>> +#include <media/v4l2-fwnode.h>
>> +#include <media/v4l2-subdev.h>
>> +
>> +#define CSI2HOST_N_LANES     0x04
>> +#define CSI2HOST_CSI2_RESETN 0x10
>> +#define CSI2HOST_PHY_STATE   0x14
>> +#define CSI2HOST_ERR1	     0x20
>> +#define CSI2HOST_ERR2	     0x24
>> +#define CSI2HOST_MSK1	     0x28
>> +#define CSI2HOST_MSK2	     0x2c
>> +#define CSI2HOST_CONTROL     0x40
> 
> I'm trying to get to the bottom of the CSI-2 RX integration questions
> for the RK3568. Some of the registers here seem to the CSI2RX_1C00 block
> as documented starting on page 1059 of the RK3568 TRM (revision 1.1),
> but they're not an exact match. The control register, in particular,
> doesn't match at all. Where is this CSI-2 receiver documented in the TRM
> ?

That would be in Chapter 27 of the RK3568 TRM: "MIPI CSI HOST"

The registers are at 0xfdfb0000 "CSI_RX_CTRL1", cf. Chapter 1 "System
Overview".

Naturally, this is quite confusing, as there is a "CSI2RX" whose
registers are embedded in the ISP register map.

"MIPI CSI HOST" and "CSI2RX" seem to be different IP cores but perform
the same job in principle. CSI2RX seems to have additional features
related to writing raw data into memory and to HDR, though.

Hope that helps!

Michael

> 
>> +
>> +#define SW_CPHY_EN(x)	     ((x) << 0)
>> +#define SW_DSI_EN(x)	     ((x) << 4)
>> +#define SW_DATATYPE_FS(x)    ((x) << 8)
>> +#define SW_DATATYPE_FE(x)    ((x) << 14)
>> +#define SW_DATATYPE_LS(x)    ((x) << 20)
>> +#define SW_DATATYPE_LE(x)    ((x) << 26)
>> +
>> +#define RKCIF_CSI_CLKS_MAX   1
>> +
>> +enum {
>> +	RKCIF_CSI_PAD_SINK,
>> +	RKCIF_CSI_PAD_SRC,
>> +	RKCIF_CSI_PAD_MAX,
>> +};
>> +
>> +struct rkcif_csi_format {
>> +	u32 code;
>> +	u8 depth;
>> +	u8 csi_dt;
>> +};
>> +
>> +struct rkcif_csi_device {
>> +	struct device *dev;
>> +
>> +	void __iomem *base_addr;
>> +	struct clk_bulk_data *clks;
>> +	unsigned int clks_num;
>> +	struct phy *phy;
>> +	struct reset_control *reset;
>> +
>> +	const struct rkcif_csi_format *formats;
>> +	unsigned int formats_num;
>> +
>> +	struct media_pad pads[RKCIF_CSI_PAD_MAX];
>> +	struct v4l2_async_notifier notifier;
>> +	struct v4l2_fwnode_endpoint vep;
>> +	struct v4l2_subdev sd;
>> +
>> +	struct v4l2_subdev *source_sd;
>> +	u32 source_pad;
>> +};
>> +
>> +static const struct v4l2_mbus_framefmt default_format = {
>> +	.width = 3840,
>> +	.height = 2160,
>> +	.code = MEDIA_BUS_FMT_SRGGB10_1X10,
>> +	.field = V4L2_FIELD_NONE,
>> +	.colorspace = V4L2_COLORSPACE_RAW,
>> +	.ycbcr_enc = V4L2_YCBCR_ENC_601,
>> +	.quantization = V4L2_QUANTIZATION_FULL_RANGE,
>> +	.xfer_func = V4L2_XFER_FUNC_NONE,
>> +};
>> +
>> +static const struct rkcif_csi_format formats[] = {
>> +	/* YUV formats */
>> +	{
>> +		.code = MEDIA_BUS_FMT_YUYV8_1X16,
>> +		.depth = 16,
>> +		.csi_dt = MIPI_CSI2_DT_YUV422_8B,
>> +	},
>> +	{
>> +		.code = MEDIA_BUS_FMT_UYVY8_1X16,
>> +		.depth = 16,
>> +		.csi_dt = MIPI_CSI2_DT_YUV422_8B,
>> +	},
>> +	{
>> +		.code = MEDIA_BUS_FMT_YVYU8_1X16,
>> +		.depth = 16,
>> +		.csi_dt = MIPI_CSI2_DT_YUV422_8B,
>> +	},
>> +	{
>> +		.code = MEDIA_BUS_FMT_VYUY8_1X16,
>> +		.depth = 16,
>> +		.csi_dt = MIPI_CSI2_DT_YUV422_8B,
>> +	},
>> +	/* RGB formats */
>> +	{
>> +		.code = MEDIA_BUS_FMT_RGB888_1X24,
>> +		.depth = 24,
>> +		.csi_dt = MIPI_CSI2_DT_RGB888,
>> +	},
>> +	{
>> +		.code = MEDIA_BUS_FMT_BGR888_1X24,
>> +		.depth = 24,
>> +		.csi_dt = MIPI_CSI2_DT_RGB888,
>> +	},
>> +	/* Bayer formats */
>> +	{
>> +		.code = MEDIA_BUS_FMT_SBGGR8_1X8,
>> +		.depth = 8,
>> +		.csi_dt = MIPI_CSI2_DT_RAW8,
>> +	},
>> +	{
>> +		.code = MEDIA_BUS_FMT_SGBRG8_1X8,
>> +		.depth = 8,
>> +		.csi_dt = MIPI_CSI2_DT_RAW8,
>> +	},
>> +	{
>> +		.code = MEDIA_BUS_FMT_SGRBG8_1X8,
>> +		.depth = 8,
>> +		.csi_dt = MIPI_CSI2_DT_RAW8,
>> +	},
>> +	{
>> +		.code = MEDIA_BUS_FMT_SRGGB8_1X8,
>> +		.depth = 8,
>> +		.csi_dt = MIPI_CSI2_DT_RAW8,
>> +	},
>> +	{
>> +		.code = MEDIA_BUS_FMT_SBGGR10_1X10,
>> +		.depth = 10,
>> +		.csi_dt = MIPI_CSI2_DT_RAW10,
>> +	},
>> +	{
>> +		.code = MEDIA_BUS_FMT_SGBRG10_1X10,
>> +		.depth = 10,
>> +		.csi_dt = MIPI_CSI2_DT_RAW10,
>> +	},
>> +	{
>> +		.code = MEDIA_BUS_FMT_SGRBG10_1X10,
>> +		.depth = 10,
>> +		.csi_dt = MIPI_CSI2_DT_RAW10,
>> +	},
>> +	{
>> +		.code = MEDIA_BUS_FMT_SRGGB10_1X10,
>> +		.depth = 10,
>> +		.csi_dt = MIPI_CSI2_DT_RAW10,
>> +	},
>> +	{
>> +		.code = MEDIA_BUS_FMT_SBGGR12_1X12,
>> +		.depth = 12,
>> +		.csi_dt = MIPI_CSI2_DT_RAW12,
>> +	},
>> +	{
>> +		.code = MEDIA_BUS_FMT_SGBRG12_1X12,
>> +		.depth = 12,
>> +		.csi_dt = MIPI_CSI2_DT_RAW12,
>> +	},
>> +	{
>> +		.code = MEDIA_BUS_FMT_SGRBG12_1X12,
>> +		.depth = 12,
>> +		.csi_dt = MIPI_CSI2_DT_RAW12,
>> +	},
>> +	{
>> +		.code = MEDIA_BUS_FMT_SRGGB12_1X12,
>> +		.depth = 12,
>> +		.csi_dt = MIPI_CSI2_DT_RAW12,
>> +	},
>> +};
>> +
>> +static inline struct rkcif_csi_device *to_rkcif_csi(struct v4l2_subdev *sd)
>> +{
>> +	return container_of(sd, struct rkcif_csi_device, sd);
>> +}
>> +
>> +static inline __maybe_unused void
>> +rkcif_csi_write(struct rkcif_csi_device *csi_dev, unsigned int addr, u32 val)
>> +{
>> +	writel(val, csi_dev->base_addr + addr);
>> +}
>> +
>> +static inline __maybe_unused u32
>> +rkcif_csi_read(struct rkcif_csi_device *csi_dev, unsigned int addr)
>> +{
>> +	return readl(csi_dev->base_addr + addr);
>> +}
>> +
>> +static const struct rkcif_csi_format *
>> +rkcif_csi_find_format(struct rkcif_csi_device *csi_dev, u32 mbus_code)
>> +{
>> +	const struct rkcif_csi_format *format;
>> +
>> +	WARN_ON(csi_dev->formats_num == 0);
>> +
>> +	for (int i = 0; i < csi_dev->formats_num; i++) {
>> +		format = &csi_dev->formats[i];
>> +		if (format->code == mbus_code)
>> +			return format;
>> +	}
>> +
>> +	return NULL;
>> +}
>> +
>> +static int rkcif_csi_start(struct rkcif_csi_device *csi_dev)
>> +{
>> +	enum v4l2_mbus_type bus_type = csi_dev->vep.bus_type;
>> +	union phy_configure_opts opts;
>> +	s64 link_freq;
>> +	u32 lanes = csi_dev->vep.bus.mipi_csi2.num_data_lanes;
>> +	u32 control = 0;
>> +
>> +	if (lanes < 1 || lanes > 4)
>> +		return -EINVAL;
>> +
>> +	/* set mult and div to 0, thus completely rely on V4L2_CID_LINK_FREQ */
>> +	link_freq = v4l2_get_link_freq(csi_dev->source_sd->ctrl_handler, 0, 0);
>> +	if (link_freq <= 0)
>> +		return -EINVAL;
>> +
>> +	if (bus_type == V4L2_MBUS_CSI2_DPHY) {
>> +		struct phy_configure_opts_mipi_dphy *cfg = &opts.mipi_dphy;
>> +
>> +		phy_mipi_dphy_get_default_config_for_hsclk(link_freq * 2, lanes,
>> +							   cfg);
>> +		phy_set_mode(csi_dev->phy, PHY_MODE_MIPI_DPHY);
>> +		phy_configure(csi_dev->phy, &opts);
>> +
>> +		control |= SW_CPHY_EN(0);
>> +
>> +	} else if (bus_type == V4L2_MBUS_CSI2_CPHY) {
>> +		control |= SW_CPHY_EN(1);
>> +
>> +		/* TODO: implement CPHY configuration */
>> +	} else {
>> +		return -EINVAL;
>> +	}
>> +
>> +	control |= SW_DATATYPE_FS(0x00) | SW_DATATYPE_FE(0x01) |
>> +		   SW_DATATYPE_LS(0x02) | SW_DATATYPE_LE(0x03);
>> +
>> +	rkcif_csi_write(csi_dev, CSI2HOST_N_LANES, lanes - 1);
>> +	rkcif_csi_write(csi_dev, CSI2HOST_CONTROL, control);
>> +	rkcif_csi_write(csi_dev, CSI2HOST_CSI2_RESETN, 1);
>> +
>> +	phy_power_on(csi_dev->phy);
>> +
>> +	return 0;
>> +}
>> +
>> +static void rkcif_csi_stop(struct rkcif_csi_device *csi_dev)
>> +{
>> +	phy_power_off(csi_dev->phy);
>> +
>> +	rkcif_csi_write(csi_dev, CSI2HOST_CSI2_RESETN, 0);
>> +	rkcif_csi_write(csi_dev, CSI2HOST_MSK1, ~0);
>> +	rkcif_csi_write(csi_dev, CSI2HOST_MSK2, ~0);
>> +}
>> +
>> +static const struct media_entity_operations rkcif_csi_media_ops = {
>> +	.link_validate = v4l2_subdev_link_validate,
>> +};
>> +
>> +static int rkcif_csi_enum_mbus_code(struct v4l2_subdev *sd,
>> +				    struct v4l2_subdev_state *sd_state,
>> +				    struct v4l2_subdev_mbus_code_enum *code)
>> +{
>> +	struct rkcif_csi_device *csi_dev = to_rkcif_csi(sd);
>> +
>> +	if (code->pad == RKCIF_CSI_PAD_SRC) {
>> +		const struct v4l2_mbus_framefmt *sink_fmt;
>> +
>> +		if (code->index)
>> +			return -EINVAL;
>> +
>> +		sink_fmt = v4l2_subdev_state_get_format(sd_state,
>> +							RKCIF_CSI_PAD_SINK);
>> +		code->code = sink_fmt->code;
>> +
>> +		return 0;
>> +	} else if (code->pad == RKCIF_CSI_PAD_SINK) {
>> +		if (code->index > csi_dev->formats_num)
>> +			return -EINVAL;
>> +
>> +		code->code = csi_dev->formats[code->index].code;
>> +		return 0;
>> +	}
>> +
>> +	return -EINVAL;
>> +}
>> +
>> +static int rkcif_csi_set_fmt(struct v4l2_subdev *sd,
>> +			     struct v4l2_subdev_state *state,
>> +			     struct v4l2_subdev_format *format)
>> +{
>> +	struct rkcif_csi_device *csi_dev = to_rkcif_csi(sd);
>> +	const struct rkcif_csi_format *fmt;
>> +	struct v4l2_mbus_framefmt *sink, *src;
>> +
>> +	/* the format on the source pad always matches the sink pad */
>> +	if (format->pad == RKCIF_CSI_PAD_SRC)
>> +		return v4l2_subdev_get_fmt(sd, state, format);
>> +
>> +	sink = v4l2_subdev_state_get_format(state, format->pad, format->stream);
>> +	if (!sink)
>> +		return -EINVAL;
>> +
>> +	fmt = rkcif_csi_find_format(csi_dev, format->format.code);
>> +	if (fmt)
>> +		*sink = format->format;
>> +	else
>> +		*sink = default_format;
>> +
>> +	/* propagate the format to the source pad */
>> +	src = v4l2_subdev_state_get_opposite_stream_format(state, format->pad,
>> +							   format->stream);
>> +	if (!src)
>> +		return -EINVAL;
>> +
>> +	*src = *sink;
>> +
>> +	return 0;
>> +}
>> +
>> +static int rkcif_csi_set_routing(struct v4l2_subdev *sd,
>> +				 struct v4l2_subdev_state *state,
>> +				 enum v4l2_subdev_format_whence which,
>> +				 struct v4l2_subdev_krouting *routing)
>> +{
>> +	int ret;
>> +
>> +	ret = v4l2_subdev_routing_validate(sd, routing,
>> +					   V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
>> +	if (ret)
>> +		return ret;
>> +
>> +	ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing,
>> +					       &default_format);
>> +	if (ret)
>> +		return ret;
>> +
>> +	return 0;
>> +}
>> +
>> +static int rkcif_csi_enable_streams(struct v4l2_subdev *sd,
>> +				    struct v4l2_subdev_state *state, u32 pad,
>> +				    u64 streams_mask)
>> +{
>> +	struct rkcif_csi_device *csi_dev = to_rkcif_csi(sd);
>> +	struct v4l2_subdev *remote_sd;
>> +	struct media_pad *sink_pad, *remote_pad;
>> +	struct device *dev = csi_dev->dev;
>> +	u64 mask;
>> +	int ret;
>> +
>> +	sink_pad = &sd->entity.pads[RKCIF_CSI_PAD_SINK];
>> +	remote_pad = media_pad_remote_pad_first(sink_pad);
>> +	remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity);
>> +
>> +	mask = v4l2_subdev_state_xlate_streams(state, RKCIF_CSI_PAD_SINK,
>> +					       RKCIF_CSI_PAD_SRC,
>> +					       &streams_mask);
>> +
>> +	ret = pm_runtime_resume_and_get(dev);
>> +	if (ret)
>> +		goto err;
>> +
>> +	ret = rkcif_csi_start(csi_dev);
>> +	if (ret) {
>> +		dev_err(dev, "failed to enable CSI hardware\n");
>> +		goto err_pm_runtime_put;
>> +	}
>> +
>> +	ret = v4l2_subdev_enable_streams(remote_sd, remote_pad->index, mask);
>> +	if (ret)
>> +		goto err_csi_stop;
>> +
>> +	return 0;
>> +
>> +err_csi_stop:
>> +	rkcif_csi_stop(csi_dev);
>> +err_pm_runtime_put:
>> +	pm_runtime_put_sync(dev);
>> +err:
>> +	return ret;
>> +}
>> +
>> +static int rkcif_csi_disable_streams(struct v4l2_subdev *sd,
>> +				     struct v4l2_subdev_state *state, u32 pad,
>> +				     u64 streams_mask)
>> +{
>> +	struct rkcif_csi_device *csi_dev = to_rkcif_csi(sd);
>> +	struct v4l2_subdev *remote_sd;
>> +	struct media_pad *sink_pad, *remote_pad;
>> +	struct device *dev = csi_dev->dev;
>> +	u64 mask;
>> +	int ret;
>> +
>> +	sink_pad = &sd->entity.pads[RKCIF_CSI_PAD_SINK];
>> +	remote_pad = media_pad_remote_pad_first(sink_pad);
>> +	remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity);
>> +
>> +	mask = v4l2_subdev_state_xlate_streams(state, RKCIF_CSI_PAD_SINK,
>> +					       RKCIF_CSI_PAD_SRC,
>> +					       &streams_mask);
>> +
>> +	ret = v4l2_subdev_disable_streams(remote_sd, remote_pad->index, mask);
>> +
>> +	rkcif_csi_stop(csi_dev);
>> +
>> +	pm_runtime_mark_last_busy(dev);
>> +	pm_runtime_put_autosuspend(dev);
>> +
>> +	return ret;
>> +}
>> +
>> +static const struct v4l2_subdev_pad_ops rkcif_csi_pad_ops = {
>> +	.enum_mbus_code = rkcif_csi_enum_mbus_code,
>> +	.get_fmt = v4l2_subdev_get_fmt,
>> +	.set_fmt = rkcif_csi_set_fmt,
>> +	.set_routing = rkcif_csi_set_routing,
>> +	.enable_streams = rkcif_csi_enable_streams,
>> +	.disable_streams = rkcif_csi_disable_streams,
>> +};
>> +
>> +static const struct v4l2_subdev_ops rkcif_csi_ops = {
>> +	.pad = &rkcif_csi_pad_ops,
>> +};
>> +
>> +static int rkcif_csi_init_state(struct v4l2_subdev *sd,
>> +				struct v4l2_subdev_state *state)
>> +{
>> +	struct v4l2_subdev_route routes[] = {
>> +		{
>> +			.sink_pad = RKCIF_CSI_PAD_SINK,
>> +			.sink_stream = 0,
>> +			.source_pad = RKCIF_CSI_PAD_SRC,
>> +			.source_stream = 0,
>> +			.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
>> +		},
>> +	};
>> +	struct v4l2_subdev_krouting routing = {
>> +		.len_routes = ARRAY_SIZE(routes),
>> +		.num_routes = ARRAY_SIZE(routes),
>> +		.routes = routes,
>> +	};
>> +	int ret;
>> +
>> +	ret = v4l2_subdev_set_routing_with_fmt(sd, state, &routing,
>> +					       &default_format);
>> +
>> +	return ret;
>> +}
>> +
>> +static const struct v4l2_subdev_internal_ops rkcif_csi_internal_ops = {
>> +	.init_state = rkcif_csi_init_state,
>> +};
>> +
>> +static int rkcif_csi_notifier_bound(struct v4l2_async_notifier *notifier,
>> +				    struct v4l2_subdev *sd,
>> +				    struct v4l2_async_connection *asd)
>> +{
>> +	struct rkcif_csi_device *csi_dev =
>> +		container_of(notifier, struct rkcif_csi_device, notifier);
>> +	int source_pad;
>> +
>> +	source_pad = media_entity_get_fwnode_pad(&sd->entity, sd->fwnode,
>> +						 MEDIA_PAD_FL_SOURCE);
>> +	if (source_pad < 0) {
>> +		dev_err(csi_dev->dev, "failed to find source pad for %s\n",
>> +			sd->name);
>> +		return source_pad;
>> +	}
>> +
>> +	csi_dev->source_sd = sd;
>> +	csi_dev->source_pad = source_pad;
>> +
>> +	return media_create_pad_link(&sd->entity, source_pad,
>> +				     &csi_dev->sd.entity, RKCIF_CSI_PAD_SINK,
>> +				     MEDIA_LNK_FL_ENABLED);
>> +}
>> +
>> +static const struct v4l2_async_notifier_operations rkcif_csi_notifier_ops = {
>> +	.bound = rkcif_csi_notifier_bound,
>> +};
>> +
>> +static int rkcif_csi_register_notifier(struct rkcif_csi_device *csi_dev)
>> +{
>> +	struct v4l2_async_connection *asd;
>> +	struct v4l2_async_notifier *ntf = &csi_dev->notifier;
>> +	struct v4l2_fwnode_endpoint *vep = &csi_dev->vep;
>> +	struct v4l2_subdev *sd = &csi_dev->sd;
>> +	struct device *dev = csi_dev->dev;
>> +	struct fwnode_handle *ep;
>> +	int ret = 0;
>> +
>> +	ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, 0);
>> +	if (!ep)
>> +		return dev_err_probe(dev, -ENODEV, "failed to get endpoint\n");
>> +
>> +	vep->bus_type = V4L2_MBUS_UNKNOWN;
>> +	ret = v4l2_fwnode_endpoint_parse(ep, vep);
>> +	if (ret) {
>> +		ret = dev_err_probe(dev, ret, "failed to parse endpoint\n");
>> +		goto out;
>> +	}
>> +
>> +	if (vep->bus_type != V4L2_MBUS_CSI2_DPHY &&
>> +	    vep->bus_type != V4L2_MBUS_CSI2_CPHY) {
>> +		ret = dev_err_probe(dev, -EINVAL,
>> +				    "invalid bus type of endpoint\n");
>> +		goto out;
>> +	}
>> +
>> +	v4l2_async_subdev_nf_init(ntf, sd);
>> +	ntf->ops = &rkcif_csi_notifier_ops;
>> +
>> +	asd = v4l2_async_nf_add_fwnode_remote(ntf, ep,
>> +					      struct v4l2_async_connection);
>> +	if (IS_ERR(asd)) {
>> +		ret = PTR_ERR(asd);
>> +		goto err_nf_cleanup;
>> +	}
>> +
>> +	ret = v4l2_async_nf_register(ntf);
>> +	if (ret) {
>> +		ret = dev_err_probe(dev, ret, "failed to register notifier\n");
>> +		goto err_nf_cleanup;
>> +	}
>> +
>> +	goto out;
>> +
>> +err_nf_cleanup:
>> +	v4l2_async_nf_cleanup(ntf);
>> +out:
>> +	fwnode_handle_put(ep);
>> +	return ret;
>> +}
>> +
>> +static int rkcif_csi_register(struct rkcif_csi_device *csi_dev)
>> +{
>> +	struct media_pad *pads = csi_dev->pads;
>> +	struct v4l2_subdev *sd = &csi_dev->sd;
>> +	int ret;
>> +
>> +	ret = rkcif_csi_register_notifier(csi_dev);
>> +	if (ret)
>> +		goto err;
>> +
>> +	v4l2_subdev_init(sd, &rkcif_csi_ops);
>> +	sd->dev = csi_dev->dev;
>> +	sd->entity.ops = &rkcif_csi_media_ops;
>> +	sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
>> +	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS;
>> +	sd->internal_ops = &rkcif_csi_internal_ops;
>> +	sd->owner = THIS_MODULE;
>> +	snprintf(sd->name, sizeof(sd->name), "rockchip-mipi-csi %s",
>> +		 dev_name(csi_dev->dev));
>> +
>> +	pads[RKCIF_CSI_PAD_SINK].flags = MEDIA_PAD_FL_SINK |
>> +					 MEDIA_PAD_FL_MUST_CONNECT;
>> +	pads[RKCIF_CSI_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE;
>> +	ret = media_entity_pads_init(&sd->entity, RKCIF_CSI_PAD_MAX, pads);
>> +	if (ret)
>> +		goto err_notifier_unregister;
>> +
>> +	ret = v4l2_subdev_init_finalize(sd);
>> +	if (ret)
>> +		goto err_entity_cleanup;
>> +
>> +	ret = v4l2_async_register_subdev(sd);
>> +	if (ret) {
>> +		dev_err(sd->dev, "failed to register CSI subdev\n");
>> +		goto err_subdev_cleanup;
>> +	}
>> +
>> +	return 0;
>> +
>> +err_subdev_cleanup:
>> +	v4l2_subdev_cleanup(sd);
>> +err_entity_cleanup:
>> +	media_entity_cleanup(&sd->entity);
>> +err_notifier_unregister:
>> +	v4l2_async_nf_unregister(&csi_dev->notifier);
>> +	v4l2_async_nf_cleanup(&csi_dev->notifier);
>> +err:
>> +	return ret;
>> +}
>> +
>> +static void rkcif_csi_unregister(struct rkcif_csi_device *csi_dev)
>> +{
>> +	struct v4l2_subdev *sd = &csi_dev->sd;
>> +
>> +	v4l2_async_unregister_subdev(sd);
>> +	v4l2_subdev_cleanup(sd);
>> +	media_entity_cleanup(&sd->entity);
>> +	v4l2_async_nf_unregister(&csi_dev->notifier);
>> +	v4l2_async_nf_cleanup(&csi_dev->notifier);
>> +}
>> +
>> +static const struct of_device_id rkcif_csi_of_match[] = {
>> +	{
>> +		.compatible = "rockchip,rk3568-mipi-csi",
>> +	},
>> +	{}
>> +};
>> +MODULE_DEVICE_TABLE(of, rkcif_csi_of_match);
>> +
>> +static int rkcif_csi_probe(struct platform_device *pdev)
>> +{
>> +	struct device *dev = &pdev->dev;
>> +	struct rkcif_csi_device *csi_dev;
>> +	int ret;
>> +
>> +	csi_dev = devm_kzalloc(dev, sizeof(*csi_dev), GFP_KERNEL);
>> +	if (!csi_dev)
>> +		return -ENOMEM;
>> +	csi_dev->dev = dev;
>> +	dev_set_drvdata(dev, csi_dev);
>> +
>> +	csi_dev->base_addr = devm_platform_ioremap_resource(pdev, 0);
>> +	if (IS_ERR(csi_dev->base_addr))
>> +		return PTR_ERR(csi_dev->base_addr);
>> +
>> +	ret = devm_clk_bulk_get_all(dev, &csi_dev->clks);
>> +	if (ret != RKCIF_CSI_CLKS_MAX)
>> +		return dev_err_probe(dev, -ENODEV, "failed to get clocks\n");
>> +	csi_dev->clks_num = ret;
>> +
>> +	csi_dev->phy = devm_phy_get(dev, NULL);
>> +	if (IS_ERR(csi_dev->phy))
>> +		return dev_err_probe(dev, PTR_ERR(csi_dev->phy),
>> +				     "failed to get MIPI CSI PHY\n");
>> +
>> +	csi_dev->reset = devm_reset_control_array_get_exclusive(dev);
>> +	if (IS_ERR(csi_dev->reset))
>> +		return dev_err_probe(dev, PTR_ERR(csi_dev->reset),
>> +				     "failed to get reset\n");
>> +
>> +	csi_dev->formats = formats;
>> +	csi_dev->formats_num = ARRAY_SIZE(formats);
>> +
>> +	pm_runtime_enable(dev);
>> +
>> +	ret = phy_init(csi_dev->phy);
>> +	if (ret) {
>> +		ret = dev_err_probe(dev, ret,
>> +				    "failed to initialize MIPI CSI PHY\n");
>> +		goto err_pm_runtime_disable;
>> +	}
>> +
>> +	ret = rkcif_csi_register(csi_dev);
>> +	if (ret)
>> +		goto err_phy_exit;
>> +
>> +	return 0;
>> +
>> +err_phy_exit:
>> +	phy_exit(csi_dev->phy);
>> +err_pm_runtime_disable:
>> +	pm_runtime_disable(dev);
>> +	return ret;
>> +}
>> +
>> +static void rkcif_csi_remove(struct platform_device *pdev)
>> +{
>> +	struct rkcif_csi_device *csi_dev = platform_get_drvdata(pdev);
>> +	struct device *dev = &pdev->dev;
>> +
>> +	rkcif_csi_unregister(csi_dev);
>> +	phy_exit(csi_dev->phy);
>> +	pm_runtime_disable(dev);
>> +}
>> +
>> +static int rkcif_csi_runtime_suspend(struct device *dev)
>> +{
>> +	struct rkcif_csi_device *csi_dev = dev_get_drvdata(dev);
>> +
>> +	clk_bulk_disable_unprepare(csi_dev->clks_num, csi_dev->clks);
>> +
>> +	return 0;
>> +}
>> +
>> +static int rkcif_csi_runtime_resume(struct device *dev)
>> +{
>> +	struct rkcif_csi_device *csi_dev = dev_get_drvdata(dev);
>> +	int ret;
>> +
>> +	reset_control_assert(csi_dev->reset);
>> +	udelay(5);
>> +	reset_control_deassert(csi_dev->reset);
>> +
>> +	ret = clk_bulk_prepare_enable(csi_dev->clks_num, csi_dev->clks);
>> +	if (ret) {
>> +		dev_err(dev, "failed to enable clocks\n");
>> +		return ret;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct dev_pm_ops rkcif_csi_pm_ops = {
>> +	.runtime_suspend = rkcif_csi_runtime_suspend,
>> +	.runtime_resume = rkcif_csi_runtime_resume,
>> +};
>> +
>> +static struct platform_driver rkcif_csi_drv = {
>> +	.driver = {
>> +		   .name = "rockchip-mipi-csi",
>> +		   .of_match_table = rkcif_csi_of_match,
>> +		   .pm = &rkcif_csi_pm_ops,
>> +	},
>> +	.probe = rkcif_csi_probe,
>> +	.remove = rkcif_csi_remove,
>> +};
>> +module_platform_driver(rkcif_csi_drv);
>> +
>> +MODULE_DESCRIPTION("Rockchip MIPI CSI-2 Receiver platform driver");
>> +MODULE_LICENSE("GPL");
> 


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

* Re: [PATCH v6 07/13] media: rockchip: rkcif: add driver for mipi csi-2 receiver
  2025-05-02 14:19     ` Michael Riesch
@ 2025-05-02 14:35       ` Laurent Pinchart
  2025-05-06 18:39         ` Michael Riesch
  0 siblings, 1 reply; 34+ messages in thread
From: Laurent Pinchart @ 2025-05-02 14:35 UTC (permalink / raw)
  To: Michael Riesch
  Cc: Mehdi Djait, Maxime Chevallier, Théo Lebrun, Gerald Loacker,
	Thomas Petazzoni, Mauro Carvalho Chehab, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner, Kever Yang,
	Nicolas Dufresne, Sebastian Reichel, Collabora Kernel Team,
	Paul Kocialkowski, Alexander Shiyan, Val Packett, Rob Herring,
	Philipp Zabel, Sakari Ailus, linux-media, devicetree,
	linux-kernel, linux-arm-kernel, linux-rockchip, Michael Riesch

On Fri, May 02, 2025 at 04:19:59PM +0200, Michael Riesch wrote:
> On 5/2/25 15:31, Laurent Pinchart wrote:
> > On Wed, Apr 30, 2025 at 11:15:56AM +0200, Michael Riesch via B4 Relay wrote:
> >> From: Michael Riesch <michael.riesch@wolfvision.net>
> >>
> >> The Rockchip RK3568 MIPI CSI-2 Receiver is a CSI-2 bridge with one
> >> input port and one output port. It receives the data with the help
> >> of an external MIPI PHY (C-PHY or D-PHY) and passes it to the
> >> Rockchip RK3568 Video Capture (VICAP) block.
> >>
> >> Add a V4L2 subdevice driver for this unit.
> >>
> >> Signed-off-by: Michael Riesch <michael.riesch@wolfvision.net>
> >> Signed-off-by: Michael Riesch <michael.riesch@collabora.com>
> >> ---
> >>  drivers/media/platform/rockchip/rkcif/Makefile     |   3 +
> >>  .../rockchip/rkcif/rkcif-mipi-csi-receiver.c       | 731 +++++++++++++++++++++
> >>  2 files changed, 734 insertions(+)
> >>
> >> diff --git a/drivers/media/platform/rockchip/rkcif/Makefile b/drivers/media/platform/rockchip/rkcif/Makefile
> >> index 818424972c7b..a5c18a45c213 100644
> >> --- a/drivers/media/platform/rockchip/rkcif/Makefile
> >> +++ b/drivers/media/platform/rockchip/rkcif/Makefile
> >> @@ -5,3 +5,6 @@ rockchip-cif-objs += rkcif-dev.o \
> >>  	rkcif-capture-mipi.o \
> >>  	rkcif-interface.o \
> >>  	rkcif-stream.o
> >> +
> >> +obj-$(CONFIG_VIDEO_ROCKCHIP_CIF) += rockchip-mipi-csi.o
> >> +rockchip-mipi-csi-objs += rkcif-mipi-csi-receiver.o
> >> diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-mipi-csi-receiver.c b/drivers/media/platform/rockchip/rkcif/rkcif-mipi-csi-receiver.c
> >> new file mode 100644
> >> index 000000000000..81489f70490f
> >> --- /dev/null
> >> +++ b/drivers/media/platform/rockchip/rkcif/rkcif-mipi-csi-receiver.c
> >> @@ -0,0 +1,731 @@
> >> +// SPDX-License-Identifier: GPL-2.0
> >> +/*
> >> + * Rockchip MIPI CSI-2 Receiver Driver
> >> + *
> >> + * Copyright (C) 2019 Rockchip Electronics Co., Ltd.
> >> + * Copyright (C) 2025 Michael Riesch <michael.riesch@wolfvision.net>
> >> + */
> >> +
> >> +#include <linux/clk.h>
> >> +#include <linux/delay.h>
> >> +#include <linux/io.h>
> >> +#include <linux/module.h>
> >> +#include <linux/of.h>
> >> +#include <linux/of_graph.h>
> >> +#include <linux/of_platform.h>
> >> +#include <linux/phy/phy.h>
> >> +#include <linux/platform_device.h>
> >> +#include <linux/pm_runtime.h>
> >> +#include <linux/reset.h>
> >> +
> >> +#include <media/mipi-csi2.h>
> >> +#include <media/v4l2-ctrls.h>
> >> +#include <media/v4l2-fwnode.h>
> >> +#include <media/v4l2-subdev.h>
> >> +
> >> +#define CSI2HOST_N_LANES     0x04
> >> +#define CSI2HOST_CSI2_RESETN 0x10
> >> +#define CSI2HOST_PHY_STATE   0x14
> >> +#define CSI2HOST_ERR1	     0x20
> >> +#define CSI2HOST_ERR2	     0x24
> >> +#define CSI2HOST_MSK1	     0x28
> >> +#define CSI2HOST_MSK2	     0x2c
> >> +#define CSI2HOST_CONTROL     0x40
> > 
> > I'm trying to get to the bottom of the CSI-2 RX integration questions
> > for the RK3568. Some of the registers here seem to the CSI2RX_1C00 block
> > as documented starting on page 1059 of the RK3568 TRM (revision 1.1),
> > but they're not an exact match. The control register, in particular,
> > doesn't match at all. Where is this CSI-2 receiver documented in the TRM
> > ?
> 
> That would be in Chapter 27 of the RK3568 TRM: "MIPI CSI HOST"
> 
> The registers are at 0xfdfb0000 "CSI_RX_CTRL1", cf. Chapter 1 "System
> Overview".
> 
> Naturally, this is quite confusing, as there is a "CSI2RX" whose
> registers are embedded in the ISP register map.
> 
> "MIPI CSI HOST" and "CSI2RX" seem to be different IP cores but perform
> the same job in principle. CSI2RX seems to have additional features
> related to writing raw data into memory and to HDR, though.
> 
> Hope that helps!

It does, thank you. I wonder how I missed that.

The IP seems very similar to other CSI-2 receivers already supported
upstream, see drivers/media/platform/raspberrypi/rp1-cfe/dphy.c,
drivers/media/platform/renesas/rcar-csi2.c and
drivers/staging/media/imx/imx6-mipi-csi2.c. Those drivers combine
support for the CSI-2 RX and D-PHY, explaining some of the differences
(and in the case of the rcar-csi2 driver, I think it also combines
support for different CSI-2 RX in a single driver).

Is there something we could do to avoid adding a 4th driver for the same
IP core family (from Synopsys or Cadence I assume) ? It could possibly
be done on top of this series to avoid delaying VICAP support, but I'd
really like to get this sorted out.

> >> +
> >> +#define SW_CPHY_EN(x)	     ((x) << 0)
> >> +#define SW_DSI_EN(x)	     ((x) << 4)
> >> +#define SW_DATATYPE_FS(x)    ((x) << 8)
> >> +#define SW_DATATYPE_FE(x)    ((x) << 14)
> >> +#define SW_DATATYPE_LS(x)    ((x) << 20)
> >> +#define SW_DATATYPE_LE(x)    ((x) << 26)
> >> +
> >> +#define RKCIF_CSI_CLKS_MAX   1
> >> +
> >> +enum {
> >> +	RKCIF_CSI_PAD_SINK,
> >> +	RKCIF_CSI_PAD_SRC,
> >> +	RKCIF_CSI_PAD_MAX,
> >> +};
> >> +
> >> +struct rkcif_csi_format {
> >> +	u32 code;
> >> +	u8 depth;
> >> +	u8 csi_dt;
> >> +};
> >> +
> >> +struct rkcif_csi_device {
> >> +	struct device *dev;
> >> +
> >> +	void __iomem *base_addr;
> >> +	struct clk_bulk_data *clks;
> >> +	unsigned int clks_num;
> >> +	struct phy *phy;
> >> +	struct reset_control *reset;
> >> +
> >> +	const struct rkcif_csi_format *formats;
> >> +	unsigned int formats_num;
> >> +
> >> +	struct media_pad pads[RKCIF_CSI_PAD_MAX];
> >> +	struct v4l2_async_notifier notifier;
> >> +	struct v4l2_fwnode_endpoint vep;
> >> +	struct v4l2_subdev sd;
> >> +
> >> +	struct v4l2_subdev *source_sd;
> >> +	u32 source_pad;
> >> +};
> >> +
> >> +static const struct v4l2_mbus_framefmt default_format = {
> >> +	.width = 3840,
> >> +	.height = 2160,
> >> +	.code = MEDIA_BUS_FMT_SRGGB10_1X10,
> >> +	.field = V4L2_FIELD_NONE,
> >> +	.colorspace = V4L2_COLORSPACE_RAW,
> >> +	.ycbcr_enc = V4L2_YCBCR_ENC_601,
> >> +	.quantization = V4L2_QUANTIZATION_FULL_RANGE,
> >> +	.xfer_func = V4L2_XFER_FUNC_NONE,
> >> +};
> >> +
> >> +static const struct rkcif_csi_format formats[] = {
> >> +	/* YUV formats */
> >> +	{
> >> +		.code = MEDIA_BUS_FMT_YUYV8_1X16,
> >> +		.depth = 16,
> >> +		.csi_dt = MIPI_CSI2_DT_YUV422_8B,
> >> +	},
> >> +	{
> >> +		.code = MEDIA_BUS_FMT_UYVY8_1X16,
> >> +		.depth = 16,
> >> +		.csi_dt = MIPI_CSI2_DT_YUV422_8B,
> >> +	},
> >> +	{
> >> +		.code = MEDIA_BUS_FMT_YVYU8_1X16,
> >> +		.depth = 16,
> >> +		.csi_dt = MIPI_CSI2_DT_YUV422_8B,
> >> +	},
> >> +	{
> >> +		.code = MEDIA_BUS_FMT_VYUY8_1X16,
> >> +		.depth = 16,
> >> +		.csi_dt = MIPI_CSI2_DT_YUV422_8B,
> >> +	},
> >> +	/* RGB formats */
> >> +	{
> >> +		.code = MEDIA_BUS_FMT_RGB888_1X24,
> >> +		.depth = 24,
> >> +		.csi_dt = MIPI_CSI2_DT_RGB888,
> >> +	},
> >> +	{
> >> +		.code = MEDIA_BUS_FMT_BGR888_1X24,
> >> +		.depth = 24,
> >> +		.csi_dt = MIPI_CSI2_DT_RGB888,
> >> +	},
> >> +	/* Bayer formats */
> >> +	{
> >> +		.code = MEDIA_BUS_FMT_SBGGR8_1X8,
> >> +		.depth = 8,
> >> +		.csi_dt = MIPI_CSI2_DT_RAW8,
> >> +	},
> >> +	{
> >> +		.code = MEDIA_BUS_FMT_SGBRG8_1X8,
> >> +		.depth = 8,
> >> +		.csi_dt = MIPI_CSI2_DT_RAW8,
> >> +	},
> >> +	{
> >> +		.code = MEDIA_BUS_FMT_SGRBG8_1X8,
> >> +		.depth = 8,
> >> +		.csi_dt = MIPI_CSI2_DT_RAW8,
> >> +	},
> >> +	{
> >> +		.code = MEDIA_BUS_FMT_SRGGB8_1X8,
> >> +		.depth = 8,
> >> +		.csi_dt = MIPI_CSI2_DT_RAW8,
> >> +	},
> >> +	{
> >> +		.code = MEDIA_BUS_FMT_SBGGR10_1X10,
> >> +		.depth = 10,
> >> +		.csi_dt = MIPI_CSI2_DT_RAW10,
> >> +	},
> >> +	{
> >> +		.code = MEDIA_BUS_FMT_SGBRG10_1X10,
> >> +		.depth = 10,
> >> +		.csi_dt = MIPI_CSI2_DT_RAW10,
> >> +	},
> >> +	{
> >> +		.code = MEDIA_BUS_FMT_SGRBG10_1X10,
> >> +		.depth = 10,
> >> +		.csi_dt = MIPI_CSI2_DT_RAW10,
> >> +	},
> >> +	{
> >> +		.code = MEDIA_BUS_FMT_SRGGB10_1X10,
> >> +		.depth = 10,
> >> +		.csi_dt = MIPI_CSI2_DT_RAW10,
> >> +	},
> >> +	{
> >> +		.code = MEDIA_BUS_FMT_SBGGR12_1X12,
> >> +		.depth = 12,
> >> +		.csi_dt = MIPI_CSI2_DT_RAW12,
> >> +	},
> >> +	{
> >> +		.code = MEDIA_BUS_FMT_SGBRG12_1X12,
> >> +		.depth = 12,
> >> +		.csi_dt = MIPI_CSI2_DT_RAW12,
> >> +	},
> >> +	{
> >> +		.code = MEDIA_BUS_FMT_SGRBG12_1X12,
> >> +		.depth = 12,
> >> +		.csi_dt = MIPI_CSI2_DT_RAW12,
> >> +	},
> >> +	{
> >> +		.code = MEDIA_BUS_FMT_SRGGB12_1X12,
> >> +		.depth = 12,
> >> +		.csi_dt = MIPI_CSI2_DT_RAW12,
> >> +	},
> >> +};
> >> +
> >> +static inline struct rkcif_csi_device *to_rkcif_csi(struct v4l2_subdev *sd)
> >> +{
> >> +	return container_of(sd, struct rkcif_csi_device, sd);
> >> +}
> >> +
> >> +static inline __maybe_unused void
> >> +rkcif_csi_write(struct rkcif_csi_device *csi_dev, unsigned int addr, u32 val)
> >> +{
> >> +	writel(val, csi_dev->base_addr + addr);
> >> +}
> >> +
> >> +static inline __maybe_unused u32
> >> +rkcif_csi_read(struct rkcif_csi_device *csi_dev, unsigned int addr)
> >> +{
> >> +	return readl(csi_dev->base_addr + addr);
> >> +}
> >> +
> >> +static const struct rkcif_csi_format *
> >> +rkcif_csi_find_format(struct rkcif_csi_device *csi_dev, u32 mbus_code)
> >> +{
> >> +	const struct rkcif_csi_format *format;
> >> +
> >> +	WARN_ON(csi_dev->formats_num == 0);
> >> +
> >> +	for (int i = 0; i < csi_dev->formats_num; i++) {
> >> +		format = &csi_dev->formats[i];
> >> +		if (format->code == mbus_code)
> >> +			return format;
> >> +	}
> >> +
> >> +	return NULL;
> >> +}
> >> +
> >> +static int rkcif_csi_start(struct rkcif_csi_device *csi_dev)
> >> +{
> >> +	enum v4l2_mbus_type bus_type = csi_dev->vep.bus_type;
> >> +	union phy_configure_opts opts;
> >> +	s64 link_freq;
> >> +	u32 lanes = csi_dev->vep.bus.mipi_csi2.num_data_lanes;
> >> +	u32 control = 0;
> >> +
> >> +	if (lanes < 1 || lanes > 4)
> >> +		return -EINVAL;
> >> +
> >> +	/* set mult and div to 0, thus completely rely on V4L2_CID_LINK_FREQ */
> >> +	link_freq = v4l2_get_link_freq(csi_dev->source_sd->ctrl_handler, 0, 0);
> >> +	if (link_freq <= 0)
> >> +		return -EINVAL;
> >> +
> >> +	if (bus_type == V4L2_MBUS_CSI2_DPHY) {
> >> +		struct phy_configure_opts_mipi_dphy *cfg = &opts.mipi_dphy;
> >> +
> >> +		phy_mipi_dphy_get_default_config_for_hsclk(link_freq * 2, lanes,
> >> +							   cfg);
> >> +		phy_set_mode(csi_dev->phy, PHY_MODE_MIPI_DPHY);
> >> +		phy_configure(csi_dev->phy, &opts);
> >> +
> >> +		control |= SW_CPHY_EN(0);
> >> +
> >> +	} else if (bus_type == V4L2_MBUS_CSI2_CPHY) {
> >> +		control |= SW_CPHY_EN(1);
> >> +
> >> +		/* TODO: implement CPHY configuration */
> >> +	} else {
> >> +		return -EINVAL;
> >> +	}
> >> +
> >> +	control |= SW_DATATYPE_FS(0x00) | SW_DATATYPE_FE(0x01) |
> >> +		   SW_DATATYPE_LS(0x02) | SW_DATATYPE_LE(0x03);
> >> +
> >> +	rkcif_csi_write(csi_dev, CSI2HOST_N_LANES, lanes - 1);
> >> +	rkcif_csi_write(csi_dev, CSI2HOST_CONTROL, control);
> >> +	rkcif_csi_write(csi_dev, CSI2HOST_CSI2_RESETN, 1);
> >> +
> >> +	phy_power_on(csi_dev->phy);
> >> +
> >> +	return 0;
> >> +}
> >> +
> >> +static void rkcif_csi_stop(struct rkcif_csi_device *csi_dev)
> >> +{
> >> +	phy_power_off(csi_dev->phy);
> >> +
> >> +	rkcif_csi_write(csi_dev, CSI2HOST_CSI2_RESETN, 0);
> >> +	rkcif_csi_write(csi_dev, CSI2HOST_MSK1, ~0);
> >> +	rkcif_csi_write(csi_dev, CSI2HOST_MSK2, ~0);
> >> +}
> >> +
> >> +static const struct media_entity_operations rkcif_csi_media_ops = {
> >> +	.link_validate = v4l2_subdev_link_validate,
> >> +};
> >> +
> >> +static int rkcif_csi_enum_mbus_code(struct v4l2_subdev *sd,
> >> +				    struct v4l2_subdev_state *sd_state,
> >> +				    struct v4l2_subdev_mbus_code_enum *code)
> >> +{
> >> +	struct rkcif_csi_device *csi_dev = to_rkcif_csi(sd);
> >> +
> >> +	if (code->pad == RKCIF_CSI_PAD_SRC) {
> >> +		const struct v4l2_mbus_framefmt *sink_fmt;
> >> +
> >> +		if (code->index)
> >> +			return -EINVAL;
> >> +
> >> +		sink_fmt = v4l2_subdev_state_get_format(sd_state,
> >> +							RKCIF_CSI_PAD_SINK);
> >> +		code->code = sink_fmt->code;
> >> +
> >> +		return 0;
> >> +	} else if (code->pad == RKCIF_CSI_PAD_SINK) {
> >> +		if (code->index > csi_dev->formats_num)
> >> +			return -EINVAL;
> >> +
> >> +		code->code = csi_dev->formats[code->index].code;
> >> +		return 0;
> >> +	}
> >> +
> >> +	return -EINVAL;
> >> +}
> >> +
> >> +static int rkcif_csi_set_fmt(struct v4l2_subdev *sd,
> >> +			     struct v4l2_subdev_state *state,
> >> +			     struct v4l2_subdev_format *format)
> >> +{
> >> +	struct rkcif_csi_device *csi_dev = to_rkcif_csi(sd);
> >> +	const struct rkcif_csi_format *fmt;
> >> +	struct v4l2_mbus_framefmt *sink, *src;
> >> +
> >> +	/* the format on the source pad always matches the sink pad */
> >> +	if (format->pad == RKCIF_CSI_PAD_SRC)
> >> +		return v4l2_subdev_get_fmt(sd, state, format);
> >> +
> >> +	sink = v4l2_subdev_state_get_format(state, format->pad, format->stream);
> >> +	if (!sink)
> >> +		return -EINVAL;
> >> +
> >> +	fmt = rkcif_csi_find_format(csi_dev, format->format.code);
> >> +	if (fmt)
> >> +		*sink = format->format;
> >> +	else
> >> +		*sink = default_format;
> >> +
> >> +	/* propagate the format to the source pad */
> >> +	src = v4l2_subdev_state_get_opposite_stream_format(state, format->pad,
> >> +							   format->stream);
> >> +	if (!src)
> >> +		return -EINVAL;
> >> +
> >> +	*src = *sink;
> >> +
> >> +	return 0;
> >> +}
> >> +
> >> +static int rkcif_csi_set_routing(struct v4l2_subdev *sd,
> >> +				 struct v4l2_subdev_state *state,
> >> +				 enum v4l2_subdev_format_whence which,
> >> +				 struct v4l2_subdev_krouting *routing)
> >> +{
> >> +	int ret;
> >> +
> >> +	ret = v4l2_subdev_routing_validate(sd, routing,
> >> +					   V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
> >> +	if (ret)
> >> +		return ret;
> >> +
> >> +	ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing,
> >> +					       &default_format);
> >> +	if (ret)
> >> +		return ret;
> >> +
> >> +	return 0;
> >> +}
> >> +
> >> +static int rkcif_csi_enable_streams(struct v4l2_subdev *sd,
> >> +				    struct v4l2_subdev_state *state, u32 pad,
> >> +				    u64 streams_mask)
> >> +{
> >> +	struct rkcif_csi_device *csi_dev = to_rkcif_csi(sd);
> >> +	struct v4l2_subdev *remote_sd;
> >> +	struct media_pad *sink_pad, *remote_pad;
> >> +	struct device *dev = csi_dev->dev;
> >> +	u64 mask;
> >> +	int ret;
> >> +
> >> +	sink_pad = &sd->entity.pads[RKCIF_CSI_PAD_SINK];
> >> +	remote_pad = media_pad_remote_pad_first(sink_pad);
> >> +	remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity);
> >> +
> >> +	mask = v4l2_subdev_state_xlate_streams(state, RKCIF_CSI_PAD_SINK,
> >> +					       RKCIF_CSI_PAD_SRC,
> >> +					       &streams_mask);
> >> +
> >> +	ret = pm_runtime_resume_and_get(dev);
> >> +	if (ret)
> >> +		goto err;
> >> +
> >> +	ret = rkcif_csi_start(csi_dev);
> >> +	if (ret) {
> >> +		dev_err(dev, "failed to enable CSI hardware\n");
> >> +		goto err_pm_runtime_put;
> >> +	}
> >> +
> >> +	ret = v4l2_subdev_enable_streams(remote_sd, remote_pad->index, mask);
> >> +	if (ret)
> >> +		goto err_csi_stop;
> >> +
> >> +	return 0;
> >> +
> >> +err_csi_stop:
> >> +	rkcif_csi_stop(csi_dev);
> >> +err_pm_runtime_put:
> >> +	pm_runtime_put_sync(dev);
> >> +err:
> >> +	return ret;
> >> +}
> >> +
> >> +static int rkcif_csi_disable_streams(struct v4l2_subdev *sd,
> >> +				     struct v4l2_subdev_state *state, u32 pad,
> >> +				     u64 streams_mask)
> >> +{
> >> +	struct rkcif_csi_device *csi_dev = to_rkcif_csi(sd);
> >> +	struct v4l2_subdev *remote_sd;
> >> +	struct media_pad *sink_pad, *remote_pad;
> >> +	struct device *dev = csi_dev->dev;
> >> +	u64 mask;
> >> +	int ret;
> >> +
> >> +	sink_pad = &sd->entity.pads[RKCIF_CSI_PAD_SINK];
> >> +	remote_pad = media_pad_remote_pad_first(sink_pad);
> >> +	remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity);
> >> +
> >> +	mask = v4l2_subdev_state_xlate_streams(state, RKCIF_CSI_PAD_SINK,
> >> +					       RKCIF_CSI_PAD_SRC,
> >> +					       &streams_mask);
> >> +
> >> +	ret = v4l2_subdev_disable_streams(remote_sd, remote_pad->index, mask);
> >> +
> >> +	rkcif_csi_stop(csi_dev);
> >> +
> >> +	pm_runtime_mark_last_busy(dev);
> >> +	pm_runtime_put_autosuspend(dev);
> >> +
> >> +	return ret;
> >> +}
> >> +
> >> +static const struct v4l2_subdev_pad_ops rkcif_csi_pad_ops = {
> >> +	.enum_mbus_code = rkcif_csi_enum_mbus_code,
> >> +	.get_fmt = v4l2_subdev_get_fmt,
> >> +	.set_fmt = rkcif_csi_set_fmt,
> >> +	.set_routing = rkcif_csi_set_routing,
> >> +	.enable_streams = rkcif_csi_enable_streams,
> >> +	.disable_streams = rkcif_csi_disable_streams,
> >> +};
> >> +
> >> +static const struct v4l2_subdev_ops rkcif_csi_ops = {
> >> +	.pad = &rkcif_csi_pad_ops,
> >> +};
> >> +
> >> +static int rkcif_csi_init_state(struct v4l2_subdev *sd,
> >> +				struct v4l2_subdev_state *state)
> >> +{
> >> +	struct v4l2_subdev_route routes[] = {
> >> +		{
> >> +			.sink_pad = RKCIF_CSI_PAD_SINK,
> >> +			.sink_stream = 0,
> >> +			.source_pad = RKCIF_CSI_PAD_SRC,
> >> +			.source_stream = 0,
> >> +			.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
> >> +		},
> >> +	};
> >> +	struct v4l2_subdev_krouting routing = {
> >> +		.len_routes = ARRAY_SIZE(routes),
> >> +		.num_routes = ARRAY_SIZE(routes),
> >> +		.routes = routes,
> >> +	};
> >> +	int ret;
> >> +
> >> +	ret = v4l2_subdev_set_routing_with_fmt(sd, state, &routing,
> >> +					       &default_format);
> >> +
> >> +	return ret;
> >> +}
> >> +
> >> +static const struct v4l2_subdev_internal_ops rkcif_csi_internal_ops = {
> >> +	.init_state = rkcif_csi_init_state,
> >> +};
> >> +
> >> +static int rkcif_csi_notifier_bound(struct v4l2_async_notifier *notifier,
> >> +				    struct v4l2_subdev *sd,
> >> +				    struct v4l2_async_connection *asd)
> >> +{
> >> +	struct rkcif_csi_device *csi_dev =
> >> +		container_of(notifier, struct rkcif_csi_device, notifier);
> >> +	int source_pad;
> >> +
> >> +	source_pad = media_entity_get_fwnode_pad(&sd->entity, sd->fwnode,
> >> +						 MEDIA_PAD_FL_SOURCE);
> >> +	if (source_pad < 0) {
> >> +		dev_err(csi_dev->dev, "failed to find source pad for %s\n",
> >> +			sd->name);
> >> +		return source_pad;
> >> +	}
> >> +
> >> +	csi_dev->source_sd = sd;
> >> +	csi_dev->source_pad = source_pad;
> >> +
> >> +	return media_create_pad_link(&sd->entity, source_pad,
> >> +				     &csi_dev->sd.entity, RKCIF_CSI_PAD_SINK,
> >> +				     MEDIA_LNK_FL_ENABLED);
> >> +}
> >> +
> >> +static const struct v4l2_async_notifier_operations rkcif_csi_notifier_ops = {
> >> +	.bound = rkcif_csi_notifier_bound,
> >> +};
> >> +
> >> +static int rkcif_csi_register_notifier(struct rkcif_csi_device *csi_dev)
> >> +{
> >> +	struct v4l2_async_connection *asd;
> >> +	struct v4l2_async_notifier *ntf = &csi_dev->notifier;
> >> +	struct v4l2_fwnode_endpoint *vep = &csi_dev->vep;
> >> +	struct v4l2_subdev *sd = &csi_dev->sd;
> >> +	struct device *dev = csi_dev->dev;
> >> +	struct fwnode_handle *ep;
> >> +	int ret = 0;
> >> +
> >> +	ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, 0);
> >> +	if (!ep)
> >> +		return dev_err_probe(dev, -ENODEV, "failed to get endpoint\n");
> >> +
> >> +	vep->bus_type = V4L2_MBUS_UNKNOWN;
> >> +	ret = v4l2_fwnode_endpoint_parse(ep, vep);
> >> +	if (ret) {
> >> +		ret = dev_err_probe(dev, ret, "failed to parse endpoint\n");
> >> +		goto out;
> >> +	}
> >> +
> >> +	if (vep->bus_type != V4L2_MBUS_CSI2_DPHY &&
> >> +	    vep->bus_type != V4L2_MBUS_CSI2_CPHY) {
> >> +		ret = dev_err_probe(dev, -EINVAL,
> >> +				    "invalid bus type of endpoint\n");
> >> +		goto out;
> >> +	}
> >> +
> >> +	v4l2_async_subdev_nf_init(ntf, sd);
> >> +	ntf->ops = &rkcif_csi_notifier_ops;
> >> +
> >> +	asd = v4l2_async_nf_add_fwnode_remote(ntf, ep,
> >> +					      struct v4l2_async_connection);
> >> +	if (IS_ERR(asd)) {
> >> +		ret = PTR_ERR(asd);
> >> +		goto err_nf_cleanup;
> >> +	}
> >> +
> >> +	ret = v4l2_async_nf_register(ntf);
> >> +	if (ret) {
> >> +		ret = dev_err_probe(dev, ret, "failed to register notifier\n");
> >> +		goto err_nf_cleanup;
> >> +	}
> >> +
> >> +	goto out;
> >> +
> >> +err_nf_cleanup:
> >> +	v4l2_async_nf_cleanup(ntf);
> >> +out:
> >> +	fwnode_handle_put(ep);
> >> +	return ret;
> >> +}
> >> +
> >> +static int rkcif_csi_register(struct rkcif_csi_device *csi_dev)
> >> +{
> >> +	struct media_pad *pads = csi_dev->pads;
> >> +	struct v4l2_subdev *sd = &csi_dev->sd;
> >> +	int ret;
> >> +
> >> +	ret = rkcif_csi_register_notifier(csi_dev);
> >> +	if (ret)
> >> +		goto err;
> >> +
> >> +	v4l2_subdev_init(sd, &rkcif_csi_ops);
> >> +	sd->dev = csi_dev->dev;
> >> +	sd->entity.ops = &rkcif_csi_media_ops;
> >> +	sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
> >> +	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS;
> >> +	sd->internal_ops = &rkcif_csi_internal_ops;
> >> +	sd->owner = THIS_MODULE;
> >> +	snprintf(sd->name, sizeof(sd->name), "rockchip-mipi-csi %s",
> >> +		 dev_name(csi_dev->dev));
> >> +
> >> +	pads[RKCIF_CSI_PAD_SINK].flags = MEDIA_PAD_FL_SINK |
> >> +					 MEDIA_PAD_FL_MUST_CONNECT;
> >> +	pads[RKCIF_CSI_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE;
> >> +	ret = media_entity_pads_init(&sd->entity, RKCIF_CSI_PAD_MAX, pads);
> >> +	if (ret)
> >> +		goto err_notifier_unregister;
> >> +
> >> +	ret = v4l2_subdev_init_finalize(sd);
> >> +	if (ret)
> >> +		goto err_entity_cleanup;
> >> +
> >> +	ret = v4l2_async_register_subdev(sd);
> >> +	if (ret) {
> >> +		dev_err(sd->dev, "failed to register CSI subdev\n");
> >> +		goto err_subdev_cleanup;
> >> +	}
> >> +
> >> +	return 0;
> >> +
> >> +err_subdev_cleanup:
> >> +	v4l2_subdev_cleanup(sd);
> >> +err_entity_cleanup:
> >> +	media_entity_cleanup(&sd->entity);
> >> +err_notifier_unregister:
> >> +	v4l2_async_nf_unregister(&csi_dev->notifier);
> >> +	v4l2_async_nf_cleanup(&csi_dev->notifier);
> >> +err:
> >> +	return ret;
> >> +}
> >> +
> >> +static void rkcif_csi_unregister(struct rkcif_csi_device *csi_dev)
> >> +{
> >> +	struct v4l2_subdev *sd = &csi_dev->sd;
> >> +
> >> +	v4l2_async_unregister_subdev(sd);
> >> +	v4l2_subdev_cleanup(sd);
> >> +	media_entity_cleanup(&sd->entity);
> >> +	v4l2_async_nf_unregister(&csi_dev->notifier);
> >> +	v4l2_async_nf_cleanup(&csi_dev->notifier);
> >> +}
> >> +
> >> +static const struct of_device_id rkcif_csi_of_match[] = {
> >> +	{
> >> +		.compatible = "rockchip,rk3568-mipi-csi",
> >> +	},
> >> +	{}
> >> +};
> >> +MODULE_DEVICE_TABLE(of, rkcif_csi_of_match);
> >> +
> >> +static int rkcif_csi_probe(struct platform_device *pdev)
> >> +{
> >> +	struct device *dev = &pdev->dev;
> >> +	struct rkcif_csi_device *csi_dev;
> >> +	int ret;
> >> +
> >> +	csi_dev = devm_kzalloc(dev, sizeof(*csi_dev), GFP_KERNEL);
> >> +	if (!csi_dev)
> >> +		return -ENOMEM;
> >> +	csi_dev->dev = dev;
> >> +	dev_set_drvdata(dev, csi_dev);
> >> +
> >> +	csi_dev->base_addr = devm_platform_ioremap_resource(pdev, 0);
> >> +	if (IS_ERR(csi_dev->base_addr))
> >> +		return PTR_ERR(csi_dev->base_addr);
> >> +
> >> +	ret = devm_clk_bulk_get_all(dev, &csi_dev->clks);
> >> +	if (ret != RKCIF_CSI_CLKS_MAX)
> >> +		return dev_err_probe(dev, -ENODEV, "failed to get clocks\n");
> >> +	csi_dev->clks_num = ret;
> >> +
> >> +	csi_dev->phy = devm_phy_get(dev, NULL);
> >> +	if (IS_ERR(csi_dev->phy))
> >> +		return dev_err_probe(dev, PTR_ERR(csi_dev->phy),
> >> +				     "failed to get MIPI CSI PHY\n");
> >> +
> >> +	csi_dev->reset = devm_reset_control_array_get_exclusive(dev);
> >> +	if (IS_ERR(csi_dev->reset))
> >> +		return dev_err_probe(dev, PTR_ERR(csi_dev->reset),
> >> +				     "failed to get reset\n");
> >> +
> >> +	csi_dev->formats = formats;
> >> +	csi_dev->formats_num = ARRAY_SIZE(formats);
> >> +
> >> +	pm_runtime_enable(dev);
> >> +
> >> +	ret = phy_init(csi_dev->phy);
> >> +	if (ret) {
> >> +		ret = dev_err_probe(dev, ret,
> >> +				    "failed to initialize MIPI CSI PHY\n");
> >> +		goto err_pm_runtime_disable;
> >> +	}
> >> +
> >> +	ret = rkcif_csi_register(csi_dev);
> >> +	if (ret)
> >> +		goto err_phy_exit;
> >> +
> >> +	return 0;
> >> +
> >> +err_phy_exit:
> >> +	phy_exit(csi_dev->phy);
> >> +err_pm_runtime_disable:
> >> +	pm_runtime_disable(dev);
> >> +	return ret;
> >> +}
> >> +
> >> +static void rkcif_csi_remove(struct platform_device *pdev)
> >> +{
> >> +	struct rkcif_csi_device *csi_dev = platform_get_drvdata(pdev);
> >> +	struct device *dev = &pdev->dev;
> >> +
> >> +	rkcif_csi_unregister(csi_dev);
> >> +	phy_exit(csi_dev->phy);
> >> +	pm_runtime_disable(dev);
> >> +}
> >> +
> >> +static int rkcif_csi_runtime_suspend(struct device *dev)
> >> +{
> >> +	struct rkcif_csi_device *csi_dev = dev_get_drvdata(dev);
> >> +
> >> +	clk_bulk_disable_unprepare(csi_dev->clks_num, csi_dev->clks);
> >> +
> >> +	return 0;
> >> +}
> >> +
> >> +static int rkcif_csi_runtime_resume(struct device *dev)
> >> +{
> >> +	struct rkcif_csi_device *csi_dev = dev_get_drvdata(dev);
> >> +	int ret;
> >> +
> >> +	reset_control_assert(csi_dev->reset);
> >> +	udelay(5);
> >> +	reset_control_deassert(csi_dev->reset);
> >> +
> >> +	ret = clk_bulk_prepare_enable(csi_dev->clks_num, csi_dev->clks);
> >> +	if (ret) {
> >> +		dev_err(dev, "failed to enable clocks\n");
> >> +		return ret;
> >> +	}
> >> +
> >> +	return 0;
> >> +}
> >> +
> >> +static const struct dev_pm_ops rkcif_csi_pm_ops = {
> >> +	.runtime_suspend = rkcif_csi_runtime_suspend,
> >> +	.runtime_resume = rkcif_csi_runtime_resume,
> >> +};
> >> +
> >> +static struct platform_driver rkcif_csi_drv = {
> >> +	.driver = {
> >> +		   .name = "rockchip-mipi-csi",
> >> +		   .of_match_table = rkcif_csi_of_match,
> >> +		   .pm = &rkcif_csi_pm_ops,
> >> +	},
> >> +	.probe = rkcif_csi_probe,
> >> +	.remove = rkcif_csi_remove,
> >> +};
> >> +module_platform_driver(rkcif_csi_drv);
> >> +
> >> +MODULE_DESCRIPTION("Rockchip MIPI CSI-2 Receiver platform driver");
> >> +MODULE_LICENSE("GPL");

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v6 06/13] media: rockchip: add a driver for the rockchip camera interface
  2025-04-30  9:15 ` [PATCH v6 06/13] media: rockchip: add a driver for the rockchip camera interface Michael Riesch via B4 Relay
  2025-05-01  0:43   ` Bryan O'Donoghue
@ 2025-05-06 10:37   ` Mehdi Djait
  2025-05-06 20:32     ` Michael Riesch
  1 sibling, 1 reply; 34+ messages in thread
From: Mehdi Djait @ 2025-05-06 10:37 UTC (permalink / raw)
  To: michael.riesch
  Cc: Maxime Chevallier, Théo Lebrun, Gerald Loacker,
	Thomas Petazzoni, Laurent Pinchart, Mauro Carvalho Chehab,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner,
	Kever Yang, Nicolas Dufresne, Sebastian Reichel,
	Collabora Kernel Team, Paul Kocialkowski, Alexander Shiyan,
	Val Packett, Rob Herring, Philipp Zabel, Sakari Ailus,
	linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-rockchip, Michael Riesch, Mehdi Djait

Hi Michael,

Thank you for the patch!

Is it possible to sent the v4l2-compliance output in the next version ?

On Wed, Apr 30, 2025 at 11:15:55AM +0200, Michael Riesch via B4 Relay wrote:
> From: Michael Riesch <michael.riesch@wolfvision.net>
> 

SNIP

> +irqreturn_t rkcif_dvp_isr(int irq, void *ctx)
> +{
> +	struct device *dev = ctx;
> +	struct rkcif_device *rkcif = dev_get_drvdata(dev);
> +	struct rkcif_stream *stream;
> +	u32 intstat, lastline, lastpix, cif_frmst;
> +	irqreturn_t ret = IRQ_NONE;
> +
> +	if (!rkcif->match_data->dvp)
> +		return ret;
> +
> +	intstat = cif_dvp_read(rkcif, RKCIF_DVP_INTSTAT);
> +	cif_frmst = cif_dvp_read(rkcif, RKCIF_DVP_FRAME_STATUS);
> +	lastline = RKCIF_FETCH_Y(cif_dvp_read(rkcif, RKCIF_DVP_LAST_LINE));
> +	lastpix = RKCIF_FETCH_Y(cif_dvp_read(rkcif, RKCIF_DVP_LAST_PIX));
> +
> +	if (intstat & RKCIF_INTSTAT_FRAME_END) {
> +		cif_dvp_write(rkcif, RKCIF_DVP_INTSTAT,
> +			      RKCIF_INTSTAT_FRAME_END_CLR |
> +				      RKCIF_INTSTAT_LINE_END_CLR);
> +
> +		stream = &rkcif->interfaces[RKCIF_DVP].streams[RKCIF_ID0];
> +
> +		if (stream->stopping) {
> +			cif_dvp_stop_streaming(stream);
> +			wake_up(&stream->wq_stopped);
> +			return IRQ_HANDLED;
> +		}
> +
> +		if (lastline != stream->pix.height) {
> +			v4l2_err(&rkcif->v4l2_dev,
> +				 "bad frame, irq:%#x frmst:%#x size:%dx%d\n",
> +				 intstat, cif_frmst, lastpix, lastline);
> +
> +			cif_dvp_reset_stream(rkcif);
> +		}
> +
> +		rkcif_stream_pingpong(stream);
> +
> +		ret = IRQ_HANDLED;

just return IRQ_HANDLED like above ?

> +	}
> +
> +	return ret;
> +}
> +
> +int rkcif_dvp_register(struct rkcif_device *rkcif)
> +{
> +	struct rkcif_interface *interface;
> +	int ret, i;
> +
> +	if (!rkcif->match_data->dvp)
> +		return 0;
> +
> +	interface = &rkcif->interfaces[RKCIF_DVP];
> +	interface->index = RKCIF_DVP;
> +	interface->type = RKCIF_IF_DVP;
> +	interface->in_fmts = rkcif->match_data->dvp->in_fmts;
> +	interface->in_fmts_num = rkcif->match_data->dvp->in_fmts_num;
> +	interface->set_crop = rkcif_dvp_set_crop;
> +	ret = rkcif_interface_register(rkcif, interface);
> +	if (ret)
> +		return 0;
		|
		+-> Copy-paste error ?

> +
> +	if (rkcif->match_data->dvp->setup)
> +		rkcif->match_data->dvp->setup(rkcif);
> +
> +	interface->streams_num = rkcif->match_data->dvp->has_ids ? 4 : 1;
> +	for (i = 0; i < interface->streams_num; i++) {
> +		struct rkcif_stream *stream = &interface->streams[i];
> +
> +		stream->id = i;
> +		stream->interface = interface;
> +		stream->out_fmts = rkcif->match_data->dvp->out_fmts;
> +		stream->out_fmts_num = rkcif->match_data->dvp->out_fmts_num;
> +		stream->queue_buffer = cif_dvp_queue_buffer;
> +		stream->start_streaming = cif_dvp_start_streaming;
> +		stream->stop_streaming = cif_dvp_stop_streaming;
> +
> +		ret = rkcif_stream_register(rkcif, stream);
> +		if (ret)
> +			goto err_streams_unregister;
> +	}
> +	return 0;
> +
> +err_streams_unregister:
> +	for (; i >= 0; i--)
> +		rkcif_stream_unregister(&interface->streams[i]);
> +	rkcif_interface_unregister(interface);
> +
> +	return ret;
> +}
> +

SNIP

> +static inline struct rkcif_buffer *to_rkcif_buffer(struct vb2_v4l2_buffer *vb)
> +{
> +	return container_of(vb, struct rkcif_buffer, vb);
> +}
> +
> +static inline struct rkcif_stream *to_rkcif_stream(struct video_device *vdev)
> +{
> +	return container_of(vdev, struct rkcif_stream, vdev);
> +}
> +
> +static struct rkcif_buffer *rkcif_stream_pop_buffer(struct rkcif_stream *stream)
> +{
> +	struct rkcif_buffer *buffer = NULL;
> +	unsigned long lock_flags;
> +
> +	spin_lock_irqsave(&stream->driver_queue_lock, lock_flags);

guard(spinlock_irqsave)(&stream->driver_queue_lock) will simplify this function.

> +
> +	if (list_empty(&stream->driver_queue))
> +		goto err_empty;
> +
> +	buffer = list_first_entry(&stream->driver_queue, struct rkcif_buffer,
> +				  queue);
> +	list_del(&buffer->queue);
> +
> +err_empty:
> +	spin_unlock_irqrestore(&stream->driver_queue_lock, lock_flags);
> +	return buffer;
> +}
> +
> +static void rkcif_stream_push_buffer(struct rkcif_stream *stream,
> +				     struct rkcif_buffer *buffer)
> +{
> +	unsigned long lock_flags;
> +
> +	spin_lock_irqsave(&stream->driver_queue_lock, lock_flags);
> +	list_add_tail(&buffer->queue, &stream->driver_queue);
> +	spin_unlock_irqrestore(&stream->driver_queue_lock, lock_flags);
> +}
> +
> +static inline void rkcif_stream_return_buffer(struct rkcif_buffer *buffer,
> +					      enum vb2_buffer_state state)
> +{
> +	struct vb2_v4l2_buffer *vb = &buffer->vb;
> +
> +	vb2_buffer_done(&vb->vb2_buf, state);
> +}
> +
> +static void rkcif_stream_complete_buffer(struct rkcif_stream *stream,
> +					 struct rkcif_buffer *buffer)
> +{
> +	struct vb2_v4l2_buffer *vb = &buffer->vb;
> +
> +	vb->vb2_buf.timestamp = ktime_get_ns();
> +	vb->sequence = stream->frame_idx;
> +	vb2_buffer_done(&vb->vb2_buf, VB2_BUF_STATE_DONE);
> +	stream->frame_idx++;
> +}
> +
> +void rkcif_stream_pingpong(struct rkcif_stream *stream)
> +{
> +	struct rkcif_buffer *buffer;
> +
> +	buffer = stream->buffers[stream->frame_phase];
> +	if (!buffer->is_dummy)
> +		rkcif_stream_complete_buffer(stream, buffer);

You can actually keep this frame dropping mechanism without using the
dummy buffer.

You can set a drop flag to TRUE: keep overwriting the buffer you already have
without returning it to user-space until you can get another buffer, set
the flag again to FALSE and resume returning the buffers to user-space.

> +
> +	buffer = rkcif_stream_pop_buffer(stream);
> +	if (buffer) {
> +		stream->buffers[stream->frame_phase] = buffer;
> +		stream->buffers[stream->frame_phase]->is_dummy = false;
> +	} else {
> +		stream->buffers[stream->frame_phase] = &stream->dummy.buffer;
> +		stream->buffers[stream->frame_phase]->is_dummy = true;
> +		dev_warn(stream->rkcif->dev,
> +			 "no buffer available, frame will be dropped\n");

This warning can quickly flood the kernel logs if the user-space is too slow in
enqueuing the buffers.

> +	}
> +
> +	if (stream->queue_buffer)
> +		stream->queue_buffer(stream, stream->frame_phase);

is this if statement really needed ?

> +
> +	stream->frame_phase = 1 - stream->frame_phase;
> +}
> +
> +static int rkcif_stream_init_buffers(struct rkcif_stream *stream)
> +{
> +	struct v4l2_pix_format_mplane *pix = &stream->pix;
> +	int i;
> +
> +	stream->buffers[0] = rkcif_stream_pop_buffer(stream);
> +	if (!stream->buffers[0])
> +		goto err_buff_0;
> +
> +	stream->buffers[1] = rkcif_stream_pop_buffer(stream);
> +	if (!stream->buffers[1])
> +		goto err_buff_1;
> +
> +	if (stream->queue_buffer) {
> +		stream->queue_buffer(stream, 0);
> +		stream->queue_buffer(stream, 1);
> +	}
> +
> +	stream->dummy.size = pix->num_planes * pix->plane_fmt[0].sizeimage;
> +	stream->dummy.vaddr =
> +		dma_alloc_attrs(stream->rkcif->dev, stream->dummy.size,
> +				&stream->dummy.buffer.buff_addr[0], GFP_KERNEL,
> +				DMA_ATTR_NO_KERNEL_MAPPING);
> +	if (!stream->dummy.vaddr)
> +		goto err_dummy;
> +
> +	for (i = 1; i < pix->num_planes; i++)
> +		stream->dummy.buffer.buff_addr[i] =
> +			stream->dummy.buffer.buff_addr[i - 1] +
> +			pix->plane_fmt[i - 1].bytesperline * pix->height;
> +
> +	return 0;
> +
> +err_dummy:
> +	rkcif_stream_return_buffer(stream->buffers[1], VB2_BUF_STATE_QUEUED);
> +	stream->buffers[1] = NULL;
> +
> +err_buff_1:
> +	rkcif_stream_return_buffer(stream->buffers[0], VB2_BUF_STATE_QUEUED);
> +	stream->buffers[0] = NULL;
> +err_buff_0:
> +	return -EINVAL;
> +}
> +

SNIP

> +static int rkcif_stream_init_vb2_queue(struct vb2_queue *q,
> +				       struct rkcif_stream *stream)
> +{
> +	q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
> +	q->io_modes = VB2_MMAP | VB2_DMABUF;
> +	q->drv_priv = stream;
> +	q->ops = &rkcif_stream_vb2_ops;
> +	q->mem_ops = &vb2_dma_contig_memops;
> +	q->buf_struct_size = sizeof(struct rkcif_buffer);
> +	q->min_queued_buffers = CIF_REQ_BUFS_MIN;

If I recall correctly min_queued_buffers should be the strict minimum
number of buffers you need to start streaming. So in this case it should
be 3 = 2 pingpong buffers + 1 dummy buffer.

VIDIOC_REQBUFS will allocate min_queued_buffers + 1 and user-space will
probably allocate even more anyway.

> +	q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
> +	q->lock = &stream->vlock;
> +	q->dev = stream->rkcif->dev;
> +
> +	return vb2_queue_init(q);
> +}

--
Kind Regards
Mehdi Djait

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

* Re: [PATCH v6 05/13] media: dt-bindings: add rockchip rk3568 mipi csi receiver
  2025-04-30  9:15 ` [PATCH v6 05/13] media: dt-bindings: add rockchip rk3568 mipi csi receiver Michael Riesch via B4 Relay
  2025-04-30 10:50   ` Rob Herring (Arm)
@ 2025-05-06 12:09   ` Mehdi Djait
  2025-05-06 19:54     ` Michael Riesch
  1 sibling, 1 reply; 34+ messages in thread
From: Mehdi Djait @ 2025-05-06 12:09 UTC (permalink / raw)
  To: michael.riesch
  Cc: Maxime Chevallier, Théo Lebrun, Gerald Loacker,
	Thomas Petazzoni, Laurent Pinchart, Mauro Carvalho Chehab,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner,
	Kever Yang, Nicolas Dufresne, Sebastian Reichel,
	Collabora Kernel Team, Paul Kocialkowski, Alexander Shiyan,
	Val Packett, Rob Herring, Philipp Zabel, Sakari Ailus,
	linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-rockchip, Michael Riesch

Hi Michael,

thank you for the patch!

On Wed, Apr 30, 2025 at 11:15:54AM +0200, Michael Riesch via B4 Relay wrote:
> From: Michael Riesch <michael.riesch@wolfvision.net>
> 
> Add documentation for the Rockchip RK3568 MIPI CSI-2 Receiver.
> 
> Signed-off-by: Michael Riesch <michael.riesch@wolfvision.net>
> Signed-off-by: Michael Riesch <michael.riesch@collabora.com>
> ---
>  .../bindings/media/rockchip,rk3568-mipi-csi.yaml   | 113 +++++++++++++++++++++
>  MAINTAINERS                                        |   1 +
>  2 files changed, 114 insertions(+)
> 
> diff --git a/Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi.yaml b/Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi.yaml
> new file mode 100644
> index 000000000000..d5004cb288dd
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi.yaml
> @@ -0,0 +1,113 @@
> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/media/rockchip,rk3568-mipi-csi.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Rockchip RK3568 MIPI CSI-2 Receiver
> +
> +maintainers:
> +  - Michael Riesch <michael.riesch@collabora.com>
> +
> +description:
> +  The Rockchip RK3568 MIPI CSI-2 Receiver is a CSI-2 bridge with one input port
> +  and one output port. It receives the data with the help of an external
> +  MIPI PHY (C-PHY or D-PHY) and passes it to the Rockchip RK3568 Video Capture
> +  (VICAP) block.
> +
> +properties:
> +  compatible:
> +    const: rockchip,rk3568-mipi-csi
> +
> +  reg:
> +    maxItems: 1
> +
> +  clocks:
> +    maxItems: 1
> +
> +  phys:
> +    maxItems: 1
> +    description: MIPI C-PHY or D-PHY.
> +
> +  power-domains:
> +    maxItems: 1
> +
> +  resets:
> +    maxItems: 1
> +
> +  ports:
> +    $ref: /schemas/graph.yaml#/properties/ports
> +
> +    properties:
> +      port@0:
> +        $ref: /schemas/graph.yaml#/$defs/port-base
> +        unevaluatedProperties: false
> +        description: Input port node. Connect to e.g., a MIPI CSI-2 image sensor.
> +
> +        properties:
> +          endpoint:
> +            $ref: video-interfaces.yaml#
> +            unevaluatedProperties: false
> +
> +            properties:
> +              bus-type:
> +                enum: [1, 4]

shouldn't you add data-lanes property here ?

                 data-lanes:
                   minItems: 1
                   maxItems: 4
> +
> +            required:
> +              - bus-type

and add it to required:
                 - data-lanes

you are actually checking for data-lanes when you enable the stream in:

rkcif-mipi-csi-receiver.c +226

	u32 lanes = csi_dev->vep.bus.mipi_csi2.num_data_lanes;

	if (lanes < 1 || lanes > 4)
		return -EINVAL;

> +
> +      port@1:
> +        $ref: /schemas/graph.yaml#/properties/port
> +        description: Output port connected to a RK3568 VICAP port.
> +
> +    required:
> +      - port@0
> +      - port@1
> +
> +required:
> +  - compatible
> +  - reg
> +  - clocks
> +  - phys
> +  - phy-names
> +  - ports
> +  - power-domains
> +  - resets
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    #include <dt-bindings/clock/rk3568-cru.h>
> +    #include <dt-bindings/power/rk3568-power.h>
> +
> +    soc {
> +        #address-cells = <2>;
> +        #size-cells = <2>;
> +
> +        csi: csi@fdfb0000 {
> +            compatible = "rockchip,rk3568-mipi-csi";
> +            reg = <0x0 0xfdfb0000 0x0 0x10000>;
> +            clocks = <&cru PCLK_CSI2HOST1>;
> +            phys = <&csi_dphy>;
> +            power-domains = <&power RK3568_PD_VI>;
> +            resets = <&cru SRST_P_CSI2HOST1>;
> +
> +            ports {
> +                #address-cells = <1>;
> +                #size-cells = <0>;
> +
> +                csi_in: port@0 {
> +                    reg = <0>;
> +                };
> +
> +                csi_out: port@1 {
> +                    reg = <1>;
> +
> +                    csi_output: endpoint {
> +                        remote-endpoint = <&vicap_mipi_input>;
> +                    };
> +                };
> +            };
> +        };
> +    };

--
Kind Regards
Mehdi Djait

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

* Re: [PATCH v6 07/13] media: rockchip: rkcif: add driver for mipi csi-2 receiver
  2025-05-02 14:35       ` Laurent Pinchart
@ 2025-05-06 18:39         ` Michael Riesch
  2025-05-07  8:38           ` Laurent Pinchart
  0 siblings, 1 reply; 34+ messages in thread
From: Michael Riesch @ 2025-05-06 18:39 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Mehdi Djait, Maxime Chevallier, Théo Lebrun, Gerald Loacker,
	Thomas Petazzoni, Mauro Carvalho Chehab, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner, Kever Yang,
	Nicolas Dufresne, Sebastian Reichel, Collabora Kernel Team,
	Paul Kocialkowski, Alexander Shiyan, Val Packett, Rob Herring,
	Philipp Zabel, Sakari Ailus, linux-media, devicetree,
	linux-kernel, linux-arm-kernel, linux-rockchip, Michael Riesch

Hi Laurent,

On 5/2/25 16:35, Laurent Pinchart wrote:
> On Fri, May 02, 2025 at 04:19:59PM +0200, Michael Riesch wrote:
>> On 5/2/25 15:31, Laurent Pinchart wrote:
>>> On Wed, Apr 30, 2025 at 11:15:56AM +0200, Michael Riesch via B4 Relay wrote:
>>>> From: Michael Riesch <michael.riesch@wolfvision.net>
>>>>
>>>> The Rockchip RK3568 MIPI CSI-2 Receiver is a CSI-2 bridge with one
>>>> input port and one output port. It receives the data with the help
>>>> of an external MIPI PHY (C-PHY or D-PHY) and passes it to the
>>>> Rockchip RK3568 Video Capture (VICAP) block.
>>>>
>>>> Add a V4L2 subdevice driver for this unit.
>>>>
>>>> Signed-off-by: Michael Riesch <michael.riesch@wolfvision.net>
>>>> Signed-off-by: Michael Riesch <michael.riesch@collabora.com>
>>>> ---
>>>>  drivers/media/platform/rockchip/rkcif/Makefile     |   3 +
>>>>  .../rockchip/rkcif/rkcif-mipi-csi-receiver.c       | 731 +++++++++++++++++++++
>>>>  2 files changed, 734 insertions(+)
>>>>
>>>> diff --git a/drivers/media/platform/rockchip/rkcif/Makefile b/drivers/media/platform/rockchip/rkcif/Makefile
>>>> index 818424972c7b..a5c18a45c213 100644
>>>> --- a/drivers/media/platform/rockchip/rkcif/Makefile
>>>> +++ b/drivers/media/platform/rockchip/rkcif/Makefile
>>>> @@ -5,3 +5,6 @@ rockchip-cif-objs += rkcif-dev.o \
>>>>  	rkcif-capture-mipi.o \
>>>>  	rkcif-interface.o \
>>>>  	rkcif-stream.o
>>>> +
>>>> +obj-$(CONFIG_VIDEO_ROCKCHIP_CIF) += rockchip-mipi-csi.o
>>>> +rockchip-mipi-csi-objs += rkcif-mipi-csi-receiver.o
>>>> diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-mipi-csi-receiver.c b/drivers/media/platform/rockchip/rkcif/rkcif-mipi-csi-receiver.c
>>>> new file mode 100644
>>>> index 000000000000..81489f70490f
>>>> --- /dev/null
>>>> +++ b/drivers/media/platform/rockchip/rkcif/rkcif-mipi-csi-receiver.c
>>>> @@ -0,0 +1,731 @@
>>>> +// SPDX-License-Identifier: GPL-2.0
>>>> +/*
>>>> + * Rockchip MIPI CSI-2 Receiver Driver
>>>> + *
>>>> + * Copyright (C) 2019 Rockchip Electronics Co., Ltd.
>>>> + * Copyright (C) 2025 Michael Riesch <michael.riesch@wolfvision.net>
>>>> + */
>>>> +
>>>> +#include <linux/clk.h>
>>>> +#include <linux/delay.h>
>>>> +#include <linux/io.h>
>>>> +#include <linux/module.h>
>>>> +#include <linux/of.h>
>>>> +#include <linux/of_graph.h>
>>>> +#include <linux/of_platform.h>
>>>> +#include <linux/phy/phy.h>
>>>> +#include <linux/platform_device.h>
>>>> +#include <linux/pm_runtime.h>
>>>> +#include <linux/reset.h>
>>>> +
>>>> +#include <media/mipi-csi2.h>
>>>> +#include <media/v4l2-ctrls.h>
>>>> +#include <media/v4l2-fwnode.h>
>>>> +#include <media/v4l2-subdev.h>
>>>> +
>>>> +#define CSI2HOST_N_LANES     0x04
>>>> +#define CSI2HOST_CSI2_RESETN 0x10
>>>> +#define CSI2HOST_PHY_STATE   0x14
>>>> +#define CSI2HOST_ERR1	     0x20
>>>> +#define CSI2HOST_ERR2	     0x24
>>>> +#define CSI2HOST_MSK1	     0x28
>>>> +#define CSI2HOST_MSK2	     0x2c
>>>> +#define CSI2HOST_CONTROL     0x40
>>>
>>> I'm trying to get to the bottom of the CSI-2 RX integration questions
>>> for the RK3568. Some of the registers here seem to the CSI2RX_1C00 block
>>> as documented starting on page 1059 of the RK3568 TRM (revision 1.1),
>>> but they're not an exact match. The control register, in particular,
>>> doesn't match at all. Where is this CSI-2 receiver documented in the TRM
>>> ?
>>
>> That would be in Chapter 27 of the RK3568 TRM: "MIPI CSI HOST"
>>
>> The registers are at 0xfdfb0000 "CSI_RX_CTRL1", cf. Chapter 1 "System
>> Overview".
>>
>> Naturally, this is quite confusing, as there is a "CSI2RX" whose
>> registers are embedded in the ISP register map.
>>
>> "MIPI CSI HOST" and "CSI2RX" seem to be different IP cores but perform
>> the same job in principle. CSI2RX seems to have additional features
>> related to writing raw data into memory and to HDR, though.
>>
>> Hope that helps!
> 
> It does, thank you. I wonder how I missed that.
> 
> The IP seems very similar to other CSI-2 receivers already supported
> upstream, see drivers/media/platform/raspberrypi/rp1-cfe/dphy.c,
> drivers/media/platform/renesas/rcar-csi2.c and
> drivers/staging/media/imx/imx6-mipi-csi2.c. Those drivers combine
> support for the CSI-2 RX and D-PHY, explaining some of the differences
> (and in the case of the rcar-csi2 driver, I think it also combines
> support for different CSI-2 RX in a single driver).

Before I started writing the driver under discussion, I did scan the
existing drivers, but could not see any similarities to rcar-csi2 and
rp1-cfe/dphy.c (and I still don't see them), respectively (but maybe
those two are the same).
Unfortunately, I forgot to look into staging. Hm. Indeed it resembles
the imx6-mipi-csi2.c driver, at least judging by the register layout.

According to Kever, while a Synopsys IP core was used in older SoCs, the
newer SoCs feature an IP core developed by Rockchip. Probably the
register map was recycled but slightly modified.

> Is there something we could do to avoid adding a 4th driver for the same
> IP core family (from Synopsys or Cadence I assume) ? It could possibly
> be done on top of this series to avoid delaying VICAP support, but I'd
> really like to get this sorted out.

Based on my current level of knowledge I wouldn't be comfortable with
placing this driver in media/platform/synopsys/something. If you all
advise me to do so, I will comply, though.

I am not too keen on digging out a quite dated staging driver and basing
the current efforts on that one.

We could, however, move this driver to
media/platform/rockchip/mipi-csi-receiver/ (name to be bikeshedded, open
to suggestions). It is a separate kernel module already anyway, and
maybe this way it is more visible to the next person developing a driver
for a similar IP core. Sound reasonable?

Best regards,
Michael

> 
>>>> +
>>>> +#define SW_CPHY_EN(x)	     ((x) << 0)
>>>> +#define SW_DSI_EN(x)	     ((x) << 4)
>>>> +#define SW_DATATYPE_FS(x)    ((x) << 8)
>>>> +#define SW_DATATYPE_FE(x)    ((x) << 14)
>>>> +#define SW_DATATYPE_LS(x)    ((x) << 20)
>>>> +#define SW_DATATYPE_LE(x)    ((x) << 26)
>>>> +
>>>> +#define RKCIF_CSI_CLKS_MAX   1
>>>> +
>>>> +enum {
>>>> +	RKCIF_CSI_PAD_SINK,
>>>> +	RKCIF_CSI_PAD_SRC,
>>>> +	RKCIF_CSI_PAD_MAX,
>>>> +};
>>>> +
>>>> +struct rkcif_csi_format {
>>>> +	u32 code;
>>>> +	u8 depth;
>>>> +	u8 csi_dt;
>>>> +};
>>>> +
>>>> +struct rkcif_csi_device {
>>>> +	struct device *dev;
>>>> +
>>>> +	void __iomem *base_addr;
>>>> +	struct clk_bulk_data *clks;
>>>> +	unsigned int clks_num;
>>>> +	struct phy *phy;
>>>> +	struct reset_control *reset;
>>>> +
>>>> +	const struct rkcif_csi_format *formats;
>>>> +	unsigned int formats_num;
>>>> +
>>>> +	struct media_pad pads[RKCIF_CSI_PAD_MAX];
>>>> +	struct v4l2_async_notifier notifier;
>>>> +	struct v4l2_fwnode_endpoint vep;
>>>> +	struct v4l2_subdev sd;
>>>> +
>>>> +	struct v4l2_subdev *source_sd;
>>>> +	u32 source_pad;
>>>> +};
>>>> +
>>>> +static const struct v4l2_mbus_framefmt default_format = {
>>>> +	.width = 3840,
>>>> +	.height = 2160,
>>>> +	.code = MEDIA_BUS_FMT_SRGGB10_1X10,
>>>> +	.field = V4L2_FIELD_NONE,
>>>> +	.colorspace = V4L2_COLORSPACE_RAW,
>>>> +	.ycbcr_enc = V4L2_YCBCR_ENC_601,
>>>> +	.quantization = V4L2_QUANTIZATION_FULL_RANGE,
>>>> +	.xfer_func = V4L2_XFER_FUNC_NONE,
>>>> +};
>>>> +
>>>> +static const struct rkcif_csi_format formats[] = {
>>>> +	/* YUV formats */
>>>> +	{
>>>> +		.code = MEDIA_BUS_FMT_YUYV8_1X16,
>>>> +		.depth = 16,
>>>> +		.csi_dt = MIPI_CSI2_DT_YUV422_8B,
>>>> +	},
>>>> +	{
>>>> +		.code = MEDIA_BUS_FMT_UYVY8_1X16,
>>>> +		.depth = 16,
>>>> +		.csi_dt = MIPI_CSI2_DT_YUV422_8B,
>>>> +	},
>>>> +	{
>>>> +		.code = MEDIA_BUS_FMT_YVYU8_1X16,
>>>> +		.depth = 16,
>>>> +		.csi_dt = MIPI_CSI2_DT_YUV422_8B,
>>>> +	},
>>>> +	{
>>>> +		.code = MEDIA_BUS_FMT_VYUY8_1X16,
>>>> +		.depth = 16,
>>>> +		.csi_dt = MIPI_CSI2_DT_YUV422_8B,
>>>> +	},
>>>> +	/* RGB formats */
>>>> +	{
>>>> +		.code = MEDIA_BUS_FMT_RGB888_1X24,
>>>> +		.depth = 24,
>>>> +		.csi_dt = MIPI_CSI2_DT_RGB888,
>>>> +	},
>>>> +	{
>>>> +		.code = MEDIA_BUS_FMT_BGR888_1X24,
>>>> +		.depth = 24,
>>>> +		.csi_dt = MIPI_CSI2_DT_RGB888,
>>>> +	},
>>>> +	/* Bayer formats */
>>>> +	{
>>>> +		.code = MEDIA_BUS_FMT_SBGGR8_1X8,
>>>> +		.depth = 8,
>>>> +		.csi_dt = MIPI_CSI2_DT_RAW8,
>>>> +	},
>>>> +	{
>>>> +		.code = MEDIA_BUS_FMT_SGBRG8_1X8,
>>>> +		.depth = 8,
>>>> +		.csi_dt = MIPI_CSI2_DT_RAW8,
>>>> +	},
>>>> +	{
>>>> +		.code = MEDIA_BUS_FMT_SGRBG8_1X8,
>>>> +		.depth = 8,
>>>> +		.csi_dt = MIPI_CSI2_DT_RAW8,
>>>> +	},
>>>> +	{
>>>> +		.code = MEDIA_BUS_FMT_SRGGB8_1X8,
>>>> +		.depth = 8,
>>>> +		.csi_dt = MIPI_CSI2_DT_RAW8,
>>>> +	},
>>>> +	{
>>>> +		.code = MEDIA_BUS_FMT_SBGGR10_1X10,
>>>> +		.depth = 10,
>>>> +		.csi_dt = MIPI_CSI2_DT_RAW10,
>>>> +	},
>>>> +	{
>>>> +		.code = MEDIA_BUS_FMT_SGBRG10_1X10,
>>>> +		.depth = 10,
>>>> +		.csi_dt = MIPI_CSI2_DT_RAW10,
>>>> +	},
>>>> +	{
>>>> +		.code = MEDIA_BUS_FMT_SGRBG10_1X10,
>>>> +		.depth = 10,
>>>> +		.csi_dt = MIPI_CSI2_DT_RAW10,
>>>> +	},
>>>> +	{
>>>> +		.code = MEDIA_BUS_FMT_SRGGB10_1X10,
>>>> +		.depth = 10,
>>>> +		.csi_dt = MIPI_CSI2_DT_RAW10,
>>>> +	},
>>>> +	{
>>>> +		.code = MEDIA_BUS_FMT_SBGGR12_1X12,
>>>> +		.depth = 12,
>>>> +		.csi_dt = MIPI_CSI2_DT_RAW12,
>>>> +	},
>>>> +	{
>>>> +		.code = MEDIA_BUS_FMT_SGBRG12_1X12,
>>>> +		.depth = 12,
>>>> +		.csi_dt = MIPI_CSI2_DT_RAW12,
>>>> +	},
>>>> +	{
>>>> +		.code = MEDIA_BUS_FMT_SGRBG12_1X12,
>>>> +		.depth = 12,
>>>> +		.csi_dt = MIPI_CSI2_DT_RAW12,
>>>> +	},
>>>> +	{
>>>> +		.code = MEDIA_BUS_FMT_SRGGB12_1X12,
>>>> +		.depth = 12,
>>>> +		.csi_dt = MIPI_CSI2_DT_RAW12,
>>>> +	},
>>>> +};
>>>> +
>>>> +static inline struct rkcif_csi_device *to_rkcif_csi(struct v4l2_subdev *sd)
>>>> +{
>>>> +	return container_of(sd, struct rkcif_csi_device, sd);
>>>> +}
>>>> +
>>>> +static inline __maybe_unused void
>>>> +rkcif_csi_write(struct rkcif_csi_device *csi_dev, unsigned int addr, u32 val)
>>>> +{
>>>> +	writel(val, csi_dev->base_addr + addr);
>>>> +}
>>>> +
>>>> +static inline __maybe_unused u32
>>>> +rkcif_csi_read(struct rkcif_csi_device *csi_dev, unsigned int addr)
>>>> +{
>>>> +	return readl(csi_dev->base_addr + addr);
>>>> +}
>>>> +
>>>> +static const struct rkcif_csi_format *
>>>> +rkcif_csi_find_format(struct rkcif_csi_device *csi_dev, u32 mbus_code)
>>>> +{
>>>> +	const struct rkcif_csi_format *format;
>>>> +
>>>> +	WARN_ON(csi_dev->formats_num == 0);
>>>> +
>>>> +	for (int i = 0; i < csi_dev->formats_num; i++) {
>>>> +		format = &csi_dev->formats[i];
>>>> +		if (format->code == mbus_code)
>>>> +			return format;
>>>> +	}
>>>> +
>>>> +	return NULL;
>>>> +}
>>>> +
>>>> +static int rkcif_csi_start(struct rkcif_csi_device *csi_dev)
>>>> +{
>>>> +	enum v4l2_mbus_type bus_type = csi_dev->vep.bus_type;
>>>> +	union phy_configure_opts opts;
>>>> +	s64 link_freq;
>>>> +	u32 lanes = csi_dev->vep.bus.mipi_csi2.num_data_lanes;
>>>> +	u32 control = 0;
>>>> +
>>>> +	if (lanes < 1 || lanes > 4)
>>>> +		return -EINVAL;
>>>> +
>>>> +	/* set mult and div to 0, thus completely rely on V4L2_CID_LINK_FREQ */
>>>> +	link_freq = v4l2_get_link_freq(csi_dev->source_sd->ctrl_handler, 0, 0);
>>>> +	if (link_freq <= 0)
>>>> +		return -EINVAL;
>>>> +
>>>> +	if (bus_type == V4L2_MBUS_CSI2_DPHY) {
>>>> +		struct phy_configure_opts_mipi_dphy *cfg = &opts.mipi_dphy;
>>>> +
>>>> +		phy_mipi_dphy_get_default_config_for_hsclk(link_freq * 2, lanes,
>>>> +							   cfg);
>>>> +		phy_set_mode(csi_dev->phy, PHY_MODE_MIPI_DPHY);
>>>> +		phy_configure(csi_dev->phy, &opts);
>>>> +
>>>> +		control |= SW_CPHY_EN(0);
>>>> +
>>>> +	} else if (bus_type == V4L2_MBUS_CSI2_CPHY) {
>>>> +		control |= SW_CPHY_EN(1);
>>>> +
>>>> +		/* TODO: implement CPHY configuration */
>>>> +	} else {
>>>> +		return -EINVAL;
>>>> +	}
>>>> +
>>>> +	control |= SW_DATATYPE_FS(0x00) | SW_DATATYPE_FE(0x01) |
>>>> +		   SW_DATATYPE_LS(0x02) | SW_DATATYPE_LE(0x03);
>>>> +
>>>> +	rkcif_csi_write(csi_dev, CSI2HOST_N_LANES, lanes - 1);
>>>> +	rkcif_csi_write(csi_dev, CSI2HOST_CONTROL, control);
>>>> +	rkcif_csi_write(csi_dev, CSI2HOST_CSI2_RESETN, 1);
>>>> +
>>>> +	phy_power_on(csi_dev->phy);
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +static void rkcif_csi_stop(struct rkcif_csi_device *csi_dev)
>>>> +{
>>>> +	phy_power_off(csi_dev->phy);
>>>> +
>>>> +	rkcif_csi_write(csi_dev, CSI2HOST_CSI2_RESETN, 0);
>>>> +	rkcif_csi_write(csi_dev, CSI2HOST_MSK1, ~0);
>>>> +	rkcif_csi_write(csi_dev, CSI2HOST_MSK2, ~0);
>>>> +}
>>>> +
>>>> +static const struct media_entity_operations rkcif_csi_media_ops = {
>>>> +	.link_validate = v4l2_subdev_link_validate,
>>>> +};
>>>> +
>>>> +static int rkcif_csi_enum_mbus_code(struct v4l2_subdev *sd,
>>>> +				    struct v4l2_subdev_state *sd_state,
>>>> +				    struct v4l2_subdev_mbus_code_enum *code)
>>>> +{
>>>> +	struct rkcif_csi_device *csi_dev = to_rkcif_csi(sd);
>>>> +
>>>> +	if (code->pad == RKCIF_CSI_PAD_SRC) {
>>>> +		const struct v4l2_mbus_framefmt *sink_fmt;
>>>> +
>>>> +		if (code->index)
>>>> +			return -EINVAL;
>>>> +
>>>> +		sink_fmt = v4l2_subdev_state_get_format(sd_state,
>>>> +							RKCIF_CSI_PAD_SINK);
>>>> +		code->code = sink_fmt->code;
>>>> +
>>>> +		return 0;
>>>> +	} else if (code->pad == RKCIF_CSI_PAD_SINK) {
>>>> +		if (code->index > csi_dev->formats_num)
>>>> +			return -EINVAL;
>>>> +
>>>> +		code->code = csi_dev->formats[code->index].code;
>>>> +		return 0;
>>>> +	}
>>>> +
>>>> +	return -EINVAL;
>>>> +}
>>>> +
>>>> +static int rkcif_csi_set_fmt(struct v4l2_subdev *sd,
>>>> +			     struct v4l2_subdev_state *state,
>>>> +			     struct v4l2_subdev_format *format)
>>>> +{
>>>> +	struct rkcif_csi_device *csi_dev = to_rkcif_csi(sd);
>>>> +	const struct rkcif_csi_format *fmt;
>>>> +	struct v4l2_mbus_framefmt *sink, *src;
>>>> +
>>>> +	/* the format on the source pad always matches the sink pad */
>>>> +	if (format->pad == RKCIF_CSI_PAD_SRC)
>>>> +		return v4l2_subdev_get_fmt(sd, state, format);
>>>> +
>>>> +	sink = v4l2_subdev_state_get_format(state, format->pad, format->stream);
>>>> +	if (!sink)
>>>> +		return -EINVAL;
>>>> +
>>>> +	fmt = rkcif_csi_find_format(csi_dev, format->format.code);
>>>> +	if (fmt)
>>>> +		*sink = format->format;
>>>> +	else
>>>> +		*sink = default_format;
>>>> +
>>>> +	/* propagate the format to the source pad */
>>>> +	src = v4l2_subdev_state_get_opposite_stream_format(state, format->pad,
>>>> +							   format->stream);
>>>> +	if (!src)
>>>> +		return -EINVAL;
>>>> +
>>>> +	*src = *sink;
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +static int rkcif_csi_set_routing(struct v4l2_subdev *sd,
>>>> +				 struct v4l2_subdev_state *state,
>>>> +				 enum v4l2_subdev_format_whence which,
>>>> +				 struct v4l2_subdev_krouting *routing)
>>>> +{
>>>> +	int ret;
>>>> +
>>>> +	ret = v4l2_subdev_routing_validate(sd, routing,
>>>> +					   V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
>>>> +	if (ret)
>>>> +		return ret;
>>>> +
>>>> +	ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing,
>>>> +					       &default_format);
>>>> +	if (ret)
>>>> +		return ret;
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +static int rkcif_csi_enable_streams(struct v4l2_subdev *sd,
>>>> +				    struct v4l2_subdev_state *state, u32 pad,
>>>> +				    u64 streams_mask)
>>>> +{
>>>> +	struct rkcif_csi_device *csi_dev = to_rkcif_csi(sd);
>>>> +	struct v4l2_subdev *remote_sd;
>>>> +	struct media_pad *sink_pad, *remote_pad;
>>>> +	struct device *dev = csi_dev->dev;
>>>> +	u64 mask;
>>>> +	int ret;
>>>> +
>>>> +	sink_pad = &sd->entity.pads[RKCIF_CSI_PAD_SINK];
>>>> +	remote_pad = media_pad_remote_pad_first(sink_pad);
>>>> +	remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity);
>>>> +
>>>> +	mask = v4l2_subdev_state_xlate_streams(state, RKCIF_CSI_PAD_SINK,
>>>> +					       RKCIF_CSI_PAD_SRC,
>>>> +					       &streams_mask);
>>>> +
>>>> +	ret = pm_runtime_resume_and_get(dev);
>>>> +	if (ret)
>>>> +		goto err;
>>>> +
>>>> +	ret = rkcif_csi_start(csi_dev);
>>>> +	if (ret) {
>>>> +		dev_err(dev, "failed to enable CSI hardware\n");
>>>> +		goto err_pm_runtime_put;
>>>> +	}
>>>> +
>>>> +	ret = v4l2_subdev_enable_streams(remote_sd, remote_pad->index, mask);
>>>> +	if (ret)
>>>> +		goto err_csi_stop;
>>>> +
>>>> +	return 0;
>>>> +
>>>> +err_csi_stop:
>>>> +	rkcif_csi_stop(csi_dev);
>>>> +err_pm_runtime_put:
>>>> +	pm_runtime_put_sync(dev);
>>>> +err:
>>>> +	return ret;
>>>> +}
>>>> +
>>>> +static int rkcif_csi_disable_streams(struct v4l2_subdev *sd,
>>>> +				     struct v4l2_subdev_state *state, u32 pad,
>>>> +				     u64 streams_mask)
>>>> +{
>>>> +	struct rkcif_csi_device *csi_dev = to_rkcif_csi(sd);
>>>> +	struct v4l2_subdev *remote_sd;
>>>> +	struct media_pad *sink_pad, *remote_pad;
>>>> +	struct device *dev = csi_dev->dev;
>>>> +	u64 mask;
>>>> +	int ret;
>>>> +
>>>> +	sink_pad = &sd->entity.pads[RKCIF_CSI_PAD_SINK];
>>>> +	remote_pad = media_pad_remote_pad_first(sink_pad);
>>>> +	remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity);
>>>> +
>>>> +	mask = v4l2_subdev_state_xlate_streams(state, RKCIF_CSI_PAD_SINK,
>>>> +					       RKCIF_CSI_PAD_SRC,
>>>> +					       &streams_mask);
>>>> +
>>>> +	ret = v4l2_subdev_disable_streams(remote_sd, remote_pad->index, mask);
>>>> +
>>>> +	rkcif_csi_stop(csi_dev);
>>>> +
>>>> +	pm_runtime_mark_last_busy(dev);
>>>> +	pm_runtime_put_autosuspend(dev);
>>>> +
>>>> +	return ret;
>>>> +}
>>>> +
>>>> +static const struct v4l2_subdev_pad_ops rkcif_csi_pad_ops = {
>>>> +	.enum_mbus_code = rkcif_csi_enum_mbus_code,
>>>> +	.get_fmt = v4l2_subdev_get_fmt,
>>>> +	.set_fmt = rkcif_csi_set_fmt,
>>>> +	.set_routing = rkcif_csi_set_routing,
>>>> +	.enable_streams = rkcif_csi_enable_streams,
>>>> +	.disable_streams = rkcif_csi_disable_streams,
>>>> +};
>>>> +
>>>> +static const struct v4l2_subdev_ops rkcif_csi_ops = {
>>>> +	.pad = &rkcif_csi_pad_ops,
>>>> +};
>>>> +
>>>> +static int rkcif_csi_init_state(struct v4l2_subdev *sd,
>>>> +				struct v4l2_subdev_state *state)
>>>> +{
>>>> +	struct v4l2_subdev_route routes[] = {
>>>> +		{
>>>> +			.sink_pad = RKCIF_CSI_PAD_SINK,
>>>> +			.sink_stream = 0,
>>>> +			.source_pad = RKCIF_CSI_PAD_SRC,
>>>> +			.source_stream = 0,
>>>> +			.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
>>>> +		},
>>>> +	};
>>>> +	struct v4l2_subdev_krouting routing = {
>>>> +		.len_routes = ARRAY_SIZE(routes),
>>>> +		.num_routes = ARRAY_SIZE(routes),
>>>> +		.routes = routes,
>>>> +	};
>>>> +	int ret;
>>>> +
>>>> +	ret = v4l2_subdev_set_routing_with_fmt(sd, state, &routing,
>>>> +					       &default_format);
>>>> +
>>>> +	return ret;
>>>> +}
>>>> +
>>>> +static const struct v4l2_subdev_internal_ops rkcif_csi_internal_ops = {
>>>> +	.init_state = rkcif_csi_init_state,
>>>> +};
>>>> +
>>>> +static int rkcif_csi_notifier_bound(struct v4l2_async_notifier *notifier,
>>>> +				    struct v4l2_subdev *sd,
>>>> +				    struct v4l2_async_connection *asd)
>>>> +{
>>>> +	struct rkcif_csi_device *csi_dev =
>>>> +		container_of(notifier, struct rkcif_csi_device, notifier);
>>>> +	int source_pad;
>>>> +
>>>> +	source_pad = media_entity_get_fwnode_pad(&sd->entity, sd->fwnode,
>>>> +						 MEDIA_PAD_FL_SOURCE);
>>>> +	if (source_pad < 0) {
>>>> +		dev_err(csi_dev->dev, "failed to find source pad for %s\n",
>>>> +			sd->name);
>>>> +		return source_pad;
>>>> +	}
>>>> +
>>>> +	csi_dev->source_sd = sd;
>>>> +	csi_dev->source_pad = source_pad;
>>>> +
>>>> +	return media_create_pad_link(&sd->entity, source_pad,
>>>> +				     &csi_dev->sd.entity, RKCIF_CSI_PAD_SINK,
>>>> +				     MEDIA_LNK_FL_ENABLED);
>>>> +}
>>>> +
>>>> +static const struct v4l2_async_notifier_operations rkcif_csi_notifier_ops = {
>>>> +	.bound = rkcif_csi_notifier_bound,
>>>> +};
>>>> +
>>>> +static int rkcif_csi_register_notifier(struct rkcif_csi_device *csi_dev)
>>>> +{
>>>> +	struct v4l2_async_connection *asd;
>>>> +	struct v4l2_async_notifier *ntf = &csi_dev->notifier;
>>>> +	struct v4l2_fwnode_endpoint *vep = &csi_dev->vep;
>>>> +	struct v4l2_subdev *sd = &csi_dev->sd;
>>>> +	struct device *dev = csi_dev->dev;
>>>> +	struct fwnode_handle *ep;
>>>> +	int ret = 0;
>>>> +
>>>> +	ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, 0);
>>>> +	if (!ep)
>>>> +		return dev_err_probe(dev, -ENODEV, "failed to get endpoint\n");
>>>> +
>>>> +	vep->bus_type = V4L2_MBUS_UNKNOWN;
>>>> +	ret = v4l2_fwnode_endpoint_parse(ep, vep);
>>>> +	if (ret) {
>>>> +		ret = dev_err_probe(dev, ret, "failed to parse endpoint\n");
>>>> +		goto out;
>>>> +	}
>>>> +
>>>> +	if (vep->bus_type != V4L2_MBUS_CSI2_DPHY &&
>>>> +	    vep->bus_type != V4L2_MBUS_CSI2_CPHY) {
>>>> +		ret = dev_err_probe(dev, -EINVAL,
>>>> +				    "invalid bus type of endpoint\n");
>>>> +		goto out;
>>>> +	}
>>>> +
>>>> +	v4l2_async_subdev_nf_init(ntf, sd);
>>>> +	ntf->ops = &rkcif_csi_notifier_ops;
>>>> +
>>>> +	asd = v4l2_async_nf_add_fwnode_remote(ntf, ep,
>>>> +					      struct v4l2_async_connection);
>>>> +	if (IS_ERR(asd)) {
>>>> +		ret = PTR_ERR(asd);
>>>> +		goto err_nf_cleanup;
>>>> +	}
>>>> +
>>>> +	ret = v4l2_async_nf_register(ntf);
>>>> +	if (ret) {
>>>> +		ret = dev_err_probe(dev, ret, "failed to register notifier\n");
>>>> +		goto err_nf_cleanup;
>>>> +	}
>>>> +
>>>> +	goto out;
>>>> +
>>>> +err_nf_cleanup:
>>>> +	v4l2_async_nf_cleanup(ntf);
>>>> +out:
>>>> +	fwnode_handle_put(ep);
>>>> +	return ret;
>>>> +}
>>>> +
>>>> +static int rkcif_csi_register(struct rkcif_csi_device *csi_dev)
>>>> +{
>>>> +	struct media_pad *pads = csi_dev->pads;
>>>> +	struct v4l2_subdev *sd = &csi_dev->sd;
>>>> +	int ret;
>>>> +
>>>> +	ret = rkcif_csi_register_notifier(csi_dev);
>>>> +	if (ret)
>>>> +		goto err;
>>>> +
>>>> +	v4l2_subdev_init(sd, &rkcif_csi_ops);
>>>> +	sd->dev = csi_dev->dev;
>>>> +	sd->entity.ops = &rkcif_csi_media_ops;
>>>> +	sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
>>>> +	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS;
>>>> +	sd->internal_ops = &rkcif_csi_internal_ops;
>>>> +	sd->owner = THIS_MODULE;
>>>> +	snprintf(sd->name, sizeof(sd->name), "rockchip-mipi-csi %s",
>>>> +		 dev_name(csi_dev->dev));
>>>> +
>>>> +	pads[RKCIF_CSI_PAD_SINK].flags = MEDIA_PAD_FL_SINK |
>>>> +					 MEDIA_PAD_FL_MUST_CONNECT;
>>>> +	pads[RKCIF_CSI_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE;
>>>> +	ret = media_entity_pads_init(&sd->entity, RKCIF_CSI_PAD_MAX, pads);
>>>> +	if (ret)
>>>> +		goto err_notifier_unregister;
>>>> +
>>>> +	ret = v4l2_subdev_init_finalize(sd);
>>>> +	if (ret)
>>>> +		goto err_entity_cleanup;
>>>> +
>>>> +	ret = v4l2_async_register_subdev(sd);
>>>> +	if (ret) {
>>>> +		dev_err(sd->dev, "failed to register CSI subdev\n");
>>>> +		goto err_subdev_cleanup;
>>>> +	}
>>>> +
>>>> +	return 0;
>>>> +
>>>> +err_subdev_cleanup:
>>>> +	v4l2_subdev_cleanup(sd);
>>>> +err_entity_cleanup:
>>>> +	media_entity_cleanup(&sd->entity);
>>>> +err_notifier_unregister:
>>>> +	v4l2_async_nf_unregister(&csi_dev->notifier);
>>>> +	v4l2_async_nf_cleanup(&csi_dev->notifier);
>>>> +err:
>>>> +	return ret;
>>>> +}
>>>> +
>>>> +static void rkcif_csi_unregister(struct rkcif_csi_device *csi_dev)
>>>> +{
>>>> +	struct v4l2_subdev *sd = &csi_dev->sd;
>>>> +
>>>> +	v4l2_async_unregister_subdev(sd);
>>>> +	v4l2_subdev_cleanup(sd);
>>>> +	media_entity_cleanup(&sd->entity);
>>>> +	v4l2_async_nf_unregister(&csi_dev->notifier);
>>>> +	v4l2_async_nf_cleanup(&csi_dev->notifier);
>>>> +}
>>>> +
>>>> +static const struct of_device_id rkcif_csi_of_match[] = {
>>>> +	{
>>>> +		.compatible = "rockchip,rk3568-mipi-csi",
>>>> +	},
>>>> +	{}
>>>> +};
>>>> +MODULE_DEVICE_TABLE(of, rkcif_csi_of_match);
>>>> +
>>>> +static int rkcif_csi_probe(struct platform_device *pdev)
>>>> +{
>>>> +	struct device *dev = &pdev->dev;
>>>> +	struct rkcif_csi_device *csi_dev;
>>>> +	int ret;
>>>> +
>>>> +	csi_dev = devm_kzalloc(dev, sizeof(*csi_dev), GFP_KERNEL);
>>>> +	if (!csi_dev)
>>>> +		return -ENOMEM;
>>>> +	csi_dev->dev = dev;
>>>> +	dev_set_drvdata(dev, csi_dev);
>>>> +
>>>> +	csi_dev->base_addr = devm_platform_ioremap_resource(pdev, 0);
>>>> +	if (IS_ERR(csi_dev->base_addr))
>>>> +		return PTR_ERR(csi_dev->base_addr);
>>>> +
>>>> +	ret = devm_clk_bulk_get_all(dev, &csi_dev->clks);
>>>> +	if (ret != RKCIF_CSI_CLKS_MAX)
>>>> +		return dev_err_probe(dev, -ENODEV, "failed to get clocks\n");
>>>> +	csi_dev->clks_num = ret;
>>>> +
>>>> +	csi_dev->phy = devm_phy_get(dev, NULL);
>>>> +	if (IS_ERR(csi_dev->phy))
>>>> +		return dev_err_probe(dev, PTR_ERR(csi_dev->phy),
>>>> +				     "failed to get MIPI CSI PHY\n");
>>>> +
>>>> +	csi_dev->reset = devm_reset_control_array_get_exclusive(dev);
>>>> +	if (IS_ERR(csi_dev->reset))
>>>> +		return dev_err_probe(dev, PTR_ERR(csi_dev->reset),
>>>> +				     "failed to get reset\n");
>>>> +
>>>> +	csi_dev->formats = formats;
>>>> +	csi_dev->formats_num = ARRAY_SIZE(formats);
>>>> +
>>>> +	pm_runtime_enable(dev);
>>>> +
>>>> +	ret = phy_init(csi_dev->phy);
>>>> +	if (ret) {
>>>> +		ret = dev_err_probe(dev, ret,
>>>> +				    "failed to initialize MIPI CSI PHY\n");
>>>> +		goto err_pm_runtime_disable;
>>>> +	}
>>>> +
>>>> +	ret = rkcif_csi_register(csi_dev);
>>>> +	if (ret)
>>>> +		goto err_phy_exit;
>>>> +
>>>> +	return 0;
>>>> +
>>>> +err_phy_exit:
>>>> +	phy_exit(csi_dev->phy);
>>>> +err_pm_runtime_disable:
>>>> +	pm_runtime_disable(dev);
>>>> +	return ret;
>>>> +}
>>>> +
>>>> +static void rkcif_csi_remove(struct platform_device *pdev)
>>>> +{
>>>> +	struct rkcif_csi_device *csi_dev = platform_get_drvdata(pdev);
>>>> +	struct device *dev = &pdev->dev;
>>>> +
>>>> +	rkcif_csi_unregister(csi_dev);
>>>> +	phy_exit(csi_dev->phy);
>>>> +	pm_runtime_disable(dev);
>>>> +}
>>>> +
>>>> +static int rkcif_csi_runtime_suspend(struct device *dev)
>>>> +{
>>>> +	struct rkcif_csi_device *csi_dev = dev_get_drvdata(dev);
>>>> +
>>>> +	clk_bulk_disable_unprepare(csi_dev->clks_num, csi_dev->clks);
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +static int rkcif_csi_runtime_resume(struct device *dev)
>>>> +{
>>>> +	struct rkcif_csi_device *csi_dev = dev_get_drvdata(dev);
>>>> +	int ret;
>>>> +
>>>> +	reset_control_assert(csi_dev->reset);
>>>> +	udelay(5);
>>>> +	reset_control_deassert(csi_dev->reset);
>>>> +
>>>> +	ret = clk_bulk_prepare_enable(csi_dev->clks_num, csi_dev->clks);
>>>> +	if (ret) {
>>>> +		dev_err(dev, "failed to enable clocks\n");
>>>> +		return ret;
>>>> +	}
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +static const struct dev_pm_ops rkcif_csi_pm_ops = {
>>>> +	.runtime_suspend = rkcif_csi_runtime_suspend,
>>>> +	.runtime_resume = rkcif_csi_runtime_resume,
>>>> +};
>>>> +
>>>> +static struct platform_driver rkcif_csi_drv = {
>>>> +	.driver = {
>>>> +		   .name = "rockchip-mipi-csi",
>>>> +		   .of_match_table = rkcif_csi_of_match,
>>>> +		   .pm = &rkcif_csi_pm_ops,
>>>> +	},
>>>> +	.probe = rkcif_csi_probe,
>>>> +	.remove = rkcif_csi_remove,
>>>> +};
>>>> +module_platform_driver(rkcif_csi_drv);
>>>> +
>>>> +MODULE_DESCRIPTION("Rockchip MIPI CSI-2 Receiver platform driver");
>>>> +MODULE_LICENSE("GPL");
> 


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

* Re: [PATCH v6 07/13] media: rockchip: rkcif: add driver for mipi csi-2 receiver
  2025-05-01  0:29   ` Bryan O'Donoghue
@ 2025-05-06 18:56     ` Michael Riesch
  0 siblings, 0 replies; 34+ messages in thread
From: Michael Riesch @ 2025-05-06 18:56 UTC (permalink / raw)
  To: Bryan O'Donoghue, Mehdi Djait, Maxime Chevallier,
	Théo Lebrun, Gerald Loacker, Thomas Petazzoni,
	Laurent Pinchart, Mauro Carvalho Chehab, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner, Kever Yang,
	Nicolas Dufresne, Sebastian Reichel, Collabora Kernel Team,
	Paul Kocialkowski, Alexander Shiyan, Val Packett, Rob Herring,
	Philipp Zabel, Sakari Ailus
  Cc: linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-rockchip, Michael Riesch

Hi Bryan,

On 5/1/25 02:29, Bryan O'Donoghue wrote:
> On 30/04/2025 10:15, Michael Riesch via B4 Relay wrote:
>> From: Michael Riesch <michael.riesch@wolfvision.net>
>>
>> The Rockchip RK3568 MIPI CSI-2 Receiver is a CSI-2 bridge with one
>> input port and one output port. It receives the data with the help
>> of an external MIPI PHY (C-PHY or D-PHY) and passes it to the
>> Rockchip RK3568 Video Capture (VICAP) block.
>>
>> Add a V4L2 subdevice driver for this unit.
>>
>> Signed-off-by: Michael Riesch <michael.riesch@wolfvision.net>
>> Signed-off-by: Michael Riesch <michael.riesch@collabora.com>
>> ---
>>   drivers/media/platform/rockchip/rkcif/Makefile     |   3 +
>>   .../rockchip/rkcif/rkcif-mipi-csi-receiver.c       | 731 +++++++++++
>> ++++++++++
>>   2 files changed, 734 insertions(+)
>>
>> diff --git a/drivers/media/platform/rockchip/rkcif/Makefile b/drivers/
>> media/platform/rockchip/rkcif/Makefile
>> index 818424972c7b..a5c18a45c213 100644
>> --- a/drivers/media/platform/rockchip/rkcif/Makefile
>> +++ b/drivers/media/platform/rockchip/rkcif/Makefile
>> @@ -5,3 +5,6 @@ rockchip-cif-objs += rkcif-dev.o \
>>       rkcif-capture-mipi.o \
>>       rkcif-interface.o \
>>       rkcif-stream.o
>> +
>> +obj-$(CONFIG_VIDEO_ROCKCHIP_CIF) += rockchip-mipi-csi.o
>> +rockchip-mipi-csi-objs += rkcif-mipi-csi-receiver.o
>> diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-mipi-csi-
>> receiver.c b/drivers/media/platform/rockchip/rkcif/rkcif-mipi-csi-
>> receiver.c
>> new file mode 100644
>> index 000000000000..81489f70490f
>> --- /dev/null
>> +++ b/drivers/media/platform/rockchip/rkcif/rkcif-mipi-csi-receiver.c
>> @@ -0,0 +1,731 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Rockchip MIPI CSI-2 Receiver Driver
>> + *
>> + * Copyright (C) 2019 Rockchip Electronics Co., Ltd.
>> + * Copyright (C) 2025 Michael Riesch <michael.riesch@wolfvision.net>
>> + */
>> +
>> +#include <linux/clk.h>
>> +#include <linux/delay.h>
>> +#include <linux/io.h>
>> +#include <linux/module.h>
>> +#include <linux/of.h>
>> +#include <linux/of_graph.h>
>> +#include <linux/of_platform.h>
>> +#include <linux/phy/phy.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/pm_runtime.h>
>> +#include <linux/reset.h>
>> +
>> +#include <media/mipi-csi2.h>
>> +#include <media/v4l2-ctrls.h>
>> +#include <media/v4l2-fwnode.h>
>> +#include <media/v4l2-subdev.h>
>> +
>> +#define CSI2HOST_N_LANES     0x04
>> +#define CSI2HOST_CSI2_RESETN 0x10
>> +#define CSI2HOST_PHY_STATE   0x14
>> +#define CSI2HOST_ERR1         0x20
>> +#define CSI2HOST_ERR2         0x24
>> +#define CSI2HOST_MSK1         0x28
>> +#define CSI2HOST_MSK2         0x2c
>> +#define CSI2HOST_CONTROL     0x40
>> +
>> +#define SW_CPHY_EN(x)         ((x) << 0)
>> +#define SW_DSI_EN(x)         ((x) << 4)
>> +#define SW_DATATYPE_FS(x)    ((x) << 8)
>> +#define SW_DATATYPE_FE(x)    ((x) << 14)
>> +#define SW_DATATYPE_LS(x)    ((x) << 20)
>> +#define SW_DATATYPE_LE(x)    ((x) << 26)
>> +
>> +#define RKCIF_CSI_CLKS_MAX   1
>> +
>> +enum {
>> +    RKCIF_CSI_PAD_SINK,
>> +    RKCIF_CSI_PAD_SRC,
>> +    RKCIF_CSI_PAD_MAX,
>> +};
>> +
>> +struct rkcif_csi_format {
>> +    u32 code;
>> +    u8 depth;
>> +    u8 csi_dt;
>> +};
>> +
>> +struct rkcif_csi_device {
>> +    struct device *dev;
>> +
>> +    void __iomem *base_addr;
>> +    struct clk_bulk_data *clks;
>> +    unsigned int clks_num;
>> +    struct phy *phy;
>> +    struct reset_control *reset;
>> +
>> +    const struct rkcif_csi_format *formats;
>> +    unsigned int formats_num;
>> +
>> +    struct media_pad pads[RKCIF_CSI_PAD_MAX];
>> +    struct v4l2_async_notifier notifier;
>> +    struct v4l2_fwnode_endpoint vep;
>> +    struct v4l2_subdev sd;
>> +
>> +    struct v4l2_subdev *source_sd;
>> +    u32 source_pad;
>> +};
>> +
>> +static const struct v4l2_mbus_framefmt default_format = {
>> +    .width = 3840,
>> +    .height = 2160,
>> +    .code = MEDIA_BUS_FMT_SRGGB10_1X10,
>> +    .field = V4L2_FIELD_NONE,
>> +    .colorspace = V4L2_COLORSPACE_RAW,
>> +    .ycbcr_enc = V4L2_YCBCR_ENC_601,
>> +    .quantization = V4L2_QUANTIZATION_FULL_RANGE,
>> +    .xfer_func = V4L2_XFER_FUNC_NONE,
>> +};
>> +
>> +static const struct rkcif_csi_format formats[] = {
>> +    /* YUV formats */
>> +    {
>> +        .code = MEDIA_BUS_FMT_YUYV8_1X16,
>> +        .depth = 16,
>> +        .csi_dt = MIPI_CSI2_DT_YUV422_8B,
>> +    },
>> +    {
>> +        .code = MEDIA_BUS_FMT_UYVY8_1X16,
>> +        .depth = 16,
>> +        .csi_dt = MIPI_CSI2_DT_YUV422_8B,
>> +    },
>> +    {
>> +        .code = MEDIA_BUS_FMT_YVYU8_1X16,
>> +        .depth = 16,
>> +        .csi_dt = MIPI_CSI2_DT_YUV422_8B,
>> +    },
>> +    {
>> +        .code = MEDIA_BUS_FMT_VYUY8_1X16,
>> +        .depth = 16,
>> +        .csi_dt = MIPI_CSI2_DT_YUV422_8B,
>> +    },
>> +    /* RGB formats */
>> +    {
>> +        .code = MEDIA_BUS_FMT_RGB888_1X24,
>> +        .depth = 24,
>> +        .csi_dt = MIPI_CSI2_DT_RGB888,
>> +    },
>> +    {
>> +        .code = MEDIA_BUS_FMT_BGR888_1X24,
>> +        .depth = 24,
>> +        .csi_dt = MIPI_CSI2_DT_RGB888,
>> +    },
>> +    /* Bayer formats */
>> +    {
>> +        .code = MEDIA_BUS_FMT_SBGGR8_1X8,
>> +        .depth = 8,
>> +        .csi_dt = MIPI_CSI2_DT_RAW8,
>> +    },
>> +    {
>> +        .code = MEDIA_BUS_FMT_SGBRG8_1X8,
>> +        .depth = 8,
>> +        .csi_dt = MIPI_CSI2_DT_RAW8,
>> +    },
>> +    {
>> +        .code = MEDIA_BUS_FMT_SGRBG8_1X8,
>> +        .depth = 8,
>> +        .csi_dt = MIPI_CSI2_DT_RAW8,
>> +    },
>> +    {
>> +        .code = MEDIA_BUS_FMT_SRGGB8_1X8,
>> +        .depth = 8,
>> +        .csi_dt = MIPI_CSI2_DT_RAW8,
>> +    },
>> +    {
>> +        .code = MEDIA_BUS_FMT_SBGGR10_1X10,
>> +        .depth = 10,
>> +        .csi_dt = MIPI_CSI2_DT_RAW10,
>> +    },
>> +    {
>> +        .code = MEDIA_BUS_FMT_SGBRG10_1X10,
>> +        .depth = 10,
>> +        .csi_dt = MIPI_CSI2_DT_RAW10,
>> +    },
>> +    {
>> +        .code = MEDIA_BUS_FMT_SGRBG10_1X10,
>> +        .depth = 10,
>> +        .csi_dt = MIPI_CSI2_DT_RAW10,
>> +    },
>> +    {
>> +        .code = MEDIA_BUS_FMT_SRGGB10_1X10,
>> +        .depth = 10,
>> +        .csi_dt = MIPI_CSI2_DT_RAW10,
>> +    },
>> +    {
>> +        .code = MEDIA_BUS_FMT_SBGGR12_1X12,
>> +        .depth = 12,
>> +        .csi_dt = MIPI_CSI2_DT_RAW12,
>> +    },
>> +    {
>> +        .code = MEDIA_BUS_FMT_SGBRG12_1X12,
>> +        .depth = 12,
>> +        .csi_dt = MIPI_CSI2_DT_RAW12,
>> +    },
>> +    {
>> +        .code = MEDIA_BUS_FMT_SGRBG12_1X12,
>> +        .depth = 12,
>> +        .csi_dt = MIPI_CSI2_DT_RAW12,
>> +    },
>> +    {
>> +        .code = MEDIA_BUS_FMT_SRGGB12_1X12,
>> +        .depth = 12,
>> +        .csi_dt = MIPI_CSI2_DT_RAW12,
>> +    },
>> +};
>> +
>> +static inline struct rkcif_csi_device *to_rkcif_csi(struct
>> v4l2_subdev *sd)
>> +{
>> +    return container_of(sd, struct rkcif_csi_device, sd);
>> +}
>> +
>> +static inline __maybe_unused void
>> +rkcif_csi_write(struct rkcif_csi_device *csi_dev, unsigned int addr,
>> u32 val)
>> +{
>> +    writel(val, csi_dev->base_addr + addr);
>> +}
>> +
>> +static inline __maybe_unused u32
>> +rkcif_csi_read(struct rkcif_csi_device *csi_dev, unsigned int addr)
>> +{
>> +    return readl(csi_dev->base_addr + addr);
>> +}
>> +
>> +static const struct rkcif_csi_format *
>> +rkcif_csi_find_format(struct rkcif_csi_device *csi_dev, u32 mbus_code)
>> +{
>> +    const struct rkcif_csi_format *format;
>> +
>> +    WARN_ON(csi_dev->formats_num == 0);
>> +
>> +    for (int i = 0; i < csi_dev->formats_num; i++) {
>> +        format = &csi_dev->formats[i];
>> +        if (format->code == mbus_code)
>> +            return format;
>> +    }
>> +
>> +    return NULL;
>> +}
>> +
>> +static int rkcif_csi_start(struct rkcif_csi_device *csi_dev)
>> +{
>> +    enum v4l2_mbus_type bus_type = csi_dev->vep.bus_type;
>> +    union phy_configure_opts opts;
>> +    s64 link_freq;
>> +    u32 lanes = csi_dev->vep.bus.mipi_csi2.num_data_lanes;
>> +    u32 control = 0;
>> +
>> +    if (lanes < 1 || lanes > 4)
>> +        return -EINVAL;
>> +
>> +    /* set mult and div to 0, thus completely rely on
>> V4L2_CID_LINK_FREQ */
>> +    link_freq = v4l2_get_link_freq(csi_dev->source_sd->ctrl_handler,
>> 0, 0);
>> +    if (link_freq <= 0)
>> +        return -EINVAL;
>> +
>> +    if (bus_type == V4L2_MBUS_CSI2_DPHY) {
>> +        struct phy_configure_opts_mipi_dphy *cfg = &opts.mipi_dphy;
>> +
>> +        phy_mipi_dphy_get_default_config_for_hsclk(link_freq * 2, lanes,
>> +                               cfg);
>> +        phy_set_mode(csi_dev->phy, PHY_MODE_MIPI_DPHY);
>> +        phy_configure(csi_dev->phy, &opts);
>> +
>> +        control |= SW_CPHY_EN(0);
>> +
>> +    } else if (bus_type == V4L2_MBUS_CSI2_CPHY) {
>> +        control |= SW_CPHY_EN(1);
> 
> return -ENOTSUPP;

Ack.

> 
>> +        /* TODO: implement CPHY configuration */
>> +    } else {
>> +        return -EINVAL;
>> +    }
>> +
>> +    control |= SW_DATATYPE_FS(0x00) | SW_DATATYPE_FE(0x01) |
>> +           SW_DATATYPE_LS(0x02) | SW_DATATYPE_LE(0x03);
>> +
>> +    rkcif_csi_write(csi_dev, CSI2HOST_N_LANES, lanes - 1);
>> +    rkcif_csi_write(csi_dev, CSI2HOST_CONTROL, control);
>> +    rkcif_csi_write(csi_dev, CSI2HOST_CSI2_RESETN, 1);
>> +
>> +    phy_power_on(csi_dev->phy);
> 
> this can fail
> 
> ret = phy_power_on();
> if (ret)
>     return ret;

Ack!


Thanks for your review. Added the fixes to v7.

Best regards,
Michael

> 
>> +
>> +    return 0;
>> +}
>> +
>> +static void rkcif_csi_stop(struct rkcif_csi_device *csi_dev)
>> +{
>> +    phy_power_off(csi_dev->phy);
>> +
>> +    rkcif_csi_write(csi_dev, CSI2HOST_CSI2_RESETN, 0);
>> +    rkcif_csi_write(csi_dev, CSI2HOST_MSK1, ~0);
>> +    rkcif_csi_write(csi_dev, CSI2HOST_MSK2, ~0);
>> +}
>> +
>> +static const struct media_entity_operations rkcif_csi_media_ops = {
>> +    .link_validate = v4l2_subdev_link_validate,
>> +};
>> +
>> +static int rkcif_csi_enum_mbus_code(struct v4l2_subdev *sd,
>> +                    struct v4l2_subdev_state *sd_state,
>> +                    struct v4l2_subdev_mbus_code_enum *code)
>> +{
>> +    struct rkcif_csi_device *csi_dev = to_rkcif_csi(sd);
>> +
>> +    if (code->pad == RKCIF_CSI_PAD_SRC) {
>> +        const struct v4l2_mbus_framefmt *sink_fmt;
>> +
>> +        if (code->index)
>> +            return -EINVAL;
>> +
>> +        sink_fmt = v4l2_subdev_state_get_format(sd_state,
>> +                            RKCIF_CSI_PAD_SINK);
>> +        code->code = sink_fmt->code;
>> +
>> +        return 0;
>> +    } else if (code->pad == RKCIF_CSI_PAD_SINK) {
>> +        if (code->index > csi_dev->formats_num)
>> +            return -EINVAL;
>> +
>> +        code->code = csi_dev->formats[code->index].code;
>> +        return 0;
>> +    }
>> +
>> +    return -EINVAL;
>> +}
>> +
>> +static int rkcif_csi_set_fmt(struct v4l2_subdev *sd,
>> +                 struct v4l2_subdev_state *state,
>> +                 struct v4l2_subdev_format *format)
>> +{
>> +    struct rkcif_csi_device *csi_dev = to_rkcif_csi(sd);
>> +    const struct rkcif_csi_format *fmt;
>> +    struct v4l2_mbus_framefmt *sink, *src;
>> +
>> +    /* the format on the source pad always matches the sink pad */
>> +    if (format->pad == RKCIF_CSI_PAD_SRC)
>> +        return v4l2_subdev_get_fmt(sd, state, format);
>> +
>> +    sink = v4l2_subdev_state_get_format(state, format->pad, format-
>> >stream);
>> +    if (!sink)
>> +        return -EINVAL;
>> +
>> +    fmt = rkcif_csi_find_format(csi_dev, format->format.code);
>> +    if (fmt)
>> +        *sink = format->format;
>> +    else
>> +        *sink = default_format;
>> +
>> +    /* propagate the format to the source pad */
>> +    src = v4l2_subdev_state_get_opposite_stream_format(state, format-
>> >pad,
>> +                               format->stream);
>> +    if (!src)
>> +        return -EINVAL;
>> +
>> +    *src = *sink;
>> +
>> +    return 0;
>> +}
>> +
>> +static int rkcif_csi_set_routing(struct v4l2_subdev *sd,
>> +                 struct v4l2_subdev_state *state,
>> +                 enum v4l2_subdev_format_whence which,
>> +                 struct v4l2_subdev_krouting *routing)
>> +{
>> +    int ret;
>> +
>> +    ret = v4l2_subdev_routing_validate(sd, routing,
>> +                       V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
>> +    if (ret)
>> +        return ret;
>> +
>> +    ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing,
>> +                           &default_format);
>> +    if (ret)
>> +        return ret;
>> +
>> +    return 0;
>> +}
>> +
>> +static int rkcif_csi_enable_streams(struct v4l2_subdev *sd,
>> +                    struct v4l2_subdev_state *state, u32 pad,
>> +                    u64 streams_mask)
>> +{
>> +    struct rkcif_csi_device *csi_dev = to_rkcif_csi(sd);
>> +    struct v4l2_subdev *remote_sd;
>> +    struct media_pad *sink_pad, *remote_pad;
>> +    struct device *dev = csi_dev->dev;
>> +    u64 mask;
>> +    int ret;
>> +
>> +    sink_pad = &sd->entity.pads[RKCIF_CSI_PAD_SINK];
>> +    remote_pad = media_pad_remote_pad_first(sink_pad);
>> +    remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity);
>> +
>> +    mask = v4l2_subdev_state_xlate_streams(state, RKCIF_CSI_PAD_SINK,
>> +                           RKCIF_CSI_PAD_SRC,
>> +                           &streams_mask);
>> +
>> +    ret = pm_runtime_resume_and_get(dev);
>> +    if (ret)
>> +        goto err;
>> +
>> +    ret = rkcif_csi_start(csi_dev);
>> +    if (ret) {
>> +        dev_err(dev, "failed to enable CSI hardware\n");
>> +        goto err_pm_runtime_put;
>> +    }
>> +
>> +    ret = v4l2_subdev_enable_streams(remote_sd, remote_pad->index,
>> mask);
>> +    if (ret)
>> +        goto err_csi_stop;
>> +
>> +    return 0;
>> +
>> +err_csi_stop:
>> +    rkcif_csi_stop(csi_dev);
>> +err_pm_runtime_put:
>> +    pm_runtime_put_sync(dev);
>> +err:
>> +    return ret;
>> +}
>> +
>> +static int rkcif_csi_disable_streams(struct v4l2_subdev *sd,
>> +                     struct v4l2_subdev_state *state, u32 pad,
>> +                     u64 streams_mask)
>> +{
>> +    struct rkcif_csi_device *csi_dev = to_rkcif_csi(sd);
>> +    struct v4l2_subdev *remote_sd;
>> +    struct media_pad *sink_pad, *remote_pad;
>> +    struct device *dev = csi_dev->dev;
>> +    u64 mask;
>> +    int ret;
>> +
>> +    sink_pad = &sd->entity.pads[RKCIF_CSI_PAD_SINK];
>> +    remote_pad = media_pad_remote_pad_first(sink_pad);
>> +    remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity);
>> +
>> +    mask = v4l2_subdev_state_xlate_streams(state, RKCIF_CSI_PAD_SINK,
>> +                           RKCIF_CSI_PAD_SRC,
>> +                           &streams_mask);
>> +
>> +    ret = v4l2_subdev_disable_streams(remote_sd, remote_pad->index,
>> mask);
>> +
>> +    rkcif_csi_stop(csi_dev);
>> +
>> +    pm_runtime_mark_last_busy(dev);
>> +    pm_runtime_put_autosuspend(dev);
>> +
>> +    return ret;
>> +}
>> +
>> +static const struct v4l2_subdev_pad_ops rkcif_csi_pad_ops = {
>> +    .enum_mbus_code = rkcif_csi_enum_mbus_code,
>> +    .get_fmt = v4l2_subdev_get_fmt,
>> +    .set_fmt = rkcif_csi_set_fmt,
>> +    .set_routing = rkcif_csi_set_routing,
>> +    .enable_streams = rkcif_csi_enable_streams,
>> +    .disable_streams = rkcif_csi_disable_streams,
>> +};
>> +
>> +static const struct v4l2_subdev_ops rkcif_csi_ops = {
>> +    .pad = &rkcif_csi_pad_ops,
>> +};
>> +
>> +static int rkcif_csi_init_state(struct v4l2_subdev *sd,
>> +                struct v4l2_subdev_state *state)
>> +{
>> +    struct v4l2_subdev_route routes[] = {
>> +        {
>> +            .sink_pad = RKCIF_CSI_PAD_SINK,
>> +            .sink_stream = 0,
>> +            .source_pad = RKCIF_CSI_PAD_SRC,
>> +            .source_stream = 0,
>> +            .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
>> +        },
>> +    };
>> +    struct v4l2_subdev_krouting routing = {
>> +        .len_routes = ARRAY_SIZE(routes),
>> +        .num_routes = ARRAY_SIZE(routes),
>> +        .routes = routes,
>> +    };
>> +    int ret;
>> +
>> +    ret = v4l2_subdev_set_routing_with_fmt(sd, state, &routing,
>> +                           &default_format);
>> +
>> +    return ret;
>> +}
>> +
>> +static const struct v4l2_subdev_internal_ops rkcif_csi_internal_ops = {
>> +    .init_state = rkcif_csi_init_state,
>> +};
>> +
>> +static int rkcif_csi_notifier_bound(struct v4l2_async_notifier
>> *notifier,
>> +                    struct v4l2_subdev *sd,
>> +                    struct v4l2_async_connection *asd)
>> +{
>> +    struct rkcif_csi_device *csi_dev =
>> +        container_of(notifier, struct rkcif_csi_device, notifier);
>> +    int source_pad;
>> +
>> +    source_pad = media_entity_get_fwnode_pad(&sd->entity, sd->fwnode,
>> +                         MEDIA_PAD_FL_SOURCE);
>> +    if (source_pad < 0) {
>> +        dev_err(csi_dev->dev, "failed to find source pad for %s\n",
>> +            sd->name);
>> +        return source_pad;
>> +    }
>> +
>> +    csi_dev->source_sd = sd;
>> +    csi_dev->source_pad = source_pad;
>> +
>> +    return media_create_pad_link(&sd->entity, source_pad,
>> +                     &csi_dev->sd.entity, RKCIF_CSI_PAD_SINK,
>> +                     MEDIA_LNK_FL_ENABLED);
>> +}
>> +
>> +static const struct v4l2_async_notifier_operations
>> rkcif_csi_notifier_ops = {
>> +    .bound = rkcif_csi_notifier_bound,
>> +};
>> +
>> +static int rkcif_csi_register_notifier(struct rkcif_csi_device *csi_dev)
>> +{
>> +    struct v4l2_async_connection *asd;
>> +    struct v4l2_async_notifier *ntf = &csi_dev->notifier;
>> +    struct v4l2_fwnode_endpoint *vep = &csi_dev->vep;
>> +    struct v4l2_subdev *sd = &csi_dev->sd;
>> +    struct device *dev = csi_dev->dev;
>> +    struct fwnode_handle *ep;
>> +    int ret = 0;
>> +
>> +    ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, 0);
>> +    if (!ep)
>> +        return dev_err_probe(dev, -ENODEV, "failed to get endpoint\n");
>> +
>> +    vep->bus_type = V4L2_MBUS_UNKNOWN;
>> +    ret = v4l2_fwnode_endpoint_parse(ep, vep);
>> +    if (ret) {
>> +        ret = dev_err_probe(dev, ret, "failed to parse endpoint\n");
>> +        goto out;
>> +    }
>> +
>> +    if (vep->bus_type != V4L2_MBUS_CSI2_DPHY &&
>> +        vep->bus_type != V4L2_MBUS_CSI2_CPHY) {
>> +        ret = dev_err_probe(dev, -EINVAL,
>> +                    "invalid bus type of endpoint\n");
>> +        goto out;
>> +    }
>> +
>> +    v4l2_async_subdev_nf_init(ntf, sd);
>> +    ntf->ops = &rkcif_csi_notifier_ops;
>> +
>> +    asd = v4l2_async_nf_add_fwnode_remote(ntf, ep,
>> +                          struct v4l2_async_connection);
>> +    if (IS_ERR(asd)) {
>> +        ret = PTR_ERR(asd);
>> +        goto err_nf_cleanup;
>> +    }
>> +
>> +    ret = v4l2_async_nf_register(ntf);
>> +    if (ret) {
>> +        ret = dev_err_probe(dev, ret, "failed to register notifier\n");
>> +        goto err_nf_cleanup;
>> +    }
>> +
>> +    goto out;
>> +
>> +err_nf_cleanup:
>> +    v4l2_async_nf_cleanup(ntf);
>> +out:
>> +    fwnode_handle_put(ep);
>> +    return ret;
>> +}
>> +
>> +static int rkcif_csi_register(struct rkcif_csi_device *csi_dev)
>> +{
>> +    struct media_pad *pads = csi_dev->pads;
>> +    struct v4l2_subdev *sd = &csi_dev->sd;
>> +    int ret;
>> +
>> +    ret = rkcif_csi_register_notifier(csi_dev);
>> +    if (ret)
>> +        goto err;
>> +
>> +    v4l2_subdev_init(sd, &rkcif_csi_ops);
>> +    sd->dev = csi_dev->dev;
>> +    sd->entity.ops = &rkcif_csi_media_ops;
>> +    sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
>> +    sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS;
>> +    sd->internal_ops = &rkcif_csi_internal_ops;
>> +    sd->owner = THIS_MODULE;
>> +    snprintf(sd->name, sizeof(sd->name), "rockchip-mipi-csi %s",
>> +         dev_name(csi_dev->dev));
>> +
>> +    pads[RKCIF_CSI_PAD_SINK].flags = MEDIA_PAD_FL_SINK |
>> +                     MEDIA_PAD_FL_MUST_CONNECT;
>> +    pads[RKCIF_CSI_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE;
>> +    ret = media_entity_pads_init(&sd->entity, RKCIF_CSI_PAD_MAX, pads);
>> +    if (ret)
>> +        goto err_notifier_unregister;
>> +
>> +    ret = v4l2_subdev_init_finalize(sd);
>> +    if (ret)
>> +        goto err_entity_cleanup;
>> +
>> +    ret = v4l2_async_register_subdev(sd);
>> +    if (ret) {
>> +        dev_err(sd->dev, "failed to register CSI subdev\n");
>> +        goto err_subdev_cleanup;
>> +    }
>> +
>> +    return 0;
>> +
>> +err_subdev_cleanup:
>> +    v4l2_subdev_cleanup(sd);
>> +err_entity_cleanup:
>> +    media_entity_cleanup(&sd->entity);
>> +err_notifier_unregister:
>> +    v4l2_async_nf_unregister(&csi_dev->notifier);
>> +    v4l2_async_nf_cleanup(&csi_dev->notifier);
>> +err:
>> +    return ret;
>> +}
>> +
>> +static void rkcif_csi_unregister(struct rkcif_csi_device *csi_dev)
>> +{
>> +    struct v4l2_subdev *sd = &csi_dev->sd;
>> +
>> +    v4l2_async_unregister_subdev(sd);
>> +    v4l2_subdev_cleanup(sd);
>> +    media_entity_cleanup(&sd->entity);
>> +    v4l2_async_nf_unregister(&csi_dev->notifier);
>> +    v4l2_async_nf_cleanup(&csi_dev->notifier);
>> +}
>> +
>> +static const struct of_device_id rkcif_csi_of_match[] = {
>> +    {
>> +        .compatible = "rockchip,rk3568-mipi-csi",
>> +    },
>> +    {}
>> +};
>> +MODULE_DEVICE_TABLE(of, rkcif_csi_of_match);
>> +
>> +static int rkcif_csi_probe(struct platform_device *pdev)
>> +{
>> +    struct device *dev = &pdev->dev;
>> +    struct rkcif_csi_device *csi_dev;
>> +    int ret;
>> +
>> +    csi_dev = devm_kzalloc(dev, sizeof(*csi_dev), GFP_KERNEL);
>> +    if (!csi_dev)
>> +        return -ENOMEM;
>> +    csi_dev->dev = dev;
>> +    dev_set_drvdata(dev, csi_dev);
>> +
>> +    csi_dev->base_addr = devm_platform_ioremap_resource(pdev, 0);
>> +    if (IS_ERR(csi_dev->base_addr))
>> +        return PTR_ERR(csi_dev->base_addr);
>> +
>> +    ret = devm_clk_bulk_get_all(dev, &csi_dev->clks);
>> +    if (ret != RKCIF_CSI_CLKS_MAX)
>> +        return dev_err_probe(dev, -ENODEV, "failed to get clocks\n");
>> +    csi_dev->clks_num = ret;
>> +
>> +    csi_dev->phy = devm_phy_get(dev, NULL);
>> +    if (IS_ERR(csi_dev->phy))
>> +        return dev_err_probe(dev, PTR_ERR(csi_dev->phy),
>> +                     "failed to get MIPI CSI PHY\n");
>> +
>> +    csi_dev->reset = devm_reset_control_array_get_exclusive(dev);
>> +    if (IS_ERR(csi_dev->reset))
>> +        return dev_err_probe(dev, PTR_ERR(csi_dev->reset),
>> +                     "failed to get reset\n");
>> +
>> +    csi_dev->formats = formats;
>> +    csi_dev->formats_num = ARRAY_SIZE(formats);
>> +
>> +    pm_runtime_enable(dev);
>> +
>> +    ret = phy_init(csi_dev->phy);
>> +    if (ret) {
>> +        ret = dev_err_probe(dev, ret,
>> +                    "failed to initialize MIPI CSI PHY\n");
>> +        goto err_pm_runtime_disable;
>> +    }
>> +
>> +    ret = rkcif_csi_register(csi_dev);
>> +    if (ret)
>> +        goto err_phy_exit;
>> +
>> +    return 0;
>> +
>> +err_phy_exit:
>> +    phy_exit(csi_dev->phy);
>> +err_pm_runtime_disable:
>> +    pm_runtime_disable(dev);
>> +    return ret;
>> +}
>> +
>> +static void rkcif_csi_remove(struct platform_device *pdev)
>> +{
>> +    struct rkcif_csi_device *csi_dev = platform_get_drvdata(pdev);
>> +    struct device *dev = &pdev->dev;
>> +
>> +    rkcif_csi_unregister(csi_dev);
>> +    phy_exit(csi_dev->phy);
>> +    pm_runtime_disable(dev);
>> +}
>> +
>> +static int rkcif_csi_runtime_suspend(struct device *dev)
>> +{
>> +    struct rkcif_csi_device *csi_dev = dev_get_drvdata(dev);
>> +
>> +    clk_bulk_disable_unprepare(csi_dev->clks_num, csi_dev->clks);
>> +
>> +    return 0;
>> +}
>> +
>> +static int rkcif_csi_runtime_resume(struct device *dev)
>> +{
>> +    struct rkcif_csi_device *csi_dev = dev_get_drvdata(dev);
>> +    int ret;
>> +
>> +    reset_control_assert(csi_dev->reset);
>> +    udelay(5);
>> +    reset_control_deassert(csi_dev->reset);
>> +
>> +    ret = clk_bulk_prepare_enable(csi_dev->clks_num, csi_dev->clks);
>> +    if (ret) {
>> +        dev_err(dev, "failed to enable clocks\n");
>> +        return ret;
>> +    }
>> +
>> +    return 0;
>> +}
>> +
>> +static const struct dev_pm_ops rkcif_csi_pm_ops = {
>> +    .runtime_suspend = rkcif_csi_runtime_suspend,
>> +    .runtime_resume = rkcif_csi_runtime_resume,
>> +};
>> +
>> +static struct platform_driver rkcif_csi_drv = {
>> +    .driver = {
>> +           .name = "rockchip-mipi-csi",
>> +           .of_match_table = rkcif_csi_of_match,
>> +           .pm = &rkcif_csi_pm_ops,
>> +    },
>> +    .probe = rkcif_csi_probe,
>> +    .remove = rkcif_csi_remove,
>> +};
>> +module_platform_driver(rkcif_csi_drv);
>> +
>> +MODULE_DESCRIPTION("Rockchip MIPI CSI-2 Receiver platform driver");
>> +MODULE_LICENSE("GPL");
>>
>> -- 
>> 2.39.5
>>
>>
>>
> 


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

* Re: [PATCH v6 05/13] media: dt-bindings: add rockchip rk3568 mipi csi receiver
  2025-05-06 12:09   ` Mehdi Djait
@ 2025-05-06 19:54     ` Michael Riesch
  0 siblings, 0 replies; 34+ messages in thread
From: Michael Riesch @ 2025-05-06 19:54 UTC (permalink / raw)
  To: Mehdi Djait
  Cc: Maxime Chevallier, Théo Lebrun, Gerald Loacker,
	Thomas Petazzoni, Laurent Pinchart, Mauro Carvalho Chehab,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner,
	Kever Yang, Nicolas Dufresne, Sebastian Reichel,
	Collabora Kernel Team, Paul Kocialkowski, Alexander Shiyan,
	Val Packett, Rob Herring, Philipp Zabel, Sakari Ailus,
	linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-rockchip, Michael Riesch

Hi Mehdi,

On 5/6/25 14:09, Mehdi Djait wrote:
> Hi Michael,
> 
> thank you for the patch!
> 
> On Wed, Apr 30, 2025 at 11:15:54AM +0200, Michael Riesch via B4 Relay wrote:
>> From: Michael Riesch <michael.riesch@wolfvision.net>
>>
>> Add documentation for the Rockchip RK3568 MIPI CSI-2 Receiver.
>>
>> Signed-off-by: Michael Riesch <michael.riesch@wolfvision.net>
>> Signed-off-by: Michael Riesch <michael.riesch@collabora.com>
>> ---
>>  .../bindings/media/rockchip,rk3568-mipi-csi.yaml   | 113 +++++++++++++++++++++
>>  MAINTAINERS                                        |   1 +
>>  2 files changed, 114 insertions(+)
>>
>> diff --git a/Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi.yaml b/Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi.yaml
>> new file mode 100644
>> index 000000000000..d5004cb288dd
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi.yaml
>> @@ -0,0 +1,113 @@
>> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
>> +%YAML 1.2
>> +---
>> +$id: http://devicetree.org/schemas/media/rockchip,rk3568-mipi-csi.yaml#
>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>> +
>> +title: Rockchip RK3568 MIPI CSI-2 Receiver
>> +
>> +maintainers:
>> +  - Michael Riesch <michael.riesch@collabora.com>
>> +
>> +description:
>> +  The Rockchip RK3568 MIPI CSI-2 Receiver is a CSI-2 bridge with one input port
>> +  and one output port. It receives the data with the help of an external
>> +  MIPI PHY (C-PHY or D-PHY) and passes it to the Rockchip RK3568 Video Capture
>> +  (VICAP) block.
>> +
>> +properties:
>> +  compatible:
>> +    const: rockchip,rk3568-mipi-csi
>> +
>> +  reg:
>> +    maxItems: 1
>> +
>> +  clocks:
>> +    maxItems: 1
>> +
>> +  phys:
>> +    maxItems: 1
>> +    description: MIPI C-PHY or D-PHY.
>> +
>> +  power-domains:
>> +    maxItems: 1
>> +
>> +  resets:
>> +    maxItems: 1
>> +
>> +  ports:
>> +    $ref: /schemas/graph.yaml#/properties/ports
>> +
>> +    properties:
>> +      port@0:
>> +        $ref: /schemas/graph.yaml#/$defs/port-base
>> +        unevaluatedProperties: false
>> +        description: Input port node. Connect to e.g., a MIPI CSI-2 image sensor.
>> +
>> +        properties:
>> +          endpoint:
>> +            $ref: video-interfaces.yaml#
>> +            unevaluatedProperties: false
>> +
>> +            properties:
>> +              bus-type:
>> +                enum: [1, 4]
> 
> shouldn't you add data-lanes property here ?

Yes, makes sense.

Thanks for pointing it out -- will be in v7.

Best regards,
Michael

> 
>                  data-lanes:
>                    minItems: 1
>                    maxItems: 4
>> +
>> +            required:
>> +              - bus-type
> 
> and add it to required:
>                  - data-lanes
> 
> you are actually checking for data-lanes when you enable the stream in:
> 
> rkcif-mipi-csi-receiver.c +226
> 
> 	u32 lanes = csi_dev->vep.bus.mipi_csi2.num_data_lanes;
> 
> 	if (lanes < 1 || lanes > 4)
> 		return -EINVAL;
> 
>> +
>> +      port@1:
>> +        $ref: /schemas/graph.yaml#/properties/port
>> +        description: Output port connected to a RK3568 VICAP port.
>> +
>> +    required:
>> +      - port@0
>> +      - port@1
>> +
>> +required:
>> +  - compatible
>> +  - reg
>> +  - clocks
>> +  - phys
>> +  - phy-names
>> +  - ports
>> +  - power-domains
>> +  - resets
>> +
>> +additionalProperties: false
>> +
>> +examples:
>> +  - |
>> +    #include <dt-bindings/clock/rk3568-cru.h>
>> +    #include <dt-bindings/power/rk3568-power.h>
>> +
>> +    soc {
>> +        #address-cells = <2>;
>> +        #size-cells = <2>;
>> +
>> +        csi: csi@fdfb0000 {
>> +            compatible = "rockchip,rk3568-mipi-csi";
>> +            reg = <0x0 0xfdfb0000 0x0 0x10000>;
>> +            clocks = <&cru PCLK_CSI2HOST1>;
>> +            phys = <&csi_dphy>;
>> +            power-domains = <&power RK3568_PD_VI>;
>> +            resets = <&cru SRST_P_CSI2HOST1>;
>> +
>> +            ports {
>> +                #address-cells = <1>;
>> +                #size-cells = <0>;
>> +
>> +                csi_in: port@0 {
>> +                    reg = <0>;
>> +                };
>> +
>> +                csi_out: port@1 {
>> +                    reg = <1>;
>> +
>> +                    csi_output: endpoint {
>> +                        remote-endpoint = <&vicap_mipi_input>;
>> +                    };
>> +                };
>> +            };
>> +        };
>> +    };
> 
> --
> Kind Regards
> Mehdi Djait


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

* Re: [PATCH v6 06/13] media: rockchip: add a driver for the rockchip camera interface
  2025-05-06 10:37   ` Mehdi Djait
@ 2025-05-06 20:32     ` Michael Riesch
  2025-05-07  9:36       ` Mehdi Djait
  0 siblings, 1 reply; 34+ messages in thread
From: Michael Riesch @ 2025-05-06 20:32 UTC (permalink / raw)
  To: Mehdi Djait
  Cc: Maxime Chevallier, Théo Lebrun, Gerald Loacker,
	Thomas Petazzoni, Laurent Pinchart, Mauro Carvalho Chehab,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner,
	Kever Yang, Nicolas Dufresne, Sebastian Reichel,
	Collabora Kernel Team, Paul Kocialkowski, Alexander Shiyan,
	Val Packett, Rob Herring, Philipp Zabel, Sakari Ailus,
	linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-rockchip, Michael Riesch, Mehdi Djait

Hi Mehdi,

Thanks for your review!

On 5/6/25 12:37, Mehdi Djait wrote:
> Hi Michael,
> 
> Thank you for the patch!
> 
> Is it possible to sent the v4l2-compliance output in the next version ?
> 
> On Wed, Apr 30, 2025 at 11:15:55AM +0200, Michael Riesch via B4 Relay wrote:
>> From: Michael Riesch <michael.riesch@wolfvision.net>
>>
> 
> SNIP
> 
>> +irqreturn_t rkcif_dvp_isr(int irq, void *ctx)
>> +{
>> +	struct device *dev = ctx;
>> +	struct rkcif_device *rkcif = dev_get_drvdata(dev);
>> +	struct rkcif_stream *stream;
>> +	u32 intstat, lastline, lastpix, cif_frmst;
>> +	irqreturn_t ret = IRQ_NONE;
>> +
>> +	if (!rkcif->match_data->dvp)
>> +		return ret;
>> +
>> +	intstat = cif_dvp_read(rkcif, RKCIF_DVP_INTSTAT);
>> +	cif_frmst = cif_dvp_read(rkcif, RKCIF_DVP_FRAME_STATUS);
>> +	lastline = RKCIF_FETCH_Y(cif_dvp_read(rkcif, RKCIF_DVP_LAST_LINE));
>> +	lastpix = RKCIF_FETCH_Y(cif_dvp_read(rkcif, RKCIF_DVP_LAST_PIX));
>> +
>> +	if (intstat & RKCIF_INTSTAT_FRAME_END) {
>> +		cif_dvp_write(rkcif, RKCIF_DVP_INTSTAT,
>> +			      RKCIF_INTSTAT_FRAME_END_CLR |
>> +				      RKCIF_INTSTAT_LINE_END_CLR);
>> +
>> +		stream = &rkcif->interfaces[RKCIF_DVP].streams[RKCIF_ID0];
>> +
>> +		if (stream->stopping) {
>> +			cif_dvp_stop_streaming(stream);
>> +			wake_up(&stream->wq_stopped);
>> +			return IRQ_HANDLED;
>> +		}
>> +
>> +		if (lastline != stream->pix.height) {
>> +			v4l2_err(&rkcif->v4l2_dev,
>> +				 "bad frame, irq:%#x frmst:%#x size:%dx%d\n",
>> +				 intstat, cif_frmst, lastpix, lastline);
>> +
>> +			cif_dvp_reset_stream(rkcif);
>> +		}
>> +
>> +		rkcif_stream_pingpong(stream);
>> +
>> +		ret = IRQ_HANDLED;
> 
> just return IRQ_HANDLED like above ?

I think I'll go along Bryan's suggestion to make it more consistent.

> 
>> +	}
>> +
>> +	return ret;
>> +}
>> +
>> +int rkcif_dvp_register(struct rkcif_device *rkcif)
>> +{
>> +	struct rkcif_interface *interface;
>> +	int ret, i;
>> +
>> +	if (!rkcif->match_data->dvp)
>> +		return 0;
>> +
>> +	interface = &rkcif->interfaces[RKCIF_DVP];
>> +	interface->index = RKCIF_DVP;
>> +	interface->type = RKCIF_IF_DVP;
>> +	interface->in_fmts = rkcif->match_data->dvp->in_fmts;
>> +	interface->in_fmts_num = rkcif->match_data->dvp->in_fmts_num;
>> +	interface->set_crop = rkcif_dvp_set_crop;
>> +	ret = rkcif_interface_register(rkcif, interface);
>> +	if (ret)
>> +		return 0;
> 		|
> 		+-> Copy-paste error ?

Hm. It's not a mistake. But maybe it is a bit misleading.

The point here is that if something fails with registering the DVP, the
driver may continue to register other entities, such as the MIPI capture
thing.

I'll have another look over this mechanism and will try to make it more
comprehensible.

> 
>> +
>> +	if (rkcif->match_data->dvp->setup)
>> +		rkcif->match_data->dvp->setup(rkcif);
>> +
>> +	interface->streams_num = rkcif->match_data->dvp->has_ids ? 4 : 1;
>> +	for (i = 0; i < interface->streams_num; i++) {
>> +		struct rkcif_stream *stream = &interface->streams[i];
>> +
>> +		stream->id = i;
>> +		stream->interface = interface;
>> +		stream->out_fmts = rkcif->match_data->dvp->out_fmts;
>> +		stream->out_fmts_num = rkcif->match_data->dvp->out_fmts_num;
>> +		stream->queue_buffer = cif_dvp_queue_buffer;
>> +		stream->start_streaming = cif_dvp_start_streaming;
>> +		stream->stop_streaming = cif_dvp_stop_streaming;
>> +
>> +		ret = rkcif_stream_register(rkcif, stream);
>> +		if (ret)
>> +			goto err_streams_unregister;
>> +	}
>> +	return 0;
>> +
>> +err_streams_unregister:
>> +	for (; i >= 0; i--)
>> +		rkcif_stream_unregister(&interface->streams[i]);
>> +	rkcif_interface_unregister(interface);
>> +
>> +	return ret;
>> +}
>> +
> 
> SNIP
> 
>> +static inline struct rkcif_buffer *to_rkcif_buffer(struct vb2_v4l2_buffer *vb)
>> +{
>> +	return container_of(vb, struct rkcif_buffer, vb);
>> +}
>> +
>> +static inline struct rkcif_stream *to_rkcif_stream(struct video_device *vdev)
>> +{
>> +	return container_of(vdev, struct rkcif_stream, vdev);
>> +}
>> +
>> +static struct rkcif_buffer *rkcif_stream_pop_buffer(struct rkcif_stream *stream)
>> +{
>> +	struct rkcif_buffer *buffer = NULL;
>> +	unsigned long lock_flags;
>> +
>> +	spin_lock_irqsave(&stream->driver_queue_lock, lock_flags);
> 
> guard(spinlock_irqsave)(&stream->driver_queue_lock) will simplify this function.

I'll guard up these methods in v7.

> 
>> +
>> +	if (list_empty(&stream->driver_queue))
>> +		goto err_empty;
>> +
>> +	buffer = list_first_entry(&stream->driver_queue, struct rkcif_buffer,
>> +				  queue);
>> +	list_del(&buffer->queue);
>> +
>> +err_empty:
>> +	spin_unlock_irqrestore(&stream->driver_queue_lock, lock_flags);
>> +	return buffer;
>> +}
>> +
>> +static void rkcif_stream_push_buffer(struct rkcif_stream *stream,
>> +				     struct rkcif_buffer *buffer)
>> +{
>> +	unsigned long lock_flags;
>> +
>> +	spin_lock_irqsave(&stream->driver_queue_lock, lock_flags);
>> +	list_add_tail(&buffer->queue, &stream->driver_queue);
>> +	spin_unlock_irqrestore(&stream->driver_queue_lock, lock_flags);
>> +}
>> +
>> +static inline void rkcif_stream_return_buffer(struct rkcif_buffer *buffer,
>> +					      enum vb2_buffer_state state)
>> +{
>> +	struct vb2_v4l2_buffer *vb = &buffer->vb;
>> +
>> +	vb2_buffer_done(&vb->vb2_buf, state);
>> +}
>> +
>> +static void rkcif_stream_complete_buffer(struct rkcif_stream *stream,
>> +					 struct rkcif_buffer *buffer)
>> +{
>> +	struct vb2_v4l2_buffer *vb = &buffer->vb;
>> +
>> +	vb->vb2_buf.timestamp = ktime_get_ns();
>> +	vb->sequence = stream->frame_idx;
>> +	vb2_buffer_done(&vb->vb2_buf, VB2_BUF_STATE_DONE);
>> +	stream->frame_idx++;
>> +}
>> +
>> +void rkcif_stream_pingpong(struct rkcif_stream *stream)
>> +{
>> +	struct rkcif_buffer *buffer;
>> +
>> +	buffer = stream->buffers[stream->frame_phase];
>> +	if (!buffer->is_dummy)
>> +		rkcif_stream_complete_buffer(stream, buffer);
> 
> You can actually keep this frame dropping mechanism without using the
> dummy buffer.
> 
> You can set a drop flag to TRUE: keep overwriting the buffer you already have
> without returning it to user-space until you can get another buffer, set
> the flag again to FALSE and resume returning the buffers to user-space.

The approach you describe is what the downstream driver does and I am
not really happy with it. A perfectly fine frame is sacrificed in a
buffer starvation situation.

The approach in the patch series at hand follows the example in the
rkisp1 driver, which should be a good reference.

>> +
>> +	buffer = rkcif_stream_pop_buffer(stream);
>> +	if (buffer) {
>> +		stream->buffers[stream->frame_phase] = buffer;
>> +		stream->buffers[stream->frame_phase]->is_dummy = false;
>> +	} else {
>> +		stream->buffers[stream->frame_phase] = &stream->dummy.buffer;
>> +		stream->buffers[stream->frame_phase]->is_dummy = true;
>> +		dev_warn(stream->rkcif->dev,
>> +			 "no buffer available, frame will be dropped\n");
> 
> This warning can quickly flood the kernel logs if the user-space is too slow in
> enqueuing the buffers.

True. dev_warn_ratelimited(...)?

> 
>> +	}
>> +
>> +	if (stream->queue_buffer)
>> +		stream->queue_buffer(stream, stream->frame_phase);
> 
> is this if statement really needed ?

I find it good practice to check the callbacks before calling them. But
this is a matter of taste, of course.

> 
>> +
>> +	stream->frame_phase = 1 - stream->frame_phase;
>> +}
>> +
>> +static int rkcif_stream_init_buffers(struct rkcif_stream *stream)
>> +{
>> +	struct v4l2_pix_format_mplane *pix = &stream->pix;
>> +	int i;
>> +
>> +	stream->buffers[0] = rkcif_stream_pop_buffer(stream);
>> +	if (!stream->buffers[0])
>> +		goto err_buff_0;
>> +
>> +	stream->buffers[1] = rkcif_stream_pop_buffer(stream);
>> +	if (!stream->buffers[1])
>> +		goto err_buff_1;
>> +
>> +	if (stream->queue_buffer) {
>> +		stream->queue_buffer(stream, 0);
>> +		stream->queue_buffer(stream, 1);
>> +	}
>> +
>> +	stream->dummy.size = pix->num_planes * pix->plane_fmt[0].sizeimage;
>> +	stream->dummy.vaddr =
>> +		dma_alloc_attrs(stream->rkcif->dev, stream->dummy.size,
>> +				&stream->dummy.buffer.buff_addr[0], GFP_KERNEL,
>> +				DMA_ATTR_NO_KERNEL_MAPPING);
>> +	if (!stream->dummy.vaddr)
>> +		goto err_dummy;
>> +
>> +	for (i = 1; i < pix->num_planes; i++)
>> +		stream->dummy.buffer.buff_addr[i] =
>> +			stream->dummy.buffer.buff_addr[i - 1] +
>> +			pix->plane_fmt[i - 1].bytesperline * pix->height;
>> +
>> +	return 0;
>> +
>> +err_dummy:
>> +	rkcif_stream_return_buffer(stream->buffers[1], VB2_BUF_STATE_QUEUED);
>> +	stream->buffers[1] = NULL;
>> +
>> +err_buff_1:
>> +	rkcif_stream_return_buffer(stream->buffers[0], VB2_BUF_STATE_QUEUED);
>> +	stream->buffers[0] = NULL;
>> +err_buff_0:
>> +	return -EINVAL;
>> +}
>> +
> 
> SNIP
> 
>> +static int rkcif_stream_init_vb2_queue(struct vb2_queue *q,
>> +				       struct rkcif_stream *stream)
>> +{
>> +	q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
>> +	q->io_modes = VB2_MMAP | VB2_DMABUF;
>> +	q->drv_priv = stream;
>> +	q->ops = &rkcif_stream_vb2_ops;
>> +	q->mem_ops = &vb2_dma_contig_memops;
>> +	q->buf_struct_size = sizeof(struct rkcif_buffer);
>> +	q->min_queued_buffers = CIF_REQ_BUFS_MIN;
> 
> If I recall correctly min_queued_buffers should be the strict minimum
> number of buffers you need to start streaming. So in this case it should
> be 3 = 2 pingpong buffers + 1 dummy buffer.

The dummy buffer is allocated separately and does not need to be
accounted for.

Two pingpong buffers is what the hardware can queue, but in practice, to
start (and, above all, keep on) streaming you'll need more.

> VIDIOC_REQBUFS will allocate min_queued_buffers + 1 and user-space will
> probably allocate even more anyway.

Is that so? I found that user space relies too much on this minimum
buffer count and experienced several buffer starvation situations
because kernel AND user space were to cheap in terms of buffer count.
Maybe 8 is too many, but in practice four buffers are required at least
for a decent 2160p stream (one ready for DMA write, one ongoing DMA
write, one stable for processing (maybe DRM scanout or whatever the
application is), one spare).

I am open to suggestions but please keep real life situations in mind
and move away from theoretical stand-alone-capture-hw setups.

Thanks and best regards,
Michael


> 
>> +	q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
>> +	q->lock = &stream->vlock;
>> +	q->dev = stream->rkcif->dev;
>> +
>> +	return vb2_queue_init(q);
>> +}
> 
> --
> Kind Regards
> Mehdi Djait


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

* Re: [PATCH v6 07/13] media: rockchip: rkcif: add driver for mipi csi-2 receiver
  2025-05-06 18:39         ` Michael Riesch
@ 2025-05-07  8:38           ` Laurent Pinchart
  0 siblings, 0 replies; 34+ messages in thread
From: Laurent Pinchart @ 2025-05-07  8:38 UTC (permalink / raw)
  To: Michael Riesch
  Cc: Mehdi Djait, Maxime Chevallier, Théo Lebrun, Gerald Loacker,
	Thomas Petazzoni, Mauro Carvalho Chehab, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner, Kever Yang,
	Nicolas Dufresne, Sebastian Reichel, Collabora Kernel Team,
	Paul Kocialkowski, Alexander Shiyan, Val Packett, Rob Herring,
	Philipp Zabel, Sakari Ailus, linux-media, devicetree,
	linux-kernel, linux-arm-kernel, linux-rockchip, Michael Riesch

On Tue, May 06, 2025 at 08:39:31PM +0200, Michael Riesch wrote:
> On 5/2/25 16:35, Laurent Pinchart wrote:
> > On Fri, May 02, 2025 at 04:19:59PM +0200, Michael Riesch wrote:
> >> On 5/2/25 15:31, Laurent Pinchart wrote:
> >>> On Wed, Apr 30, 2025 at 11:15:56AM +0200, Michael Riesch via B4 Relay wrote:
> >>>> From: Michael Riesch <michael.riesch@wolfvision.net>
> >>>>
> >>>> The Rockchip RK3568 MIPI CSI-2 Receiver is a CSI-2 bridge with one
> >>>> input port and one output port. It receives the data with the help
> >>>> of an external MIPI PHY (C-PHY or D-PHY) and passes it to the
> >>>> Rockchip RK3568 Video Capture (VICAP) block.
> >>>>
> >>>> Add a V4L2 subdevice driver for this unit.
> >>>>
> >>>> Signed-off-by: Michael Riesch <michael.riesch@wolfvision.net>
> >>>> Signed-off-by: Michael Riesch <michael.riesch@collabora.com>
> >>>> ---
> >>>>  drivers/media/platform/rockchip/rkcif/Makefile     |   3 +
> >>>>  .../rockchip/rkcif/rkcif-mipi-csi-receiver.c       | 731 +++++++++++++++++++++
> >>>>  2 files changed, 734 insertions(+)
> >>>>
> >>>> diff --git a/drivers/media/platform/rockchip/rkcif/Makefile b/drivers/media/platform/rockchip/rkcif/Makefile
> >>>> index 818424972c7b..a5c18a45c213 100644
> >>>> --- a/drivers/media/platform/rockchip/rkcif/Makefile
> >>>> +++ b/drivers/media/platform/rockchip/rkcif/Makefile
> >>>> @@ -5,3 +5,6 @@ rockchip-cif-objs += rkcif-dev.o \
> >>>>  	rkcif-capture-mipi.o \
> >>>>  	rkcif-interface.o \
> >>>>  	rkcif-stream.o
> >>>> +
> >>>> +obj-$(CONFIG_VIDEO_ROCKCHIP_CIF) += rockchip-mipi-csi.o
> >>>> +rockchip-mipi-csi-objs += rkcif-mipi-csi-receiver.o
> >>>> diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-mipi-csi-receiver.c b/drivers/media/platform/rockchip/rkcif/rkcif-mipi-csi-receiver.c
> >>>> new file mode 100644
> >>>> index 000000000000..81489f70490f
> >>>> --- /dev/null
> >>>> +++ b/drivers/media/platform/rockchip/rkcif/rkcif-mipi-csi-receiver.c
> >>>> @@ -0,0 +1,731 @@
> >>>> +// SPDX-License-Identifier: GPL-2.0
> >>>> +/*
> >>>> + * Rockchip MIPI CSI-2 Receiver Driver
> >>>> + *
> >>>> + * Copyright (C) 2019 Rockchip Electronics Co., Ltd.
> >>>> + * Copyright (C) 2025 Michael Riesch <michael.riesch@wolfvision.net>
> >>>> + */
> >>>> +
> >>>> +#include <linux/clk.h>
> >>>> +#include <linux/delay.h>
> >>>> +#include <linux/io.h>
> >>>> +#include <linux/module.h>
> >>>> +#include <linux/of.h>
> >>>> +#include <linux/of_graph.h>
> >>>> +#include <linux/of_platform.h>
> >>>> +#include <linux/phy/phy.h>
> >>>> +#include <linux/platform_device.h>
> >>>> +#include <linux/pm_runtime.h>
> >>>> +#include <linux/reset.h>
> >>>> +
> >>>> +#include <media/mipi-csi2.h>
> >>>> +#include <media/v4l2-ctrls.h>
> >>>> +#include <media/v4l2-fwnode.h>
> >>>> +#include <media/v4l2-subdev.h>
> >>>> +
> >>>> +#define CSI2HOST_N_LANES     0x04
> >>>> +#define CSI2HOST_CSI2_RESETN 0x10
> >>>> +#define CSI2HOST_PHY_STATE   0x14
> >>>> +#define CSI2HOST_ERR1	     0x20
> >>>> +#define CSI2HOST_ERR2	     0x24
> >>>> +#define CSI2HOST_MSK1	     0x28
> >>>> +#define CSI2HOST_MSK2	     0x2c
> >>>> +#define CSI2HOST_CONTROL     0x40
> >>>
> >>> I'm trying to get to the bottom of the CSI-2 RX integration questions
> >>> for the RK3568. Some of the registers here seem to the CSI2RX_1C00 block
> >>> as documented starting on page 1059 of the RK3568 TRM (revision 1.1),
> >>> but they're not an exact match. The control register, in particular,
> >>> doesn't match at all. Where is this CSI-2 receiver documented in the TRM
> >>> ?
> >>
> >> That would be in Chapter 27 of the RK3568 TRM: "MIPI CSI HOST"
> >>
> >> The registers are at 0xfdfb0000 "CSI_RX_CTRL1", cf. Chapter 1 "System
> >> Overview".
> >>
> >> Naturally, this is quite confusing, as there is a "CSI2RX" whose
> >> registers are embedded in the ISP register map.
> >>
> >> "MIPI CSI HOST" and "CSI2RX" seem to be different IP cores but perform
> >> the same job in principle. CSI2RX seems to have additional features
> >> related to writing raw data into memory and to HDR, though.
> >>
> >> Hope that helps!
> > 
> > It does, thank you. I wonder how I missed that.
> > 
> > The IP seems very similar to other CSI-2 receivers already supported
> > upstream, see drivers/media/platform/raspberrypi/rp1-cfe/dphy.c,
> > drivers/media/platform/renesas/rcar-csi2.c and
> > drivers/staging/media/imx/imx6-mipi-csi2.c. Those drivers combine
> > support for the CSI-2 RX and D-PHY, explaining some of the differences
> > (and in the case of the rcar-csi2 driver, I think it also combines
> > support for different CSI-2 RX in a single driver).
> 
> Before I started writing the driver under discussion, I did scan the
> existing drivers, but could not see any similarities to rcar-csi2 and
> rp1-cfe/dphy.c (and I still don't see them), respectively (but maybe
> those two are the same).
> Unfortunately, I forgot to look into staging. Hm. Indeed it resembles
> the imx6-mipi-csi2.c driver, at least judging by the register layout.
> 
> According to Kever, while a Synopsys IP core was used in older SoCs, the
> newer SoCs feature an IP core developed by Rockchip. Probably the
> register map was recycled but slightly modified.

Let's keep this driver Rockchip-specific then. Thanks for checking.

> > Is there something we could do to avoid adding a 4th driver for the same
> > IP core family (from Synopsys or Cadence I assume) ? It could possibly
> > be done on top of this series to avoid delaying VICAP support, but I'd
> > really like to get this sorted out.
> 
> Based on my current level of knowledge I wouldn't be comfortable with
> placing this driver in media/platform/synopsys/something. If you all
> advise me to do so, I will comply, though.
> 
> I am not too keen on digging out a quite dated staging driver and basing
> the current efforts on that one.
> 
> We could, however, move this driver to
> media/platform/rockchip/mipi-csi-receiver/ (name to be bikeshedded, open
> to suggestions). It is a separate kernel module already anyway, and
> maybe this way it is more visible to the next person developing a driver
> for a similar IP core. Sound reasonable?

Sounds good to me.

> >>>> +
> >>>> +#define SW_CPHY_EN(x)	     ((x) << 0)
> >>>> +#define SW_DSI_EN(x)	     ((x) << 4)
> >>>> +#define SW_DATATYPE_FS(x)    ((x) << 8)
> >>>> +#define SW_DATATYPE_FE(x)    ((x) << 14)
> >>>> +#define SW_DATATYPE_LS(x)    ((x) << 20)
> >>>> +#define SW_DATATYPE_LE(x)    ((x) << 26)
> >>>> +
> >>>> +#define RKCIF_CSI_CLKS_MAX   1
> >>>> +
> >>>> +enum {
> >>>> +	RKCIF_CSI_PAD_SINK,
> >>>> +	RKCIF_CSI_PAD_SRC,
> >>>> +	RKCIF_CSI_PAD_MAX,
> >>>> +};
> >>>> +
> >>>> +struct rkcif_csi_format {
> >>>> +	u32 code;
> >>>> +	u8 depth;
> >>>> +	u8 csi_dt;
> >>>> +};
> >>>> +
> >>>> +struct rkcif_csi_device {
> >>>> +	struct device *dev;
> >>>> +
> >>>> +	void __iomem *base_addr;
> >>>> +	struct clk_bulk_data *clks;
> >>>> +	unsigned int clks_num;
> >>>> +	struct phy *phy;
> >>>> +	struct reset_control *reset;
> >>>> +
> >>>> +	const struct rkcif_csi_format *formats;
> >>>> +	unsigned int formats_num;
> >>>> +
> >>>> +	struct media_pad pads[RKCIF_CSI_PAD_MAX];
> >>>> +	struct v4l2_async_notifier notifier;
> >>>> +	struct v4l2_fwnode_endpoint vep;
> >>>> +	struct v4l2_subdev sd;
> >>>> +
> >>>> +	struct v4l2_subdev *source_sd;
> >>>> +	u32 source_pad;
> >>>> +};
> >>>> +
> >>>> +static const struct v4l2_mbus_framefmt default_format = {
> >>>> +	.width = 3840,
> >>>> +	.height = 2160,
> >>>> +	.code = MEDIA_BUS_FMT_SRGGB10_1X10,
> >>>> +	.field = V4L2_FIELD_NONE,
> >>>> +	.colorspace = V4L2_COLORSPACE_RAW,
> >>>> +	.ycbcr_enc = V4L2_YCBCR_ENC_601,
> >>>> +	.quantization = V4L2_QUANTIZATION_FULL_RANGE,
> >>>> +	.xfer_func = V4L2_XFER_FUNC_NONE,
> >>>> +};
> >>>> +
> >>>> +static const struct rkcif_csi_format formats[] = {
> >>>> +	/* YUV formats */
> >>>> +	{
> >>>> +		.code = MEDIA_BUS_FMT_YUYV8_1X16,
> >>>> +		.depth = 16,
> >>>> +		.csi_dt = MIPI_CSI2_DT_YUV422_8B,
> >>>> +	},
> >>>> +	{
> >>>> +		.code = MEDIA_BUS_FMT_UYVY8_1X16,
> >>>> +		.depth = 16,
> >>>> +		.csi_dt = MIPI_CSI2_DT_YUV422_8B,
> >>>> +	},
> >>>> +	{
> >>>> +		.code = MEDIA_BUS_FMT_YVYU8_1X16,
> >>>> +		.depth = 16,
> >>>> +		.csi_dt = MIPI_CSI2_DT_YUV422_8B,
> >>>> +	},
> >>>> +	{
> >>>> +		.code = MEDIA_BUS_FMT_VYUY8_1X16,
> >>>> +		.depth = 16,
> >>>> +		.csi_dt = MIPI_CSI2_DT_YUV422_8B,
> >>>> +	},
> >>>> +	/* RGB formats */
> >>>> +	{
> >>>> +		.code = MEDIA_BUS_FMT_RGB888_1X24,
> >>>> +		.depth = 24,
> >>>> +		.csi_dt = MIPI_CSI2_DT_RGB888,
> >>>> +	},
> >>>> +	{
> >>>> +		.code = MEDIA_BUS_FMT_BGR888_1X24,
> >>>> +		.depth = 24,
> >>>> +		.csi_dt = MIPI_CSI2_DT_RGB888,
> >>>> +	},
> >>>> +	/* Bayer formats */
> >>>> +	{
> >>>> +		.code = MEDIA_BUS_FMT_SBGGR8_1X8,
> >>>> +		.depth = 8,
> >>>> +		.csi_dt = MIPI_CSI2_DT_RAW8,
> >>>> +	},
> >>>> +	{
> >>>> +		.code = MEDIA_BUS_FMT_SGBRG8_1X8,
> >>>> +		.depth = 8,
> >>>> +		.csi_dt = MIPI_CSI2_DT_RAW8,
> >>>> +	},
> >>>> +	{
> >>>> +		.code = MEDIA_BUS_FMT_SGRBG8_1X8,
> >>>> +		.depth = 8,
> >>>> +		.csi_dt = MIPI_CSI2_DT_RAW8,
> >>>> +	},
> >>>> +	{
> >>>> +		.code = MEDIA_BUS_FMT_SRGGB8_1X8,
> >>>> +		.depth = 8,
> >>>> +		.csi_dt = MIPI_CSI2_DT_RAW8,
> >>>> +	},
> >>>> +	{
> >>>> +		.code = MEDIA_BUS_FMT_SBGGR10_1X10,
> >>>> +		.depth = 10,
> >>>> +		.csi_dt = MIPI_CSI2_DT_RAW10,
> >>>> +	},
> >>>> +	{
> >>>> +		.code = MEDIA_BUS_FMT_SGBRG10_1X10,
> >>>> +		.depth = 10,
> >>>> +		.csi_dt = MIPI_CSI2_DT_RAW10,
> >>>> +	},
> >>>> +	{
> >>>> +		.code = MEDIA_BUS_FMT_SGRBG10_1X10,
> >>>> +		.depth = 10,
> >>>> +		.csi_dt = MIPI_CSI2_DT_RAW10,
> >>>> +	},
> >>>> +	{
> >>>> +		.code = MEDIA_BUS_FMT_SRGGB10_1X10,
> >>>> +		.depth = 10,
> >>>> +		.csi_dt = MIPI_CSI2_DT_RAW10,
> >>>> +	},
> >>>> +	{
> >>>> +		.code = MEDIA_BUS_FMT_SBGGR12_1X12,
> >>>> +		.depth = 12,
> >>>> +		.csi_dt = MIPI_CSI2_DT_RAW12,
> >>>> +	},
> >>>> +	{
> >>>> +		.code = MEDIA_BUS_FMT_SGBRG12_1X12,
> >>>> +		.depth = 12,
> >>>> +		.csi_dt = MIPI_CSI2_DT_RAW12,
> >>>> +	},
> >>>> +	{
> >>>> +		.code = MEDIA_BUS_FMT_SGRBG12_1X12,
> >>>> +		.depth = 12,
> >>>> +		.csi_dt = MIPI_CSI2_DT_RAW12,
> >>>> +	},
> >>>> +	{
> >>>> +		.code = MEDIA_BUS_FMT_SRGGB12_1X12,
> >>>> +		.depth = 12,
> >>>> +		.csi_dt = MIPI_CSI2_DT_RAW12,
> >>>> +	},
> >>>> +};
> >>>> +
> >>>> +static inline struct rkcif_csi_device *to_rkcif_csi(struct v4l2_subdev *sd)
> >>>> +{
> >>>> +	return container_of(sd, struct rkcif_csi_device, sd);
> >>>> +}
> >>>> +
> >>>> +static inline __maybe_unused void
> >>>> +rkcif_csi_write(struct rkcif_csi_device *csi_dev, unsigned int addr, u32 val)
> >>>> +{
> >>>> +	writel(val, csi_dev->base_addr + addr);
> >>>> +}
> >>>> +
> >>>> +static inline __maybe_unused u32
> >>>> +rkcif_csi_read(struct rkcif_csi_device *csi_dev, unsigned int addr)
> >>>> +{
> >>>> +	return readl(csi_dev->base_addr + addr);
> >>>> +}
> >>>> +
> >>>> +static const struct rkcif_csi_format *
> >>>> +rkcif_csi_find_format(struct rkcif_csi_device *csi_dev, u32 mbus_code)
> >>>> +{
> >>>> +	const struct rkcif_csi_format *format;
> >>>> +
> >>>> +	WARN_ON(csi_dev->formats_num == 0);
> >>>> +
> >>>> +	for (int i = 0; i < csi_dev->formats_num; i++) {
> >>>> +		format = &csi_dev->formats[i];
> >>>> +		if (format->code == mbus_code)
> >>>> +			return format;
> >>>> +	}
> >>>> +
> >>>> +	return NULL;
> >>>> +}
> >>>> +
> >>>> +static int rkcif_csi_start(struct rkcif_csi_device *csi_dev)
> >>>> +{
> >>>> +	enum v4l2_mbus_type bus_type = csi_dev->vep.bus_type;
> >>>> +	union phy_configure_opts opts;
> >>>> +	s64 link_freq;
> >>>> +	u32 lanes = csi_dev->vep.bus.mipi_csi2.num_data_lanes;
> >>>> +	u32 control = 0;
> >>>> +
> >>>> +	if (lanes < 1 || lanes > 4)
> >>>> +		return -EINVAL;
> >>>> +
> >>>> +	/* set mult and div to 0, thus completely rely on V4L2_CID_LINK_FREQ */
> >>>> +	link_freq = v4l2_get_link_freq(csi_dev->source_sd->ctrl_handler, 0, 0);
> >>>> +	if (link_freq <= 0)
> >>>> +		return -EINVAL;
> >>>> +
> >>>> +	if (bus_type == V4L2_MBUS_CSI2_DPHY) {
> >>>> +		struct phy_configure_opts_mipi_dphy *cfg = &opts.mipi_dphy;
> >>>> +
> >>>> +		phy_mipi_dphy_get_default_config_for_hsclk(link_freq * 2, lanes,
> >>>> +							   cfg);
> >>>> +		phy_set_mode(csi_dev->phy, PHY_MODE_MIPI_DPHY);
> >>>> +		phy_configure(csi_dev->phy, &opts);
> >>>> +
> >>>> +		control |= SW_CPHY_EN(0);
> >>>> +
> >>>> +	} else if (bus_type == V4L2_MBUS_CSI2_CPHY) {
> >>>> +		control |= SW_CPHY_EN(1);
> >>>> +
> >>>> +		/* TODO: implement CPHY configuration */
> >>>> +	} else {
> >>>> +		return -EINVAL;
> >>>> +	}
> >>>> +
> >>>> +	control |= SW_DATATYPE_FS(0x00) | SW_DATATYPE_FE(0x01) |
> >>>> +		   SW_DATATYPE_LS(0x02) | SW_DATATYPE_LE(0x03);
> >>>> +
> >>>> +	rkcif_csi_write(csi_dev, CSI2HOST_N_LANES, lanes - 1);
> >>>> +	rkcif_csi_write(csi_dev, CSI2HOST_CONTROL, control);
> >>>> +	rkcif_csi_write(csi_dev, CSI2HOST_CSI2_RESETN, 1);
> >>>> +
> >>>> +	phy_power_on(csi_dev->phy);
> >>>> +
> >>>> +	return 0;
> >>>> +}
> >>>> +
> >>>> +static void rkcif_csi_stop(struct rkcif_csi_device *csi_dev)
> >>>> +{
> >>>> +	phy_power_off(csi_dev->phy);
> >>>> +
> >>>> +	rkcif_csi_write(csi_dev, CSI2HOST_CSI2_RESETN, 0);
> >>>> +	rkcif_csi_write(csi_dev, CSI2HOST_MSK1, ~0);
> >>>> +	rkcif_csi_write(csi_dev, CSI2HOST_MSK2, ~0);
> >>>> +}
> >>>> +
> >>>> +static const struct media_entity_operations rkcif_csi_media_ops = {
> >>>> +	.link_validate = v4l2_subdev_link_validate,
> >>>> +};
> >>>> +
> >>>> +static int rkcif_csi_enum_mbus_code(struct v4l2_subdev *sd,
> >>>> +				    struct v4l2_subdev_state *sd_state,
> >>>> +				    struct v4l2_subdev_mbus_code_enum *code)
> >>>> +{
> >>>> +	struct rkcif_csi_device *csi_dev = to_rkcif_csi(sd);
> >>>> +
> >>>> +	if (code->pad == RKCIF_CSI_PAD_SRC) {
> >>>> +		const struct v4l2_mbus_framefmt *sink_fmt;
> >>>> +
> >>>> +		if (code->index)
> >>>> +			return -EINVAL;
> >>>> +
> >>>> +		sink_fmt = v4l2_subdev_state_get_format(sd_state,
> >>>> +							RKCIF_CSI_PAD_SINK);
> >>>> +		code->code = sink_fmt->code;
> >>>> +
> >>>> +		return 0;
> >>>> +	} else if (code->pad == RKCIF_CSI_PAD_SINK) {
> >>>> +		if (code->index > csi_dev->formats_num)
> >>>> +			return -EINVAL;
> >>>> +
> >>>> +		code->code = csi_dev->formats[code->index].code;
> >>>> +		return 0;
> >>>> +	}
> >>>> +
> >>>> +	return -EINVAL;
> >>>> +}
> >>>> +
> >>>> +static int rkcif_csi_set_fmt(struct v4l2_subdev *sd,
> >>>> +			     struct v4l2_subdev_state *state,
> >>>> +			     struct v4l2_subdev_format *format)
> >>>> +{
> >>>> +	struct rkcif_csi_device *csi_dev = to_rkcif_csi(sd);
> >>>> +	const struct rkcif_csi_format *fmt;
> >>>> +	struct v4l2_mbus_framefmt *sink, *src;
> >>>> +
> >>>> +	/* the format on the source pad always matches the sink pad */
> >>>> +	if (format->pad == RKCIF_CSI_PAD_SRC)
> >>>> +		return v4l2_subdev_get_fmt(sd, state, format);
> >>>> +
> >>>> +	sink = v4l2_subdev_state_get_format(state, format->pad, format->stream);
> >>>> +	if (!sink)
> >>>> +		return -EINVAL;
> >>>> +
> >>>> +	fmt = rkcif_csi_find_format(csi_dev, format->format.code);
> >>>> +	if (fmt)
> >>>> +		*sink = format->format;
> >>>> +	else
> >>>> +		*sink = default_format;
> >>>> +
> >>>> +	/* propagate the format to the source pad */
> >>>> +	src = v4l2_subdev_state_get_opposite_stream_format(state, format->pad,
> >>>> +							   format->stream);
> >>>> +	if (!src)
> >>>> +		return -EINVAL;
> >>>> +
> >>>> +	*src = *sink;
> >>>> +
> >>>> +	return 0;
> >>>> +}
> >>>> +
> >>>> +static int rkcif_csi_set_routing(struct v4l2_subdev *sd,
> >>>> +				 struct v4l2_subdev_state *state,
> >>>> +				 enum v4l2_subdev_format_whence which,
> >>>> +				 struct v4l2_subdev_krouting *routing)
> >>>> +{
> >>>> +	int ret;
> >>>> +
> >>>> +	ret = v4l2_subdev_routing_validate(sd, routing,
> >>>> +					   V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
> >>>> +	if (ret)
> >>>> +		return ret;
> >>>> +
> >>>> +	ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing,
> >>>> +					       &default_format);
> >>>> +	if (ret)
> >>>> +		return ret;
> >>>> +
> >>>> +	return 0;
> >>>> +}
> >>>> +
> >>>> +static int rkcif_csi_enable_streams(struct v4l2_subdev *sd,
> >>>> +				    struct v4l2_subdev_state *state, u32 pad,
> >>>> +				    u64 streams_mask)
> >>>> +{
> >>>> +	struct rkcif_csi_device *csi_dev = to_rkcif_csi(sd);
> >>>> +	struct v4l2_subdev *remote_sd;
> >>>> +	struct media_pad *sink_pad, *remote_pad;
> >>>> +	struct device *dev = csi_dev->dev;
> >>>> +	u64 mask;
> >>>> +	int ret;
> >>>> +
> >>>> +	sink_pad = &sd->entity.pads[RKCIF_CSI_PAD_SINK];
> >>>> +	remote_pad = media_pad_remote_pad_first(sink_pad);
> >>>> +	remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity);
> >>>> +
> >>>> +	mask = v4l2_subdev_state_xlate_streams(state, RKCIF_CSI_PAD_SINK,
> >>>> +					       RKCIF_CSI_PAD_SRC,
> >>>> +					       &streams_mask);
> >>>> +
> >>>> +	ret = pm_runtime_resume_and_get(dev);
> >>>> +	if (ret)
> >>>> +		goto err;
> >>>> +
> >>>> +	ret = rkcif_csi_start(csi_dev);
> >>>> +	if (ret) {
> >>>> +		dev_err(dev, "failed to enable CSI hardware\n");
> >>>> +		goto err_pm_runtime_put;
> >>>> +	}
> >>>> +
> >>>> +	ret = v4l2_subdev_enable_streams(remote_sd, remote_pad->index, mask);
> >>>> +	if (ret)
> >>>> +		goto err_csi_stop;
> >>>> +
> >>>> +	return 0;
> >>>> +
> >>>> +err_csi_stop:
> >>>> +	rkcif_csi_stop(csi_dev);
> >>>> +err_pm_runtime_put:
> >>>> +	pm_runtime_put_sync(dev);
> >>>> +err:
> >>>> +	return ret;
> >>>> +}
> >>>> +
> >>>> +static int rkcif_csi_disable_streams(struct v4l2_subdev *sd,
> >>>> +				     struct v4l2_subdev_state *state, u32 pad,
> >>>> +				     u64 streams_mask)
> >>>> +{
> >>>> +	struct rkcif_csi_device *csi_dev = to_rkcif_csi(sd);
> >>>> +	struct v4l2_subdev *remote_sd;
> >>>> +	struct media_pad *sink_pad, *remote_pad;
> >>>> +	struct device *dev = csi_dev->dev;
> >>>> +	u64 mask;
> >>>> +	int ret;
> >>>> +
> >>>> +	sink_pad = &sd->entity.pads[RKCIF_CSI_PAD_SINK];
> >>>> +	remote_pad = media_pad_remote_pad_first(sink_pad);
> >>>> +	remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity);
> >>>> +
> >>>> +	mask = v4l2_subdev_state_xlate_streams(state, RKCIF_CSI_PAD_SINK,
> >>>> +					       RKCIF_CSI_PAD_SRC,
> >>>> +					       &streams_mask);
> >>>> +
> >>>> +	ret = v4l2_subdev_disable_streams(remote_sd, remote_pad->index, mask);
> >>>> +
> >>>> +	rkcif_csi_stop(csi_dev);
> >>>> +
> >>>> +	pm_runtime_mark_last_busy(dev);
> >>>> +	pm_runtime_put_autosuspend(dev);
> >>>> +
> >>>> +	return ret;
> >>>> +}
> >>>> +
> >>>> +static const struct v4l2_subdev_pad_ops rkcif_csi_pad_ops = {
> >>>> +	.enum_mbus_code = rkcif_csi_enum_mbus_code,
> >>>> +	.get_fmt = v4l2_subdev_get_fmt,
> >>>> +	.set_fmt = rkcif_csi_set_fmt,
> >>>> +	.set_routing = rkcif_csi_set_routing,
> >>>> +	.enable_streams = rkcif_csi_enable_streams,
> >>>> +	.disable_streams = rkcif_csi_disable_streams,
> >>>> +};
> >>>> +
> >>>> +static const struct v4l2_subdev_ops rkcif_csi_ops = {
> >>>> +	.pad = &rkcif_csi_pad_ops,
> >>>> +};
> >>>> +
> >>>> +static int rkcif_csi_init_state(struct v4l2_subdev *sd,
> >>>> +				struct v4l2_subdev_state *state)
> >>>> +{
> >>>> +	struct v4l2_subdev_route routes[] = {
> >>>> +		{
> >>>> +			.sink_pad = RKCIF_CSI_PAD_SINK,
> >>>> +			.sink_stream = 0,
> >>>> +			.source_pad = RKCIF_CSI_PAD_SRC,
> >>>> +			.source_stream = 0,
> >>>> +			.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
> >>>> +		},
> >>>> +	};
> >>>> +	struct v4l2_subdev_krouting routing = {
> >>>> +		.len_routes = ARRAY_SIZE(routes),
> >>>> +		.num_routes = ARRAY_SIZE(routes),
> >>>> +		.routes = routes,
> >>>> +	};
> >>>> +	int ret;
> >>>> +
> >>>> +	ret = v4l2_subdev_set_routing_with_fmt(sd, state, &routing,
> >>>> +					       &default_format);
> >>>> +
> >>>> +	return ret;
> >>>> +}
> >>>> +
> >>>> +static const struct v4l2_subdev_internal_ops rkcif_csi_internal_ops = {
> >>>> +	.init_state = rkcif_csi_init_state,
> >>>> +};
> >>>> +
> >>>> +static int rkcif_csi_notifier_bound(struct v4l2_async_notifier *notifier,
> >>>> +				    struct v4l2_subdev *sd,
> >>>> +				    struct v4l2_async_connection *asd)
> >>>> +{
> >>>> +	struct rkcif_csi_device *csi_dev =
> >>>> +		container_of(notifier, struct rkcif_csi_device, notifier);
> >>>> +	int source_pad;
> >>>> +
> >>>> +	source_pad = media_entity_get_fwnode_pad(&sd->entity, sd->fwnode,
> >>>> +						 MEDIA_PAD_FL_SOURCE);
> >>>> +	if (source_pad < 0) {
> >>>> +		dev_err(csi_dev->dev, "failed to find source pad for %s\n",
> >>>> +			sd->name);
> >>>> +		return source_pad;
> >>>> +	}
> >>>> +
> >>>> +	csi_dev->source_sd = sd;
> >>>> +	csi_dev->source_pad = source_pad;
> >>>> +
> >>>> +	return media_create_pad_link(&sd->entity, source_pad,
> >>>> +				     &csi_dev->sd.entity, RKCIF_CSI_PAD_SINK,
> >>>> +				     MEDIA_LNK_FL_ENABLED);
> >>>> +}
> >>>> +
> >>>> +static const struct v4l2_async_notifier_operations rkcif_csi_notifier_ops = {
> >>>> +	.bound = rkcif_csi_notifier_bound,
> >>>> +};
> >>>> +
> >>>> +static int rkcif_csi_register_notifier(struct rkcif_csi_device *csi_dev)
> >>>> +{
> >>>> +	struct v4l2_async_connection *asd;
> >>>> +	struct v4l2_async_notifier *ntf = &csi_dev->notifier;
> >>>> +	struct v4l2_fwnode_endpoint *vep = &csi_dev->vep;
> >>>> +	struct v4l2_subdev *sd = &csi_dev->sd;
> >>>> +	struct device *dev = csi_dev->dev;
> >>>> +	struct fwnode_handle *ep;
> >>>> +	int ret = 0;
> >>>> +
> >>>> +	ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, 0);
> >>>> +	if (!ep)
> >>>> +		return dev_err_probe(dev, -ENODEV, "failed to get endpoint\n");
> >>>> +
> >>>> +	vep->bus_type = V4L2_MBUS_UNKNOWN;
> >>>> +	ret = v4l2_fwnode_endpoint_parse(ep, vep);
> >>>> +	if (ret) {
> >>>> +		ret = dev_err_probe(dev, ret, "failed to parse endpoint\n");
> >>>> +		goto out;
> >>>> +	}
> >>>> +
> >>>> +	if (vep->bus_type != V4L2_MBUS_CSI2_DPHY &&
> >>>> +	    vep->bus_type != V4L2_MBUS_CSI2_CPHY) {
> >>>> +		ret = dev_err_probe(dev, -EINVAL,
> >>>> +				    "invalid bus type of endpoint\n");
> >>>> +		goto out;
> >>>> +	}
> >>>> +
> >>>> +	v4l2_async_subdev_nf_init(ntf, sd);
> >>>> +	ntf->ops = &rkcif_csi_notifier_ops;
> >>>> +
> >>>> +	asd = v4l2_async_nf_add_fwnode_remote(ntf, ep,
> >>>> +					      struct v4l2_async_connection);
> >>>> +	if (IS_ERR(asd)) {
> >>>> +		ret = PTR_ERR(asd);
> >>>> +		goto err_nf_cleanup;
> >>>> +	}
> >>>> +
> >>>> +	ret = v4l2_async_nf_register(ntf);
> >>>> +	if (ret) {
> >>>> +		ret = dev_err_probe(dev, ret, "failed to register notifier\n");
> >>>> +		goto err_nf_cleanup;
> >>>> +	}
> >>>> +
> >>>> +	goto out;
> >>>> +
> >>>> +err_nf_cleanup:
> >>>> +	v4l2_async_nf_cleanup(ntf);
> >>>> +out:
> >>>> +	fwnode_handle_put(ep);
> >>>> +	return ret;
> >>>> +}
> >>>> +
> >>>> +static int rkcif_csi_register(struct rkcif_csi_device *csi_dev)
> >>>> +{
> >>>> +	struct media_pad *pads = csi_dev->pads;
> >>>> +	struct v4l2_subdev *sd = &csi_dev->sd;
> >>>> +	int ret;
> >>>> +
> >>>> +	ret = rkcif_csi_register_notifier(csi_dev);
> >>>> +	if (ret)
> >>>> +		goto err;
> >>>> +
> >>>> +	v4l2_subdev_init(sd, &rkcif_csi_ops);
> >>>> +	sd->dev = csi_dev->dev;
> >>>> +	sd->entity.ops = &rkcif_csi_media_ops;
> >>>> +	sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
> >>>> +	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS;
> >>>> +	sd->internal_ops = &rkcif_csi_internal_ops;
> >>>> +	sd->owner = THIS_MODULE;
> >>>> +	snprintf(sd->name, sizeof(sd->name), "rockchip-mipi-csi %s",
> >>>> +		 dev_name(csi_dev->dev));
> >>>> +
> >>>> +	pads[RKCIF_CSI_PAD_SINK].flags = MEDIA_PAD_FL_SINK |
> >>>> +					 MEDIA_PAD_FL_MUST_CONNECT;
> >>>> +	pads[RKCIF_CSI_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE;
> >>>> +	ret = media_entity_pads_init(&sd->entity, RKCIF_CSI_PAD_MAX, pads);
> >>>> +	if (ret)
> >>>> +		goto err_notifier_unregister;
> >>>> +
> >>>> +	ret = v4l2_subdev_init_finalize(sd);
> >>>> +	if (ret)
> >>>> +		goto err_entity_cleanup;
> >>>> +
> >>>> +	ret = v4l2_async_register_subdev(sd);
> >>>> +	if (ret) {
> >>>> +		dev_err(sd->dev, "failed to register CSI subdev\n");
> >>>> +		goto err_subdev_cleanup;
> >>>> +	}
> >>>> +
> >>>> +	return 0;
> >>>> +
> >>>> +err_subdev_cleanup:
> >>>> +	v4l2_subdev_cleanup(sd);
> >>>> +err_entity_cleanup:
> >>>> +	media_entity_cleanup(&sd->entity);
> >>>> +err_notifier_unregister:
> >>>> +	v4l2_async_nf_unregister(&csi_dev->notifier);
> >>>> +	v4l2_async_nf_cleanup(&csi_dev->notifier);
> >>>> +err:
> >>>> +	return ret;
> >>>> +}
> >>>> +
> >>>> +static void rkcif_csi_unregister(struct rkcif_csi_device *csi_dev)
> >>>> +{
> >>>> +	struct v4l2_subdev *sd = &csi_dev->sd;
> >>>> +
> >>>> +	v4l2_async_unregister_subdev(sd);
> >>>> +	v4l2_subdev_cleanup(sd);
> >>>> +	media_entity_cleanup(&sd->entity);
> >>>> +	v4l2_async_nf_unregister(&csi_dev->notifier);
> >>>> +	v4l2_async_nf_cleanup(&csi_dev->notifier);
> >>>> +}
> >>>> +
> >>>> +static const struct of_device_id rkcif_csi_of_match[] = {
> >>>> +	{
> >>>> +		.compatible = "rockchip,rk3568-mipi-csi",
> >>>> +	},
> >>>> +	{}
> >>>> +};
> >>>> +MODULE_DEVICE_TABLE(of, rkcif_csi_of_match);
> >>>> +
> >>>> +static int rkcif_csi_probe(struct platform_device *pdev)
> >>>> +{
> >>>> +	struct device *dev = &pdev->dev;
> >>>> +	struct rkcif_csi_device *csi_dev;
> >>>> +	int ret;
> >>>> +
> >>>> +	csi_dev = devm_kzalloc(dev, sizeof(*csi_dev), GFP_KERNEL);
> >>>> +	if (!csi_dev)
> >>>> +		return -ENOMEM;
> >>>> +	csi_dev->dev = dev;
> >>>> +	dev_set_drvdata(dev, csi_dev);
> >>>> +
> >>>> +	csi_dev->base_addr = devm_platform_ioremap_resource(pdev, 0);
> >>>> +	if (IS_ERR(csi_dev->base_addr))
> >>>> +		return PTR_ERR(csi_dev->base_addr);
> >>>> +
> >>>> +	ret = devm_clk_bulk_get_all(dev, &csi_dev->clks);
> >>>> +	if (ret != RKCIF_CSI_CLKS_MAX)
> >>>> +		return dev_err_probe(dev, -ENODEV, "failed to get clocks\n");
> >>>> +	csi_dev->clks_num = ret;
> >>>> +
> >>>> +	csi_dev->phy = devm_phy_get(dev, NULL);
> >>>> +	if (IS_ERR(csi_dev->phy))
> >>>> +		return dev_err_probe(dev, PTR_ERR(csi_dev->phy),
> >>>> +				     "failed to get MIPI CSI PHY\n");
> >>>> +
> >>>> +	csi_dev->reset = devm_reset_control_array_get_exclusive(dev);
> >>>> +	if (IS_ERR(csi_dev->reset))
> >>>> +		return dev_err_probe(dev, PTR_ERR(csi_dev->reset),
> >>>> +				     "failed to get reset\n");
> >>>> +
> >>>> +	csi_dev->formats = formats;
> >>>> +	csi_dev->formats_num = ARRAY_SIZE(formats);
> >>>> +
> >>>> +	pm_runtime_enable(dev);
> >>>> +
> >>>> +	ret = phy_init(csi_dev->phy);
> >>>> +	if (ret) {
> >>>> +		ret = dev_err_probe(dev, ret,
> >>>> +				    "failed to initialize MIPI CSI PHY\n");
> >>>> +		goto err_pm_runtime_disable;
> >>>> +	}
> >>>> +
> >>>> +	ret = rkcif_csi_register(csi_dev);
> >>>> +	if (ret)
> >>>> +		goto err_phy_exit;
> >>>> +
> >>>> +	return 0;
> >>>> +
> >>>> +err_phy_exit:
> >>>> +	phy_exit(csi_dev->phy);
> >>>> +err_pm_runtime_disable:
> >>>> +	pm_runtime_disable(dev);
> >>>> +	return ret;
> >>>> +}
> >>>> +
> >>>> +static void rkcif_csi_remove(struct platform_device *pdev)
> >>>> +{
> >>>> +	struct rkcif_csi_device *csi_dev = platform_get_drvdata(pdev);
> >>>> +	struct device *dev = &pdev->dev;
> >>>> +
> >>>> +	rkcif_csi_unregister(csi_dev);
> >>>> +	phy_exit(csi_dev->phy);
> >>>> +	pm_runtime_disable(dev);
> >>>> +}
> >>>> +
> >>>> +static int rkcif_csi_runtime_suspend(struct device *dev)
> >>>> +{
> >>>> +	struct rkcif_csi_device *csi_dev = dev_get_drvdata(dev);
> >>>> +
> >>>> +	clk_bulk_disable_unprepare(csi_dev->clks_num, csi_dev->clks);
> >>>> +
> >>>> +	return 0;
> >>>> +}
> >>>> +
> >>>> +static int rkcif_csi_runtime_resume(struct device *dev)
> >>>> +{
> >>>> +	struct rkcif_csi_device *csi_dev = dev_get_drvdata(dev);
> >>>> +	int ret;
> >>>> +
> >>>> +	reset_control_assert(csi_dev->reset);
> >>>> +	udelay(5);
> >>>> +	reset_control_deassert(csi_dev->reset);
> >>>> +
> >>>> +	ret = clk_bulk_prepare_enable(csi_dev->clks_num, csi_dev->clks);
> >>>> +	if (ret) {
> >>>> +		dev_err(dev, "failed to enable clocks\n");
> >>>> +		return ret;
> >>>> +	}
> >>>> +
> >>>> +	return 0;
> >>>> +}
> >>>> +
> >>>> +static const struct dev_pm_ops rkcif_csi_pm_ops = {
> >>>> +	.runtime_suspend = rkcif_csi_runtime_suspend,
> >>>> +	.runtime_resume = rkcif_csi_runtime_resume,
> >>>> +};
> >>>> +
> >>>> +static struct platform_driver rkcif_csi_drv = {
> >>>> +	.driver = {
> >>>> +		   .name = "rockchip-mipi-csi",
> >>>> +		   .of_match_table = rkcif_csi_of_match,
> >>>> +		   .pm = &rkcif_csi_pm_ops,
> >>>> +	},
> >>>> +	.probe = rkcif_csi_probe,
> >>>> +	.remove = rkcif_csi_remove,
> >>>> +};
> >>>> +module_platform_driver(rkcif_csi_drv);
> >>>> +
> >>>> +MODULE_DESCRIPTION("Rockchip MIPI CSI-2 Receiver platform driver");
> >>>> +MODULE_LICENSE("GPL");

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v6 06/13] media: rockchip: add a driver for the rockchip camera interface
  2025-05-06 20:32     ` Michael Riesch
@ 2025-05-07  9:36       ` Mehdi Djait
  2025-05-12  9:39         ` Michael Riesch
  0 siblings, 1 reply; 34+ messages in thread
From: Mehdi Djait @ 2025-05-07  9:36 UTC (permalink / raw)
  To: Michael Riesch, Laurent Pinchart, Hans Verkuil
  Cc: Maxime Chevallier, Théo Lebrun, Gerald Loacker,
	Thomas Petazzoni, Laurent Pinchart, Mauro Carvalho Chehab,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner,
	Kever Yang, Nicolas Dufresne, Sebastian Reichel,
	Collabora Kernel Team, Paul Kocialkowski, Alexander Shiyan,
	Val Packett, Rob Herring, Philipp Zabel, Sakari Ailus,
	linux-media, devicetree, linux-kernel, linux-arm-kernel,
	linux-rockchip, Michael Riesch, Mehdi Djait

Hi Michael,

On Tue, May 06, 2025 at 10:32:59PM +0200, Michael Riesch wrote:
> Hi Mehdi,
> 
> Thanks for your review!
> 
> On 5/6/25 12:37, Mehdi Djait wrote:
> > Hi Michael,
> > 
> > Thank you for the patch!
> > 
> > Is it possible to sent the v4l2-compliance output in the next version ?
> > 
> > On Wed, Apr 30, 2025 at 11:15:55AM +0200, Michael Riesch via B4 Relay wrote:
> >> From: Michael Riesch <michael.riesch@wolfvision.net>
> >>
> > 
> > SNIP
> > 
> >> +irqreturn_t rkcif_dvp_isr(int irq, void *ctx)
> >> +{
> >> +	struct device *dev = ctx;
> >> +	struct rkcif_device *rkcif = dev_get_drvdata(dev);
> >> +	struct rkcif_stream *stream;
> >> +	u32 intstat, lastline, lastpix, cif_frmst;
> >> +	irqreturn_t ret = IRQ_NONE;
> >> +
> >> +	if (!rkcif->match_data->dvp)
> >> +		return ret;
> >> +
> >> +	intstat = cif_dvp_read(rkcif, RKCIF_DVP_INTSTAT);
> >> +	cif_frmst = cif_dvp_read(rkcif, RKCIF_DVP_FRAME_STATUS);
> >> +	lastline = RKCIF_FETCH_Y(cif_dvp_read(rkcif, RKCIF_DVP_LAST_LINE));
> >> +	lastpix = RKCIF_FETCH_Y(cif_dvp_read(rkcif, RKCIF_DVP_LAST_PIX));
> >> +
> >> +	if (intstat & RKCIF_INTSTAT_FRAME_END) {
> >> +		cif_dvp_write(rkcif, RKCIF_DVP_INTSTAT,
> >> +			      RKCIF_INTSTAT_FRAME_END_CLR |
> >> +				      RKCIF_INTSTAT_LINE_END_CLR);
> >> +
> >> +		stream = &rkcif->interfaces[RKCIF_DVP].streams[RKCIF_ID0];
> >> +
> >> +		if (stream->stopping) {
> >> +			cif_dvp_stop_streaming(stream);
> >> +			wake_up(&stream->wq_stopped);
> >> +			return IRQ_HANDLED;
> >> +		}
> >> +
> >> +		if (lastline != stream->pix.height) {
> >> +			v4l2_err(&rkcif->v4l2_dev,
> >> +				 "bad frame, irq:%#x frmst:%#x size:%dx%d\n",
> >> +				 intstat, cif_frmst, lastpix, lastline);
> >> +
> >> +			cif_dvp_reset_stream(rkcif);
> >> +		}
> >> +
> >> +		rkcif_stream_pingpong(stream);
> >> +
> >> +		ret = IRQ_HANDLED;
> > 
> > just return IRQ_HANDLED like above ?
> 
> I think I'll go along Bryan's suggestion to make it more consistent.
> 
> > 
> >> +	}
> >> +
> >> +	return ret;
> >> +}
> >> +
> >> +int rkcif_dvp_register(struct rkcif_device *rkcif)
> >> +{
> >> +	struct rkcif_interface *interface;
> >> +	int ret, i;
> >> +
> >> +	if (!rkcif->match_data->dvp)
> >> +		return 0;
> >> +
> >> +	interface = &rkcif->interfaces[RKCIF_DVP];
> >> +	interface->index = RKCIF_DVP;
> >> +	interface->type = RKCIF_IF_DVP;
> >> +	interface->in_fmts = rkcif->match_data->dvp->in_fmts;
> >> +	interface->in_fmts_num = rkcif->match_data->dvp->in_fmts_num;
> >> +	interface->set_crop = rkcif_dvp_set_crop;
> >> +	ret = rkcif_interface_register(rkcif, interface);
> >> +	if (ret)
> >> +		return 0;
> > 		|
> > 		+-> Copy-paste error ?
> 
> Hm. It's not a mistake. But maybe it is a bit misleading.
> 
> The point here is that if something fails with registering the DVP, the
> driver may continue to register other entities, such as the MIPI capture
> thing.

what if you want to register the DVP interface and it fails ? Maybe two
separate function for rkcif_{dvp,mipi}_interface_register(), call one of
them based on match_data and verify the ret code --> fail if non-zero ?

> 
> I'll have another look over this mechanism and will try to make it more
> comprehensible.
> 
> > 
> >> +
> >> +	if (rkcif->match_data->dvp->setup)
> >> +		rkcif->match_data->dvp->setup(rkcif);
> >> +
> >> +	interface->streams_num = rkcif->match_data->dvp->has_ids ? 4 : 1;
> >> +	for (i = 0; i < interface->streams_num; i++) {
> >> +		struct rkcif_stream *stream = &interface->streams[i];
> >> +
> >> +		stream->id = i;
> >> +		stream->interface = interface;
> >> +		stream->out_fmts = rkcif->match_data->dvp->out_fmts;
> >> +		stream->out_fmts_num = rkcif->match_data->dvp->out_fmts_num;
> >> +		stream->queue_buffer = cif_dvp_queue_buffer;
> >> +		stream->start_streaming = cif_dvp_start_streaming;
> >> +		stream->stop_streaming = cif_dvp_stop_streaming;
> >> +
> >> +		ret = rkcif_stream_register(rkcif, stream);
> >> +		if (ret)
> >> +			goto err_streams_unregister;
> >> +	}
> >> +	return 0;
> >> +
> >> +err_streams_unregister:
> >> +	for (; i >= 0; i--)
> >> +		rkcif_stream_unregister(&interface->streams[i]);
> >> +	rkcif_interface_unregister(interface);
> >> +
> >> +	return ret;
> >> +}
> >> +
> > 
> > SNIP
> > 
> >> +static inline struct rkcif_buffer *to_rkcif_buffer(struct vb2_v4l2_buffer *vb)
> >> +{
> >> +	return container_of(vb, struct rkcif_buffer, vb);
> >> +}
> >> +
> >> +static inline struct rkcif_stream *to_rkcif_stream(struct video_device *vdev)
> >> +{
> >> +	return container_of(vdev, struct rkcif_stream, vdev);
> >> +}
> >> +
> >> +static struct rkcif_buffer *rkcif_stream_pop_buffer(struct rkcif_stream *stream)
> >> +{
> >> +	struct rkcif_buffer *buffer = NULL;
> >> +	unsigned long lock_flags;
> >> +
> >> +	spin_lock_irqsave(&stream->driver_queue_lock, lock_flags);
> > 
> > guard(spinlock_irqsave)(&stream->driver_queue_lock) will simplify this function.
> 
> I'll guard up these methods in v7.
> 
> > 
> >> +
> >> +	if (list_empty(&stream->driver_queue))
> >> +		goto err_empty;
> >> +
> >> +	buffer = list_first_entry(&stream->driver_queue, struct rkcif_buffer,
> >> +				  queue);
> >> +	list_del(&buffer->queue);
> >> +
> >> +err_empty:
> >> +	spin_unlock_irqrestore(&stream->driver_queue_lock, lock_flags);
> >> +	return buffer;
> >> +}
> >> +
> >> +static void rkcif_stream_push_buffer(struct rkcif_stream *stream,
> >> +				     struct rkcif_buffer *buffer)
> >> +{
> >> +	unsigned long lock_flags;
> >> +
> >> +	spin_lock_irqsave(&stream->driver_queue_lock, lock_flags);
> >> +	list_add_tail(&buffer->queue, &stream->driver_queue);
> >> +	spin_unlock_irqrestore(&stream->driver_queue_lock, lock_flags);
> >> +}
> >> +
> >> +static inline void rkcif_stream_return_buffer(struct rkcif_buffer *buffer,
> >> +					      enum vb2_buffer_state state)
> >> +{
> >> +	struct vb2_v4l2_buffer *vb = &buffer->vb;
> >> +
> >> +	vb2_buffer_done(&vb->vb2_buf, state);
> >> +}
> >> +
> >> +static void rkcif_stream_complete_buffer(struct rkcif_stream *stream,
> >> +					 struct rkcif_buffer *buffer)
> >> +{
> >> +	struct vb2_v4l2_buffer *vb = &buffer->vb;
> >> +
> >> +	vb->vb2_buf.timestamp = ktime_get_ns();
> >> +	vb->sequence = stream->frame_idx;
> >> +	vb2_buffer_done(&vb->vb2_buf, VB2_BUF_STATE_DONE);
> >> +	stream->frame_idx++;
> >> +}
> >> +
> >> +void rkcif_stream_pingpong(struct rkcif_stream *stream)
> >> +{
> >> +	struct rkcif_buffer *buffer;
> >> +
> >> +	buffer = stream->buffers[stream->frame_phase];
> >> +	if (!buffer->is_dummy)
> >> +		rkcif_stream_complete_buffer(stream, buffer);
> > 
> > You can actually keep this frame dropping mechanism without using the
> > dummy buffer.
> > 
> > You can set a drop flag to TRUE: keep overwriting the buffer you already have
> > without returning it to user-space until you can get another buffer, set
> > the flag again to FALSE and resume returning the buffers to user-space.
> 
> The approach you describe is what the downstream driver does and I am
> not really happy with it. A perfectly fine frame is sacrificed in a
> buffer starvation situation.

Oh I thought the downstream driver does it with the dummy buffer.

> 
> The approach in the patch series at hand follows the example in the
> rkisp1 driver, which should be a good reference.

Ack.

> 
> >> +
> >> +	buffer = rkcif_stream_pop_buffer(stream);
> >> +	if (buffer) {
> >> +		stream->buffers[stream->frame_phase] = buffer;
> >> +		stream->buffers[stream->frame_phase]->is_dummy = false;
> >> +	} else {
> >> +		stream->buffers[stream->frame_phase] = &stream->dummy.buffer;
> >> +		stream->buffers[stream->frame_phase]->is_dummy = true;
> >> +		dev_warn(stream->rkcif->dev,
> >> +			 "no buffer available, frame will be dropped\n");
> > 
> > This warning can quickly flood the kernel logs if the user-space is too slow in
> > enqueuing the buffers.
> 
> True. dev_warn_ratelimited(...)?
> 

Does frame dropping deserve a warning ? If you don't think so, maybe a
debug or info ?

> > 
> >> +	}
> >> +
> >> +	if (stream->queue_buffer)
> >> +		stream->queue_buffer(stream, stream->frame_phase);
> > 
> > is this if statement really needed ?
> 
> I find it good practice to check the callbacks before calling them. But
> this is a matter of taste, of course.
> 
> > 
> >> +
> >> +	stream->frame_phase = 1 - stream->frame_phase;
> >> +}
> >> +
> >> +static int rkcif_stream_init_buffers(struct rkcif_stream *stream)
> >> +{
> >> +	struct v4l2_pix_format_mplane *pix = &stream->pix;
> >> +	int i;
> >> +
> >> +	stream->buffers[0] = rkcif_stream_pop_buffer(stream);
> >> +	if (!stream->buffers[0])
> >> +		goto err_buff_0;
> >> +
> >> +	stream->buffers[1] = rkcif_stream_pop_buffer(stream);
> >> +	if (!stream->buffers[1])
> >> +		goto err_buff_1;
> >> +
> >> +	if (stream->queue_buffer) {
> >> +		stream->queue_buffer(stream, 0);
> >> +		stream->queue_buffer(stream, 1);
> >> +	}
> >> +
> >> +	stream->dummy.size = pix->num_planes * pix->plane_fmt[0].sizeimage;
> >> +	stream->dummy.vaddr =
> >> +		dma_alloc_attrs(stream->rkcif->dev, stream->dummy.size,
> >> +				&stream->dummy.buffer.buff_addr[0], GFP_KERNEL,
> >> +				DMA_ATTR_NO_KERNEL_MAPPING);
> >> +	if (!stream->dummy.vaddr)
> >> +		goto err_dummy;
> >> +
> >> +	for (i = 1; i < pix->num_planes; i++)
> >> +		stream->dummy.buffer.buff_addr[i] =
> >> +			stream->dummy.buffer.buff_addr[i - 1] +
> >> +			pix->plane_fmt[i - 1].bytesperline * pix->height;
> >> +
> >> +	return 0;
> >> +
> >> +err_dummy:
> >> +	rkcif_stream_return_buffer(stream->buffers[1], VB2_BUF_STATE_QUEUED);
> >> +	stream->buffers[1] = NULL;
> >> +
> >> +err_buff_1:
> >> +	rkcif_stream_return_buffer(stream->buffers[0], VB2_BUF_STATE_QUEUED);
> >> +	stream->buffers[0] = NULL;
> >> +err_buff_0:
> >> +	return -EINVAL;
> >> +}
> >> +
> > 
> > SNIP
> > 
> >> +static int rkcif_stream_init_vb2_queue(struct vb2_queue *q,
> >> +				       struct rkcif_stream *stream)
> >> +{
> >> +	q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
> >> +	q->io_modes = VB2_MMAP | VB2_DMABUF;
> >> +	q->drv_priv = stream;
> >> +	q->ops = &rkcif_stream_vb2_ops;
> >> +	q->mem_ops = &vb2_dma_contig_memops;
> >> +	q->buf_struct_size = sizeof(struct rkcif_buffer);
> >> +	q->min_queued_buffers = CIF_REQ_BUFS_MIN;
> > 
> > If I recall correctly min_queued_buffers should be the strict minimum
> > number of buffers you need to start streaming. So in this case it should
> > be 3 = 2 pingpong buffers + 1 dummy buffer.
> 
> The dummy buffer is allocated separately and does not need to be
> accounted for.
> 
> Two pingpong buffers is what the hardware can queue, but in practice, to
> start (and, above all, keep on) streaming you'll need more.
> 
> > VIDIOC_REQBUFS will allocate min_queued_buffers + 1 and user-space will
> > probably allocate even more anyway.
> 
> Is that so? I found that user space relies too much on this minimum
> buffer count and experienced several buffer starvation situations
> because kernel AND user space were to cheap in terms of buffer count.
> Maybe 8 is too many, but in practice four buffers are required at least
> for a decent 2160p stream (one ready for DMA write, one ongoing DMA
> write, one stable for processing (maybe DRM scanout or whatever the
> application is), one spare).
> 
> I am open to suggestions but please keep real life situations in mind
> and move away from theoretical stand-alone-capture-hw setups.

so the documentation says:
--------------------------------------------------------------------------
min_queued_buffers is used when a DMA engine cannot be started unless at
least this number of buffers have been queued into the driver.
--------------------------------------------------------------------------

and:
--------------------------------------------------------------------------
VIDIOC_REQBUFS will ensure at least @min_queued_buffers + 1
buffers will be allocated.
--------------------------------------------------------------------------

I also found theses patches:
https://lore.kernel.org/linux-media/20231211133251.150999-1-benjamin.gaignard@collabora.com/
https://lore.kernel.org/all/20241007124225.63463-1-jacopo.mondi@ideasonboard.com/

If I understood correctly there is a difference between:

- the minimal number of buffers to be allocated with VIDIOC_REQBUFS
- the minimal number of buffers to make it possible to start streaming

what you are setting is the latter, which means you need 8 buffers to
even start streaming which should not be the case for rkcif, which
should only need two (of course when using pingpong)

what you mentioned with the minimum number of buffers for a decent stream seems
to point more towards @min_reqbufs_allocation:
--------------------------------------------------------------------------
Drivers can set this if there has to be a certain number of
buffers available for the hardware to work effectively.
--------------------------------------------------------------------------

of course this being said I am not the expert here so feel free to ask
@Laurent @Hans

--
Kind Regards
Mehdi Djait

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

* Re: [PATCH v6 06/13] media: rockchip: add a driver for the rockchip camera interface
  2025-05-07  9:36       ` Mehdi Djait
@ 2025-05-12  9:39         ` Michael Riesch
  0 siblings, 0 replies; 34+ messages in thread
From: Michael Riesch @ 2025-05-12  9:39 UTC (permalink / raw)
  To: Mehdi Djait, Laurent Pinchart, Hans Verkuil
  Cc: Maxime Chevallier, Théo Lebrun, Gerald Loacker,
	Thomas Petazzoni, Mauro Carvalho Chehab, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner, Kever Yang,
	Nicolas Dufresne, Sebastian Reichel, Collabora Kernel Team,
	Paul Kocialkowski, Alexander Shiyan, Val Packett, Rob Herring,
	Philipp Zabel, Sakari Ailus, linux-media, devicetree,
	linux-kernel, linux-arm-kernel, linux-rockchip, Michael Riesch,
	Mehdi Djait

Hi Mehdi,

On 5/7/25 11:36, Mehdi Djait wrote:
> Hi Michael,
> 
> On Tue, May 06, 2025 at 10:32:59PM +0200, Michael Riesch wrote:
>> Hi Mehdi,
>>
>> Thanks for your review!
>>
>> On 5/6/25 12:37, Mehdi Djait wrote:
>>> Hi Michael,
>>>
>>> Thank you for the patch!
>>>
>>> Is it possible to sent the v4l2-compliance output in the next version ?

Missed that remark. Yes, I'll take care to send the output (maybe as
reply to v7, though).

>>>
>>> On Wed, Apr 30, 2025 at 11:15:55AM +0200, Michael Riesch via B4 Relay wrote:
>>>> From: Michael Riesch <michael.riesch@wolfvision.net>
>>>>
>>>
>>> SNIP
>>>
>>>> +irqreturn_t rkcif_dvp_isr(int irq, void *ctx)
>>>> +{
>>>> +	struct device *dev = ctx;
>>>> +	struct rkcif_device *rkcif = dev_get_drvdata(dev);
>>>> +	struct rkcif_stream *stream;
>>>> +	u32 intstat, lastline, lastpix, cif_frmst;
>>>> +	irqreturn_t ret = IRQ_NONE;
>>>> +
>>>> +	if (!rkcif->match_data->dvp)
>>>> +		return ret;
>>>> +
>>>> +	intstat = cif_dvp_read(rkcif, RKCIF_DVP_INTSTAT);
>>>> +	cif_frmst = cif_dvp_read(rkcif, RKCIF_DVP_FRAME_STATUS);
>>>> +	lastline = RKCIF_FETCH_Y(cif_dvp_read(rkcif, RKCIF_DVP_LAST_LINE));
>>>> +	lastpix = RKCIF_FETCH_Y(cif_dvp_read(rkcif, RKCIF_DVP_LAST_PIX));
>>>> +
>>>> +	if (intstat & RKCIF_INTSTAT_FRAME_END) {
>>>> +		cif_dvp_write(rkcif, RKCIF_DVP_INTSTAT,
>>>> +			      RKCIF_INTSTAT_FRAME_END_CLR |
>>>> +				      RKCIF_INTSTAT_LINE_END_CLR);
>>>> +
>>>> +		stream = &rkcif->interfaces[RKCIF_DVP].streams[RKCIF_ID0];
>>>> +
>>>> +		if (stream->stopping) {
>>>> +			cif_dvp_stop_streaming(stream);
>>>> +			wake_up(&stream->wq_stopped);
>>>> +			return IRQ_HANDLED;
>>>> +		}
>>>> +
>>>> +		if (lastline != stream->pix.height) {
>>>> +			v4l2_err(&rkcif->v4l2_dev,
>>>> +				 "bad frame, irq:%#x frmst:%#x size:%dx%d\n",
>>>> +				 intstat, cif_frmst, lastpix, lastline);
>>>> +
>>>> +			cif_dvp_reset_stream(rkcif);
>>>> +		}
>>>> +
>>>> +		rkcif_stream_pingpong(stream);
>>>> +
>>>> +		ret = IRQ_HANDLED;
>>>
>>> just return IRQ_HANDLED like above ?
>>
>> I think I'll go along Bryan's suggestion to make it more consistent.
>>
>>>
>>>> +	}
>>>> +
>>>> +	return ret;
>>>> +}
>>>> +
>>>> +int rkcif_dvp_register(struct rkcif_device *rkcif)
>>>> +{
>>>> +	struct rkcif_interface *interface;
>>>> +	int ret, i;
>>>> +
>>>> +	if (!rkcif->match_data->dvp)
>>>> +		return 0;
>>>> +
>>>> +	interface = &rkcif->interfaces[RKCIF_DVP];
>>>> +	interface->index = RKCIF_DVP;
>>>> +	interface->type = RKCIF_IF_DVP;
>>>> +	interface->in_fmts = rkcif->match_data->dvp->in_fmts;
>>>> +	interface->in_fmts_num = rkcif->match_data->dvp->in_fmts_num;
>>>> +	interface->set_crop = rkcif_dvp_set_crop;
>>>> +	ret = rkcif_interface_register(rkcif, interface);
>>>> +	if (ret)
>>>> +		return 0;
>>> 		|
>>> 		+-> Copy-paste error ?
>>
>> Hm. It's not a mistake. But maybe it is a bit misleading.
>>
>> The point here is that if something fails with registering the DVP, the
>> driver may continue to register other entities, such as the MIPI capture
>> thing.
> 
> what if you want to register the DVP interface and it fails ? Maybe two
> separate function for rkcif_{dvp,mipi}_interface_register(), call one of
> them based on match_data and verify the ret code --> fail if non-zero ?

Seems I prepared everything in rkcif-dev.c, but failed to complete it in
rkcif_{dvp,mipi}_capture :-/

rkcif_register() in rkcif-dev.c tolerates -ENODEV, so if e.g. DVP is not
available on a board, the function will proceed to call
rkcif_mipi_register. So we should return ret; here. Sounds reasonable?

> 
>>
>> I'll have another look over this mechanism and will try to make it more
>> comprehensible.
>>
>>>
>>>> +
>>>> +	if (rkcif->match_data->dvp->setup)
>>>> +		rkcif->match_data->dvp->setup(rkcif);
>>>> +
>>>> +	interface->streams_num = rkcif->match_data->dvp->has_ids ? 4 : 1;
>>>> +	for (i = 0; i < interface->streams_num; i++) {
>>>> +		struct rkcif_stream *stream = &interface->streams[i];
>>>> +
>>>> +		stream->id = i;
>>>> +		stream->interface = interface;
>>>> +		stream->out_fmts = rkcif->match_data->dvp->out_fmts;
>>>> +		stream->out_fmts_num = rkcif->match_data->dvp->out_fmts_num;
>>>> +		stream->queue_buffer = cif_dvp_queue_buffer;
>>>> +		stream->start_streaming = cif_dvp_start_streaming;
>>>> +		stream->stop_streaming = cif_dvp_stop_streaming;
>>>> +
>>>> +		ret = rkcif_stream_register(rkcif, stream);
>>>> +		if (ret)
>>>> +			goto err_streams_unregister;
>>>> +	}
>>>> +	return 0;
>>>> +
>>>> +err_streams_unregister:
>>>> +	for (; i >= 0; i--)
>>>> +		rkcif_stream_unregister(&interface->streams[i]);
>>>> +	rkcif_interface_unregister(interface);
>>>> +
>>>> +	return ret;
>>>> +}
>>>> +
>>>
>>> SNIP
>>>
>>>> +static inline struct rkcif_buffer *to_rkcif_buffer(struct vb2_v4l2_buffer *vb)
>>>> +{
>>>> +	return container_of(vb, struct rkcif_buffer, vb);
>>>> +}
>>>> +
>>>> +static inline struct rkcif_stream *to_rkcif_stream(struct video_device *vdev)
>>>> +{
>>>> +	return container_of(vdev, struct rkcif_stream, vdev);
>>>> +}
>>>> +
>>>> +static struct rkcif_buffer *rkcif_stream_pop_buffer(struct rkcif_stream *stream)
>>>> +{
>>>> +	struct rkcif_buffer *buffer = NULL;
>>>> +	unsigned long lock_flags;
>>>> +
>>>> +	spin_lock_irqsave(&stream->driver_queue_lock, lock_flags);
>>>
>>> guard(spinlock_irqsave)(&stream->driver_queue_lock) will simplify this function.
>>
>> I'll guard up these methods in v7.
>>
>>>
>>>> +
>>>> +	if (list_empty(&stream->driver_queue))
>>>> +		goto err_empty;
>>>> +
>>>> +	buffer = list_first_entry(&stream->driver_queue, struct rkcif_buffer,
>>>> +				  queue);
>>>> +	list_del(&buffer->queue);
>>>> +
>>>> +err_empty:
>>>> +	spin_unlock_irqrestore(&stream->driver_queue_lock, lock_flags);
>>>> +	return buffer;
>>>> +}
>>>> +
>>>> +static void rkcif_stream_push_buffer(struct rkcif_stream *stream,
>>>> +				     struct rkcif_buffer *buffer)
>>>> +{
>>>> +	unsigned long lock_flags;
>>>> +
>>>> +	spin_lock_irqsave(&stream->driver_queue_lock, lock_flags);
>>>> +	list_add_tail(&buffer->queue, &stream->driver_queue);
>>>> +	spin_unlock_irqrestore(&stream->driver_queue_lock, lock_flags);
>>>> +}
>>>> +
>>>> +static inline void rkcif_stream_return_buffer(struct rkcif_buffer *buffer,
>>>> +					      enum vb2_buffer_state state)
>>>> +{
>>>> +	struct vb2_v4l2_buffer *vb = &buffer->vb;
>>>> +
>>>> +	vb2_buffer_done(&vb->vb2_buf, state);
>>>> +}
>>>> +
>>>> +static void rkcif_stream_complete_buffer(struct rkcif_stream *stream,
>>>> +					 struct rkcif_buffer *buffer)
>>>> +{
>>>> +	struct vb2_v4l2_buffer *vb = &buffer->vb;
>>>> +
>>>> +	vb->vb2_buf.timestamp = ktime_get_ns();
>>>> +	vb->sequence = stream->frame_idx;
>>>> +	vb2_buffer_done(&vb->vb2_buf, VB2_BUF_STATE_DONE);
>>>> +	stream->frame_idx++;
>>>> +}
>>>> +
>>>> +void rkcif_stream_pingpong(struct rkcif_stream *stream)
>>>> +{
>>>> +	struct rkcif_buffer *buffer;
>>>> +
>>>> +	buffer = stream->buffers[stream->frame_phase];
>>>> +	if (!buffer->is_dummy)
>>>> +		rkcif_stream_complete_buffer(stream, buffer);
>>>
>>> You can actually keep this frame dropping mechanism without using the
>>> dummy buffer.
>>>
>>> You can set a drop flag to TRUE: keep overwriting the buffer you already have
>>> without returning it to user-space until you can get another buffer, set
>>> the flag again to FALSE and resume returning the buffers to user-space.
>>
>> The approach you describe is what the downstream driver does and I am
>> not really happy with it. A perfectly fine frame is sacrificed in a
>> buffer starvation situation.
> 
> Oh I thought the downstream driver does it with the dummy buffer.
> 
>>
>> The approach in the patch series at hand follows the example in the
>> rkisp1 driver, which should be a good reference.
> 
> Ack.

Just FWIW: after some discussions off-list I am not sure anymore that
the dummy buffer approach is a good idea. However, maybe we can defer
this -- this is something that can be changed anytime once the initial
driver is mainline.

> 
>>
>>>> +
>>>> +	buffer = rkcif_stream_pop_buffer(stream);
>>>> +	if (buffer) {
>>>> +		stream->buffers[stream->frame_phase] = buffer;
>>>> +		stream->buffers[stream->frame_phase]->is_dummy = false;
>>>> +	} else {
>>>> +		stream->buffers[stream->frame_phase] = &stream->dummy.buffer;
>>>> +		stream->buffers[stream->frame_phase]->is_dummy = true;
>>>> +		dev_warn(stream->rkcif->dev,
>>>> +			 "no buffer available, frame will be dropped\n");
>>>
>>> This warning can quickly flood the kernel logs if the user-space is too slow in
>>> enqueuing the buffers.
>>
>> True. dev_warn_ratelimited(...)?
>>
> 
> Does frame dropping deserve a warning ? If you don't think so, maybe a
> debug or info ?

_dbg sounds reasonable for that.

> 
>>>
>>>> +	}
>>>> +
>>>> +	if (stream->queue_buffer)
>>>> +		stream->queue_buffer(stream, stream->frame_phase);
>>>
>>> is this if statement really needed ?
>>
>> I find it good practice to check the callbacks before calling them. But
>> this is a matter of taste, of course.
>>
>>>
>>>> +
>>>> +	stream->frame_phase = 1 - stream->frame_phase;
>>>> +}
>>>> +
>>>> +static int rkcif_stream_init_buffers(struct rkcif_stream *stream)
>>>> +{
>>>> +	struct v4l2_pix_format_mplane *pix = &stream->pix;
>>>> +	int i;
>>>> +
>>>> +	stream->buffers[0] = rkcif_stream_pop_buffer(stream);
>>>> +	if (!stream->buffers[0])
>>>> +		goto err_buff_0;
>>>> +
>>>> +	stream->buffers[1] = rkcif_stream_pop_buffer(stream);
>>>> +	if (!stream->buffers[1])
>>>> +		goto err_buff_1;
>>>> +
>>>> +	if (stream->queue_buffer) {
>>>> +		stream->queue_buffer(stream, 0);
>>>> +		stream->queue_buffer(stream, 1);
>>>> +	}
>>>> +
>>>> +	stream->dummy.size = pix->num_planes * pix->plane_fmt[0].sizeimage;
>>>> +	stream->dummy.vaddr =
>>>> +		dma_alloc_attrs(stream->rkcif->dev, stream->dummy.size,
>>>> +				&stream->dummy.buffer.buff_addr[0], GFP_KERNEL,
>>>> +				DMA_ATTR_NO_KERNEL_MAPPING);
>>>> +	if (!stream->dummy.vaddr)
>>>> +		goto err_dummy;
>>>> +
>>>> +	for (i = 1; i < pix->num_planes; i++)
>>>> +		stream->dummy.buffer.buff_addr[i] =
>>>> +			stream->dummy.buffer.buff_addr[i - 1] +
>>>> +			pix->plane_fmt[i - 1].bytesperline * pix->height;
>>>> +
>>>> +	return 0;
>>>> +
>>>> +err_dummy:
>>>> +	rkcif_stream_return_buffer(stream->buffers[1], VB2_BUF_STATE_QUEUED);
>>>> +	stream->buffers[1] = NULL;
>>>> +
>>>> +err_buff_1:
>>>> +	rkcif_stream_return_buffer(stream->buffers[0], VB2_BUF_STATE_QUEUED);
>>>> +	stream->buffers[0] = NULL;
>>>> +err_buff_0:
>>>> +	return -EINVAL;
>>>> +}
>>>> +
>>>
>>> SNIP
>>>
>>>> +static int rkcif_stream_init_vb2_queue(struct vb2_queue *q,
>>>> +				       struct rkcif_stream *stream)
>>>> +{
>>>> +	q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
>>>> +	q->io_modes = VB2_MMAP | VB2_DMABUF;
>>>> +	q->drv_priv = stream;
>>>> +	q->ops = &rkcif_stream_vb2_ops;
>>>> +	q->mem_ops = &vb2_dma_contig_memops;
>>>> +	q->buf_struct_size = sizeof(struct rkcif_buffer);
>>>> +	q->min_queued_buffers = CIF_REQ_BUFS_MIN;
>>>
>>> If I recall correctly min_queued_buffers should be the strict minimum
>>> number of buffers you need to start streaming. So in this case it should
>>> be 3 = 2 pingpong buffers + 1 dummy buffer.
>>
>> The dummy buffer is allocated separately and does not need to be
>> accounted for.
>>
>> Two pingpong buffers is what the hardware can queue, but in practice, to
>> start (and, above all, keep on) streaming you'll need more.
>>
>>> VIDIOC_REQBUFS will allocate min_queued_buffers + 1 and user-space will
>>> probably allocate even more anyway.
>>
>> Is that so? I found that user space relies too much on this minimum
>> buffer count and experienced several buffer starvation situations
>> because kernel AND user space were to cheap in terms of buffer count.
>> Maybe 8 is too many, but in practice four buffers are required at least
>> for a decent 2160p stream (one ready for DMA write, one ongoing DMA
>> write, one stable for processing (maybe DRM scanout or whatever the
>> application is), one spare).
>>
>> I am open to suggestions but please keep real life situations in mind
>> and move away from theoretical stand-alone-capture-hw setups.
> 
> so the documentation says:
> --------------------------------------------------------------------------
> min_queued_buffers is used when a DMA engine cannot be started unless at
> least this number of buffers have been queued into the driver.
> --------------------------------------------------------------------------
> 
> and:
> --------------------------------------------------------------------------
> VIDIOC_REQBUFS will ensure at least @min_queued_buffers + 1
> buffers will be allocated.
> --------------------------------------------------------------------------
> 
> I also found theses patches:
> https://lore.kernel.org/linux-media/20231211133251.150999-1-benjamin.gaignard@collabora.com/
> https://lore.kernel.org/all/20241007124225.63463-1-jacopo.mondi@ideasonboard.com/
> 
> If I understood correctly there is a difference between:
> 
> - the minimal number of buffers to be allocated with VIDIOC_REQBUFS
> - the minimal number of buffers to make it possible to start streaming
> 
> what you are setting is the latter, which means you need 8 buffers to
> even start streaming which should not be the case for rkcif, which
> should only need two (of course when using pingpong)
> 
> what you mentioned with the minimum number of buffers for a decent stream seems
> to point more towards @min_reqbufs_allocation:
> --------------------------------------------------------------------------
> Drivers can set this if there has to be a certain number of
> buffers available for the hardware to work effectively.
> --------------------------------------------------------------------------
> 
> of course this being said I am not the expert here so feel free to ask
> @Laurent @Hans

Thanks a lot for digging out all this info. In particular, the pointer
to Jacopo's change to the rkisp1 is interesting. I feel kind of stupid
now because I stumbled over this exact issue when I tried to capture a
single frame with the rkisp1 driver.

So in general we should let user space decide, as user space knows best
about the exact application (one-shot capture, stream capture, stream
capture with extended postprocessing = deeper pipeline, ...).

And following Jacopo's reasoning for the rkisp1 we should set the value
to 1 here, as we also have a dummy buffer approach.

Best regards,
Michael

> 
> --
> Kind Regards
> Mehdi Djait


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

end of thread, other threads:[~2025-05-12  9:39 UTC | newest]

Thread overview: 34+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-04-30  9:15 [PATCH v6 00/13] media: rockchip: add a driver for the rockchip camera interface Michael Riesch via B4 Relay
2025-04-30  9:15 ` [PATCH v6 01/13] Documentation: admin-guide: media: add " Michael Riesch via B4 Relay
2025-04-30  9:42   ` Heiko Stübner
2025-04-30  9:15 ` [PATCH v6 02/13] media: dt-bindings: video-interfaces: add defines for sampling modes Michael Riesch via B4 Relay
2025-04-30  9:15 ` [PATCH v6 03/13] media: dt-bindings: add rockchip px30 vip Michael Riesch via B4 Relay
2025-04-30  9:15 ` [PATCH v6 04/13] media: dt-bindings: add rockchip rk3568 vicap Michael Riesch via B4 Relay
2025-05-01 11:49   ` Krzysztof Kozlowski
2025-04-30  9:15 ` [PATCH v6 05/13] media: dt-bindings: add rockchip rk3568 mipi csi receiver Michael Riesch via B4 Relay
2025-04-30 10:50   ` Rob Herring (Arm)
2025-05-06 12:09   ` Mehdi Djait
2025-05-06 19:54     ` Michael Riesch
2025-04-30  9:15 ` [PATCH v6 06/13] media: rockchip: add a driver for the rockchip camera interface Michael Riesch via B4 Relay
2025-05-01  0:43   ` Bryan O'Donoghue
2025-05-06 10:37   ` Mehdi Djait
2025-05-06 20:32     ` Michael Riesch
2025-05-07  9:36       ` Mehdi Djait
2025-05-12  9:39         ` Michael Riesch
2025-04-30  9:15 ` [PATCH v6 07/13] media: rockchip: rkcif: add driver for mipi csi-2 receiver Michael Riesch via B4 Relay
2025-05-01  0:29   ` Bryan O'Donoghue
2025-05-06 18:56     ` Michael Riesch
2025-05-01 21:57   ` Sakari Ailus
2025-05-02 13:31   ` Laurent Pinchart
2025-05-02 14:19     ` Michael Riesch
2025-05-02 14:35       ` Laurent Pinchart
2025-05-06 18:39         ` Michael Riesch
2025-05-07  8:38           ` Laurent Pinchart
2025-04-30  9:15 ` [PATCH v6 08/13] media: rockchip: rkcif: add support for mipi csi-2 capture Michael Riesch via B4 Relay
2025-05-01  0:48   ` Bryan O'Donoghue
2025-04-30  9:15 ` [PATCH v6 09/13] arm64: defconfig: enable rockchip camera interface Michael Riesch via B4 Relay
2025-05-01  0:47   ` Bryan O'Donoghue
2025-04-30  9:15 ` [PATCH v6 10/13] arm64: dts: rockchip: add the vip node to px30 Michael Riesch via B4 Relay
2025-04-30  9:16 ` [PATCH v6 11/13] arm64: dts: rockchip: add vicap node to rk356x Michael Riesch via B4 Relay
2025-04-30  9:16 ` [PATCH v6 12/13] arm64: dts: rockchip: add mipi csi receiver " Michael Riesch via B4 Relay
2025-04-30  9:16 ` [PATCH v6 13/13] arm64: dts: rockchip: enable vicap dvp on wolfvision pf5 io expander Michael Riesch via B4 Relay

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).