linux-media.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v4 0/3] Add Input Video Control Block driver for RZ/V2H
@ 2025-07-14 15:19 Daniel Scally
  2025-07-14 15:19 ` [PATCH v4 1/3] dt-bindings: media: Add bindings for the RZ/V2H IVC block Daniel Scally
                   ` (2 more replies)
  0 siblings, 3 replies; 12+ messages in thread
From: Daniel Scally @ 2025-07-14 15:19 UTC (permalink / raw)
  To: linux-media, devicetree, linux-renesas-soc
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Geert Uytterhoeven, Magnus Damm, Philipp Zabel, jacopo.mondi,
	biju.das.jz, laurent.pinchart, Krzysztof Kozlowski, Daniel Scally

Hello all

This series adds a driver for the Input Video Control Block in the
RZ/V2H SoC. The IVC block transmits input image data from memory to
the ISP core (on this SoC, a Mali-C55 ISP). The driver registers an
output video device for userspace to queue image buffers to. One
noteworthy feature is that - because it is not a part of the main ISP
drive - the IVC driver also registers a subdevice, which connects to
the media device created by the ISP driver through the usual v4l2
async framework. This requires delaying the registration of the video
device until the .registered() callback of the subdevice, so that the
struct v4l2_dev pointer the subdevice connected to can be set to the
video device.

This version of the driver drops the reliance on the new media
framework that was posted recently [1], so can be merged without it
and updated later. The series is also based on top of the latest
version of the Mali-C55 driver [2].

Thanks
Dan

[1] https://lore.kernel.org/linux-media/20250624-media-jobs-v2-0-8e649b069a96@ideasonboard.com/T/#t
[2] https://lore.kernel.org/linux-media/20250714-c55-v11-0-bc20e460e42a@ideasonboard.com/

---
Changes in v4:
- Dropped the mc / V4L2 helper patches - they're in the C55 series instead, the aim
  being to avoid creating a circular dependency between the two sets
- Link to v3: https://lore.kernel.org/r/20250704-ivc-v3-0-5c45d936ef2e@ideasonboard.com

Changes in v3:
- Added two new patches that create helpers in V4L2 and mc core that
  the driver then consumes.

- Link to v2: https://lore.kernel.org/r/20250624-ivc-v2-0-e4ecdddb0a96@ideasonboard.com

The rest of this message is the v4l2-compliance report for the video
device and subdevice:

v4l2-compliance 1.31.0-5380, 64 bits, 64-bit time_t
v4l2-compliance SHA: 1cc84dfb41d8 2025-07-08 08:28:16

Compliance test for device /dev/v4l-subdev3:

Driver Info:
        Driver version   : 6.15.0
        Capabilities     : 0x00000000
        Client Capabilities: 0x0000000000000003
streams interval-uses-which 
Required ioctls:
        test VIDIOC_SUDBEV_QUERYCAP: OK
        test invalid ioctls: OK

Allow for multiple opens:
        test second /dev/v4l-subdev3 open: OK
        test VIDIOC_SUBDEV_QUERYCAP: OK
        test for unlimited opens: OK

Debug ioctls:
        test VIDIOC_LOG_STATUS: OK (Not Supported)

Input ioctls:
        test VIDIOC_G/S_TUNER/ENUM_FREQ_BANDS: OK (Not Supported)
        test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
        test VIDIOC_S_HW_FREQ_SEEK: OK (Not Supported)
        test VIDIOC_ENUMAUDIO: OK (Not Supported)
        test VIDIOC_G/S/ENUMINPUT: OK (Not Supported)
        test VIDIOC_G/S_AUDIO: OK (Not Supported)
        Inputs: 0 Audio Inputs: 0 Tuners: 0

Output ioctls:
        test VIDIOC_G/S_MODULATOR: OK (Not Supported)
        test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
        test VIDIOC_ENUMAUDOUT: OK (Not Supported)
        test VIDIOC_G/S/ENUMOUTPUT: OK (Not Supported)
        test VIDIOC_G/S_AUDOUT: OK (Not Supported)
        Outputs: 0 Audio Outputs: 0 Modulators: 0

Input/Output configuration ioctls:
        test VIDIOC_ENUM/G/S/QUERY_STD: OK (Not Supported)
        test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: OK (Not Supported)
        test VIDIOC_DV_TIMINGS_CAP: OK (Not Supported)
        test VIDIOC_G/S_EDID: OK (Not Supported)

Control ioctls:
        test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: OK (Not Supported)
        test VIDIOC_QUERYCTRL: OK (Not Supported)
        test VIDIOC_G/S_CTRL: OK (Not Supported)
        test VIDIOC_G/S/TRY_EXT_CTRLS: OK (Not Supported)
        test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK (Not Supported)
        test VIDIOC_G/S_JPEGCOMP: OK (Not Supported)
        Standard Controls: 0 Private Controls: 0

Format ioctls:
        test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK (Not Supported)
        test VIDIOC_G/S_PARM: OK (Not Supported)
        test VIDIOC_G_FBUF: OK (Not Supported)
        test VIDIOC_G_FMT: OK (Not Supported)
        test VIDIOC_TRY_FMT: OK (Not Supported)
        test VIDIOC_S_FMT: OK (Not Supported)
        test VIDIOC_G_SLICED_VBI_CAP: OK (Not Supported)
        test Cropping: OK (Not Supported)
        test Composing: OK (Not Supported)
        test Scaling: OK (Not Supported)

Codec ioctls:
        test VIDIOC_(TRY_)ENCODER_CMD: OK (Not Supported)
        test VIDIOC_G_ENC_INDEX: OK (Not Supported)
        test VIDIOC_(TRY_)DECODER_CMD: OK (Not Supported)

Buffer ioctls:
        test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK (Not Supported)
        test CREATE_BUFS maximum buffers: OK
        test VIDIOC_REMOVE_BUFS: OK
        test VIDIOC_EXPBUF: OK (Not Supported)
        test Requests: OK (Not Supported)
        test blocking wait: OK (Not Supported)

Total for device /dev/v4l-subdev3: 46, Succeeded: 46, Failed: 0, Warnings: 0

v4l2-compliance 1.31.0-5380, 64 bits, 64-bit time_t
v4l2-compliance SHA: 1cc84dfb41d8 2025-07-08 08:28:16

Compliance test for rzv2h-ivc device /dev/video3:

Driver Info:
        Driver name      : rzv2h-ivc
        Card type        : Renesas Input Video Control
        Bus info         : platform:16080000.isp
        Driver version   : 6.15.0
        Capabilities     : 0x84202000
                Video Output Multiplanar
                Streaming
                Extended Pix Format
                Device Capabilities
        Device Caps      : 0x04202000
                Video Output Multiplanar
                Streaming
                Extended Pix Format
Media Driver Info:
        Driver name      : mali-c55
        Model            : ARM Mali-C55 ISP
        Serial           : 
        Bus info         : platform:16080000.isp
        Media version    : 6.15.0
        Hardware revision: 0x01d982d6 (31032022)
        Driver version   : 6.15.0
Interface Info:
        ID               : 0x0300002a
        Type             : V4L Video
Entity Info:
        ID               : 0x00000028 (40)
        Name             : rzv2h-ivc
        Function         : V4L2 I/O
        Pad 0x01000029   : 0: Source
          Link 0x0200002c: to remote pad 0x1000026 of entity 'rzv2h ivc block' (Video Pixel Formatter): Data, Enabled, Immutable

Required ioctls:
        test MC information (see 'Media Driver Info' above): OK
        test VIDIOC_QUERYCAP: OK
        test invalid ioctls: OK

Allow for multiple opens:
        test second /dev/video3 open: OK
        test VIDIOC_QUERYCAP: OK
        test VIDIOC_G/S_PRIORITY: OK
        test for unlimited opens: OK

Debug ioctls:
        test VIDIOC_DBG_G/S_REGISTER: OK (Not Supported)
        test VIDIOC_LOG_STATUS: OK (Not Supported)

Input ioctls:
        test VIDIOC_G/S_TUNER/ENUM_FREQ_BANDS: OK (Not Supported)
        test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
        test VIDIOC_S_HW_FREQ_SEEK: OK (Not Supported)
        test VIDIOC_ENUMAUDIO: OK (Not Supported)
        test VIDIOC_G/S/ENUMINPUT: OK (Not Supported)
        test VIDIOC_G/S_AUDIO: OK (Not Supported)
        Inputs: 0 Audio Inputs: 0 Tuners: 0

Output ioctls:
        test VIDIOC_G/S_MODULATOR: OK (Not Supported)
        test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
        test VIDIOC_ENUMAUDOUT: OK (Not Supported)
        test VIDIOC_G/S/ENUMOUTPUT: OK (Not Supported)
        test VIDIOC_G/S_AUDOUT: OK (Not Supported)
        Outputs: 0 Audio Outputs: 0 Modulators: 0

Input/Output configuration ioctls:
        test VIDIOC_ENUM/G/S/QUERY_STD: OK (Not Supported)
        test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: OK (Not Supported)
        test VIDIOC_DV_TIMINGS_CAP: OK (Not Supported)
        test VIDIOC_G/S_EDID: OK (Not Supported)

Control ioctls:
        test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: OK (Not Supported)
        test VIDIOC_QUERYCTRL: OK (Not Supported)
        test VIDIOC_G/S_CTRL: OK (Not Supported)
        test VIDIOC_G/S/TRY_EXT_CTRLS: OK (Not Supported)
        test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK (Not Supported)
        test VIDIOC_G/S_JPEGCOMP: OK (Not Supported)
        Standard Controls: 0 Private Controls: 0

Format ioctls:
        test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK
        test VIDIOC_G/S_PARM: OK (Not Supported)
        test VIDIOC_G_FBUF: OK (Not Supported)
        test VIDIOC_G_FMT: OK
        test VIDIOC_TRY_FMT: OK
        test VIDIOC_S_FMT: OK
        test VIDIOC_G_SLICED_VBI_CAP: OK (Not Supported)
        test Cropping: OK (Not Supported)
        test Composing: OK (Not Supported)
        test Scaling: OK

Codec ioctls:
        test VIDIOC_(TRY_)ENCODER_CMD: OK (Not Supported)
        test VIDIOC_G_ENC_INDEX: OK (Not Supported)
        test VIDIOC_(TRY_)DECODER_CMD: OK (Not Supported)

Buffer ioctls:
        test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK
        test CREATE_BUFS maximum buffers: OK
        test VIDIOC_REMOVE_BUFS: OK
        test VIDIOC_EXPBUF: OK
        test Requests: OK (Not Supported)
        test blocking wait: OK

Total for rzv2h-ivc device /dev/video3: 49, Succeeded: 49, Failed: 0, Warnings: 0

To: linux-media@vger.kernel.org
To: devicetree@vger.kernel.org
To: linux-renesas-soc@vger.kernel.org
Cc: Rob Herring <robh@kernel.org>
Cc: Krzysztof Kozlowski <krzk+dt@kernel.org>
Cc: Conor Dooley <conor+dt@kernel.org>
Cc: Geert Uytterhoeven <geert+renesas@glider.be>
Cc: Magnus Damm <magnus.damm@gmail.com>
Cc: Philipp Zabel <p.zabel@pengutronix.de>
Cc: jacopo.mondi@ideasonboard.com
Cc: biju.das.jz@bp.renesas.com
Cc: laurent.pinchart@ideasonboard.com

---
Daniel Scally (3):
      dt-bindings: media: Add bindings for the RZ/V2H IVC block
      media: platform: Add Renesas Input Video Control block driver
      MAINTAINERS: Add entry for rzv2h-ivc driver

 .../bindings/media/renesas,r9a09g057-ivc.yaml      | 103 ++++
 MAINTAINERS                                        |   7 +
 drivers/media/platform/renesas/Kconfig             |   1 +
 drivers/media/platform/renesas/Makefile            |   1 +
 drivers/media/platform/renesas/rzv2h-ivc/Kconfig   |  18 +
 drivers/media/platform/renesas/rzv2h-ivc/Makefile  |   5 +
 .../platform/renesas/rzv2h-ivc/rzv2h-ivc-dev.c     | 229 +++++++++
 .../platform/renesas/rzv2h-ivc/rzv2h-ivc-subdev.c  | 376 ++++++++++++++
 .../platform/renesas/rzv2h-ivc/rzv2h-ivc-video.c   | 568 +++++++++++++++++++++
 .../media/platform/renesas/rzv2h-ivc/rzv2h-ivc.h   | 131 +++++
 10 files changed, 1439 insertions(+)
---
base-commit: a8598c7de1bcd94461ca54c972efa9b4ea501fb9
change-id: 20250624-ivc-833d24376167
prerequisite-change-id: 20250701-extensible-parameters-validation-c831f7f5cc0b:v2
prerequisite-patch-id: eedc860a79e00a7b875b51c1b27369b7d9343ea7
prerequisite-patch-id: 92fbff7664257963606b4b528a9ce517462a9b6c
prerequisite-patch-id: e7986bc6bf23aced55153711a840568810638aed
prerequisite-patch-id: b5e5b4110ff198562face15fdb99813d3792ebe1
prerequisite-patch-id: e6ebc9baad32ef8286b05f9ebc77b25b2c53d39c
prerequisite-patch-id: 2b002f36ec01afc3571f19cf6b6474e59cd8ed65
prerequisite-patch-id: 64d6914de4c8c877567752ba4fec82328a8a9ea9
prerequisite-patch-id: 35f7062835d60a07930bc8733b3f47771f68c074
prerequisite-change-id: 20250624-c55-b3c36b2e1d8c:v11
prerequisite-patch-id: eedc860a79e00a7b875b51c1b27369b7d9343ea7
prerequisite-patch-id: 92fbff7664257963606b4b528a9ce517462a9b6c
prerequisite-patch-id: e7986bc6bf23aced55153711a840568810638aed
prerequisite-patch-id: b5e5b4110ff198562face15fdb99813d3792ebe1
prerequisite-patch-id: e6ebc9baad32ef8286b05f9ebc77b25b2c53d39c
prerequisite-patch-id: 2b002f36ec01afc3571f19cf6b6474e59cd8ed65
prerequisite-patch-id: 64d6914de4c8c877567752ba4fec82328a8a9ea9
prerequisite-patch-id: 35f7062835d60a07930bc8733b3f47771f68c074
prerequisite-patch-id: 833a35b9ac1ed96d059b3d4ba779593b0e5b156e
prerequisite-patch-id: 17de205479b4594a017ba8e444b7f5629d85b380
prerequisite-patch-id: ecc5483454fc52289c093e711d5423e1cdd8bc3b
prerequisite-patch-id: 1aea6316a2a4a7b56316dbef3ca6034de6ec1672
prerequisite-patch-id: e48d41a6a7af49983fa9b34499e25ee4657d5af2
prerequisite-patch-id: e9aa66a8455fc64be546098dcb0573567ca0a389
prerequisite-patch-id: e3d958df2440718af06d00ba377126fd9179db1b
prerequisite-patch-id: 19226131d738efc4319fe493e68d8287936670c0
prerequisite-patch-id: 03df7faabe3da3b07bb69127206f1123ea6d601b
prerequisite-patch-id: dff49267a0db686172ae90cee86ad82af985b292
prerequisite-patch-id: 8b5b0ff8043abbe1eb15c005697a91171365e272
prerequisite-patch-id: 67c6aaf1985cc437c3a82ab88e1b5fbd14bb0737
prerequisite-patch-id: 09c97e68cbd196d71d289ba2ee71ad6752f7f03d
prerequisite-patch-id: db4133370a99861b1ec50eea46b13797473cab22
prerequisite-patch-id: 7843ebd2523bcdbdcd8446a2a5abac6d1a8f85c1
prerequisite-patch-id: 8f4c5c8f7aca170aa951b0ce02a6720a47a231da
prerequisite-patch-id: 704ce2c3f16eafb4ec974d9b6b574fe58991313a
prerequisite-patch-id: b0683fafcd57b8a64c40910b9795370748a46a84
prerequisite-patch-id: ba97bc1b7d997001ba18b7ba3a1a80009d5625d4

Best regards,
-- 
Daniel Scally <dan.scally@ideasonboard.com>


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

* [PATCH v4 1/3] dt-bindings: media: Add bindings for the RZ/V2H IVC block
  2025-07-14 15:19 [PATCH v4 0/3] Add Input Video Control Block driver for RZ/V2H Daniel Scally
@ 2025-07-14 15:19 ` Daniel Scally
  2025-07-24 15:55   ` Lad, Prabhakar
  2025-07-14 15:19 ` [PATCH v4 2/3] media: platform: Add Renesas Input Video Control block driver Daniel Scally
  2025-07-14 15:19 ` [PATCH v4 3/3] MAINTAINERS: Add entry for rzv2h-ivc driver Daniel Scally
  2 siblings, 1 reply; 12+ messages in thread
From: Daniel Scally @ 2025-07-14 15:19 UTC (permalink / raw)
  To: linux-media, devicetree, linux-renesas-soc
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Geert Uytterhoeven, Magnus Damm, Philipp Zabel, jacopo.mondi,
	biju.das.jz, laurent.pinchart, Krzysztof Kozlowski, Daniel Scally

The RZ/V2H SoC has a block called the Input Video Control block which
feeds image data into the Image Signal Processor. Add dt bindings to
describe the IVC.

Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com>
---
Changes in v3:

	- Rename from rzv2h-ivc.yaml to r9a09g057-ivc.yaml
	- Update clock and reset names

Changes in v2:

	- compatible matches filename
	- Added power-domains
	- Aligned clock and reset entries on opening "<"
	- Removed status = "okay"; from example
---
 .../bindings/media/renesas,r9a09g057-ivc.yaml      | 103 +++++++++++++++++++++
 1 file changed, 103 insertions(+)

diff --git a/Documentation/devicetree/bindings/media/renesas,r9a09g057-ivc.yaml b/Documentation/devicetree/bindings/media/renesas,r9a09g057-ivc.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..3ecccf0a4b05ffcf90c130924bfe50065b06f87e
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/renesas,r9a09g057-ivc.yaml
@@ -0,0 +1,103 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/renesas,r9a09g057-ivc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Renesas RZ/V2H Input Video Control Block
+
+maintainers:
+  - Daniel Scally <dan.scally@ideasonboard.com>
+
+description:
+  The IVC block is a module that takes video frames from memory and feeds them
+  to the Image Signal Processor for processing.
+
+properties:
+  compatible:
+    const: renesas,r9a09g057-ivc
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+
+  clocks:
+    items:
+      - description: Input Video Control block register access clock
+      - description: Video input data AXI bus clock
+      - description: ISP system clock
+
+  clock-names:
+    items:
+      - const: reg
+      - const: axi
+      - const: isp
+
+  power-domains:
+    maxItems: 1
+
+  resets:
+    items:
+      - description: Input Video Control block register access reset
+      - description: Video input data AXI bus reset
+      - description: ISP core reset
+
+  reset-names:
+    items:
+      - const: reg
+      - const: axi
+      - const: isp
+
+  port:
+    $ref: /schemas/graph.yaml#/properties/port
+    description: Output parallel video bus
+
+    properties:
+      endpoint:
+        $ref: /schemas/graph.yaml#/properties/endpoint
+
+required:
+  - compatible
+  - reg
+  - interrupts
+  - clocks
+  - clock-names
+  - power-domains
+  - resets
+  - reset-names
+  - port
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/clock/renesas,r9a09g057-cpg.h>
+    #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+    isp-input@16040000 {
+      compatible = "renesas,r9a09g057-ivc";
+      reg = <0x16040000 0x230>;
+
+      clocks = <&cpg CPG_MOD 0xe3>,
+               <&cpg CPG_MOD 0xe4>,
+               <&cpg CPG_MOD 0xe5>;
+      clock-names = "reg", "axi", "isp";
+
+      power-domains = <&cpg>;
+
+      resets = <&cpg 0xd4>,
+               <&cpg 0xd1>,
+               <&cpg 0xd3>;
+      reset-names = "reg", "axi", "isp";
+
+      interrupts = <GIC_SPI 861 IRQ_TYPE_EDGE_RISING>;
+
+      port {
+        ivc_out: endpoint {
+          remote-endpoint = <&isp_in>;
+        };
+      };
+    };
+...

-- 
2.34.1


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

* [PATCH v4 2/3] media: platform: Add Renesas Input Video Control block driver
  2025-07-14 15:19 [PATCH v4 0/3] Add Input Video Control Block driver for RZ/V2H Daniel Scally
  2025-07-14 15:19 ` [PATCH v4 1/3] dt-bindings: media: Add bindings for the RZ/V2H IVC block Daniel Scally
@ 2025-07-14 15:19 ` Daniel Scally
  2025-07-22  9:40   ` Jacopo Mondi
                     ` (2 more replies)
  2025-07-14 15:19 ` [PATCH v4 3/3] MAINTAINERS: Add entry for rzv2h-ivc driver Daniel Scally
  2 siblings, 3 replies; 12+ messages in thread
From: Daniel Scally @ 2025-07-14 15:19 UTC (permalink / raw)
  To: linux-media, devicetree, linux-renesas-soc
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Geert Uytterhoeven, Magnus Damm, Philipp Zabel, jacopo.mondi,
	biju.das.jz, laurent.pinchart, Daniel Scally

Add a driver for the Input Video Control block in an RZ/V2H SoC which
feeds data into the Arm Mali-C55 ISP.

Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com>
---
Changes in v5:

	- Fixed .enum_frame_sizes() to properly check that the
	  given mbus_code matches the source pads format.
	- Tidy up extra space in Kconfig
	- Revise Kconfig option message
	- Don't mark functions inline
	- Fixup misleading comment
	- select CONFIG_PM
	- Use the new pm_sleep_ptr() functionality
	- Minor formatting

Changes in v4:

	- Update the compatible to renesas,r9a09g057-ivc
	- Dropped the media jobs / scheduler functionality, and re
	  worked the driver to have its own workqueue pushing frames
	- Fix .enum_mbus_code() to return 20-bit output for source
	  pad.
	- Fix some alignment issues
	- Make the forwarding of sink to source pad format a more
	  explicit operation.
	- Rename rzv2h_initialise_video_device_and_queue()
	- Reversed order of v4l2_subdev_init_finalize() and
	  v4l2_async_register_subdev() to make sure everything is
	  finished initialising before registering the subdev.
	- Change function to MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER
	- Use a parametised macro for min vblank
	- Minor formatting
	- Use the DEFAULT macros for quantization / ycbcr_enc values
	- Switch to using the mplane API
	- Dropped select RESET_CONTROLLER
	- Used the new helpers for starting a media pipeline
	- Switch from threaded irq to normal with driver workqueue
	  and revised startup routine

Changes in v3:

	- Account for the renamed CRU pixel formats

Changes in v2:

	- Added selects and depends statements to Kconfig entry
	- Fixed copyright year
	- Stopped including in .c files headers already included in .h
	- Fixed uninitialized variable in iterator
	- Only check vvalid member in interrupt function and wait
	  unconditionally elsewhere
	- __maybe_unused for the PM ops
	- Initialise the subdevice after setting up PM
	- Fixed the remove function for the driver to actually do
	  something.
	- Some minor formatting changes
	- Fixed the quantization member for the format
	- Changes accounting for the v2 of the media jobs framework
	- Change min_queued_buffers to 0
---
 drivers/media/platform/renesas/Kconfig             |   1 +
 drivers/media/platform/renesas/Makefile            |   1 +
 drivers/media/platform/renesas/rzv2h-ivc/Kconfig   |  18 +
 drivers/media/platform/renesas/rzv2h-ivc/Makefile  |   5 +
 .../platform/renesas/rzv2h-ivc/rzv2h-ivc-dev.c     | 229 +++++++++
 .../platform/renesas/rzv2h-ivc/rzv2h-ivc-subdev.c  | 376 ++++++++++++++
 .../platform/renesas/rzv2h-ivc/rzv2h-ivc-video.c   | 568 +++++++++++++++++++++
 .../media/platform/renesas/rzv2h-ivc/rzv2h-ivc.h   | 131 +++++
 8 files changed, 1329 insertions(+)

diff --git a/drivers/media/platform/renesas/Kconfig b/drivers/media/platform/renesas/Kconfig
index 27a54fa7908384f2e8200f0f7283a82b0ae8435c..bd8247c0b8aa734d2b412438e694f3908d910b25 100644
--- a/drivers/media/platform/renesas/Kconfig
+++ b/drivers/media/platform/renesas/Kconfig
@@ -42,6 +42,7 @@ config VIDEO_SH_VOU
 source "drivers/media/platform/renesas/rcar-isp/Kconfig"
 source "drivers/media/platform/renesas/rcar-vin/Kconfig"
 source "drivers/media/platform/renesas/rzg2l-cru/Kconfig"
+source "drivers/media/platform/renesas/rzv2h-ivc/Kconfig"
 
 # Mem2mem drivers
 
diff --git a/drivers/media/platform/renesas/Makefile b/drivers/media/platform/renesas/Makefile
index 1127259c09d6a51b70803e76c495918e06777f67..b6b4abf01db246aaf8269b8027efee9b0b32083a 100644
--- a/drivers/media/platform/renesas/Makefile
+++ b/drivers/media/platform/renesas/Makefile
@@ -6,6 +6,7 @@
 obj-y += rcar-isp/
 obj-y += rcar-vin/
 obj-y += rzg2l-cru/
+obj-y += rzv2h-ivc/
 obj-y += vsp1/
 
 obj-$(CONFIG_VIDEO_RCAR_CSI2) += rcar-csi2.o
diff --git a/drivers/media/platform/renesas/rzv2h-ivc/Kconfig b/drivers/media/platform/renesas/rzv2h-ivc/Kconfig
new file mode 100644
index 0000000000000000000000000000000000000000..5a4a3c052a3ae0f242e844689132d91a75b8a302
--- /dev/null
+++ b/drivers/media/platform/renesas/rzv2h-ivc/Kconfig
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config VIDEO_RZV2H_IVC
+	tristate "Renesas RZ/V2H Input Video Control block driver"
+	depends on V4L_PLATFORM_DRIVERS
+	depends on VIDEO_DEV
+	depends on ARCH_RENESAS || COMPILE_TEST
+	depends on OF
+	select CONFIG_PM
+	select VIDEOBUF2_DMA_CONTIG
+	select MEDIA_CONTROLLER
+	select VIDEO_V4L2_SUBDEV_API
+	help
+          Support for the Renesas RZ/V2H Input Video Control Block
+          (IVC).
+
+          To compile this driver as a module, choose M here: the
+          module will be called rzv2h-ivc.
diff --git a/drivers/media/platform/renesas/rzv2h-ivc/Makefile b/drivers/media/platform/renesas/rzv2h-ivc/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..080ee3570f09c236d87abeaea5d8dd578fefb6d3
--- /dev/null
+++ b/drivers/media/platform/renesas/rzv2h-ivc/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0
+
+rzv2h-ivc-y := rzv2h-ivc-dev.o rzv2h-ivc-subdev.o rzv2h-ivc-video.o
+
+obj-$(CONFIG_VIDEO_RZV2H_IVC) += rzv2h-ivc.o
diff --git a/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-dev.c b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-dev.c
new file mode 100644
index 0000000000000000000000000000000000000000..ce2e3a3af8d19900241add7d261f7a40f2551265
--- /dev/null
+++ b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-dev.c
@@ -0,0 +1,229 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Renesas RZ/V2H Input Video Control Block driver
+ *
+ * Copyright (C) 2025 Ideas on Board Oy
+ */
+
+#include "rzv2h-ivc.h"
+
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/reset.h>
+
+void rzv2h_ivc_write(struct rzv2h_ivc *ivc, u32 addr, u32 val)
+{
+	writel(val, ivc->base + addr);
+}
+
+void rzv2h_ivc_update_bits(struct rzv2h_ivc *ivc, unsigned int addr,
+			   u32 mask, u32 val)
+{
+	u32 orig, new;
+
+	orig = readl(ivc->base + addr);
+
+	new = orig & ~mask;
+	new |= val & mask;
+
+	if (new != orig)
+		writel(new, ivc->base + addr);
+}
+
+static int rzv2h_ivc_get_hardware_resources(struct rzv2h_ivc *ivc,
+					    struct platform_device *pdev)
+{
+	const char * const resource_names[RZV2H_IVC_NUM_HW_RESOURCES] = {
+		"reg",
+		"axi",
+		"isp",
+	};
+	struct resource *res;
+	int ret;
+
+	ivc->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+	if (IS_ERR(ivc->base))
+		return dev_err_probe(ivc->dev, PTR_ERR(ivc->base),
+				     "failed to map IO memory\n");
+
+	for (unsigned int i = 0; i < ARRAY_SIZE(resource_names); i++)
+		ivc->clks[i].id = resource_names[i];
+
+	ret = devm_clk_bulk_get(ivc->dev, ARRAY_SIZE(resource_names), ivc->clks);
+	if (ret)
+		return dev_err_probe(ivc->dev, ret, "failed to acquire clks\n");
+
+	for (unsigned int i = 0; i < ARRAY_SIZE(resource_names); i++)
+		ivc->resets[i].id = resource_names[i];
+
+	ret = devm_reset_control_bulk_get_optional_shared(
+		ivc->dev, ARRAY_SIZE(resource_names), ivc->resets);
+	if (ret)
+		return dev_err_probe(ivc->dev, ret, "failed to acquire resets\n");
+
+	return 0;
+}
+
+static void rzv2h_ivc_global_config(struct rzv2h_ivc *ivc)
+{
+	/* Currently we only support single-exposure input */
+	rzv2h_ivc_write(ivc, RZV2H_IVC_REG_AXIRX_PLNUM, RZV2H_IVC_ONE_EXPOSURE);
+
+	/*
+	 * Datasheet says we should disable the interrupts before changing mode
+	 * to avoid spurious IFP interrupt.
+	 */
+	rzv2h_ivc_write(ivc, RZV2H_IVC_REG_FM_INT_EN, 0x0);
+
+	/*
+	 * RZ/V2H documentation says software controlled single context mode is
+	 * is not supported, and currently the driver does not support the
+	 * multi-context mode. That being so we just set single context sw-hw
+	 * mode.
+	 */
+	rzv2h_ivc_write(ivc, RZV2H_IVC_REG_FM_CONTEXT,
+			RZV2H_IVC_SINGLE_CONTEXT_SW_HW_CFG);
+
+	/*
+	 * We enable the frame end interrupt so that we know when we should send
+	 * follow-up frames.
+	 */
+	rzv2h_ivc_write(ivc, RZV2H_IVC_REG_FM_INT_EN, RZV2H_IVC_VVAL_IFPE);
+}
+
+static irqreturn_t rzv2h_ivc_isr(int irq, void *context)
+{
+	struct device *dev = context;
+	struct rzv2h_ivc *ivc = dev_get_drvdata(dev);
+
+	guard(spinlock)(&ivc->spinlock);
+
+	if (!--ivc->vvalid_ifp)
+		queue_work(ivc->buffers.async_wq, &ivc->buffers.work);
+
+	return IRQ_HANDLED;
+}
+
+static int rzv2h_ivc_runtime_resume(struct device *dev)
+{
+	struct rzv2h_ivc *ivc = dev_get_drvdata(dev);
+	int ret;
+
+	ret = request_irq(ivc->irqnum, rzv2h_ivc_isr, 0, dev_driver_string(dev),
+			  dev);
+	if (ret) {
+		dev_err(dev, "failed to request irq\n");
+		return ret;
+	}
+
+	ret = clk_bulk_prepare_enable(ARRAY_SIZE(ivc->clks), ivc->clks);
+	if (ret) {
+		dev_err(ivc->dev, "failed to enable clocks\n");
+		goto err_free_irqnum;
+	}
+
+	ret = reset_control_bulk_deassert(ARRAY_SIZE(ivc->resets), ivc->resets);
+	if (ret) {
+		dev_err(ivc->dev, "failed to deassert resets\n");
+		goto err_disable_clks;
+	}
+
+	rzv2h_ivc_global_config(ivc);
+
+	return 0;
+
+err_disable_clks:
+	clk_bulk_disable_unprepare(ARRAY_SIZE(ivc->clks), ivc->clks);
+err_free_irqnum:
+	free_irq(ivc->irqnum, dev);
+
+	return ret;
+}
+
+static int rzv2h_ivc_runtime_suspend(struct device *dev)
+{
+	struct rzv2h_ivc *ivc = dev_get_drvdata(dev);
+
+	reset_control_bulk_assert(ARRAY_SIZE(ivc->resets), ivc->resets);
+	clk_bulk_disable_unprepare(ARRAY_SIZE(ivc->clks), ivc->clks);
+	free_irq(ivc->irqnum, dev);
+
+	return 0;
+}
+
+static const struct dev_pm_ops rzv2h_ivc_pm_ops = {
+	SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+				pm_runtime_force_resume)
+	RUNTIME_PM_OPS(rzv2h_ivc_runtime_suspend, rzv2h_ivc_runtime_resume,
+			   NULL)
+};
+
+static int rzv2h_ivc_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct rzv2h_ivc *ivc;
+	int ret;
+
+	ivc = devm_kzalloc(dev, sizeof(*ivc), GFP_KERNEL);
+	if (!ivc)
+		return -ENOMEM;
+
+	ivc->dev = dev;
+	platform_set_drvdata(pdev, ivc);
+	mutex_init(&ivc->lock);
+	spin_lock_init(&ivc->spinlock);
+
+	ret = rzv2h_ivc_get_hardware_resources(ivc, pdev);
+	if (ret)
+		return ret;
+
+	pm_runtime_set_autosuspend_delay(dev, 2000);
+	pm_runtime_use_autosuspend(dev);
+	pm_runtime_enable(dev);
+
+	ivc->irqnum = platform_get_irq(pdev, 0);
+	if (ivc->irqnum < 0) {
+		dev_err(dev, "failed to get interrupt\n");
+		return ret;
+	}
+
+	ret = rzv2h_ivc_initialise_subdevice(ivc);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static void rzv2h_ivc_remove(struct platform_device *pdev)
+{
+	struct rzv2h_ivc *ivc = platform_get_drvdata(pdev);
+
+	rzv2h_deinit_video_dev_and_queue(ivc);
+	rzv2h_ivc_deinit_subdevice(ivc);
+	mutex_destroy(&ivc->lock);
+}
+
+static const struct of_device_id rzv2h_ivc_of_match[] = {
+	{ .compatible = "renesas,r9a09g057-ivc", },
+	{ /* Sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, rzv2h_ivc_of_match);
+
+static struct platform_driver rzv2h_ivc_driver = {
+	.driver = {
+		.name = "rzv2h-ivc",
+		.of_match_table = rzv2h_ivc_of_match,
+		.pm = &rzv2h_ivc_pm_ops,
+	},
+	.probe = rzv2h_ivc_probe,
+	.remove = rzv2h_ivc_remove,
+};
+
+module_platform_driver(rzv2h_ivc_driver);
+
+MODULE_AUTHOR("Daniel Scally <dan.scally@ideasonboard.com>");
+MODULE_DESCRIPTION("Renesas RZ/V2H Input Video Control Block driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-subdev.c b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-subdev.c
new file mode 100644
index 0000000000000000000000000000000000000000..eb2913153d406fbad2491bb36e1c5ea754bea6f2
--- /dev/null
+++ b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-subdev.c
@@ -0,0 +1,376 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Renesas RZ/V2H Input Video Control Block driver
+ *
+ * Copyright (C) 2025 Ideas on Board Oy
+ */
+
+#include "rzv2h-ivc.h"
+
+#include <linux/media.h>
+#include <linux/media-bus-format.h>
+#include <linux/v4l2-mediabus.h>
+
+#include <media/v4l2-async.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-dev.h>
+#include <media/v4l2-event.h>
+
+#define RZV2H_IVC_N_INPUTS_PER_OUTPUT		6
+
+/*
+ * We support 8/10/12/14/16/20 bit input in any bayer order, but the output
+ * format is fixed at 20-bits with the same order as the input.
+ */
+static const struct {
+	u32 inputs[RZV2H_IVC_N_INPUTS_PER_OUTPUT];
+	u32 output;
+} rzv2h_ivc_formats[] = {
+	{
+		.inputs = {
+			MEDIA_BUS_FMT_SBGGR8_1X8,
+			MEDIA_BUS_FMT_SBGGR10_1X10,
+			MEDIA_BUS_FMT_SBGGR12_1X12,
+			MEDIA_BUS_FMT_SBGGR14_1X14,
+			MEDIA_BUS_FMT_SBGGR16_1X16,
+			MEDIA_BUS_FMT_SBGGR20_1X20,
+		},
+		.output = MEDIA_BUS_FMT_SBGGR20_1X20
+	},
+	{
+		.inputs = {
+			MEDIA_BUS_FMT_SGBRG8_1X8,
+			MEDIA_BUS_FMT_SGBRG10_1X10,
+			MEDIA_BUS_FMT_SGBRG12_1X12,
+			MEDIA_BUS_FMT_SGBRG14_1X14,
+			MEDIA_BUS_FMT_SGBRG16_1X16,
+			MEDIA_BUS_FMT_SGBRG20_1X20,
+		},
+		.output = MEDIA_BUS_FMT_SGBRG20_1X20
+	},
+	{
+		.inputs = {
+			MEDIA_BUS_FMT_SGRBG8_1X8,
+			MEDIA_BUS_FMT_SGRBG10_1X10,
+			MEDIA_BUS_FMT_SGRBG12_1X12,
+			MEDIA_BUS_FMT_SGRBG14_1X14,
+			MEDIA_BUS_FMT_SGRBG16_1X16,
+			MEDIA_BUS_FMT_SGRBG20_1X20,
+		},
+		.output = MEDIA_BUS_FMT_SGRBG20_1X20
+	},
+	{
+		.inputs = {
+			MEDIA_BUS_FMT_SRGGB8_1X8,
+			MEDIA_BUS_FMT_SRGGB10_1X10,
+			MEDIA_BUS_FMT_SRGGB12_1X12,
+			MEDIA_BUS_FMT_SRGGB14_1X14,
+			MEDIA_BUS_FMT_SRGGB16_1X16,
+			MEDIA_BUS_FMT_SRGGB20_1X20,
+		},
+		.output = MEDIA_BUS_FMT_SRGGB20_1X20
+	},
+};
+
+static u32 rzv2h_ivc_get_mbus_output_from_input(u32 mbus_code)
+{
+	unsigned int i, j;
+
+	for (i = 0; i < ARRAY_SIZE(rzv2h_ivc_formats); i++) {
+		for (j = 0; j < RZV2H_IVC_N_INPUTS_PER_OUTPUT; j++) {
+			if (rzv2h_ivc_formats[i].inputs[j] == mbus_code)
+				return rzv2h_ivc_formats[i].output;
+		}
+	}
+
+	return 0;
+}
+
+static int rzv2h_ivc_enum_mbus_code(struct v4l2_subdev *sd,
+				    struct v4l2_subdev_state *state,
+				    struct v4l2_subdev_mbus_code_enum *code)
+{
+	const struct v4l2_mbus_framefmt *fmt;
+	unsigned int order_index;
+	unsigned int index;
+
+	/*
+	 * On the source pad, only the 20-bit format corresponding to the sink
+	 * pad format's bayer order is supported.
+	 */
+	if (code->pad == RZV2H_IVC_SUBDEV_SOURCE_PAD) {
+		if (code->index)
+			return -EINVAL;
+
+		fmt = v4l2_subdev_state_get_format(state,
+						   RZV2H_IVC_SUBDEV_SINK_PAD);
+		code->code = rzv2h_ivc_get_mbus_output_from_input(fmt->code);
+
+		return 0;
+	}
+
+	if (code->index >= ARRAY_SIZE(rzv2h_ivc_formats) *
+				      RZV2H_IVC_N_INPUTS_PER_OUTPUT)
+		return -EINVAL;
+
+	order_index = code->index / RZV2H_IVC_N_INPUTS_PER_OUTPUT;
+	index = code->index % RZV2H_IVC_N_INPUTS_PER_OUTPUT;
+
+	code->code = rzv2h_ivc_formats[order_index].inputs[index];
+
+	return 0;
+}
+
+static int rzv2h_ivc_enum_frame_size(struct v4l2_subdev *sd,
+				     struct v4l2_subdev_state *state,
+				     struct v4l2_subdev_frame_size_enum *fse)
+{
+	const struct v4l2_mbus_framefmt *fmt;
+
+	if (fse->index > 0)
+		return -EINVAL;
+
+	if (fse->pad == RZV2H_IVC_SUBDEV_SOURCE_PAD) {
+		fmt = v4l2_subdev_state_get_format(state,
+						   RZV2H_IVC_SUBDEV_SINK_PAD);
+
+		if (fse->code != rzv2h_ivc_get_mbus_output_from_input(fmt->code))
+			return -EINVAL;
+
+		fse->min_width = fmt->width;
+		fse->max_width = fmt->width;
+		fse->min_height = fmt->height;
+		fse->max_height = fmt->height;
+
+		return 0;
+	}
+
+	if (!rzv2h_ivc_get_mbus_output_from_input(fse->code))
+		return -EINVAL;
+
+	fse->min_width = RZV2H_IVC_MIN_WIDTH;
+	fse->max_width = RZV2H_IVC_MAX_WIDTH;
+	fse->min_height = RZV2H_IVC_MIN_HEIGHT;
+	fse->max_height = RZV2H_IVC_MAX_HEIGHT;
+
+	return 0;
+}
+
+static int rzv2h_ivc_set_fmt(struct v4l2_subdev *sd,
+			     struct v4l2_subdev_state *state,
+			     struct v4l2_subdev_format *format)
+{
+	struct v4l2_mbus_framefmt *fmt = &format->format;
+	struct v4l2_mbus_framefmt *src_fmt, *sink_fmt;
+
+	if (format->pad == RZV2H_IVC_SUBDEV_SOURCE_PAD)
+		return v4l2_subdev_get_fmt(sd, state, format);
+
+	sink_fmt = v4l2_subdev_state_get_format(state,
+						RZV2H_IVC_SUBDEV_SINK_PAD);
+
+	sink_fmt->code = rzv2h_ivc_get_mbus_output_from_input(fmt->code) ?
+			 fmt->code : rzv2h_ivc_formats[0].inputs[0];
+
+	sink_fmt->width = clamp(fmt->width, RZV2H_IVC_MIN_WIDTH,
+				RZV2H_IVC_MAX_WIDTH);
+	sink_fmt->height = clamp(fmt->height, RZV2H_IVC_MIN_HEIGHT,
+				 RZV2H_IVC_MAX_HEIGHT);
+
+	*fmt = *sink_fmt;
+
+	src_fmt = v4l2_subdev_state_get_format(state,
+					       RZV2H_IVC_SUBDEV_SOURCE_PAD);
+	*src_fmt = *sink_fmt;
+	src_fmt->code = rzv2h_ivc_get_mbus_output_from_input(sink_fmt->code);
+
+	return 0;
+}
+
+static int rzv2h_ivc_enable_streams(struct v4l2_subdev *sd,
+				    struct v4l2_subdev_state *state, u32 pad,
+				    u64 streams_mask)
+{
+	/*
+	 * We have a single source pad, which has a single stream. V4L2 core has
+	 * already validated those things. The actual power-on and programming
+	 * of registers will be done through the video device's .vidioc_streamon
+	 * so there's nothing to actually do here...
+	 */
+
+	return 0;
+}
+
+static int rzv2h_ivc_disable_streams(struct v4l2_subdev *sd,
+				     struct v4l2_subdev_state *state, u32 pad,
+				     u64 streams_mask)
+{
+	return 0;
+}
+
+static const struct v4l2_subdev_pad_ops rzv2h_ivc_pad_ops = {
+	.enum_mbus_code		= rzv2h_ivc_enum_mbus_code,
+	.enum_frame_size	= rzv2h_ivc_enum_frame_size,
+	.get_fmt		= v4l2_subdev_get_fmt,
+	.set_fmt		= rzv2h_ivc_set_fmt,
+	.enable_streams		= rzv2h_ivc_enable_streams,
+	.disable_streams	= rzv2h_ivc_disable_streams,
+};
+
+static const struct v4l2_subdev_core_ops rzv2h_ivc_core_ops = {
+	.subscribe_event = v4l2_ctrl_subdev_subscribe_event,
+	.unsubscribe_event = v4l2_event_subdev_unsubscribe,
+};
+
+static const struct v4l2_subdev_ops rzv2h_ivc_subdev_ops = {
+	.core	= &rzv2h_ivc_core_ops,
+	.pad	= &rzv2h_ivc_pad_ops,
+};
+
+static int rzv2h_ivc_init_state(struct v4l2_subdev *sd,
+				struct v4l2_subdev_state *state)
+{
+	struct v4l2_mbus_framefmt *sink_fmt, *src_fmt;
+
+	sink_fmt = v4l2_subdev_state_get_format(state,
+						RZV2H_IVC_SUBDEV_SINK_PAD);
+	sink_fmt->width = RZV2H_IVC_DEFAULT_WIDTH;
+	sink_fmt->height = RZV2H_IVC_DEFAULT_HEIGHT;
+	sink_fmt->field = V4L2_FIELD_NONE;
+	sink_fmt->code = MEDIA_BUS_FMT_SRGGB16_1X16;
+	sink_fmt->colorspace = V4L2_COLORSPACE_RAW;
+	sink_fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(sink_fmt->colorspace);
+	sink_fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(sink_fmt->colorspace);
+	sink_fmt->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(
+		true, sink_fmt->colorspace, sink_fmt->ycbcr_enc);
+
+	src_fmt = v4l2_subdev_state_get_format(state,
+					       RZV2H_IVC_SUBDEV_SOURCE_PAD);
+
+	*src_fmt = *sink_fmt;
+	src_fmt->code = MEDIA_BUS_FMT_SRGGB20_1X20;
+
+	return 0;
+}
+
+static int rzv2h_ivc_registered(struct v4l2_subdev *sd)
+{
+	struct rzv2h_ivc *ivc = container_of(sd, struct rzv2h_ivc, subdev.sd);
+
+	return rzv2h_ivc_init_vdev(ivc, sd->v4l2_dev);
+}
+
+static const struct v4l2_subdev_internal_ops rzv2h_ivc_subdev_internal_ops = {
+	.init_state = rzv2h_ivc_init_state,
+	.registered = rzv2h_ivc_registered,
+};
+
+static int rzv2h_ivc_link_validate(struct media_link *link)
+{
+	struct video_device *vdev =
+		media_entity_to_video_device(link->source->entity);
+	struct rzv2h_ivc *ivc = video_get_drvdata(vdev);
+	struct v4l2_subdev *sd =
+		media_entity_to_v4l2_subdev(link->sink->entity);
+	const struct rzv2h_ivc_format *fmt;
+	const struct v4l2_pix_format_mplane *pix;
+	struct v4l2_subdev_state *state;
+	struct v4l2_mbus_framefmt *mf;
+	unsigned int i;
+	int ret = 0;
+
+	state = v4l2_subdev_lock_and_get_active_state(sd);
+	mf = v4l2_subdev_state_get_format(state, link->sink->index);
+
+	pix = &ivc->format.pix;
+	fmt = ivc->format.fmt;
+
+	if (mf->width != pix->width || mf->height != pix->height) {
+		dev_dbg(ivc->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,
+			mf->width, mf->height,
+			pix->width, pix->height);
+		ret = -EPIPE;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(fmt->mbus_codes); i++)
+		if (mf->code == fmt->mbus_codes[i])
+			break;
+
+	if (i == ARRAY_SIZE(fmt->mbus_codes)) {
+		dev_dbg(ivc->dev,
+			"link '%s':%u -> '%s':%u not valid: pixel format %p4cc cannot produce mbus_code 0x%04x\n",
+			link->source->entity->name, link->source->index,
+			link->sink->entity->name, link->sink->index,
+			&pix->pixelformat, mf->code);
+		ret = -EPIPE;
+	}
+
+	v4l2_subdev_unlock_state(state);
+
+	return ret;
+}
+
+static const struct media_entity_operations rzv2h_ivc_media_ops = {
+	.link_validate = rzv2h_ivc_link_validate,
+};
+
+int rzv2h_ivc_initialise_subdevice(struct rzv2h_ivc *ivc)
+{
+	struct v4l2_subdev *sd;
+	int ret;
+
+	/* Initialise subdevice */
+	sd = &ivc->subdev.sd;
+	sd->dev = ivc->dev;
+	v4l2_subdev_init(sd, &rzv2h_ivc_subdev_ops);
+	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS;
+	sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;
+	sd->internal_ops = &rzv2h_ivc_subdev_internal_ops;
+	sd->entity.ops = &rzv2h_ivc_media_ops;
+
+	ivc->subdev.pads[RZV2H_IVC_SUBDEV_SINK_PAD].flags = MEDIA_PAD_FL_SINK;
+	ivc->subdev.pads[RZV2H_IVC_SUBDEV_SOURCE_PAD].flags = MEDIA_PAD_FL_SOURCE;
+
+	snprintf(sd->name, sizeof(sd->name), "rzv2h ivc block");
+
+	ret = media_entity_pads_init(&sd->entity, RZV2H_IVC_NUM_SUBDEV_PADS,
+				     ivc->subdev.pads);
+	if (ret) {
+		dev_err(ivc->dev, "failed to initialise media entity\n");
+		return ret;
+	}
+
+	ret = v4l2_subdev_init_finalize(sd);
+	if (ret) {
+		dev_err(ivc->dev, "failed to finalize subdev init\n");
+		goto err_cleanup_subdev_entity;
+	}
+
+	ret = v4l2_async_register_subdev(sd);
+	if (ret) {
+		dev_err(ivc->dev, "failed to register subdevice\n");
+		goto err_cleanup_subdev;
+	}
+
+	return 0;
+
+err_cleanup_subdev:
+	v4l2_subdev_cleanup(sd);
+err_cleanup_subdev_entity:
+	media_entity_cleanup(&sd->entity);
+
+	return ret;
+}
+
+void rzv2h_ivc_deinit_subdevice(struct rzv2h_ivc *ivc)
+{
+	struct v4l2_subdev *sd = &ivc->subdev.sd;
+
+	v4l2_subdev_cleanup(sd);
+	media_entity_remove_links(&sd->entity);
+	v4l2_async_unregister_subdev(sd);
+	media_entity_cleanup(&sd->entity);
+}
diff --git a/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-video.c b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-video.c
new file mode 100644
index 0000000000000000000000000000000000000000..005a5700b5e2351b1e7ba5d99539ce4468f3db8b
--- /dev/null
+++ b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-video.c
@@ -0,0 +1,568 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Renesas RZ/V2H Input Video Control Block driver
+ *
+ * Copyright (C) 2025 Ideas on Board Oy
+ */
+
+#include "rzv2h-ivc.h"
+
+#include <linux/cleanup.h>
+#include <linux/iopoll.h>
+#include <linux/media-bus-format.h>
+#include <linux/minmax.h>
+#include <linux/mutex.h>
+#include <linux/pm_runtime.h>
+
+#include <media/mipi-csi2.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-dev.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-fh.h>
+#include <media/v4l2-ioctl.h>
+#include <media/videobuf2-dma-contig.h>
+
+#define RZV2H_IVC_FIXED_HBLANK			0x20
+#define RZV2H_IVC_MIN_VBLANK(hts)		max(0x1b, 15 + (120501 / (hts)))
+
+struct rzv2h_ivc_buf {
+	struct vb2_v4l2_buffer vb;
+	struct list_head queue;
+	dma_addr_t addr;
+};
+
+#define to_rzv2h_ivc_buf(vbuf) \
+	container_of(vbuf, struct rzv2h_ivc_buf, vb)
+
+static const struct rzv2h_ivc_format rzv2h_ivc_formats[] = {
+	{
+		.fourcc = V4L2_PIX_FMT_SBGGR8,
+		.mbus_codes = {
+			MEDIA_BUS_FMT_SBGGR8_1X8,
+		},
+		.dtype = MIPI_CSI2_DT_RAW8,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SGBRG8,
+		.mbus_codes = {
+			MEDIA_BUS_FMT_SGBRG8_1X8,
+		},
+		.dtype = MIPI_CSI2_DT_RAW8,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SGRBG8,
+		.mbus_codes = {
+			MEDIA_BUS_FMT_SGRBG8_1X8,
+		},
+		.dtype = MIPI_CSI2_DT_RAW8,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SRGGB8,
+		.mbus_codes = {
+			MEDIA_BUS_FMT_SRGGB8_1X8,
+		},
+		.dtype = MIPI_CSI2_DT_RAW8,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_RAW_CRU10,
+		.mbus_codes = {
+			MEDIA_BUS_FMT_SBGGR10_1X10,
+			MEDIA_BUS_FMT_SGBRG10_1X10,
+			MEDIA_BUS_FMT_SGRBG10_1X10,
+			MEDIA_BUS_FMT_SRGGB10_1X10
+		},
+		.dtype = MIPI_CSI2_DT_RAW10,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_RAW_CRU12,
+		.mbus_codes = {
+			MEDIA_BUS_FMT_SBGGR12_1X12,
+			MEDIA_BUS_FMT_SGBRG12_1X12,
+			MEDIA_BUS_FMT_SGRBG12_1X12,
+			MEDIA_BUS_FMT_SRGGB12_1X12
+		},
+		.dtype = MIPI_CSI2_DT_RAW12,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_RAW_CRU14,
+		.mbus_codes = {
+			MEDIA_BUS_FMT_SBGGR14_1X14,
+			MEDIA_BUS_FMT_SGBRG14_1X14,
+			MEDIA_BUS_FMT_SGRBG14_1X14,
+			MEDIA_BUS_FMT_SRGGB14_1X14
+		},
+		.dtype = MIPI_CSI2_DT_RAW14,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SBGGR16,
+		.mbus_codes = {
+			MEDIA_BUS_FMT_SBGGR16_1X16,
+		},
+		.dtype = MIPI_CSI2_DT_RAW16,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SGBRG16,
+		.mbus_codes = {
+			MEDIA_BUS_FMT_SGBRG16_1X16,
+		},
+		.dtype = MIPI_CSI2_DT_RAW16,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SGRBG16,
+		.mbus_codes = {
+			MEDIA_BUS_FMT_SGRBG16_1X16,
+		},
+		.dtype = MIPI_CSI2_DT_RAW16,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SRGGB16,
+		.mbus_codes = {
+			MEDIA_BUS_FMT_SRGGB16_1X16,
+		},
+		.dtype = MIPI_CSI2_DT_RAW16,
+	},
+};
+
+static void rzv2h_ivc_transfer_buffer(struct work_struct *work)
+{
+	struct rzv2h_ivc *ivc = container_of(work, struct rzv2h_ivc,
+					     buffers.work);
+	struct rzv2h_ivc_buf *buf;
+
+	scoped_guard(spinlock, &ivc->buffers.lock) {
+		if (ivc->buffers.curr) {
+			ivc->buffers.curr->vb.sequence = ivc->buffers.sequence++;
+			vb2_buffer_done(&ivc->buffers.curr->vb.vb2_buf,
+					VB2_BUF_STATE_DONE);
+			ivc->buffers.curr = NULL;
+		}
+
+		buf = list_first_entry_or_null(&ivc->buffers.queue,
+					       struct rzv2h_ivc_buf, queue);
+	}
+
+	if (!buf)
+		return;
+
+	list_del(&buf->queue);
+
+	ivc->buffers.curr = buf;
+	buf->addr = vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
+	rzv2h_ivc_write(ivc, RZV2H_IVC_REG_AXIRX_SADDL_P0, buf->addr);
+
+	scoped_guard(spinlock_irqsave, &ivc->spinlock) {
+		ivc->vvalid_ifp = 2;
+	}
+	rzv2h_ivc_write(ivc, RZV2H_IVC_REG_FM_FRCON, 0x1);
+}
+
+static int rzv2h_ivc_pipeline_started(struct media_entity *entity)
+{
+	struct video_device *vdev = media_entity_to_video_device(entity);
+	struct rzv2h_ivc *ivc = video_get_drvdata(vdev);
+
+	guard(spinlock)(&ivc->buffers.lock);
+
+	if (list_empty(&ivc->buffers.queue)) {
+		/*
+		 * The driver waits for interrupts to send a new frame and
+		 * tracks their receipt in the vvalid_ifp variable. .buf_queue()
+		 * will queue work if vvalid_ifp == 0 to trigger a new frame (an
+		 * event that normally would only occur if no buffer was ready
+		 * when the interrupt arrived). If there are no buffers in the
+		 * queue yet, we set vvalid_ifp to zero so that the next queue
+		 * will trigger the work.
+		 */
+		scoped_guard(spinlock_irqsave, &ivc->spinlock) {
+			ivc->vvalid_ifp = 0;
+		}
+	} else {
+		queue_work(ivc->buffers.async_wq, &ivc->buffers.work);
+	}
+
+	return 0;
+}
+
+static void rzv2h_ivc_pipeline_stopped(struct media_entity *entity)
+{
+	struct video_device *vdev = media_entity_to_video_device(entity);
+	struct rzv2h_ivc *ivc = video_get_drvdata(vdev);
+	u32 val = 0;
+
+	rzv2h_ivc_write(ivc, RZV2H_IVC_REG_FM_STOP, 0x1);
+	readl_poll_timeout(ivc->base + RZV2H_IVC_REG_FM_STOP,
+			   val, !val, 10 * USEC_PER_MSEC, 250 * USEC_PER_MSEC);
+}
+
+static const struct media_entity_operations rzv2h_ivc_media_ops = {
+	.pipeline_started = rzv2h_ivc_pipeline_started,
+	.pipeline_stopped = rzv2h_ivc_pipeline_stopped,
+};
+
+static int rzv2h_ivc_queue_setup(struct vb2_queue *q, unsigned int *num_buffers,
+				 unsigned int *num_planes, unsigned int sizes[],
+				 struct device *alloc_devs[])
+{
+	struct rzv2h_ivc *ivc = vb2_get_drv_priv(q);
+
+	if (*num_planes && *num_planes > 1)
+		return -EINVAL;
+
+	if (sizes[0] && sizes[0] < ivc->format.pix.plane_fmt[0].sizeimage)
+		return -EINVAL;
+
+	*num_planes = 1;
+
+	if (!sizes[0])
+		sizes[0] = ivc->format.pix.plane_fmt[0].sizeimage;
+
+	return 0;
+}
+
+static void rzv2h_ivc_buf_queue(struct vb2_buffer *vb)
+{
+	struct rzv2h_ivc *ivc = vb2_get_drv_priv(vb->vb2_queue);
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct rzv2h_ivc_buf *buf = to_rzv2h_ivc_buf(vbuf);
+
+	scoped_guard(spinlock, &ivc->buffers.lock) {
+		list_add_tail(&buf->queue, &ivc->buffers.queue);
+	}
+
+	scoped_guard(spinlock_irqsave, &ivc->spinlock) {
+		if (vb2_is_streaming(vb->vb2_queue) && !ivc->vvalid_ifp)
+			queue_work(ivc->buffers.async_wq, &ivc->buffers.work);
+	}
+}
+
+static void rzv2h_ivc_format_configure(struct rzv2h_ivc *ivc)
+{
+	const struct rzv2h_ivc_format *fmt = ivc->format.fmt;
+	struct v4l2_pix_format_mplane *pix = &ivc->format.pix;
+	unsigned int vblank;
+	unsigned int hts;
+
+	/* Currently only CRU packed pixel formats are supported */
+	rzv2h_ivc_write(ivc, RZV2H_IVC_REG_AXIRX_PXFMT,
+			RZV2H_IVC_INPUT_FMT_CRU_PACKED);
+
+	rzv2h_ivc_update_bits(ivc, RZV2H_IVC_REG_AXIRX_PXFMT,
+			      RZV2H_IVC_PXFMT_DTYPE, fmt->dtype);
+
+	rzv2h_ivc_write(ivc, RZV2H_IVC_REG_AXIRX_HSIZE, pix->width);
+	rzv2h_ivc_write(ivc, RZV2H_IVC_REG_AXIRX_VSIZE, pix->height);
+	rzv2h_ivc_write(ivc, RZV2H_IVC_REG_AXIRX_STRD,
+			pix->plane_fmt[0].bytesperline);
+
+	/*
+	 * The ISP has minimum vertical blanking requirements that must be
+	 * adhered to by the IVC. The minimum is a function of the Iridix blocks
+	 * clocking requirements and the width of the image and horizontal
+	 * blanking, but if we assume the worst case then it boils down to the
+	 * below (plus one to the numerator to ensure the answer is rounded up)
+	 */
+
+	hts = pix->width + RZV2H_IVC_FIXED_HBLANK;
+	vblank = RZV2H_IVC_MIN_VBLANK(hts);
+
+	rzv2h_ivc_write(ivc, RZV2H_IVC_REG_AXIRX_BLANK,
+			RZV2H_IVC_VBLANK(vblank));
+}
+
+static void rzv2h_ivc_return_buffers(struct rzv2h_ivc *ivc,
+				     enum vb2_buffer_state state)
+{
+	struct rzv2h_ivc_buf *buf, *tmp;
+
+	guard(spinlock)(&ivc->buffers.lock);
+
+	if (ivc->buffers.curr) {
+		vb2_buffer_done(&ivc->buffers.curr->vb.vb2_buf, state);
+		ivc->buffers.curr = NULL;
+	}
+
+	list_for_each_entry_safe(buf, tmp, &ivc->buffers.queue, queue) {
+		list_del(&buf->queue);
+		vb2_buffer_done(&buf->vb.vb2_buf, state);
+	}
+}
+
+static int rzv2h_ivc_start_streaming(struct vb2_queue *q, unsigned int count)
+{
+	struct rzv2h_ivc *ivc = vb2_get_drv_priv(q);
+	int ret;
+
+	ivc->buffers.sequence = 0;
+	ivc->vvalid_ifp = 2;
+
+	ret = pm_runtime_resume_and_get(ivc->dev);
+	if (ret)
+		goto err_return_buffers;
+
+	ret = video_device_pipeline_alloc_start(&ivc->vdev.dev);
+	if (ret) {
+		dev_err(ivc->dev, "failed to start media pipeline\n");
+		goto err_pm_runtime_put;
+	}
+
+	rzv2h_ivc_format_configure(ivc);
+
+	ret = video_device_pipeline_started(&ivc->vdev.dev);
+	if (ret < 0)
+		goto err_stop_pipeline;
+
+	return 0;
+
+err_stop_pipeline:
+	video_device_pipeline_stop(&ivc->vdev.dev);
+err_pm_runtime_put:
+	pm_runtime_put(ivc->dev);
+err_return_buffers:
+	rzv2h_ivc_return_buffers(ivc, VB2_BUF_STATE_QUEUED);
+
+	return ret;
+}
+
+static void rzv2h_ivc_stop_streaming(struct vb2_queue *q)
+{
+	struct rzv2h_ivc *ivc = vb2_get_drv_priv(q);
+
+	video_device_pipeline_stopped(&ivc->vdev.dev);
+	rzv2h_ivc_return_buffers(ivc, VB2_BUF_STATE_ERROR);
+	video_device_pipeline_stop(&ivc->vdev.dev);
+	pm_runtime_mark_last_busy(ivc->dev);
+	pm_runtime_put_autosuspend(ivc->dev);
+}
+
+static const struct vb2_ops rzv2h_ivc_vb2_ops = {
+	.queue_setup		= &rzv2h_ivc_queue_setup,
+	.buf_queue		= &rzv2h_ivc_buf_queue,
+	.wait_prepare		= vb2_ops_wait_prepare,
+	.wait_finish		= vb2_ops_wait_finish,
+	.start_streaming	= &rzv2h_ivc_start_streaming,
+	.stop_streaming		= &rzv2h_ivc_stop_streaming,
+};
+
+static const struct rzv2h_ivc_format *
+rzv2h_ivc_format_from_pixelformat(u32 fourcc)
+{
+	for (unsigned int i = 0; i < ARRAY_SIZE(rzv2h_ivc_formats); i++)
+		if (fourcc == rzv2h_ivc_formats[i].fourcc)
+			return &rzv2h_ivc_formats[i];
+
+	return &rzv2h_ivc_formats[0];
+}
+
+static int rzv2h_ivc_enum_fmt_vid_out(struct file *file, void *fh,
+				      struct v4l2_fmtdesc *f)
+{
+	if (f->index >= ARRAY_SIZE(rzv2h_ivc_formats))
+		return -EINVAL;
+
+	f->pixelformat = rzv2h_ivc_formats[f->index].fourcc;
+	return 0;
+}
+
+static int rzv2h_ivc_g_fmt_vid_out(struct file *file, void *fh,
+				   struct v4l2_format *f)
+{
+	struct rzv2h_ivc *ivc = video_drvdata(file);
+
+	f->fmt.pix_mp = ivc->format.pix;
+
+	return 0;
+}
+
+static void rzv2h_ivc_try_fmt(struct v4l2_pix_format_mplane *pix,
+			      const struct rzv2h_ivc_format *fmt)
+{
+	pix->pixelformat = fmt->fourcc;
+
+	pix->width = clamp(pix->width, RZV2H_IVC_MIN_WIDTH,
+			   RZV2H_IVC_MAX_WIDTH);
+	pix->height = clamp(pix->height, RZV2H_IVC_MIN_HEIGHT,
+			    RZV2H_IVC_MAX_HEIGHT);
+
+	pix->field = V4L2_FIELD_NONE;
+	pix->colorspace = V4L2_COLORSPACE_RAW;
+	pix->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(pix->colorspace);
+	pix->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true,
+							  pix->colorspace,
+							  pix->ycbcr_enc);
+
+	v4l2_fill_pixfmt_mp(pix, pix->pixelformat, pix->width, pix->height);
+}
+
+static void rzv2h_ivc_set_format(struct rzv2h_ivc *ivc,
+				 struct v4l2_pix_format_mplane *pix)
+{
+	const struct rzv2h_ivc_format *fmt;
+
+	fmt = rzv2h_ivc_format_from_pixelformat(pix->pixelformat);
+
+	rzv2h_ivc_try_fmt(pix, fmt);
+	ivc->format.pix = *pix;
+	ivc->format.fmt = fmt;
+}
+
+static int rzv2h_ivc_s_fmt_vid_out(struct file *file, void *fh,
+				   struct v4l2_format *f)
+{
+	struct rzv2h_ivc *ivc = video_drvdata(file);
+	struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;
+
+	if (vb2_is_busy(&ivc->vdev.vb2q))
+		return -EBUSY;
+
+	rzv2h_ivc_set_format(ivc, pix);
+
+	return 0;
+}
+
+static int rzv2h_ivc_try_fmt_vid_out(struct file *file, void *fh,
+				     struct v4l2_format *f)
+{
+	const struct rzv2h_ivc_format *fmt;
+
+	fmt = rzv2h_ivc_format_from_pixelformat(f->fmt.pix.pixelformat);
+	rzv2h_ivc_try_fmt(&f->fmt.pix_mp, fmt);
+
+	return 0;
+}
+
+static int rzv2h_ivc_querycap(struct file *file, void *fh,
+			      struct v4l2_capability *cap)
+{
+	strscpy(cap->driver, "rzv2h-ivc", sizeof(cap->driver));
+	strscpy(cap->card, "Renesas Input Video Control", sizeof(cap->card));
+
+	return 0;
+}
+
+static const struct v4l2_ioctl_ops rzv2h_ivc_v4l2_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_enum_fmt_vid_out = rzv2h_ivc_enum_fmt_vid_out,
+	.vidioc_g_fmt_vid_out_mplane = rzv2h_ivc_g_fmt_vid_out,
+	.vidioc_s_fmt_vid_out_mplane = rzv2h_ivc_s_fmt_vid_out,
+	.vidioc_try_fmt_vid_out_mplane = rzv2h_ivc_try_fmt_vid_out,
+	.vidioc_querycap = rzv2h_ivc_querycap,
+	.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+static const struct v4l2_file_operations rzv2h_ivc_v4l2_fops = {
+	.owner = THIS_MODULE,
+	.unlocked_ioctl = video_ioctl2,
+	.open = v4l2_fh_open,
+	.release = vb2_fop_release,
+	.poll = vb2_fop_poll,
+	.mmap = vb2_fop_mmap,
+};
+
+int rzv2h_ivc_init_vdev(struct rzv2h_ivc *ivc, struct v4l2_device *v4l2_dev)
+{
+	struct v4l2_pix_format_mplane pix = { };
+	struct video_device *vdev;
+	struct vb2_queue *vb2q;
+	int ret;
+
+	spin_lock_init(&ivc->buffers.lock);
+	INIT_LIST_HEAD(&ivc->buffers.queue);
+	INIT_WORK(&ivc->buffers.work, rzv2h_ivc_transfer_buffer);
+
+	ivc->buffers.async_wq = alloc_workqueue("rzv2h-ivc", 0, 0);
+	if (!ivc->buffers.async_wq)
+		return -EINVAL;
+
+	/* Initialise vb2 queue */
+	vb2q = &ivc->vdev.vb2q;
+	vb2q->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
+	vb2q->io_modes = VB2_MMAP | VB2_DMABUF;
+	vb2q->drv_priv = ivc;
+	vb2q->mem_ops = &vb2_dma_contig_memops;
+	vb2q->ops = &rzv2h_ivc_vb2_ops;
+	vb2q->buf_struct_size = sizeof(struct rzv2h_ivc_buf);
+	vb2q->min_queued_buffers = 0;
+	vb2q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+	vb2q->lock = &ivc->lock;
+	vb2q->dev = ivc->dev;
+
+	ret = vb2_queue_init(vb2q);
+	if (ret) {
+		dev_err(ivc->dev, "vb2 queue init failed\n");
+		goto err_destroy_workqueue;
+	}
+
+	/* Initialise Video Device */
+	vdev = &ivc->vdev.dev;
+	strscpy(vdev->name, "rzv2h-ivc", sizeof(vdev->name));
+	vdev->release = video_device_release_empty;
+	vdev->fops = &rzv2h_ivc_v4l2_fops;
+	vdev->ioctl_ops = &rzv2h_ivc_v4l2_ioctl_ops;
+	vdev->lock = &ivc->lock;
+	vdev->v4l2_dev = v4l2_dev;
+	vdev->queue = vb2q;
+	vdev->device_caps = V4L2_CAP_VIDEO_OUTPUT_MPLANE | V4L2_CAP_STREAMING;
+	vdev->vfl_dir = VFL_DIR_TX;
+	video_set_drvdata(vdev, ivc);
+
+	pix.pixelformat = V4L2_PIX_FMT_SRGGB16;
+	pix.width = RZV2H_IVC_DEFAULT_WIDTH;
+	pix.height = RZV2H_IVC_DEFAULT_HEIGHT;
+	rzv2h_ivc_set_format(ivc, &pix);
+
+	ivc->vdev.pad.flags = MEDIA_PAD_FL_SOURCE;
+	ivc->vdev.dev.entity.ops = &rzv2h_ivc_media_ops;
+	ret = media_entity_pads_init(&ivc->vdev.dev.entity, 1, &ivc->vdev.pad);
+	if (ret)
+		goto err_release_vb2q;
+
+	ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
+	if (ret) {
+		dev_err(ivc->dev, "failed to register IVC video device\n");
+		goto err_cleanup_vdev_entity;
+	}
+
+	ret = media_create_pad_link(&vdev->entity, 0, &ivc->subdev.sd.entity,
+				    RZV2H_IVC_SUBDEV_SINK_PAD,
+				    MEDIA_LNK_FL_ENABLED |
+				    MEDIA_LNK_FL_IMMUTABLE);
+	if (ret) {
+		dev_err(ivc->dev, "failed to create media link\n");
+		goto err_unregister_vdev;
+	}
+
+	return 0;
+
+err_unregister_vdev:
+	video_unregister_device(vdev);
+err_cleanup_vdev_entity:
+	media_entity_cleanup(&vdev->entity);
+err_release_vb2q:
+	vb2_queue_release(vb2q);
+err_destroy_workqueue:
+	destroy_workqueue(ivc->buffers.async_wq);
+
+	return ret;
+}
+
+void rzv2h_deinit_video_dev_and_queue(struct rzv2h_ivc *ivc)
+{
+	struct video_device *vdev = &ivc->vdev.dev;
+	struct vb2_queue *vb2q = &ivc->vdev.vb2q;
+
+	if (!ivc->sched)
+		return;
+
+	vb2_video_unregister_device(vdev);
+	media_entity_cleanup(&vdev->entity);
+	vb2_queue_release(vb2q);
+}
diff --git a/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc.h b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc.h
new file mode 100644
index 0000000000000000000000000000000000000000..709c6a9398fe2484c2acb03d443d58ea4e153a66
--- /dev/null
+++ b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc.h
@@ -0,0 +1,131 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Renesas RZ/V2H Input Video Control Block driver
+ *
+ * Copyright (C) 2025 Ideas on Board Oy
+ */
+
+#include <linux/clk.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/reset.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+#include <linux/videodev2.h>
+#include <linux/workqueue.h>
+
+#include <media/media-entity.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-subdev.h>
+#include <media/videobuf2-core.h>
+#include <media/videobuf2-v4l2.h>
+
+#define RZV2H_IVC_REG_AXIRX_PLNUM			0x0000
+#define RZV2H_IVC_ONE_EXPOSURE				0x00
+#define RZV2H_IVC_TWO_EXPOSURE				0x01
+#define RZV2H_IVC_REG_AXIRX_PXFMT			0x0004
+#define RZV2H_IVC_INPUT_FMT_MIPI			(0 << 16)
+#define RZV2H_IVC_INPUT_FMT_CRU_PACKED			(1 << 16)
+#define RZV2H_IVC_PXFMT_DTYPE				GENMASK(7, 0)
+#define RZV2H_IVC_REG_AXIRX_SADDL_P0			0x0010
+#define RZV2H_IVC_REG_AXIRX_SADDH_P0			0x0014
+#define RZV2H_IVC_REG_AXIRX_SADDL_P1			0x0018
+#define RZV2H_IVC_REG_AXIRX_SADDH_P1			0x001c
+#define RZV2H_IVC_REG_AXIRX_HSIZE			0x0020
+#define RZV2H_IVC_REG_AXIRX_VSIZE			0x0024
+#define RZV2H_IVC_REG_AXIRX_BLANK			0x0028
+#define RZV2H_IVC_VBLANK(x)				((x) << 16)
+#define RZV2H_IVC_REG_AXIRX_STRD			0x0030
+#define RZV2H_IVC_REG_AXIRX_ISSU			0x0040
+#define RZV2H_IVC_REG_AXIRX_ERACT			0x0048
+#define RZV2H_IVC_REG_FM_CONTEXT			0x0100
+#define RZV2H_IVC_SOFTWARE_CFG				0x00
+#define RZV2H_IVC_SINGLE_CONTEXT_SW_HW_CFG		BIT(0)
+#define RZV2H_IVC_MULTI_CONTEXT_SW_HW_CFG		BIT(1)
+#define RZV2H_IVC_REG_FM_MCON				0x0104
+#define RZV2H_IVC_REG_FM_FRCON				0x0108
+#define RZV2H_IVC_REG_FM_STOP				0x010c
+#define RZV2H_IVC_REG_FM_INT_EN				0x0120
+#define RZV2H_IVC_VVAL_IFPE				BIT(0)
+#define RZV2H_IVC_REG_FM_INT_STA			0x0124
+#define RZV2H_IVC_REG_AXIRX_FIFOCAP0			0x0208
+#define RZV2H_IVC_REG_CORE_CAPCON			0x020c
+#define RZV2H_IVC_REG_CORE_FIFOCAP0			0x0228
+#define RZV2H_IVC_REG_CORE_FIFOCAP1			0x022c
+
+#define RZV2H_IVC_MIN_WIDTH				640
+#define RZV2H_IVC_MAX_WIDTH				4096
+#define RZV2H_IVC_MIN_HEIGHT				480
+#define RZV2H_IVC_MAX_HEIGHT				4096
+#define RZV2H_IVC_DEFAULT_WIDTH				1920
+#define RZV2H_IVC_DEFAULT_HEIGHT			1080
+
+#define RZV2H_IVC_NUM_HW_RESOURCES			3
+
+struct device;
+
+enum rzv2h_ivc_subdev_pads {
+	RZV2H_IVC_SUBDEV_SINK_PAD,
+	RZV2H_IVC_SUBDEV_SOURCE_PAD,
+	RZV2H_IVC_NUM_SUBDEV_PADS
+};
+
+struct rzv2h_ivc_format {
+	u32 fourcc;
+	/*
+	 * The CRU packed pixel formats are bayer-order agnostic, so each could
+	 * support any one of the 4 possible media bus formats.
+	 */
+	u32 mbus_codes[4];
+	u8 dtype;
+};
+
+struct rzv2h_ivc {
+	struct device *dev;
+	void __iomem *base;
+	struct clk_bulk_data clks[RZV2H_IVC_NUM_HW_RESOURCES];
+	struct reset_control_bulk_data resets[RZV2H_IVC_NUM_HW_RESOURCES];
+	int irqnum;
+	u8 vvalid_ifp;
+
+	struct {
+		struct video_device dev;
+		struct vb2_queue vb2q;
+		struct media_pad pad;
+	} vdev;
+
+	struct {
+		struct v4l2_subdev sd;
+		struct media_pad pads[RZV2H_IVC_NUM_SUBDEV_PADS];
+	} subdev;
+
+	struct {
+		/* Spinlock to guard buffer queue */
+		spinlock_t lock;
+		struct workqueue_struct *async_wq;
+		struct work_struct work;
+		struct list_head queue;
+		struct rzv2h_ivc_buf *curr;
+		unsigned int sequence;
+	} buffers;
+
+	struct media_job_scheduler *sched;
+
+	struct {
+		struct v4l2_pix_format_mplane pix;
+		const struct rzv2h_ivc_format *fmt;
+	} format;
+
+	/* Mutex to provide to vb2 */
+	struct mutex lock;
+	/* Lock to protect the interrupt counter */
+	spinlock_t spinlock;
+};
+
+int rzv2h_ivc_init_vdev(struct rzv2h_ivc *ivc, struct v4l2_device *v4l2_dev);
+void rzv2h_deinit_video_dev_and_queue(struct rzv2h_ivc *ivc);
+int rzv2h_ivc_initialise_subdevice(struct rzv2h_ivc *ivc);
+void rzv2h_ivc_deinit_subdevice(struct rzv2h_ivc *ivc);
+void rzv2h_ivc_write(struct rzv2h_ivc *ivc, u32 addr, u32 val);
+void rzv2h_ivc_update_bits(struct rzv2h_ivc *ivc, unsigned int addr,
+			   u32 mask, u32 val);

-- 
2.34.1


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

* [PATCH v4 3/3] MAINTAINERS: Add entry for rzv2h-ivc driver
  2025-07-14 15:19 [PATCH v4 0/3] Add Input Video Control Block driver for RZ/V2H Daniel Scally
  2025-07-14 15:19 ` [PATCH v4 1/3] dt-bindings: media: Add bindings for the RZ/V2H IVC block Daniel Scally
  2025-07-14 15:19 ` [PATCH v4 2/3] media: platform: Add Renesas Input Video Control block driver Daniel Scally
@ 2025-07-14 15:19 ` Daniel Scally
  2025-07-14 16:07   ` Krzysztof Kozlowski
  2 siblings, 1 reply; 12+ messages in thread
From: Daniel Scally @ 2025-07-14 15:19 UTC (permalink / raw)
  To: linux-media, devicetree, linux-renesas-soc
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Geert Uytterhoeven, Magnus Damm, Philipp Zabel, jacopo.mondi,
	biju.das.jz, laurent.pinchart, Daniel Scally

Add an entry to the MAINTAINERS file for the rzv2h-ivc driver

Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com>
---
Changes in v2:

	- None
---
 MAINTAINERS | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 3a53565aa34897e5df13f0420908598add5b28fb..4f5bf6d0db54976360b2019119fc55f78cae6a96 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -21190,6 +21190,13 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/net/renesas,r9a09g057-gbeth.yaml
 F:	drivers/net/ethernet/stmicro/stmmac/dwmac-renesas-gbeth.c
 
+RENESAS RZ/V2H(P) INPUT VIDEO CONTROL BLOCK DRIVER
+M:	Daniel Scally <dan.scally@ideasonboard.com>
+L:	linux-media@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/media/renesas,rzv2h-ivc.yaml
+F:	drivers/media/platform/renesas/rzv2h-ivc/
+
 RENESAS RZ/V2H(P) USB2PHY PORT RESET DRIVER
 M:	Fabrizio Castro <fabrizio.castro.jz@renesas.com>
 M:	Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>

-- 
2.34.1


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

* Re: [PATCH v4 3/3] MAINTAINERS: Add entry for rzv2h-ivc driver
  2025-07-14 15:19 ` [PATCH v4 3/3] MAINTAINERS: Add entry for rzv2h-ivc driver Daniel Scally
@ 2025-07-14 16:07   ` Krzysztof Kozlowski
  0 siblings, 0 replies; 12+ messages in thread
From: Krzysztof Kozlowski @ 2025-07-14 16:07 UTC (permalink / raw)
  To: Daniel Scally, linux-media, devicetree, linux-renesas-soc
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Geert Uytterhoeven, Magnus Damm, Philipp Zabel, jacopo.mondi,
	biju.das.jz, laurent.pinchart

On 14/07/2025 17:19, Daniel Scally wrote:
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 3a53565aa34897e5df13f0420908598add5b28fb..4f5bf6d0db54976360b2019119fc55f78cae6a96 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -21190,6 +21190,13 @@ S:	Maintained
>  F:	Documentation/devicetree/bindings/net/renesas,r9a09g057-gbeth.yaml
>  F:	drivers/net/ethernet/stmicro/stmmac/dwmac-renesas-gbeth.c
>  
> +RENESAS RZ/V2H(P) INPUT VIDEO CONTROL BLOCK DRIVER
> +M:	Daniel Scally <dan.scally@ideasonboard.com>
> +L:	linux-media@vger.kernel.org
> +S:	Maintained
> +F:	Documentation/devicetree/bindings/media/renesas,rzv2h-ivc.yaml

This path looks like does not match anymore.

Best regards,
Krzysztof

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

* Re: [PATCH v4 2/3] media: platform: Add Renesas Input Video Control block driver
  2025-07-14 15:19 ` [PATCH v4 2/3] media: platform: Add Renesas Input Video Control block driver Daniel Scally
@ 2025-07-22  9:40   ` Jacopo Mondi
  2025-07-23 21:17     ` Dan Scally
  2025-07-24 16:52   ` Lad, Prabhakar
  2025-07-30 14:10   ` Geert Uytterhoeven
  2 siblings, 1 reply; 12+ messages in thread
From: Jacopo Mondi @ 2025-07-22  9:40 UTC (permalink / raw)
  To: Daniel Scally
  Cc: linux-media, devicetree, linux-renesas-soc, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Geert Uytterhoeven,
	Magnus Damm, Philipp Zabel, jacopo.mondi, biju.das.jz,
	laurent.pinchart

Hi Dan

On Mon, Jul 14, 2025 at 04:19:18PM +0100, Daniel Scally wrote:
> Add a driver for the Input Video Control block in an RZ/V2H SoC which
> feeds data into the Arm Mali-C55 ISP.
>
> Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com>
> ---
> Changes in v5:
>
> 	- Fixed .enum_frame_sizes() to properly check that the
> 	  given mbus_code matches the source pads format.
> 	- Tidy up extra space in Kconfig
> 	- Revise Kconfig option message
> 	- Don't mark functions inline
> 	- Fixup misleading comment
> 	- select CONFIG_PM
> 	- Use the new pm_sleep_ptr() functionality
> 	- Minor formatting
>
> Changes in v4:
>
> 	- Update the compatible to renesas,r9a09g057-ivc
> 	- Dropped the media jobs / scheduler functionality, and re
> 	  worked the driver to have its own workqueue pushing frames
> 	- Fix .enum_mbus_code() to return 20-bit output for source
> 	  pad.
> 	- Fix some alignment issues
> 	- Make the forwarding of sink to source pad format a more
> 	  explicit operation.
> 	- Rename rzv2h_initialise_video_device_and_queue()
> 	- Reversed order of v4l2_subdev_init_finalize() and
> 	  v4l2_async_register_subdev() to make sure everything is
> 	  finished initialising before registering the subdev.
> 	- Change function to MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER
> 	- Use a parametised macro for min vblank
> 	- Minor formatting
> 	- Use the DEFAULT macros for quantization / ycbcr_enc values
> 	- Switch to using the mplane API
> 	- Dropped select RESET_CONTROLLER
> 	- Used the new helpers for starting a media pipeline
> 	- Switch from threaded irq to normal with driver workqueue
> 	  and revised startup routine
>
> Changes in v3:
>
> 	- Account for the renamed CRU pixel formats
>
> Changes in v2:
>
> 	- Added selects and depends statements to Kconfig entry
> 	- Fixed copyright year
> 	- Stopped including in .c files headers already included in .h
> 	- Fixed uninitialized variable in iterator
> 	- Only check vvalid member in interrupt function and wait
> 	  unconditionally elsewhere
> 	- __maybe_unused for the PM ops
> 	- Initialise the subdevice after setting up PM
> 	- Fixed the remove function for the driver to actually do
> 	  something.
> 	- Some minor formatting changes
> 	- Fixed the quantization member for the format
> 	- Changes accounting for the v2 of the media jobs framework
> 	- Change min_queued_buffers to 0
> ---
>  drivers/media/platform/renesas/Kconfig             |   1 +
>  drivers/media/platform/renesas/Makefile            |   1 +
>  drivers/media/platform/renesas/rzv2h-ivc/Kconfig   |  18 +
>  drivers/media/platform/renesas/rzv2h-ivc/Makefile  |   5 +
>  .../platform/renesas/rzv2h-ivc/rzv2h-ivc-dev.c     | 229 +++++++++
>  .../platform/renesas/rzv2h-ivc/rzv2h-ivc-subdev.c  | 376 ++++++++++++++
>  .../platform/renesas/rzv2h-ivc/rzv2h-ivc-video.c   | 568 +++++++++++++++++++++
>  .../media/platform/renesas/rzv2h-ivc/rzv2h-ivc.h   | 131 +++++
>  8 files changed, 1329 insertions(+)
>
> diff --git a/drivers/media/platform/renesas/Kconfig b/drivers/media/platform/renesas/Kconfig
> index 27a54fa7908384f2e8200f0f7283a82b0ae8435c..bd8247c0b8aa734d2b412438e694f3908d910b25 100644
> --- a/drivers/media/platform/renesas/Kconfig
> +++ b/drivers/media/platform/renesas/Kconfig
> @@ -42,6 +42,7 @@ config VIDEO_SH_VOU
>  source "drivers/media/platform/renesas/rcar-isp/Kconfig"
>  source "drivers/media/platform/renesas/rcar-vin/Kconfig"
>  source "drivers/media/platform/renesas/rzg2l-cru/Kconfig"
> +source "drivers/media/platform/renesas/rzv2h-ivc/Kconfig"
>
>  # Mem2mem drivers
>
> diff --git a/drivers/media/platform/renesas/Makefile b/drivers/media/platform/renesas/Makefile
> index 1127259c09d6a51b70803e76c495918e06777f67..b6b4abf01db246aaf8269b8027efee9b0b32083a 100644
> --- a/drivers/media/platform/renesas/Makefile
> +++ b/drivers/media/platform/renesas/Makefile
> @@ -6,6 +6,7 @@
>  obj-y += rcar-isp/
>  obj-y += rcar-vin/
>  obj-y += rzg2l-cru/
> +obj-y += rzv2h-ivc/
>  obj-y += vsp1/
>
>  obj-$(CONFIG_VIDEO_RCAR_CSI2) += rcar-csi2.o
> diff --git a/drivers/media/platform/renesas/rzv2h-ivc/Kconfig b/drivers/media/platform/renesas/rzv2h-ivc/Kconfig
> new file mode 100644
> index 0000000000000000000000000000000000000000..5a4a3c052a3ae0f242e844689132d91a75b8a302
> --- /dev/null
> +++ b/drivers/media/platform/renesas/rzv2h-ivc/Kconfig
> @@ -0,0 +1,18 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +config VIDEO_RZV2H_IVC
> +	tristate "Renesas RZ/V2H Input Video Control block driver"
> +	depends on V4L_PLATFORM_DRIVERS
> +	depends on VIDEO_DEV
> +	depends on ARCH_RENESAS || COMPILE_TEST
> +	depends on OF
> +	select CONFIG_PM

Ups, no 'CONFIG_' please.
Weird that no checks/automated testing complains for a non existing
symbol in Kconfig

> +	select VIDEOBUF2_DMA_CONTIG
> +	select MEDIA_CONTROLLER
> +	select VIDEO_V4L2_SUBDEV_API
> +	help
> +          Support for the Renesas RZ/V2H Input Video Control Block
> +          (IVC).
> +
> +          To compile this driver as a module, choose M here: the
> +          module will be called rzv2h-ivc.
> diff --git a/drivers/media/platform/renesas/rzv2h-ivc/Makefile b/drivers/media/platform/renesas/rzv2h-ivc/Makefile
> new file mode 100644
> index 0000000000000000000000000000000000000000..080ee3570f09c236d87abeaea5d8dd578fefb6d3
> --- /dev/null
> +++ b/drivers/media/platform/renesas/rzv2h-ivc/Makefile
> @@ -0,0 +1,5 @@
> +# SPDX-License-Identifier: GPL-2.0
> +
> +rzv2h-ivc-y := rzv2h-ivc-dev.o rzv2h-ivc-subdev.o rzv2h-ivc-video.o
> +
> +obj-$(CONFIG_VIDEO_RZV2H_IVC) += rzv2h-ivc.o
> diff --git a/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-dev.c b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-dev.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..ce2e3a3af8d19900241add7d261f7a40f2551265
> --- /dev/null
> +++ b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-dev.c
> @@ -0,0 +1,229 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Renesas RZ/V2H Input Video Control Block driver
> + *
> + * Copyright (C) 2025 Ideas on Board Oy
> + */
> +
> +#include "rzv2h-ivc.h"
> +
> +#include <linux/device.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/reset.h>
> +
> +void rzv2h_ivc_write(struct rzv2h_ivc *ivc, u32 addr, u32 val)
> +{
> +	writel(val, ivc->base + addr);
> +}
> +
> +void rzv2h_ivc_update_bits(struct rzv2h_ivc *ivc, unsigned int addr,
> +			   u32 mask, u32 val)
> +{
> +	u32 orig, new;
> +
> +	orig = readl(ivc->base + addr);
> +
> +	new = orig & ~mask;
> +	new |= val & mask;
> +
> +	if (new != orig)
> +		writel(new, ivc->base + addr);
> +}
> +
> +static int rzv2h_ivc_get_hardware_resources(struct rzv2h_ivc *ivc,
> +					    struct platform_device *pdev)
> +{
> +	const char * const resource_names[RZV2H_IVC_NUM_HW_RESOURCES] = {
> +		"reg",
> +		"axi",
> +		"isp",
> +	};
> +	struct resource *res;
> +	int ret;
> +
> +	ivc->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
> +	if (IS_ERR(ivc->base))
> +		return dev_err_probe(ivc->dev, PTR_ERR(ivc->base),
> +				     "failed to map IO memory\n");
> +
> +	for (unsigned int i = 0; i < ARRAY_SIZE(resource_names); i++)
> +		ivc->clks[i].id = resource_names[i];
> +
> +	ret = devm_clk_bulk_get(ivc->dev, ARRAY_SIZE(resource_names), ivc->clks);
> +	if (ret)
> +		return dev_err_probe(ivc->dev, ret, "failed to acquire clks\n");
> +
> +	for (unsigned int i = 0; i < ARRAY_SIZE(resource_names); i++)
> +		ivc->resets[i].id = resource_names[i];
> +
> +	ret = devm_reset_control_bulk_get_optional_shared(
> +		ivc->dev, ARRAY_SIZE(resource_names), ivc->resets);
> +	if (ret)
> +		return dev_err_probe(ivc->dev, ret, "failed to acquire resets\n");
> +
> +	return 0;
> +}
> +
> +static void rzv2h_ivc_global_config(struct rzv2h_ivc *ivc)
> +{
> +	/* Currently we only support single-exposure input */
> +	rzv2h_ivc_write(ivc, RZV2H_IVC_REG_AXIRX_PLNUM, RZV2H_IVC_ONE_EXPOSURE);
> +
> +	/*
> +	 * Datasheet says we should disable the interrupts before changing mode
> +	 * to avoid spurious IFP interrupt.
> +	 */
> +	rzv2h_ivc_write(ivc, RZV2H_IVC_REG_FM_INT_EN, 0x0);
> +
> +	/*
> +	 * RZ/V2H documentation says software controlled single context mode is
> +	 * is not supported, and currently the driver does not support the
> +	 * multi-context mode. That being so we just set single context sw-hw
> +	 * mode.
> +	 */
> +	rzv2h_ivc_write(ivc, RZV2H_IVC_REG_FM_CONTEXT,
> +			RZV2H_IVC_SINGLE_CONTEXT_SW_HW_CFG);
> +
> +	/*
> +	 * We enable the frame end interrupt so that we know when we should send
> +	 * follow-up frames.
> +	 */
> +	rzv2h_ivc_write(ivc, RZV2H_IVC_REG_FM_INT_EN, RZV2H_IVC_VVAL_IFPE);
> +}
> +
> +static irqreturn_t rzv2h_ivc_isr(int irq, void *context)
> +{
> +	struct device *dev = context;
> +	struct rzv2h_ivc *ivc = dev_get_drvdata(dev);
> +
> +	guard(spinlock)(&ivc->spinlock);
> +
> +	if (!--ivc->vvalid_ifp)
> +		queue_work(ivc->buffers.async_wq, &ivc->buffers.work);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int rzv2h_ivc_runtime_resume(struct device *dev)
> +{
> +	struct rzv2h_ivc *ivc = dev_get_drvdata(dev);
> +	int ret;
> +
> +	ret = request_irq(ivc->irqnum, rzv2h_ivc_isr, 0, dev_driver_string(dev),
> +			  dev);
> +	if (ret) {
> +		dev_err(dev, "failed to request irq\n");
> +		return ret;
> +	}
> +
> +	ret = clk_bulk_prepare_enable(ARRAY_SIZE(ivc->clks), ivc->clks);
> +	if (ret) {
> +		dev_err(ivc->dev, "failed to enable clocks\n");
> +		goto err_free_irqnum;
> +	}
> +
> +	ret = reset_control_bulk_deassert(ARRAY_SIZE(ivc->resets), ivc->resets);
> +	if (ret) {
> +		dev_err(ivc->dev, "failed to deassert resets\n");
> +		goto err_disable_clks;
> +	}
> +
> +	rzv2h_ivc_global_config(ivc);
> +
> +	return 0;
> +
> +err_disable_clks:
> +	clk_bulk_disable_unprepare(ARRAY_SIZE(ivc->clks), ivc->clks);
> +err_free_irqnum:
> +	free_irq(ivc->irqnum, dev);
> +
> +	return ret;
> +}
> +
> +static int rzv2h_ivc_runtime_suspend(struct device *dev)
> +{
> +	struct rzv2h_ivc *ivc = dev_get_drvdata(dev);
> +
> +	reset_control_bulk_assert(ARRAY_SIZE(ivc->resets), ivc->resets);
> +	clk_bulk_disable_unprepare(ARRAY_SIZE(ivc->clks), ivc->clks);
> +	free_irq(ivc->irqnum, dev);
> +
> +	return 0;
> +}
> +
> +static const struct dev_pm_ops rzv2h_ivc_pm_ops = {
> +	SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
> +				pm_runtime_force_resume)

nit: fits on one line

> +	RUNTIME_PM_OPS(rzv2h_ivc_runtime_suspend, rzv2h_ivc_runtime_resume,
> +			   NULL)

nit: align to open (

> +};
> +
> +static int rzv2h_ivc_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct rzv2h_ivc *ivc;
> +	int ret;
> +
> +	ivc = devm_kzalloc(dev, sizeof(*ivc), GFP_KERNEL);
> +	if (!ivc)
> +		return -ENOMEM;
> +
> +	ivc->dev = dev;
> +	platform_set_drvdata(pdev, ivc);
> +	mutex_init(&ivc->lock);
> +	spin_lock_init(&ivc->spinlock);
> +
> +	ret = rzv2h_ivc_get_hardware_resources(ivc, pdev);
> +	if (ret)
> +		return ret;
> +
> +	pm_runtime_set_autosuspend_delay(dev, 2000);
> +	pm_runtime_use_autosuspend(dev);
> +	pm_runtime_enable(dev);
> +
> +	ivc->irqnum = platform_get_irq(pdev, 0);
> +	if (ivc->irqnum < 0) {
> +		dev_err(dev, "failed to get interrupt\n");
> +		return ret;
> +	}
> +
> +	ret = rzv2h_ivc_initialise_subdevice(ivc);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static void rzv2h_ivc_remove(struct platform_device *pdev)
> +{
> +	struct rzv2h_ivc *ivc = platform_get_drvdata(pdev);
> +
> +	rzv2h_deinit_video_dev_and_queue(ivc);
> +	rzv2h_ivc_deinit_subdevice(ivc);
> +	mutex_destroy(&ivc->lock);
> +}
> +
> +static const struct of_device_id rzv2h_ivc_of_match[] = {
> +	{ .compatible = "renesas,r9a09g057-ivc", },
> +	{ /* Sentinel */ },
> +};
> +MODULE_DEVICE_TABLE(of, rzv2h_ivc_of_match);
> +
> +static struct platform_driver rzv2h_ivc_driver = {
> +	.driver = {
> +		.name = "rzv2h-ivc",
> +		.of_match_table = rzv2h_ivc_of_match,
> +		.pm = &rzv2h_ivc_pm_ops,
> +	},
> +	.probe = rzv2h_ivc_probe,
> +	.remove = rzv2h_ivc_remove,
> +};
> +
> +module_platform_driver(rzv2h_ivc_driver);
> +
> +MODULE_AUTHOR("Daniel Scally <dan.scally@ideasonboard.com>");
> +MODULE_DESCRIPTION("Renesas RZ/V2H Input Video Control Block driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-subdev.c b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-subdev.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..eb2913153d406fbad2491bb36e1c5ea754bea6f2
> --- /dev/null
> +++ b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-subdev.c
> @@ -0,0 +1,376 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Renesas RZ/V2H Input Video Control Block driver
> + *
> + * Copyright (C) 2025 Ideas on Board Oy
> + */
> +
> +#include "rzv2h-ivc.h"
> +
> +#include <linux/media.h>
> +#include <linux/media-bus-format.h>
> +#include <linux/v4l2-mediabus.h>
> +
> +#include <media/v4l2-async.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-dev.h>
> +#include <media/v4l2-event.h>
> +
> +#define RZV2H_IVC_N_INPUTS_PER_OUTPUT		6
> +
> +/*
> + * We support 8/10/12/14/16/20 bit input in any bayer order, but the output
> + * format is fixed at 20-bits with the same order as the input.
> + */
> +static const struct {
> +	u32 inputs[RZV2H_IVC_N_INPUTS_PER_OUTPUT];
> +	u32 output;
> +} rzv2h_ivc_formats[] = {
> +	{
> +		.inputs = {
> +			MEDIA_BUS_FMT_SBGGR8_1X8,
> +			MEDIA_BUS_FMT_SBGGR10_1X10,
> +			MEDIA_BUS_FMT_SBGGR12_1X12,
> +			MEDIA_BUS_FMT_SBGGR14_1X14,
> +			MEDIA_BUS_FMT_SBGGR16_1X16,
> +			MEDIA_BUS_FMT_SBGGR20_1X20,
> +		},
> +		.output = MEDIA_BUS_FMT_SBGGR20_1X20
> +	},
> +	{
> +		.inputs = {
> +			MEDIA_BUS_FMT_SGBRG8_1X8,
> +			MEDIA_BUS_FMT_SGBRG10_1X10,
> +			MEDIA_BUS_FMT_SGBRG12_1X12,
> +			MEDIA_BUS_FMT_SGBRG14_1X14,
> +			MEDIA_BUS_FMT_SGBRG16_1X16,
> +			MEDIA_BUS_FMT_SGBRG20_1X20,
> +		},
> +		.output = MEDIA_BUS_FMT_SGBRG20_1X20
> +	},
> +	{
> +		.inputs = {
> +			MEDIA_BUS_FMT_SGRBG8_1X8,
> +			MEDIA_BUS_FMT_SGRBG10_1X10,
> +			MEDIA_BUS_FMT_SGRBG12_1X12,
> +			MEDIA_BUS_FMT_SGRBG14_1X14,
> +			MEDIA_BUS_FMT_SGRBG16_1X16,
> +			MEDIA_BUS_FMT_SGRBG20_1X20,
> +		},
> +		.output = MEDIA_BUS_FMT_SGRBG20_1X20
> +	},
> +	{
> +		.inputs = {
> +			MEDIA_BUS_FMT_SRGGB8_1X8,
> +			MEDIA_BUS_FMT_SRGGB10_1X10,
> +			MEDIA_BUS_FMT_SRGGB12_1X12,
> +			MEDIA_BUS_FMT_SRGGB14_1X14,
> +			MEDIA_BUS_FMT_SRGGB16_1X16,
> +			MEDIA_BUS_FMT_SRGGB20_1X20,
> +		},
> +		.output = MEDIA_BUS_FMT_SRGGB20_1X20
> +	},
> +};
> +
> +static u32 rzv2h_ivc_get_mbus_output_from_input(u32 mbus_code)
> +{
> +	unsigned int i, j;
> +
> +	for (i = 0; i < ARRAY_SIZE(rzv2h_ivc_formats); i++) {
> +		for (j = 0; j < RZV2H_IVC_N_INPUTS_PER_OUTPUT; j++) {
> +			if (rzv2h_ivc_formats[i].inputs[j] == mbus_code)
> +				return rzv2h_ivc_formats[i].output;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static int rzv2h_ivc_enum_mbus_code(struct v4l2_subdev *sd,
> +				    struct v4l2_subdev_state *state,
> +				    struct v4l2_subdev_mbus_code_enum *code)
> +{
> +	const struct v4l2_mbus_framefmt *fmt;
> +	unsigned int order_index;
> +	unsigned int index;
> +
> +	/*
> +	 * On the source pad, only the 20-bit format corresponding to the sink
> +	 * pad format's bayer order is supported.
> +	 */
> +	if (code->pad == RZV2H_IVC_SUBDEV_SOURCE_PAD) {
> +		if (code->index)
> +			return -EINVAL;
> +
> +		fmt = v4l2_subdev_state_get_format(state,
> +						   RZV2H_IVC_SUBDEV_SINK_PAD);
> +		code->code = rzv2h_ivc_get_mbus_output_from_input(fmt->code);
> +
> +		return 0;
> +	}
> +
> +	if (code->index >= ARRAY_SIZE(rzv2h_ivc_formats) *
> +				      RZV2H_IVC_N_INPUTS_PER_OUTPUT)
> +		return -EINVAL;
> +
> +	order_index = code->index / RZV2H_IVC_N_INPUTS_PER_OUTPUT;
> +	index = code->index % RZV2H_IVC_N_INPUTS_PER_OUTPUT;
> +
> +	code->code = rzv2h_ivc_formats[order_index].inputs[index];
> +
> +	return 0;
> +}
> +
> +static int rzv2h_ivc_enum_frame_size(struct v4l2_subdev *sd,
> +				     struct v4l2_subdev_state *state,
> +				     struct v4l2_subdev_frame_size_enum *fse)
> +{
> +	const struct v4l2_mbus_framefmt *fmt;
> +
> +	if (fse->index > 0)
> +		return -EINVAL;
> +
> +	if (fse->pad == RZV2H_IVC_SUBDEV_SOURCE_PAD) {
> +		fmt = v4l2_subdev_state_get_format(state,
> +						   RZV2H_IVC_SUBDEV_SINK_PAD);
> +
> +		if (fse->code != rzv2h_ivc_get_mbus_output_from_input(fmt->code))
> +			return -EINVAL;
> +
> +		fse->min_width = fmt->width;
> +		fse->max_width = fmt->width;
> +		fse->min_height = fmt->height;
> +		fse->max_height = fmt->height;
> +
> +		return 0;
> +	}
> +
> +	if (!rzv2h_ivc_get_mbus_output_from_input(fse->code))
> +		return -EINVAL;
> +
> +	fse->min_width = RZV2H_IVC_MIN_WIDTH;
> +	fse->max_width = RZV2H_IVC_MAX_WIDTH;
> +	fse->min_height = RZV2H_IVC_MIN_HEIGHT;
> +	fse->max_height = RZV2H_IVC_MAX_HEIGHT;
> +
> +	return 0;
> +}
> +
> +static int rzv2h_ivc_set_fmt(struct v4l2_subdev *sd,
> +			     struct v4l2_subdev_state *state,
> +			     struct v4l2_subdev_format *format)
> +{
> +	struct v4l2_mbus_framefmt *fmt = &format->format;
> +	struct v4l2_mbus_framefmt *src_fmt, *sink_fmt;
> +
> +	if (format->pad == RZV2H_IVC_SUBDEV_SOURCE_PAD)
> +		return v4l2_subdev_get_fmt(sd, state, format);
> +
> +	sink_fmt = v4l2_subdev_state_get_format(state,
> +						RZV2H_IVC_SUBDEV_SINK_PAD);
> +
> +	sink_fmt->code = rzv2h_ivc_get_mbus_output_from_input(fmt->code) ?
> +			 fmt->code : rzv2h_ivc_formats[0].inputs[0];
> +
> +	sink_fmt->width = clamp(fmt->width, RZV2H_IVC_MIN_WIDTH,
> +				RZV2H_IVC_MAX_WIDTH);
> +	sink_fmt->height = clamp(fmt->height, RZV2H_IVC_MIN_HEIGHT,
> +				 RZV2H_IVC_MAX_HEIGHT);
> +
> +	*fmt = *sink_fmt;
> +
> +	src_fmt = v4l2_subdev_state_get_format(state,
> +					       RZV2H_IVC_SUBDEV_SOURCE_PAD);
> +	*src_fmt = *sink_fmt;
> +	src_fmt->code = rzv2h_ivc_get_mbus_output_from_input(sink_fmt->code);
> +
> +	return 0;
> +}
> +
> +static int rzv2h_ivc_enable_streams(struct v4l2_subdev *sd,
> +				    struct v4l2_subdev_state *state, u32 pad,
> +				    u64 streams_mask)
> +{
> +	/*
> +	 * We have a single source pad, which has a single stream. V4L2 core has
> +	 * already validated those things. The actual power-on and programming
> +	 * of registers will be done through the video device's .vidioc_streamon
> +	 * so there's nothing to actually do here...
> +	 */
> +
> +	return 0;
> +}
> +
> +static int rzv2h_ivc_disable_streams(struct v4l2_subdev *sd,
> +				     struct v4l2_subdev_state *state, u32 pad,
> +				     u64 streams_mask)
> +{
> +	return 0;
> +}
> +
> +static const struct v4l2_subdev_pad_ops rzv2h_ivc_pad_ops = {
> +	.enum_mbus_code		= rzv2h_ivc_enum_mbus_code,
> +	.enum_frame_size	= rzv2h_ivc_enum_frame_size,
> +	.get_fmt		= v4l2_subdev_get_fmt,
> +	.set_fmt		= rzv2h_ivc_set_fmt,
> +	.enable_streams		= rzv2h_ivc_enable_streams,
> +	.disable_streams	= rzv2h_ivc_disable_streams,
> +};
> +
> +static const struct v4l2_subdev_core_ops rzv2h_ivc_core_ops = {
> +	.subscribe_event = v4l2_ctrl_subdev_subscribe_event,
> +	.unsubscribe_event = v4l2_event_subdev_unsubscribe,
> +};
> +
> +static const struct v4l2_subdev_ops rzv2h_ivc_subdev_ops = {
> +	.core	= &rzv2h_ivc_core_ops,
> +	.pad	= &rzv2h_ivc_pad_ops,
> +};
> +
> +static int rzv2h_ivc_init_state(struct v4l2_subdev *sd,
> +				struct v4l2_subdev_state *state)
> +{
> +	struct v4l2_mbus_framefmt *sink_fmt, *src_fmt;
> +
> +	sink_fmt = v4l2_subdev_state_get_format(state,
> +						RZV2H_IVC_SUBDEV_SINK_PAD);
> +	sink_fmt->width = RZV2H_IVC_DEFAULT_WIDTH;
> +	sink_fmt->height = RZV2H_IVC_DEFAULT_HEIGHT;
> +	sink_fmt->field = V4L2_FIELD_NONE;
> +	sink_fmt->code = MEDIA_BUS_FMT_SRGGB16_1X16;
> +	sink_fmt->colorspace = V4L2_COLORSPACE_RAW;
> +	sink_fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(sink_fmt->colorspace);
> +	sink_fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(sink_fmt->colorspace);
> +	sink_fmt->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(
> +		true, sink_fmt->colorspace, sink_fmt->ycbcr_enc);
> +
> +	src_fmt = v4l2_subdev_state_get_format(state,
> +					       RZV2H_IVC_SUBDEV_SOURCE_PAD);
> +
> +	*src_fmt = *sink_fmt;
> +	src_fmt->code = MEDIA_BUS_FMT_SRGGB20_1X20;
> +
> +	return 0;
> +}
> +
> +static int rzv2h_ivc_registered(struct v4l2_subdev *sd)
> +{
> +	struct rzv2h_ivc *ivc = container_of(sd, struct rzv2h_ivc, subdev.sd);
> +
> +	return rzv2h_ivc_init_vdev(ivc, sd->v4l2_dev);
> +}
> +
> +static const struct v4l2_subdev_internal_ops rzv2h_ivc_subdev_internal_ops = {
> +	.init_state = rzv2h_ivc_init_state,
> +	.registered = rzv2h_ivc_registered,
> +};
> +
> +static int rzv2h_ivc_link_validate(struct media_link *link)
> +{
> +	struct video_device *vdev =
> +		media_entity_to_video_device(link->source->entity);
> +	struct rzv2h_ivc *ivc = video_get_drvdata(vdev);
> +	struct v4l2_subdev *sd =
> +		media_entity_to_v4l2_subdev(link->sink->entity);
> +	const struct rzv2h_ivc_format *fmt;
> +	const struct v4l2_pix_format_mplane *pix;
> +	struct v4l2_subdev_state *state;
> +	struct v4l2_mbus_framefmt *mf;
> +	unsigned int i;
> +	int ret = 0;
> +
> +	state = v4l2_subdev_lock_and_get_active_state(sd);
> +	mf = v4l2_subdev_state_get_format(state, link->sink->index);
> +
> +	pix = &ivc->format.pix;
> +	fmt = ivc->format.fmt;
> +
> +	if (mf->width != pix->width || mf->height != pix->height) {
> +		dev_dbg(ivc->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,
> +			mf->width, mf->height,
> +			pix->width, pix->height);

nit: fits on one line

> +		ret = -EPIPE;
> +	}
> +
> +	for (i = 0; i < ARRAY_SIZE(fmt->mbus_codes); i++)
> +		if (mf->code == fmt->mbus_codes[i])
> +			break;
> +
> +	if (i == ARRAY_SIZE(fmt->mbus_codes)) {
> +		dev_dbg(ivc->dev,
> +			"link '%s':%u -> '%s':%u not valid: pixel format %p4cc cannot produce mbus_code 0x%04x\n",
> +			link->source->entity->name, link->source->index,
> +			link->sink->entity->name, link->sink->index,
> +			&pix->pixelformat, mf->code);
> +		ret = -EPIPE;
> +	}
> +
> +	v4l2_subdev_unlock_state(state);
> +
> +	return ret;
> +}
> +
> +static const struct media_entity_operations rzv2h_ivc_media_ops = {
> +	.link_validate = rzv2h_ivc_link_validate,
> +};
> +
> +int rzv2h_ivc_initialise_subdevice(struct rzv2h_ivc *ivc)
> +{
> +	struct v4l2_subdev *sd;
> +	int ret;
> +
> +	/* Initialise subdevice */
> +	sd = &ivc->subdev.sd;
> +	sd->dev = ivc->dev;
> +	v4l2_subdev_init(sd, &rzv2h_ivc_subdev_ops);
> +	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS;
> +	sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;
> +	sd->internal_ops = &rzv2h_ivc_subdev_internal_ops;
> +	sd->entity.ops = &rzv2h_ivc_media_ops;
> +
> +	ivc->subdev.pads[RZV2H_IVC_SUBDEV_SINK_PAD].flags = MEDIA_PAD_FL_SINK;
> +	ivc->subdev.pads[RZV2H_IVC_SUBDEV_SOURCE_PAD].flags = MEDIA_PAD_FL_SOURCE;
> +
> +	snprintf(sd->name, sizeof(sd->name), "rzv2h ivc block");
> +
> +	ret = media_entity_pads_init(&sd->entity, RZV2H_IVC_NUM_SUBDEV_PADS,
> +				     ivc->subdev.pads);
> +	if (ret) {
> +		dev_err(ivc->dev, "failed to initialise media entity\n");
> +		return ret;
> +	}
> +
> +	ret = v4l2_subdev_init_finalize(sd);
> +	if (ret) {
> +		dev_err(ivc->dev, "failed to finalize subdev init\n");
> +		goto err_cleanup_subdev_entity;
> +	}
> +
> +	ret = v4l2_async_register_subdev(sd);
> +	if (ret) {
> +		dev_err(ivc->dev, "failed to register subdevice\n");
> +		goto err_cleanup_subdev;
> +	}
> +
> +	return 0;
> +
> +err_cleanup_subdev:
> +	v4l2_subdev_cleanup(sd);
> +err_cleanup_subdev_entity:
> +	media_entity_cleanup(&sd->entity);
> +
> +	return ret;
> +}
> +
> +void rzv2h_ivc_deinit_subdevice(struct rzv2h_ivc *ivc)
> +{
> +	struct v4l2_subdev *sd = &ivc->subdev.sd;
> +
> +	v4l2_subdev_cleanup(sd);
> +	media_entity_remove_links(&sd->entity);
> +	v4l2_async_unregister_subdev(sd);
> +	media_entity_cleanup(&sd->entity);
> +}
> diff --git a/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-video.c b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-video.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..005a5700b5e2351b1e7ba5d99539ce4468f3db8b
> --- /dev/null
> +++ b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-video.c
> @@ -0,0 +1,568 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Renesas RZ/V2H Input Video Control Block driver
> + *
> + * Copyright (C) 2025 Ideas on Board Oy
> + */
> +
> +#include "rzv2h-ivc.h"
> +
> +#include <linux/cleanup.h>
> +#include <linux/iopoll.h>
> +#include <linux/media-bus-format.h>
> +#include <linux/minmax.h>
> +#include <linux/mutex.h>
> +#include <linux/pm_runtime.h>
> +
> +#include <media/mipi-csi2.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-dev.h>
> +#include <media/v4l2-event.h>
> +#include <media/v4l2-fh.h>
> +#include <media/v4l2-ioctl.h>
> +#include <media/videobuf2-dma-contig.h>
> +
> +#define RZV2H_IVC_FIXED_HBLANK			0x20
> +#define RZV2H_IVC_MIN_VBLANK(hts)		max(0x1b, 15 + (120501 / (hts)))
> +
> +struct rzv2h_ivc_buf {
> +	struct vb2_v4l2_buffer vb;
> +	struct list_head queue;
> +	dma_addr_t addr;
> +};
> +
> +#define to_rzv2h_ivc_buf(vbuf) \
> +	container_of(vbuf, struct rzv2h_ivc_buf, vb)
> +
> +static const struct rzv2h_ivc_format rzv2h_ivc_formats[] = {
> +	{
> +		.fourcc = V4L2_PIX_FMT_SBGGR8,
> +		.mbus_codes = {
> +			MEDIA_BUS_FMT_SBGGR8_1X8,
> +		},
> +		.dtype = MIPI_CSI2_DT_RAW8,
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_SGBRG8,
> +		.mbus_codes = {
> +			MEDIA_BUS_FMT_SGBRG8_1X8,
> +		},
> +		.dtype = MIPI_CSI2_DT_RAW8,
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_SGRBG8,
> +		.mbus_codes = {
> +			MEDIA_BUS_FMT_SGRBG8_1X8,
> +		},
> +		.dtype = MIPI_CSI2_DT_RAW8,
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_SRGGB8,
> +		.mbus_codes = {
> +			MEDIA_BUS_FMT_SRGGB8_1X8,
> +		},
> +		.dtype = MIPI_CSI2_DT_RAW8,
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_RAW_CRU10,
> +		.mbus_codes = {
> +			MEDIA_BUS_FMT_SBGGR10_1X10,
> +			MEDIA_BUS_FMT_SGBRG10_1X10,
> +			MEDIA_BUS_FMT_SGRBG10_1X10,
> +			MEDIA_BUS_FMT_SRGGB10_1X10
> +		},
> +		.dtype = MIPI_CSI2_DT_RAW10,
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_RAW_CRU12,
> +		.mbus_codes = {
> +			MEDIA_BUS_FMT_SBGGR12_1X12,
> +			MEDIA_BUS_FMT_SGBRG12_1X12,
> +			MEDIA_BUS_FMT_SGRBG12_1X12,
> +			MEDIA_BUS_FMT_SRGGB12_1X12
> +		},
> +		.dtype = MIPI_CSI2_DT_RAW12,
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_RAW_CRU14,
> +		.mbus_codes = {
> +			MEDIA_BUS_FMT_SBGGR14_1X14,
> +			MEDIA_BUS_FMT_SGBRG14_1X14,
> +			MEDIA_BUS_FMT_SGRBG14_1X14,
> +			MEDIA_BUS_FMT_SRGGB14_1X14
> +		},
> +		.dtype = MIPI_CSI2_DT_RAW14,
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_SBGGR16,
> +		.mbus_codes = {
> +			MEDIA_BUS_FMT_SBGGR16_1X16,
> +		},
> +		.dtype = MIPI_CSI2_DT_RAW16,
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_SGBRG16,
> +		.mbus_codes = {
> +			MEDIA_BUS_FMT_SGBRG16_1X16,
> +		},
> +		.dtype = MIPI_CSI2_DT_RAW16,
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_SGRBG16,
> +		.mbus_codes = {
> +			MEDIA_BUS_FMT_SGRBG16_1X16,
> +		},
> +		.dtype = MIPI_CSI2_DT_RAW16,
> +	},
> +	{
> +		.fourcc = V4L2_PIX_FMT_SRGGB16,
> +		.mbus_codes = {
> +			MEDIA_BUS_FMT_SRGGB16_1X16,
> +		},
> +		.dtype = MIPI_CSI2_DT_RAW16,
> +	},
> +};
> +
> +static void rzv2h_ivc_transfer_buffer(struct work_struct *work)
> +{
> +	struct rzv2h_ivc *ivc = container_of(work, struct rzv2h_ivc,
> +					     buffers.work);
> +	struct rzv2h_ivc_buf *buf;
> +
> +	scoped_guard(spinlock, &ivc->buffers.lock) {
> +		if (ivc->buffers.curr) {
> +			ivc->buffers.curr->vb.sequence = ivc->buffers.sequence++;
> +			vb2_buffer_done(&ivc->buffers.curr->vb.vb2_buf,
> +					VB2_BUF_STATE_DONE);
> +			ivc->buffers.curr = NULL;
> +		}
> +
> +		buf = list_first_entry_or_null(&ivc->buffers.queue,
> +					       struct rzv2h_ivc_buf, queue);
> +	}
> +
> +	if (!buf)
> +		return;
> +
> +	list_del(&buf->queue);
> +
> +	ivc->buffers.curr = buf;
> +	buf->addr = vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
> +	rzv2h_ivc_write(ivc, RZV2H_IVC_REG_AXIRX_SADDL_P0, buf->addr);
> +
> +	scoped_guard(spinlock_irqsave, &ivc->spinlock) {
> +		ivc->vvalid_ifp = 2;
> +	}
> +	rzv2h_ivc_write(ivc, RZV2H_IVC_REG_FM_FRCON, 0x1);
> +}
> +
> +static int rzv2h_ivc_pipeline_started(struct media_entity *entity)
> +{
> +	struct video_device *vdev = media_entity_to_video_device(entity);
> +	struct rzv2h_ivc *ivc = video_get_drvdata(vdev);
> +
> +	guard(spinlock)(&ivc->buffers.lock);
> +
> +	if (list_empty(&ivc->buffers.queue)) {
> +		/*
> +		 * The driver waits for interrupts to send a new frame and
> +		 * tracks their receipt in the vvalid_ifp variable. .buf_queue()
> +		 * will queue work if vvalid_ifp == 0 to trigger a new frame (an
> +		 * event that normally would only occur if no buffer was ready
> +		 * when the interrupt arrived). If there are no buffers in the
> +		 * queue yet, we set vvalid_ifp to zero so that the next queue
> +		 * will trigger the work.
> +		 */
> +		scoped_guard(spinlock_irqsave, &ivc->spinlock) {

This can be just guard() ?

> +			ivc->vvalid_ifp = 0;
> +		}
> +	} else {
> +		queue_work(ivc->buffers.async_wq, &ivc->buffers.work);
> +	}
> +
> +	return 0;
> +}
> +
> +static void rzv2h_ivc_pipeline_stopped(struct media_entity *entity)
> +{
> +	struct video_device *vdev = media_entity_to_video_device(entity);
> +	struct rzv2h_ivc *ivc = video_get_drvdata(vdev);
> +	u32 val = 0;
> +
> +	rzv2h_ivc_write(ivc, RZV2H_IVC_REG_FM_STOP, 0x1);
> +	readl_poll_timeout(ivc->base + RZV2H_IVC_REG_FM_STOP,
> +			   val, !val, 10 * USEC_PER_MSEC, 250 * USEC_PER_MSEC);
> +}
> +
> +static const struct media_entity_operations rzv2h_ivc_media_ops = {
> +	.pipeline_started = rzv2h_ivc_pipeline_started,
> +	.pipeline_stopped = rzv2h_ivc_pipeline_stopped,
> +};

I still think we shouldn't be introducing dependencies between these
two series. Can you drop anything related to pipeline_started/stopped
from this series ? Would it make sense ? After all, it only serves to
have the IVC operate with the Mali C55 and hence requires both
pipeline_started and media jobs, right ?

Can we:
- Upstream IVC without pipeline_started
- Upstream Mali C55 without pipeline started and media jobs
- Add pipeline_started and media jobs on top

Would this make sense in your opinion or should at least
pipeline_started be there ? In this case we should merge this series
and the mali c55 at the same time ?

The rest are all nits, so for this patch, provided we clarify the
above point about dependencies

Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>

Thanks
  j


> +
> +static int rzv2h_ivc_queue_setup(struct vb2_queue *q, unsigned int *num_buffers,
> +				 unsigned int *num_planes, unsigned int sizes[],
> +				 struct device *alloc_devs[])
> +{
> +	struct rzv2h_ivc *ivc = vb2_get_drv_priv(q);
> +
> +	if (*num_planes && *num_planes > 1)
> +		return -EINVAL;
> +
> +	if (sizes[0] && sizes[0] < ivc->format.pix.plane_fmt[0].sizeimage)
> +		return -EINVAL;
> +
> +	*num_planes = 1;
> +
> +	if (!sizes[0])
> +		sizes[0] = ivc->format.pix.plane_fmt[0].sizeimage;
> +
> +	return 0;
> +}
> +
> +static void rzv2h_ivc_buf_queue(struct vb2_buffer *vb)
> +{
> +	struct rzv2h_ivc *ivc = vb2_get_drv_priv(vb->vb2_queue);
> +	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
> +	struct rzv2h_ivc_buf *buf = to_rzv2h_ivc_buf(vbuf);
> +
> +	scoped_guard(spinlock, &ivc->buffers.lock) {
> +		list_add_tail(&buf->queue, &ivc->buffers.queue);
> +	}
> +
> +	scoped_guard(spinlock_irqsave, &ivc->spinlock) {
> +		if (vb2_is_streaming(vb->vb2_queue) && !ivc->vvalid_ifp)
> +			queue_work(ivc->buffers.async_wq, &ivc->buffers.work);
> +	}
> +}
> +
> +static void rzv2h_ivc_format_configure(struct rzv2h_ivc *ivc)
> +{
> +	const struct rzv2h_ivc_format *fmt = ivc->format.fmt;
> +	struct v4l2_pix_format_mplane *pix = &ivc->format.pix;
> +	unsigned int vblank;
> +	unsigned int hts;
> +
> +	/* Currently only CRU packed pixel formats are supported */
> +	rzv2h_ivc_write(ivc, RZV2H_IVC_REG_AXIRX_PXFMT,
> +			RZV2H_IVC_INPUT_FMT_CRU_PACKED);
> +
> +	rzv2h_ivc_update_bits(ivc, RZV2H_IVC_REG_AXIRX_PXFMT,
> +			      RZV2H_IVC_PXFMT_DTYPE, fmt->dtype);
> +
> +	rzv2h_ivc_write(ivc, RZV2H_IVC_REG_AXIRX_HSIZE, pix->width);
> +	rzv2h_ivc_write(ivc, RZV2H_IVC_REG_AXIRX_VSIZE, pix->height);
> +	rzv2h_ivc_write(ivc, RZV2H_IVC_REG_AXIRX_STRD,
> +			pix->plane_fmt[0].bytesperline);
> +
> +	/*
> +	 * The ISP has minimum vertical blanking requirements that must be
> +	 * adhered to by the IVC. The minimum is a function of the Iridix blocks
> +	 * clocking requirements and the width of the image and horizontal
> +	 * blanking, but if we assume the worst case then it boils down to the
> +	 * below (plus one to the numerator to ensure the answer is rounded up)
> +	 */
> +
> +	hts = pix->width + RZV2H_IVC_FIXED_HBLANK;
> +	vblank = RZV2H_IVC_MIN_VBLANK(hts);
> +
> +	rzv2h_ivc_write(ivc, RZV2H_IVC_REG_AXIRX_BLANK,
> +			RZV2H_IVC_VBLANK(vblank));
> +}
> +
> +static void rzv2h_ivc_return_buffers(struct rzv2h_ivc *ivc,
> +				     enum vb2_buffer_state state)
> +{
> +	struct rzv2h_ivc_buf *buf, *tmp;
> +
> +	guard(spinlock)(&ivc->buffers.lock);
> +
> +	if (ivc->buffers.curr) {
> +		vb2_buffer_done(&ivc->buffers.curr->vb.vb2_buf, state);
> +		ivc->buffers.curr = NULL;
> +	}
> +
> +	list_for_each_entry_safe(buf, tmp, &ivc->buffers.queue, queue) {
> +		list_del(&buf->queue);
> +		vb2_buffer_done(&buf->vb.vb2_buf, state);
> +	}
> +}
> +
> +static int rzv2h_ivc_start_streaming(struct vb2_queue *q, unsigned int count)
> +{
> +	struct rzv2h_ivc *ivc = vb2_get_drv_priv(q);
> +	int ret;
> +
> +	ivc->buffers.sequence = 0;
> +	ivc->vvalid_ifp = 2;
> +
> +	ret = pm_runtime_resume_and_get(ivc->dev);
> +	if (ret)
> +		goto err_return_buffers;
> +
> +	ret = video_device_pipeline_alloc_start(&ivc->vdev.dev);
> +	if (ret) {
> +		dev_err(ivc->dev, "failed to start media pipeline\n");
> +		goto err_pm_runtime_put;
> +	}
> +
> +	rzv2h_ivc_format_configure(ivc);
> +
> +	ret = video_device_pipeline_started(&ivc->vdev.dev);
> +	if (ret < 0)
> +		goto err_stop_pipeline;
> +
> +	return 0;
> +
> +err_stop_pipeline:
> +	video_device_pipeline_stop(&ivc->vdev.dev);
> +err_pm_runtime_put:
> +	pm_runtime_put(ivc->dev);
> +err_return_buffers:
> +	rzv2h_ivc_return_buffers(ivc, VB2_BUF_STATE_QUEUED);
> +
> +	return ret;
> +}
> +
> +static void rzv2h_ivc_stop_streaming(struct vb2_queue *q)
> +{
> +	struct rzv2h_ivc *ivc = vb2_get_drv_priv(q);
> +
> +	video_device_pipeline_stopped(&ivc->vdev.dev);
> +	rzv2h_ivc_return_buffers(ivc, VB2_BUF_STATE_ERROR);
> +	video_device_pipeline_stop(&ivc->vdev.dev);
> +	pm_runtime_mark_last_busy(ivc->dev);
> +	pm_runtime_put_autosuspend(ivc->dev);
> +}
> +
> +static const struct vb2_ops rzv2h_ivc_vb2_ops = {
> +	.queue_setup		= &rzv2h_ivc_queue_setup,
> +	.buf_queue		= &rzv2h_ivc_buf_queue,
> +	.wait_prepare		= vb2_ops_wait_prepare,
> +	.wait_finish		= vb2_ops_wait_finish,
> +	.start_streaming	= &rzv2h_ivc_start_streaming,
> +	.stop_streaming		= &rzv2h_ivc_stop_streaming,
> +};
> +
> +static const struct rzv2h_ivc_format *
> +rzv2h_ivc_format_from_pixelformat(u32 fourcc)
> +{
> +	for (unsigned int i = 0; i < ARRAY_SIZE(rzv2h_ivc_formats); i++)
> +		if (fourcc == rzv2h_ivc_formats[i].fourcc)
> +			return &rzv2h_ivc_formats[i];
> +
> +	return &rzv2h_ivc_formats[0];
> +}
> +
> +static int rzv2h_ivc_enum_fmt_vid_out(struct file *file, void *fh,
> +				      struct v4l2_fmtdesc *f)
> +{
> +	if (f->index >= ARRAY_SIZE(rzv2h_ivc_formats))
> +		return -EINVAL;
> +
> +	f->pixelformat = rzv2h_ivc_formats[f->index].fourcc;
> +	return 0;
> +}
> +
> +static int rzv2h_ivc_g_fmt_vid_out(struct file *file, void *fh,
> +				   struct v4l2_format *f)
> +{
> +	struct rzv2h_ivc *ivc = video_drvdata(file);
> +
> +	f->fmt.pix_mp = ivc->format.pix;
> +
> +	return 0;
> +}
> +
> +static void rzv2h_ivc_try_fmt(struct v4l2_pix_format_mplane *pix,
> +			      const struct rzv2h_ivc_format *fmt)
> +{
> +	pix->pixelformat = fmt->fourcc;
> +
> +	pix->width = clamp(pix->width, RZV2H_IVC_MIN_WIDTH,
> +			   RZV2H_IVC_MAX_WIDTH);
> +	pix->height = clamp(pix->height, RZV2H_IVC_MIN_HEIGHT,
> +			    RZV2H_IVC_MAX_HEIGHT);
> +
> +	pix->field = V4L2_FIELD_NONE;
> +	pix->colorspace = V4L2_COLORSPACE_RAW;
> +	pix->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(pix->colorspace);
> +	pix->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true,
> +							  pix->colorspace,
> +							  pix->ycbcr_enc);
> +
> +	v4l2_fill_pixfmt_mp(pix, pix->pixelformat, pix->width, pix->height);
> +}
> +
> +static void rzv2h_ivc_set_format(struct rzv2h_ivc *ivc,
> +				 struct v4l2_pix_format_mplane *pix)
> +{
> +	const struct rzv2h_ivc_format *fmt;
> +
> +	fmt = rzv2h_ivc_format_from_pixelformat(pix->pixelformat);
> +
> +	rzv2h_ivc_try_fmt(pix, fmt);
> +	ivc->format.pix = *pix;
> +	ivc->format.fmt = fmt;
> +}
> +
> +static int rzv2h_ivc_s_fmt_vid_out(struct file *file, void *fh,
> +				   struct v4l2_format *f)
> +{
> +	struct rzv2h_ivc *ivc = video_drvdata(file);
> +	struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;
> +
> +	if (vb2_is_busy(&ivc->vdev.vb2q))
> +		return -EBUSY;
> +
> +	rzv2h_ivc_set_format(ivc, pix);
> +
> +	return 0;
> +}
> +
> +static int rzv2h_ivc_try_fmt_vid_out(struct file *file, void *fh,
> +				     struct v4l2_format *f)
> +{
> +	const struct rzv2h_ivc_format *fmt;
> +
> +	fmt = rzv2h_ivc_format_from_pixelformat(f->fmt.pix.pixelformat);
> +	rzv2h_ivc_try_fmt(&f->fmt.pix_mp, fmt);
> +
> +	return 0;
> +}
> +
> +static int rzv2h_ivc_querycap(struct file *file, void *fh,
> +			      struct v4l2_capability *cap)
> +{
> +	strscpy(cap->driver, "rzv2h-ivc", sizeof(cap->driver));
> +	strscpy(cap->card, "Renesas Input Video Control", sizeof(cap->card));
> +
> +	return 0;
> +}
> +
> +static const struct v4l2_ioctl_ops rzv2h_ivc_v4l2_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_enum_fmt_vid_out = rzv2h_ivc_enum_fmt_vid_out,
> +	.vidioc_g_fmt_vid_out_mplane = rzv2h_ivc_g_fmt_vid_out,
> +	.vidioc_s_fmt_vid_out_mplane = rzv2h_ivc_s_fmt_vid_out,
> +	.vidioc_try_fmt_vid_out_mplane = rzv2h_ivc_try_fmt_vid_out,
> +	.vidioc_querycap = rzv2h_ivc_querycap,
> +	.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
> +	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
> +};
> +
> +static const struct v4l2_file_operations rzv2h_ivc_v4l2_fops = {
> +	.owner = THIS_MODULE,
> +	.unlocked_ioctl = video_ioctl2,
> +	.open = v4l2_fh_open,
> +	.release = vb2_fop_release,
> +	.poll = vb2_fop_poll,
> +	.mmap = vb2_fop_mmap,
> +};
> +
> +int rzv2h_ivc_init_vdev(struct rzv2h_ivc *ivc, struct v4l2_device *v4l2_dev)
> +{
> +	struct v4l2_pix_format_mplane pix = { };
> +	struct video_device *vdev;
> +	struct vb2_queue *vb2q;
> +	int ret;
> +
> +	spin_lock_init(&ivc->buffers.lock);
> +	INIT_LIST_HEAD(&ivc->buffers.queue);
> +	INIT_WORK(&ivc->buffers.work, rzv2h_ivc_transfer_buffer);
> +
> +	ivc->buffers.async_wq = alloc_workqueue("rzv2h-ivc", 0, 0);
> +	if (!ivc->buffers.async_wq)
> +		return -EINVAL;
> +
> +	/* Initialise vb2 queue */
> +	vb2q = &ivc->vdev.vb2q;
> +	vb2q->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
> +	vb2q->io_modes = VB2_MMAP | VB2_DMABUF;
> +	vb2q->drv_priv = ivc;
> +	vb2q->mem_ops = &vb2_dma_contig_memops;
> +	vb2q->ops = &rzv2h_ivc_vb2_ops;
> +	vb2q->buf_struct_size = sizeof(struct rzv2h_ivc_buf);
> +	vb2q->min_queued_buffers = 0;
> +	vb2q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
> +	vb2q->lock = &ivc->lock;
> +	vb2q->dev = ivc->dev;
> +
> +	ret = vb2_queue_init(vb2q);
> +	if (ret) {
> +		dev_err(ivc->dev, "vb2 queue init failed\n");
> +		goto err_destroy_workqueue;
> +	}
> +
> +	/* Initialise Video Device */
> +	vdev = &ivc->vdev.dev;
> +	strscpy(vdev->name, "rzv2h-ivc", sizeof(vdev->name));
> +	vdev->release = video_device_release_empty;
> +	vdev->fops = &rzv2h_ivc_v4l2_fops;
> +	vdev->ioctl_ops = &rzv2h_ivc_v4l2_ioctl_ops;
> +	vdev->lock = &ivc->lock;
> +	vdev->v4l2_dev = v4l2_dev;
> +	vdev->queue = vb2q;
> +	vdev->device_caps = V4L2_CAP_VIDEO_OUTPUT_MPLANE | V4L2_CAP_STREAMING;
> +	vdev->vfl_dir = VFL_DIR_TX;
> +	video_set_drvdata(vdev, ivc);
> +
> +	pix.pixelformat = V4L2_PIX_FMT_SRGGB16;
> +	pix.width = RZV2H_IVC_DEFAULT_WIDTH;
> +	pix.height = RZV2H_IVC_DEFAULT_HEIGHT;
> +	rzv2h_ivc_set_format(ivc, &pix);
> +
> +	ivc->vdev.pad.flags = MEDIA_PAD_FL_SOURCE;
> +	ivc->vdev.dev.entity.ops = &rzv2h_ivc_media_ops;
> +	ret = media_entity_pads_init(&ivc->vdev.dev.entity, 1, &ivc->vdev.pad);
> +	if (ret)
> +		goto err_release_vb2q;
> +
> +	ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
> +	if (ret) {
> +		dev_err(ivc->dev, "failed to register IVC video device\n");
> +		goto err_cleanup_vdev_entity;
> +	}
> +
> +	ret = media_create_pad_link(&vdev->entity, 0, &ivc->subdev.sd.entity,
> +				    RZV2H_IVC_SUBDEV_SINK_PAD,
> +				    MEDIA_LNK_FL_ENABLED |
> +				    MEDIA_LNK_FL_IMMUTABLE);
> +	if (ret) {
> +		dev_err(ivc->dev, "failed to create media link\n");
> +		goto err_unregister_vdev;
> +	}
> +
> +	return 0;
> +
> +err_unregister_vdev:
> +	video_unregister_device(vdev);
> +err_cleanup_vdev_entity:
> +	media_entity_cleanup(&vdev->entity);
> +err_release_vb2q:
> +	vb2_queue_release(vb2q);
> +err_destroy_workqueue:
> +	destroy_workqueue(ivc->buffers.async_wq);
> +
> +	return ret;
> +}
> +
> +void rzv2h_deinit_video_dev_and_queue(struct rzv2h_ivc *ivc)
> +{
> +	struct video_device *vdev = &ivc->vdev.dev;
> +	struct vb2_queue *vb2q = &ivc->vdev.vb2q;
> +
> +	if (!ivc->sched)
> +		return;
> +
> +	vb2_video_unregister_device(vdev);
> +	media_entity_cleanup(&vdev->entity);
> +	vb2_queue_release(vb2q);
> +}
> diff --git a/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc.h b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc.h
> new file mode 100644
> index 0000000000000000000000000000000000000000..709c6a9398fe2484c2acb03d443d58ea4e153a66
> --- /dev/null
> +++ b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc.h
> @@ -0,0 +1,131 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Renesas RZ/V2H Input Video Control Block driver
> + *
> + * Copyright (C) 2025 Ideas on Board Oy
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/list.h>
> +#include <linux/mutex.h>
> +#include <linux/reset.h>
> +#include <linux/spinlock.h>
> +#include <linux/types.h>
> +#include <linux/videodev2.h>
> +#include <linux/workqueue.h>
> +
> +#include <media/media-entity.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-subdev.h>
> +#include <media/videobuf2-core.h>
> +#include <media/videobuf2-v4l2.h>
> +
> +#define RZV2H_IVC_REG_AXIRX_PLNUM			0x0000
> +#define RZV2H_IVC_ONE_EXPOSURE				0x00
> +#define RZV2H_IVC_TWO_EXPOSURE				0x01
> +#define RZV2H_IVC_REG_AXIRX_PXFMT			0x0004
> +#define RZV2H_IVC_INPUT_FMT_MIPI			(0 << 16)
> +#define RZV2H_IVC_INPUT_FMT_CRU_PACKED			(1 << 16)
> +#define RZV2H_IVC_PXFMT_DTYPE				GENMASK(7, 0)
> +#define RZV2H_IVC_REG_AXIRX_SADDL_P0			0x0010
> +#define RZV2H_IVC_REG_AXIRX_SADDH_P0			0x0014
> +#define RZV2H_IVC_REG_AXIRX_SADDL_P1			0x0018
> +#define RZV2H_IVC_REG_AXIRX_SADDH_P1			0x001c
> +#define RZV2H_IVC_REG_AXIRX_HSIZE			0x0020
> +#define RZV2H_IVC_REG_AXIRX_VSIZE			0x0024
> +#define RZV2H_IVC_REG_AXIRX_BLANK			0x0028
> +#define RZV2H_IVC_VBLANK(x)				((x) << 16)
> +#define RZV2H_IVC_REG_AXIRX_STRD			0x0030
> +#define RZV2H_IVC_REG_AXIRX_ISSU			0x0040
> +#define RZV2H_IVC_REG_AXIRX_ERACT			0x0048
> +#define RZV2H_IVC_REG_FM_CONTEXT			0x0100
> +#define RZV2H_IVC_SOFTWARE_CFG				0x00
> +#define RZV2H_IVC_SINGLE_CONTEXT_SW_HW_CFG		BIT(0)
> +#define RZV2H_IVC_MULTI_CONTEXT_SW_HW_CFG		BIT(1)
> +#define RZV2H_IVC_REG_FM_MCON				0x0104
> +#define RZV2H_IVC_REG_FM_FRCON				0x0108
> +#define RZV2H_IVC_REG_FM_STOP				0x010c
> +#define RZV2H_IVC_REG_FM_INT_EN				0x0120
> +#define RZV2H_IVC_VVAL_IFPE				BIT(0)
> +#define RZV2H_IVC_REG_FM_INT_STA			0x0124
> +#define RZV2H_IVC_REG_AXIRX_FIFOCAP0			0x0208
> +#define RZV2H_IVC_REG_CORE_CAPCON			0x020c
> +#define RZV2H_IVC_REG_CORE_FIFOCAP0			0x0228
> +#define RZV2H_IVC_REG_CORE_FIFOCAP1			0x022c
> +
> +#define RZV2H_IVC_MIN_WIDTH				640
> +#define RZV2H_IVC_MAX_WIDTH				4096
> +#define RZV2H_IVC_MIN_HEIGHT				480
> +#define RZV2H_IVC_MAX_HEIGHT				4096
> +#define RZV2H_IVC_DEFAULT_WIDTH				1920
> +#define RZV2H_IVC_DEFAULT_HEIGHT			1080
> +
> +#define RZV2H_IVC_NUM_HW_RESOURCES			3
> +
> +struct device;
> +
> +enum rzv2h_ivc_subdev_pads {
> +	RZV2H_IVC_SUBDEV_SINK_PAD,
> +	RZV2H_IVC_SUBDEV_SOURCE_PAD,
> +	RZV2H_IVC_NUM_SUBDEV_PADS
> +};
> +
> +struct rzv2h_ivc_format {
> +	u32 fourcc;
> +	/*
> +	 * The CRU packed pixel formats are bayer-order agnostic, so each could
> +	 * support any one of the 4 possible media bus formats.
> +	 */
> +	u32 mbus_codes[4];
> +	u8 dtype;
> +};
> +
> +struct rzv2h_ivc {
> +	struct device *dev;
> +	void __iomem *base;
> +	struct clk_bulk_data clks[RZV2H_IVC_NUM_HW_RESOURCES];
> +	struct reset_control_bulk_data resets[RZV2H_IVC_NUM_HW_RESOURCES];
> +	int irqnum;
> +	u8 vvalid_ifp;
> +
> +	struct {
> +		struct video_device dev;
> +		struct vb2_queue vb2q;
> +		struct media_pad pad;
> +	} vdev;
> +
> +	struct {
> +		struct v4l2_subdev sd;
> +		struct media_pad pads[RZV2H_IVC_NUM_SUBDEV_PADS];
> +	} subdev;
> +
> +	struct {
> +		/* Spinlock to guard buffer queue */
> +		spinlock_t lock;
> +		struct workqueue_struct *async_wq;
> +		struct work_struct work;
> +		struct list_head queue;
> +		struct rzv2h_ivc_buf *curr;
> +		unsigned int sequence;
> +	} buffers;
> +
> +	struct media_job_scheduler *sched;
> +
> +	struct {
> +		struct v4l2_pix_format_mplane pix;
> +		const struct rzv2h_ivc_format *fmt;
> +	} format;
> +
> +	/* Mutex to provide to vb2 */
> +	struct mutex lock;
> +	/* Lock to protect the interrupt counter */
> +	spinlock_t spinlock;
> +};
> +
> +int rzv2h_ivc_init_vdev(struct rzv2h_ivc *ivc, struct v4l2_device *v4l2_dev);
> +void rzv2h_deinit_video_dev_and_queue(struct rzv2h_ivc *ivc);
> +int rzv2h_ivc_initialise_subdevice(struct rzv2h_ivc *ivc);
> +void rzv2h_ivc_deinit_subdevice(struct rzv2h_ivc *ivc);
> +void rzv2h_ivc_write(struct rzv2h_ivc *ivc, u32 addr, u32 val);
> +void rzv2h_ivc_update_bits(struct rzv2h_ivc *ivc, unsigned int addr,
> +			   u32 mask, u32 val);
>
> --
> 2.34.1
>
>

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

* Re: [PATCH v4 2/3] media: platform: Add Renesas Input Video Control block driver
  2025-07-22  9:40   ` Jacopo Mondi
@ 2025-07-23 21:17     ` Dan Scally
  0 siblings, 0 replies; 12+ messages in thread
From: Dan Scally @ 2025-07-23 21:17 UTC (permalink / raw)
  To: Jacopo Mondi
  Cc: linux-media, devicetree, linux-renesas-soc, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Geert Uytterhoeven,
	Magnus Damm, Philipp Zabel, biju.das.jz, laurent.pinchart

Hi Jacopo - thanks for the review

On 22/07/2025 10:40, Jacopo Mondi wrote:
> Hi Dan
>
> On Mon, Jul 14, 2025 at 04:19:18PM +0100, Daniel Scally wrote:
>> Add a driver for the Input Video Control block in an RZ/V2H SoC which
>> feeds data into the Arm Mali-C55 ISP.
>>
>> Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com>
>> ---
>> Changes in v5:
>>
>> 	- Fixed .enum_frame_sizes() to properly check that the
>> 	  given mbus_code matches the source pads format.
>> 	- Tidy up extra space in Kconfig
>> 	- Revise Kconfig option message
>> 	- Don't mark functions inline
>> 	- Fixup misleading comment
>> 	- select CONFIG_PM
>> 	- Use the new pm_sleep_ptr() functionality
>> 	- Minor formatting
>>
>> Changes in v4:
>>
>> 	- Update the compatible to renesas,r9a09g057-ivc
>> 	- Dropped the media jobs / scheduler functionality, and re
>> 	  worked the driver to have its own workqueue pushing frames
>> 	- Fix .enum_mbus_code() to return 20-bit output for source
>> 	  pad.
>> 	- Fix some alignment issues
>> 	- Make the forwarding of sink to source pad format a more
>> 	  explicit operation.
>> 	- Rename rzv2h_initialise_video_device_and_queue()
>> 	- Reversed order of v4l2_subdev_init_finalize() and
>> 	  v4l2_async_register_subdev() to make sure everything is
>> 	  finished initialising before registering the subdev.
>> 	- Change function to MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER
>> 	- Use a parametised macro for min vblank
>> 	- Minor formatting
>> 	- Use the DEFAULT macros for quantization / ycbcr_enc values
>> 	- Switch to using the mplane API
>> 	- Dropped select RESET_CONTROLLER
>> 	- Used the new helpers for starting a media pipeline
>> 	- Switch from threaded irq to normal with driver workqueue
>> 	  and revised startup routine
>>
>> Changes in v3:
>>
>> 	- Account for the renamed CRU pixel formats
>>
>> Changes in v2:
>>
>> 	- Added selects and depends statements to Kconfig entry
>> 	- Fixed copyright year
>> 	- Stopped including in .c files headers already included in .h
>> 	- Fixed uninitialized variable in iterator
>> 	- Only check vvalid member in interrupt function and wait
>> 	  unconditionally elsewhere
>> 	- __maybe_unused for the PM ops
>> 	- Initialise the subdevice after setting up PM
>> 	- Fixed the remove function for the driver to actually do
>> 	  something.
>> 	- Some minor formatting changes
>> 	- Fixed the quantization member for the format
>> 	- Changes accounting for the v2 of the media jobs framework
>> 	- Change min_queued_buffers to 0
>> ---
>>   drivers/media/platform/renesas/Kconfig             |   1 +
>>   drivers/media/platform/renesas/Makefile            |   1 +
>>   drivers/media/platform/renesas/rzv2h-ivc/Kconfig   |  18 +
>>   drivers/media/platform/renesas/rzv2h-ivc/Makefile  |   5 +
>>   .../platform/renesas/rzv2h-ivc/rzv2h-ivc-dev.c     | 229 +++++++++
>>   .../platform/renesas/rzv2h-ivc/rzv2h-ivc-subdev.c  | 376 ++++++++++++++
>>   .../platform/renesas/rzv2h-ivc/rzv2h-ivc-video.c   | 568 +++++++++++++++++++++
>>   .../media/platform/renesas/rzv2h-ivc/rzv2h-ivc.h   | 131 +++++
>>   8 files changed, 1329 insertions(+)
>>
>> diff --git a/drivers/media/platform/renesas/Kconfig b/drivers/media/platform/renesas/Kconfig
>> index 27a54fa7908384f2e8200f0f7283a82b0ae8435c..bd8247c0b8aa734d2b412438e694f3908d910b25 100644
>> --- a/drivers/media/platform/renesas/Kconfig
>> +++ b/drivers/media/platform/renesas/Kconfig
>> @@ -42,6 +42,7 @@ config VIDEO_SH_VOU
>>   source "drivers/media/platform/renesas/rcar-isp/Kconfig"
>>   source "drivers/media/platform/renesas/rcar-vin/Kconfig"
>>   source "drivers/media/platform/renesas/rzg2l-cru/Kconfig"
>> +source "drivers/media/platform/renesas/rzv2h-ivc/Kconfig"
>>
>>   # Mem2mem drivers
>>
>> diff --git a/drivers/media/platform/renesas/Makefile b/drivers/media/platform/renesas/Makefile
>> index 1127259c09d6a51b70803e76c495918e06777f67..b6b4abf01db246aaf8269b8027efee9b0b32083a 100644
>> --- a/drivers/media/platform/renesas/Makefile
>> +++ b/drivers/media/platform/renesas/Makefile
>> @@ -6,6 +6,7 @@
>>   obj-y += rcar-isp/
>>   obj-y += rcar-vin/
>>   obj-y += rzg2l-cru/
>> +obj-y += rzv2h-ivc/
>>   obj-y += vsp1/
>>
>>   obj-$(CONFIG_VIDEO_RCAR_CSI2) += rcar-csi2.o
>> diff --git a/drivers/media/platform/renesas/rzv2h-ivc/Kconfig b/drivers/media/platform/renesas/rzv2h-ivc/Kconfig
>> new file mode 100644
>> index 0000000000000000000000000000000000000000..5a4a3c052a3ae0f242e844689132d91a75b8a302
>> --- /dev/null
>> +++ b/drivers/media/platform/renesas/rzv2h-ivc/Kconfig
>> @@ -0,0 +1,18 @@
>> +# SPDX-License-Identifier: GPL-2.0-only
>> +
>> +config VIDEO_RZV2H_IVC
>> +	tristate "Renesas RZ/V2H Input Video Control block driver"
>> +	depends on V4L_PLATFORM_DRIVERS
>> +	depends on VIDEO_DEV
>> +	depends on ARCH_RENESAS || COMPILE_TEST
>> +	depends on OF
>> +	select CONFIG_PM
> Ups, no 'CONFIG_' please.
> Weird that no checks/automated testing complains for a non existing
> symbol in Kconfig


Dang, my bad...thanks

>
>> +	select VIDEOBUF2_DMA_CONTIG
>> +	select MEDIA_CONTROLLER
>> +	select VIDEO_V4L2_SUBDEV_API
>> +	help
>> +          Support for the Renesas RZ/V2H Input Video Control Block
>> +          (IVC).
>> +
>> +          To compile this driver as a module, choose M here: the
>> +          module will be called rzv2h-ivc.
>> diff --git a/drivers/media/platform/renesas/rzv2h-ivc/Makefile b/drivers/media/platform/renesas/rzv2h-ivc/Makefile
>> new file mode 100644
>> index 0000000000000000000000000000000000000000..080ee3570f09c236d87abeaea5d8dd578fefb6d3
>> --- /dev/null
>> +++ b/drivers/media/platform/renesas/rzv2h-ivc/Makefile
>> @@ -0,0 +1,5 @@
>> +# SPDX-License-Identifier: GPL-2.0
>> +
>> +rzv2h-ivc-y := rzv2h-ivc-dev.o rzv2h-ivc-subdev.o rzv2h-ivc-video.o
>> +
>> +obj-$(CONFIG_VIDEO_RZV2H_IVC) += rzv2h-ivc.o
>> diff --git a/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-dev.c b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-dev.c
>> new file mode 100644
>> index 0000000000000000000000000000000000000000..ce2e3a3af8d19900241add7d261f7a40f2551265
>> --- /dev/null
>> +++ b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-dev.c
>> @@ -0,0 +1,229 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Renesas RZ/V2H Input Video Control Block driver
>> + *
>> + * Copyright (C) 2025 Ideas on Board Oy
>> + */
>> +
>> +#include "rzv2h-ivc.h"
>> +
>> +#include <linux/device.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/io.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/pm_runtime.h>
>> +#include <linux/reset.h>
>> +
>> +void rzv2h_ivc_write(struct rzv2h_ivc *ivc, u32 addr, u32 val)
>> +{
>> +	writel(val, ivc->base + addr);
>> +}
>> +
>> +void rzv2h_ivc_update_bits(struct rzv2h_ivc *ivc, unsigned int addr,
>> +			   u32 mask, u32 val)
>> +{
>> +	u32 orig, new;
>> +
>> +	orig = readl(ivc->base + addr);
>> +
>> +	new = orig & ~mask;
>> +	new |= val & mask;
>> +
>> +	if (new != orig)
>> +		writel(new, ivc->base + addr);
>> +}
>> +
>> +static int rzv2h_ivc_get_hardware_resources(struct rzv2h_ivc *ivc,
>> +					    struct platform_device *pdev)
>> +{
>> +	const char * const resource_names[RZV2H_IVC_NUM_HW_RESOURCES] = {
>> +		"reg",
>> +		"axi",
>> +		"isp",
>> +	};
>> +	struct resource *res;
>> +	int ret;
>> +
>> +	ivc->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
>> +	if (IS_ERR(ivc->base))
>> +		return dev_err_probe(ivc->dev, PTR_ERR(ivc->base),
>> +				     "failed to map IO memory\n");
>> +
>> +	for (unsigned int i = 0; i < ARRAY_SIZE(resource_names); i++)
>> +		ivc->clks[i].id = resource_names[i];
>> +
>> +	ret = devm_clk_bulk_get(ivc->dev, ARRAY_SIZE(resource_names), ivc->clks);
>> +	if (ret)
>> +		return dev_err_probe(ivc->dev, ret, "failed to acquire clks\n");
>> +
>> +	for (unsigned int i = 0; i < ARRAY_SIZE(resource_names); i++)
>> +		ivc->resets[i].id = resource_names[i];
>> +
>> +	ret = devm_reset_control_bulk_get_optional_shared(
>> +		ivc->dev, ARRAY_SIZE(resource_names), ivc->resets);
>> +	if (ret)
>> +		return dev_err_probe(ivc->dev, ret, "failed to acquire resets\n");
>> +
>> +	return 0;
>> +}
>> +
>> +static void rzv2h_ivc_global_config(struct rzv2h_ivc *ivc)
>> +{
>> +	/* Currently we only support single-exposure input */
>> +	rzv2h_ivc_write(ivc, RZV2H_IVC_REG_AXIRX_PLNUM, RZV2H_IVC_ONE_EXPOSURE);
>> +
>> +	/*
>> +	 * Datasheet says we should disable the interrupts before changing mode
>> +	 * to avoid spurious IFP interrupt.
>> +	 */
>> +	rzv2h_ivc_write(ivc, RZV2H_IVC_REG_FM_INT_EN, 0x0);
>> +
>> +	/*
>> +	 * RZ/V2H documentation says software controlled single context mode is
>> +	 * is not supported, and currently the driver does not support the
>> +	 * multi-context mode. That being so we just set single context sw-hw
>> +	 * mode.
>> +	 */
>> +	rzv2h_ivc_write(ivc, RZV2H_IVC_REG_FM_CONTEXT,
>> +			RZV2H_IVC_SINGLE_CONTEXT_SW_HW_CFG);
>> +
>> +	/*
>> +	 * We enable the frame end interrupt so that we know when we should send
>> +	 * follow-up frames.
>> +	 */
>> +	rzv2h_ivc_write(ivc, RZV2H_IVC_REG_FM_INT_EN, RZV2H_IVC_VVAL_IFPE);
>> +}
>> +
>> +static irqreturn_t rzv2h_ivc_isr(int irq, void *context)
>> +{
>> +	struct device *dev = context;
>> +	struct rzv2h_ivc *ivc = dev_get_drvdata(dev);
>> +
>> +	guard(spinlock)(&ivc->spinlock);
>> +
>> +	if (!--ivc->vvalid_ifp)
>> +		queue_work(ivc->buffers.async_wq, &ivc->buffers.work);
>> +
>> +	return IRQ_HANDLED;
>> +}
>> +
>> +static int rzv2h_ivc_runtime_resume(struct device *dev)
>> +{
>> +	struct rzv2h_ivc *ivc = dev_get_drvdata(dev);
>> +	int ret;
>> +
>> +	ret = request_irq(ivc->irqnum, rzv2h_ivc_isr, 0, dev_driver_string(dev),
>> +			  dev);
>> +	if (ret) {
>> +		dev_err(dev, "failed to request irq\n");
>> +		return ret;
>> +	}
>> +
>> +	ret = clk_bulk_prepare_enable(ARRAY_SIZE(ivc->clks), ivc->clks);
>> +	if (ret) {
>> +		dev_err(ivc->dev, "failed to enable clocks\n");
>> +		goto err_free_irqnum;
>> +	}
>> +
>> +	ret = reset_control_bulk_deassert(ARRAY_SIZE(ivc->resets), ivc->resets);
>> +	if (ret) {
>> +		dev_err(ivc->dev, "failed to deassert resets\n");
>> +		goto err_disable_clks;
>> +	}
>> +
>> +	rzv2h_ivc_global_config(ivc);
>> +
>> +	return 0;
>> +
>> +err_disable_clks:
>> +	clk_bulk_disable_unprepare(ARRAY_SIZE(ivc->clks), ivc->clks);
>> +err_free_irqnum:
>> +	free_irq(ivc->irqnum, dev);
>> +
>> +	return ret;
>> +}
>> +
>> +static int rzv2h_ivc_runtime_suspend(struct device *dev)
>> +{
>> +	struct rzv2h_ivc *ivc = dev_get_drvdata(dev);
>> +
>> +	reset_control_bulk_assert(ARRAY_SIZE(ivc->resets), ivc->resets);
>> +	clk_bulk_disable_unprepare(ARRAY_SIZE(ivc->clks), ivc->clks);
>> +	free_irq(ivc->irqnum, dev);
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct dev_pm_ops rzv2h_ivc_pm_ops = {
>> +	SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
>> +				pm_runtime_force_resume)
> nit: fits on one line
>
>> +	RUNTIME_PM_OPS(rzv2h_ivc_runtime_suspend, rzv2h_ivc_runtime_resume,
>> +			   NULL)
> nit: align to open (

Hm, checkpatch must have mentioned that one...not sure why I didn't notice.

>
>> +};
>> +
>> +static int rzv2h_ivc_probe(struct platform_device *pdev)
>> +{
>> +	struct device *dev = &pdev->dev;
>> +	struct rzv2h_ivc *ivc;
>> +	int ret;
>> +
>> +	ivc = devm_kzalloc(dev, sizeof(*ivc), GFP_KERNEL);
>> +	if (!ivc)
>> +		return -ENOMEM;
>> +
>> +	ivc->dev = dev;
>> +	platform_set_drvdata(pdev, ivc);
>> +	mutex_init(&ivc->lock);
>> +	spin_lock_init(&ivc->spinlock);
>> +
>> +	ret = rzv2h_ivc_get_hardware_resources(ivc, pdev);
>> +	if (ret)
>> +		return ret;
>> +
>> +	pm_runtime_set_autosuspend_delay(dev, 2000);
>> +	pm_runtime_use_autosuspend(dev);
>> +	pm_runtime_enable(dev);
>> +
>> +	ivc->irqnum = platform_get_irq(pdev, 0);
>> +	if (ivc->irqnum < 0) {
>> +		dev_err(dev, "failed to get interrupt\n");
>> +		return ret;
>> +	}
>> +
>> +	ret = rzv2h_ivc_initialise_subdevice(ivc);
>> +	if (ret)
>> +		return ret;
>> +
>> +	return 0;
>> +}
>> +
>> +static void rzv2h_ivc_remove(struct platform_device *pdev)
>> +{
>> +	struct rzv2h_ivc *ivc = platform_get_drvdata(pdev);
>> +
>> +	rzv2h_deinit_video_dev_and_queue(ivc);
>> +	rzv2h_ivc_deinit_subdevice(ivc);
>> +	mutex_destroy(&ivc->lock);
>> +}
>> +
>> +static const struct of_device_id rzv2h_ivc_of_match[] = {
>> +	{ .compatible = "renesas,r9a09g057-ivc", },
>> +	{ /* Sentinel */ },
>> +};
>> +MODULE_DEVICE_TABLE(of, rzv2h_ivc_of_match);
>> +
>> +static struct platform_driver rzv2h_ivc_driver = {
>> +	.driver = {
>> +		.name = "rzv2h-ivc",
>> +		.of_match_table = rzv2h_ivc_of_match,
>> +		.pm = &rzv2h_ivc_pm_ops,
>> +	},
>> +	.probe = rzv2h_ivc_probe,
>> +	.remove = rzv2h_ivc_remove,
>> +};
>> +
>> +module_platform_driver(rzv2h_ivc_driver);
>> +
>> +MODULE_AUTHOR("Daniel Scally <dan.scally@ideasonboard.com>");
>> +MODULE_DESCRIPTION("Renesas RZ/V2H Input Video Control Block driver");
>> +MODULE_LICENSE("GPL");
>> diff --git a/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-subdev.c b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-subdev.c
>> new file mode 100644
>> index 0000000000000000000000000000000000000000..eb2913153d406fbad2491bb36e1c5ea754bea6f2
>> --- /dev/null
>> +++ b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-subdev.c
>> @@ -0,0 +1,376 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Renesas RZ/V2H Input Video Control Block driver
>> + *
>> + * Copyright (C) 2025 Ideas on Board Oy
>> + */
>> +
>> +#include "rzv2h-ivc.h"
>> +
>> +#include <linux/media.h>
>> +#include <linux/media-bus-format.h>
>> +#include <linux/v4l2-mediabus.h>
>> +
>> +#include <media/v4l2-async.h>
>> +#include <media/v4l2-ctrls.h>
>> +#include <media/v4l2-dev.h>
>> +#include <media/v4l2-event.h>
>> +
>> +#define RZV2H_IVC_N_INPUTS_PER_OUTPUT		6
>> +
>> +/*
>> + * We support 8/10/12/14/16/20 bit input in any bayer order, but the output
>> + * format is fixed at 20-bits with the same order as the input.
>> + */
>> +static const struct {
>> +	u32 inputs[RZV2H_IVC_N_INPUTS_PER_OUTPUT];
>> +	u32 output;
>> +} rzv2h_ivc_formats[] = {
>> +	{
>> +		.inputs = {
>> +			MEDIA_BUS_FMT_SBGGR8_1X8,
>> +			MEDIA_BUS_FMT_SBGGR10_1X10,
>> +			MEDIA_BUS_FMT_SBGGR12_1X12,
>> +			MEDIA_BUS_FMT_SBGGR14_1X14,
>> +			MEDIA_BUS_FMT_SBGGR16_1X16,
>> +			MEDIA_BUS_FMT_SBGGR20_1X20,
>> +		},
>> +		.output = MEDIA_BUS_FMT_SBGGR20_1X20
>> +	},
>> +	{
>> +		.inputs = {
>> +			MEDIA_BUS_FMT_SGBRG8_1X8,
>> +			MEDIA_BUS_FMT_SGBRG10_1X10,
>> +			MEDIA_BUS_FMT_SGBRG12_1X12,
>> +			MEDIA_BUS_FMT_SGBRG14_1X14,
>> +			MEDIA_BUS_FMT_SGBRG16_1X16,
>> +			MEDIA_BUS_FMT_SGBRG20_1X20,
>> +		},
>> +		.output = MEDIA_BUS_FMT_SGBRG20_1X20
>> +	},
>> +	{
>> +		.inputs = {
>> +			MEDIA_BUS_FMT_SGRBG8_1X8,
>> +			MEDIA_BUS_FMT_SGRBG10_1X10,
>> +			MEDIA_BUS_FMT_SGRBG12_1X12,
>> +			MEDIA_BUS_FMT_SGRBG14_1X14,
>> +			MEDIA_BUS_FMT_SGRBG16_1X16,
>> +			MEDIA_BUS_FMT_SGRBG20_1X20,
>> +		},
>> +		.output = MEDIA_BUS_FMT_SGRBG20_1X20
>> +	},
>> +	{
>> +		.inputs = {
>> +			MEDIA_BUS_FMT_SRGGB8_1X8,
>> +			MEDIA_BUS_FMT_SRGGB10_1X10,
>> +			MEDIA_BUS_FMT_SRGGB12_1X12,
>> +			MEDIA_BUS_FMT_SRGGB14_1X14,
>> +			MEDIA_BUS_FMT_SRGGB16_1X16,
>> +			MEDIA_BUS_FMT_SRGGB20_1X20,
>> +		},
>> +		.output = MEDIA_BUS_FMT_SRGGB20_1X20
>> +	},
>> +};
>> +
>> +static u32 rzv2h_ivc_get_mbus_output_from_input(u32 mbus_code)
>> +{
>> +	unsigned int i, j;
>> +
>> +	for (i = 0; i < ARRAY_SIZE(rzv2h_ivc_formats); i++) {
>> +		for (j = 0; j < RZV2H_IVC_N_INPUTS_PER_OUTPUT; j++) {
>> +			if (rzv2h_ivc_formats[i].inputs[j] == mbus_code)
>> +				return rzv2h_ivc_formats[i].output;
>> +		}
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static int rzv2h_ivc_enum_mbus_code(struct v4l2_subdev *sd,
>> +				    struct v4l2_subdev_state *state,
>> +				    struct v4l2_subdev_mbus_code_enum *code)
>> +{
>> +	const struct v4l2_mbus_framefmt *fmt;
>> +	unsigned int order_index;
>> +	unsigned int index;
>> +
>> +	/*
>> +	 * On the source pad, only the 20-bit format corresponding to the sink
>> +	 * pad format's bayer order is supported.
>> +	 */
>> +	if (code->pad == RZV2H_IVC_SUBDEV_SOURCE_PAD) {
>> +		if (code->index)
>> +			return -EINVAL;
>> +
>> +		fmt = v4l2_subdev_state_get_format(state,
>> +						   RZV2H_IVC_SUBDEV_SINK_PAD);
>> +		code->code = rzv2h_ivc_get_mbus_output_from_input(fmt->code);
>> +
>> +		return 0;
>> +	}
>> +
>> +	if (code->index >= ARRAY_SIZE(rzv2h_ivc_formats) *
>> +				      RZV2H_IVC_N_INPUTS_PER_OUTPUT)
>> +		return -EINVAL;
>> +
>> +	order_index = code->index / RZV2H_IVC_N_INPUTS_PER_OUTPUT;
>> +	index = code->index % RZV2H_IVC_N_INPUTS_PER_OUTPUT;
>> +
>> +	code->code = rzv2h_ivc_formats[order_index].inputs[index];
>> +
>> +	return 0;
>> +}
>> +
>> +static int rzv2h_ivc_enum_frame_size(struct v4l2_subdev *sd,
>> +				     struct v4l2_subdev_state *state,
>> +				     struct v4l2_subdev_frame_size_enum *fse)
>> +{
>> +	const struct v4l2_mbus_framefmt *fmt;
>> +
>> +	if (fse->index > 0)
>> +		return -EINVAL;
>> +
>> +	if (fse->pad == RZV2H_IVC_SUBDEV_SOURCE_PAD) {
>> +		fmt = v4l2_subdev_state_get_format(state,
>> +						   RZV2H_IVC_SUBDEV_SINK_PAD);
>> +
>> +		if (fse->code != rzv2h_ivc_get_mbus_output_from_input(fmt->code))
>> +			return -EINVAL;
>> +
>> +		fse->min_width = fmt->width;
>> +		fse->max_width = fmt->width;
>> +		fse->min_height = fmt->height;
>> +		fse->max_height = fmt->height;
>> +
>> +		return 0;
>> +	}
>> +
>> +	if (!rzv2h_ivc_get_mbus_output_from_input(fse->code))
>> +		return -EINVAL;
>> +
>> +	fse->min_width = RZV2H_IVC_MIN_WIDTH;
>> +	fse->max_width = RZV2H_IVC_MAX_WIDTH;
>> +	fse->min_height = RZV2H_IVC_MIN_HEIGHT;
>> +	fse->max_height = RZV2H_IVC_MAX_HEIGHT;
>> +
>> +	return 0;
>> +}
>> +
>> +static int rzv2h_ivc_set_fmt(struct v4l2_subdev *sd,
>> +			     struct v4l2_subdev_state *state,
>> +			     struct v4l2_subdev_format *format)
>> +{
>> +	struct v4l2_mbus_framefmt *fmt = &format->format;
>> +	struct v4l2_mbus_framefmt *src_fmt, *sink_fmt;
>> +
>> +	if (format->pad == RZV2H_IVC_SUBDEV_SOURCE_PAD)
>> +		return v4l2_subdev_get_fmt(sd, state, format);
>> +
>> +	sink_fmt = v4l2_subdev_state_get_format(state,
>> +						RZV2H_IVC_SUBDEV_SINK_PAD);
>> +
>> +	sink_fmt->code = rzv2h_ivc_get_mbus_output_from_input(fmt->code) ?
>> +			 fmt->code : rzv2h_ivc_formats[0].inputs[0];
>> +
>> +	sink_fmt->width = clamp(fmt->width, RZV2H_IVC_MIN_WIDTH,
>> +				RZV2H_IVC_MAX_WIDTH);
>> +	sink_fmt->height = clamp(fmt->height, RZV2H_IVC_MIN_HEIGHT,
>> +				 RZV2H_IVC_MAX_HEIGHT);
>> +
>> +	*fmt = *sink_fmt;
>> +
>> +	src_fmt = v4l2_subdev_state_get_format(state,
>> +					       RZV2H_IVC_SUBDEV_SOURCE_PAD);
>> +	*src_fmt = *sink_fmt;
>> +	src_fmt->code = rzv2h_ivc_get_mbus_output_from_input(sink_fmt->code);
>> +
>> +	return 0;
>> +}
>> +
>> +static int rzv2h_ivc_enable_streams(struct v4l2_subdev *sd,
>> +				    struct v4l2_subdev_state *state, u32 pad,
>> +				    u64 streams_mask)
>> +{
>> +	/*
>> +	 * We have a single source pad, which has a single stream. V4L2 core has
>> +	 * already validated those things. The actual power-on and programming
>> +	 * of registers will be done through the video device's .vidioc_streamon
>> +	 * so there's nothing to actually do here...
>> +	 */
>> +
>> +	return 0;
>> +}
>> +
>> +static int rzv2h_ivc_disable_streams(struct v4l2_subdev *sd,
>> +				     struct v4l2_subdev_state *state, u32 pad,
>> +				     u64 streams_mask)
>> +{
>> +	return 0;
>> +}
>> +
>> +static const struct v4l2_subdev_pad_ops rzv2h_ivc_pad_ops = {
>> +	.enum_mbus_code		= rzv2h_ivc_enum_mbus_code,
>> +	.enum_frame_size	= rzv2h_ivc_enum_frame_size,
>> +	.get_fmt		= v4l2_subdev_get_fmt,
>> +	.set_fmt		= rzv2h_ivc_set_fmt,
>> +	.enable_streams		= rzv2h_ivc_enable_streams,
>> +	.disable_streams	= rzv2h_ivc_disable_streams,
>> +};
>> +
>> +static const struct v4l2_subdev_core_ops rzv2h_ivc_core_ops = {
>> +	.subscribe_event = v4l2_ctrl_subdev_subscribe_event,
>> +	.unsubscribe_event = v4l2_event_subdev_unsubscribe,
>> +};
>> +
>> +static const struct v4l2_subdev_ops rzv2h_ivc_subdev_ops = {
>> +	.core	= &rzv2h_ivc_core_ops,
>> +	.pad	= &rzv2h_ivc_pad_ops,
>> +};
>> +
>> +static int rzv2h_ivc_init_state(struct v4l2_subdev *sd,
>> +				struct v4l2_subdev_state *state)
>> +{
>> +	struct v4l2_mbus_framefmt *sink_fmt, *src_fmt;
>> +
>> +	sink_fmt = v4l2_subdev_state_get_format(state,
>> +						RZV2H_IVC_SUBDEV_SINK_PAD);
>> +	sink_fmt->width = RZV2H_IVC_DEFAULT_WIDTH;
>> +	sink_fmt->height = RZV2H_IVC_DEFAULT_HEIGHT;
>> +	sink_fmt->field = V4L2_FIELD_NONE;
>> +	sink_fmt->code = MEDIA_BUS_FMT_SRGGB16_1X16;
>> +	sink_fmt->colorspace = V4L2_COLORSPACE_RAW;
>> +	sink_fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(sink_fmt->colorspace);
>> +	sink_fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(sink_fmt->colorspace);
>> +	sink_fmt->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(
>> +		true, sink_fmt->colorspace, sink_fmt->ycbcr_enc);
>> +
>> +	src_fmt = v4l2_subdev_state_get_format(state,
>> +					       RZV2H_IVC_SUBDEV_SOURCE_PAD);
>> +
>> +	*src_fmt = *sink_fmt;
>> +	src_fmt->code = MEDIA_BUS_FMT_SRGGB20_1X20;
>> +
>> +	return 0;
>> +}
>> +
>> +static int rzv2h_ivc_registered(struct v4l2_subdev *sd)
>> +{
>> +	struct rzv2h_ivc *ivc = container_of(sd, struct rzv2h_ivc, subdev.sd);
>> +
>> +	return rzv2h_ivc_init_vdev(ivc, sd->v4l2_dev);
>> +}
>> +
>> +static const struct v4l2_subdev_internal_ops rzv2h_ivc_subdev_internal_ops = {
>> +	.init_state = rzv2h_ivc_init_state,
>> +	.registered = rzv2h_ivc_registered,
>> +};
>> +
>> +static int rzv2h_ivc_link_validate(struct media_link *link)
>> +{
>> +	struct video_device *vdev =
>> +		media_entity_to_video_device(link->source->entity);
>> +	struct rzv2h_ivc *ivc = video_get_drvdata(vdev);
>> +	struct v4l2_subdev *sd =
>> +		media_entity_to_v4l2_subdev(link->sink->entity);
>> +	const struct rzv2h_ivc_format *fmt;
>> +	const struct v4l2_pix_format_mplane *pix;
>> +	struct v4l2_subdev_state *state;
>> +	struct v4l2_mbus_framefmt *mf;
>> +	unsigned int i;
>> +	int ret = 0;
>> +
>> +	state = v4l2_subdev_lock_and_get_active_state(sd);
>> +	mf = v4l2_subdev_state_get_format(state, link->sink->index);
>> +
>> +	pix = &ivc->format.pix;
>> +	fmt = ivc->format.fmt;
>> +
>> +	if (mf->width != pix->width || mf->height != pix->height) {
>> +		dev_dbg(ivc->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,
>> +			mf->width, mf->height,
>> +			pix->width, pix->height);
> nit: fits on one line
>
>> +		ret = -EPIPE;
>> +	}
>> +
>> +	for (i = 0; i < ARRAY_SIZE(fmt->mbus_codes); i++)
>> +		if (mf->code == fmt->mbus_codes[i])
>> +			break;
>> +
>> +	if (i == ARRAY_SIZE(fmt->mbus_codes)) {
>> +		dev_dbg(ivc->dev,
>> +			"link '%s':%u -> '%s':%u not valid: pixel format %p4cc cannot produce mbus_code 0x%04x\n",
>> +			link->source->entity->name, link->source->index,
>> +			link->sink->entity->name, link->sink->index,
>> +			&pix->pixelformat, mf->code);
>> +		ret = -EPIPE;
>> +	}
>> +
>> +	v4l2_subdev_unlock_state(state);
>> +
>> +	return ret;
>> +}
>> +
>> +static const struct media_entity_operations rzv2h_ivc_media_ops = {
>> +	.link_validate = rzv2h_ivc_link_validate,
>> +};
>> +
>> +int rzv2h_ivc_initialise_subdevice(struct rzv2h_ivc *ivc)
>> +{
>> +	struct v4l2_subdev *sd;
>> +	int ret;
>> +
>> +	/* Initialise subdevice */
>> +	sd = &ivc->subdev.sd;
>> +	sd->dev = ivc->dev;
>> +	v4l2_subdev_init(sd, &rzv2h_ivc_subdev_ops);
>> +	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS;
>> +	sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;
>> +	sd->internal_ops = &rzv2h_ivc_subdev_internal_ops;
>> +	sd->entity.ops = &rzv2h_ivc_media_ops;
>> +
>> +	ivc->subdev.pads[RZV2H_IVC_SUBDEV_SINK_PAD].flags = MEDIA_PAD_FL_SINK;
>> +	ivc->subdev.pads[RZV2H_IVC_SUBDEV_SOURCE_PAD].flags = MEDIA_PAD_FL_SOURCE;
>> +
>> +	snprintf(sd->name, sizeof(sd->name), "rzv2h ivc block");
>> +
>> +	ret = media_entity_pads_init(&sd->entity, RZV2H_IVC_NUM_SUBDEV_PADS,
>> +				     ivc->subdev.pads);
>> +	if (ret) {
>> +		dev_err(ivc->dev, "failed to initialise media entity\n");
>> +		return ret;
>> +	}
>> +
>> +	ret = v4l2_subdev_init_finalize(sd);
>> +	if (ret) {
>> +		dev_err(ivc->dev, "failed to finalize subdev init\n");
>> +		goto err_cleanup_subdev_entity;
>> +	}
>> +
>> +	ret = v4l2_async_register_subdev(sd);
>> +	if (ret) {
>> +		dev_err(ivc->dev, "failed to register subdevice\n");
>> +		goto err_cleanup_subdev;
>> +	}
>> +
>> +	return 0;
>> +
>> +err_cleanup_subdev:
>> +	v4l2_subdev_cleanup(sd);
>> +err_cleanup_subdev_entity:
>> +	media_entity_cleanup(&sd->entity);
>> +
>> +	return ret;
>> +}
>> +
>> +void rzv2h_ivc_deinit_subdevice(struct rzv2h_ivc *ivc)
>> +{
>> +	struct v4l2_subdev *sd = &ivc->subdev.sd;
>> +
>> +	v4l2_subdev_cleanup(sd);
>> +	media_entity_remove_links(&sd->entity);
>> +	v4l2_async_unregister_subdev(sd);
>> +	media_entity_cleanup(&sd->entity);
>> +}
>> diff --git a/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-video.c b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-video.c
>> new file mode 100644
>> index 0000000000000000000000000000000000000000..005a5700b5e2351b1e7ba5d99539ce4468f3db8b
>> --- /dev/null
>> +++ b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-video.c
>> @@ -0,0 +1,568 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Renesas RZ/V2H Input Video Control Block driver
>> + *
>> + * Copyright (C) 2025 Ideas on Board Oy
>> + */
>> +
>> +#include "rzv2h-ivc.h"
>> +
>> +#include <linux/cleanup.h>
>> +#include <linux/iopoll.h>
>> +#include <linux/media-bus-format.h>
>> +#include <linux/minmax.h>
>> +#include <linux/mutex.h>
>> +#include <linux/pm_runtime.h>
>> +
>> +#include <media/mipi-csi2.h>
>> +#include <media/v4l2-ctrls.h>
>> +#include <media/v4l2-dev.h>
>> +#include <media/v4l2-event.h>
>> +#include <media/v4l2-fh.h>
>> +#include <media/v4l2-ioctl.h>
>> +#include <media/videobuf2-dma-contig.h>
>> +
>> +#define RZV2H_IVC_FIXED_HBLANK			0x20
>> +#define RZV2H_IVC_MIN_VBLANK(hts)		max(0x1b, 15 + (120501 / (hts)))
>> +
>> +struct rzv2h_ivc_buf {
>> +	struct vb2_v4l2_buffer vb;
>> +	struct list_head queue;
>> +	dma_addr_t addr;
>> +};
>> +
>> +#define to_rzv2h_ivc_buf(vbuf) \
>> +	container_of(vbuf, struct rzv2h_ivc_buf, vb)
>> +
>> +static const struct rzv2h_ivc_format rzv2h_ivc_formats[] = {
>> +	{
>> +		.fourcc = V4L2_PIX_FMT_SBGGR8,
>> +		.mbus_codes = {
>> +			MEDIA_BUS_FMT_SBGGR8_1X8,
>> +		},
>> +		.dtype = MIPI_CSI2_DT_RAW8,
>> +	},
>> +	{
>> +		.fourcc = V4L2_PIX_FMT_SGBRG8,
>> +		.mbus_codes = {
>> +			MEDIA_BUS_FMT_SGBRG8_1X8,
>> +		},
>> +		.dtype = MIPI_CSI2_DT_RAW8,
>> +	},
>> +	{
>> +		.fourcc = V4L2_PIX_FMT_SGRBG8,
>> +		.mbus_codes = {
>> +			MEDIA_BUS_FMT_SGRBG8_1X8,
>> +		},
>> +		.dtype = MIPI_CSI2_DT_RAW8,
>> +	},
>> +	{
>> +		.fourcc = V4L2_PIX_FMT_SRGGB8,
>> +		.mbus_codes = {
>> +			MEDIA_BUS_FMT_SRGGB8_1X8,
>> +		},
>> +		.dtype = MIPI_CSI2_DT_RAW8,
>> +	},
>> +	{
>> +		.fourcc = V4L2_PIX_FMT_RAW_CRU10,
>> +		.mbus_codes = {
>> +			MEDIA_BUS_FMT_SBGGR10_1X10,
>> +			MEDIA_BUS_FMT_SGBRG10_1X10,
>> +			MEDIA_BUS_FMT_SGRBG10_1X10,
>> +			MEDIA_BUS_FMT_SRGGB10_1X10
>> +		},
>> +		.dtype = MIPI_CSI2_DT_RAW10,
>> +	},
>> +	{
>> +		.fourcc = V4L2_PIX_FMT_RAW_CRU12,
>> +		.mbus_codes = {
>> +			MEDIA_BUS_FMT_SBGGR12_1X12,
>> +			MEDIA_BUS_FMT_SGBRG12_1X12,
>> +			MEDIA_BUS_FMT_SGRBG12_1X12,
>> +			MEDIA_BUS_FMT_SRGGB12_1X12
>> +		},
>> +		.dtype = MIPI_CSI2_DT_RAW12,
>> +	},
>> +	{
>> +		.fourcc = V4L2_PIX_FMT_RAW_CRU14,
>> +		.mbus_codes = {
>> +			MEDIA_BUS_FMT_SBGGR14_1X14,
>> +			MEDIA_BUS_FMT_SGBRG14_1X14,
>> +			MEDIA_BUS_FMT_SGRBG14_1X14,
>> +			MEDIA_BUS_FMT_SRGGB14_1X14
>> +		},
>> +		.dtype = MIPI_CSI2_DT_RAW14,
>> +	},
>> +	{
>> +		.fourcc = V4L2_PIX_FMT_SBGGR16,
>> +		.mbus_codes = {
>> +			MEDIA_BUS_FMT_SBGGR16_1X16,
>> +		},
>> +		.dtype = MIPI_CSI2_DT_RAW16,
>> +	},
>> +	{
>> +		.fourcc = V4L2_PIX_FMT_SGBRG16,
>> +		.mbus_codes = {
>> +			MEDIA_BUS_FMT_SGBRG16_1X16,
>> +		},
>> +		.dtype = MIPI_CSI2_DT_RAW16,
>> +	},
>> +	{
>> +		.fourcc = V4L2_PIX_FMT_SGRBG16,
>> +		.mbus_codes = {
>> +			MEDIA_BUS_FMT_SGRBG16_1X16,
>> +		},
>> +		.dtype = MIPI_CSI2_DT_RAW16,
>> +	},
>> +	{
>> +		.fourcc = V4L2_PIX_FMT_SRGGB16,
>> +		.mbus_codes = {
>> +			MEDIA_BUS_FMT_SRGGB16_1X16,
>> +		},
>> +		.dtype = MIPI_CSI2_DT_RAW16,
>> +	},
>> +};
>> +
>> +static void rzv2h_ivc_transfer_buffer(struct work_struct *work)
>> +{
>> +	struct rzv2h_ivc *ivc = container_of(work, struct rzv2h_ivc,
>> +					     buffers.work);
>> +	struct rzv2h_ivc_buf *buf;
>> +
>> +	scoped_guard(spinlock, &ivc->buffers.lock) {
>> +		if (ivc->buffers.curr) {
>> +			ivc->buffers.curr->vb.sequence = ivc->buffers.sequence++;
>> +			vb2_buffer_done(&ivc->buffers.curr->vb.vb2_buf,
>> +					VB2_BUF_STATE_DONE);
>> +			ivc->buffers.curr = NULL;
>> +		}
>> +
>> +		buf = list_first_entry_or_null(&ivc->buffers.queue,
>> +					       struct rzv2h_ivc_buf, queue);
>> +	}
>> +
>> +	if (!buf)
>> +		return;
>> +
>> +	list_del(&buf->queue);
>> +
>> +	ivc->buffers.curr = buf;
>> +	buf->addr = vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
>> +	rzv2h_ivc_write(ivc, RZV2H_IVC_REG_AXIRX_SADDL_P0, buf->addr);
>> +
>> +	scoped_guard(spinlock_irqsave, &ivc->spinlock) {
>> +		ivc->vvalid_ifp = 2;
>> +	}
>> +	rzv2h_ivc_write(ivc, RZV2H_IVC_REG_FM_FRCON, 0x1);
>> +}
>> +
>> +static int rzv2h_ivc_pipeline_started(struct media_entity *entity)
>> +{
>> +	struct video_device *vdev = media_entity_to_video_device(entity);
>> +	struct rzv2h_ivc *ivc = video_get_drvdata(vdev);
>> +
>> +	guard(spinlock)(&ivc->buffers.lock);
>> +
>> +	if (list_empty(&ivc->buffers.queue)) {
>> +		/*
>> +		 * The driver waits for interrupts to send a new frame and
>> +		 * tracks their receipt in the vvalid_ifp variable. .buf_queue()
>> +		 * will queue work if vvalid_ifp == 0 to trigger a new frame (an
>> +		 * event that normally would only occur if no buffer was ready
>> +		 * when the interrupt arrived). If there are no buffers in the
>> +		 * queue yet, we set vvalid_ifp to zero so that the next queue
>> +		 * will trigger the work.
>> +		 */
>> +		scoped_guard(spinlock_irqsave, &ivc->spinlock) {
> This can be just guard() ?
probably actually given the immediate return
>
>> +			ivc->vvalid_ifp = 0;
>> +		}
>> +	} else {
>> +		queue_work(ivc->buffers.async_wq, &ivc->buffers.work);
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static void rzv2h_ivc_pipeline_stopped(struct media_entity *entity)
>> +{
>> +	struct video_device *vdev = media_entity_to_video_device(entity);
>> +	struct rzv2h_ivc *ivc = video_get_drvdata(vdev);
>> +	u32 val = 0;
>> +
>> +	rzv2h_ivc_write(ivc, RZV2H_IVC_REG_FM_STOP, 0x1);
>> +	readl_poll_timeout(ivc->base + RZV2H_IVC_REG_FM_STOP,
>> +			   val, !val, 10 * USEC_PER_MSEC, 250 * USEC_PER_MSEC);
>> +}
>> +
>> +static const struct media_entity_operations rzv2h_ivc_media_ops = {
>> +	.pipeline_started = rzv2h_ivc_pipeline_started,
>> +	.pipeline_stopped = rzv2h_ivc_pipeline_stopped,
>> +};
> I still think we shouldn't be introducing dependencies between these
> two series. Can you drop anything related to pipeline_started/stopped
> from this series ? Would it make sense ? After all, it only serves to
> have the IVC operate with the Mali C55 and hence requires both
> pipeline_started and media jobs, right ?
>
> Can we:
> - Upstream IVC without pipeline_started
> - Upstream Mali C55 without pipeline started and media jobs
> - Add pipeline_started and media jobs on top
>
> Would this make sense in your opinion or should at least
> pipeline_started be there ? In this case we should merge this series
> and the mali c55 at the same time ?


IMO we merge:


1. pipeline_ops() (plus the V4L2 helper if that's useful)

2. C55

3. IVC


Guidance from Sakari and Laurent was that hardware shouldn't start streaming until all of the video 
devices have had .start_streaming() called; I think that the .pipeline_started() and 
.pipeline_stopped() callbacks are needed to fulfill that requirement across the two drivers. Media 
jobs is not required for either driver at this stage and could be merged on top later.


>
> The rest are all nits, so for this patch, provided we clarify the
> above point about dependencies
>
> Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>


Thanks very much!


Dan

>
> Thanks
>    j
>
>
>> +
>> +static int rzv2h_ivc_queue_setup(struct vb2_queue *q, unsigned int *num_buffers,
>> +				 unsigned int *num_planes, unsigned int sizes[],
>> +				 struct device *alloc_devs[])
>> +{
>> +	struct rzv2h_ivc *ivc = vb2_get_drv_priv(q);
>> +
>> +	if (*num_planes && *num_planes > 1)
>> +		return -EINVAL;
>> +
>> +	if (sizes[0] && sizes[0] < ivc->format.pix.plane_fmt[0].sizeimage)
>> +		return -EINVAL;
>> +
>> +	*num_planes = 1;
>> +
>> +	if (!sizes[0])
>> +		sizes[0] = ivc->format.pix.plane_fmt[0].sizeimage;
>> +
>> +	return 0;
>> +}
>> +
>> +static void rzv2h_ivc_buf_queue(struct vb2_buffer *vb)
>> +{
>> +	struct rzv2h_ivc *ivc = vb2_get_drv_priv(vb->vb2_queue);
>> +	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
>> +	struct rzv2h_ivc_buf *buf = to_rzv2h_ivc_buf(vbuf);
>> +
>> +	scoped_guard(spinlock, &ivc->buffers.lock) {
>> +		list_add_tail(&buf->queue, &ivc->buffers.queue);
>> +	}
>> +
>> +	scoped_guard(spinlock_irqsave, &ivc->spinlock) {
>> +		if (vb2_is_streaming(vb->vb2_queue) && !ivc->vvalid_ifp)
>> +			queue_work(ivc->buffers.async_wq, &ivc->buffers.work);
>> +	}
>> +}
>> +
>> +static void rzv2h_ivc_format_configure(struct rzv2h_ivc *ivc)
>> +{
>> +	const struct rzv2h_ivc_format *fmt = ivc->format.fmt;
>> +	struct v4l2_pix_format_mplane *pix = &ivc->format.pix;
>> +	unsigned int vblank;
>> +	unsigned int hts;
>> +
>> +	/* Currently only CRU packed pixel formats are supported */
>> +	rzv2h_ivc_write(ivc, RZV2H_IVC_REG_AXIRX_PXFMT,
>> +			RZV2H_IVC_INPUT_FMT_CRU_PACKED);
>> +
>> +	rzv2h_ivc_update_bits(ivc, RZV2H_IVC_REG_AXIRX_PXFMT,
>> +			      RZV2H_IVC_PXFMT_DTYPE, fmt->dtype);
>> +
>> +	rzv2h_ivc_write(ivc, RZV2H_IVC_REG_AXIRX_HSIZE, pix->width);
>> +	rzv2h_ivc_write(ivc, RZV2H_IVC_REG_AXIRX_VSIZE, pix->height);
>> +	rzv2h_ivc_write(ivc, RZV2H_IVC_REG_AXIRX_STRD,
>> +			pix->plane_fmt[0].bytesperline);
>> +
>> +	/*
>> +	 * The ISP has minimum vertical blanking requirements that must be
>> +	 * adhered to by the IVC. The minimum is a function of the Iridix blocks
>> +	 * clocking requirements and the width of the image and horizontal
>> +	 * blanking, but if we assume the worst case then it boils down to the
>> +	 * below (plus one to the numerator to ensure the answer is rounded up)
>> +	 */
>> +
>> +	hts = pix->width + RZV2H_IVC_FIXED_HBLANK;
>> +	vblank = RZV2H_IVC_MIN_VBLANK(hts);
>> +
>> +	rzv2h_ivc_write(ivc, RZV2H_IVC_REG_AXIRX_BLANK,
>> +			RZV2H_IVC_VBLANK(vblank));
>> +}
>> +
>> +static void rzv2h_ivc_return_buffers(struct rzv2h_ivc *ivc,
>> +				     enum vb2_buffer_state state)
>> +{
>> +	struct rzv2h_ivc_buf *buf, *tmp;
>> +
>> +	guard(spinlock)(&ivc->buffers.lock);
>> +
>> +	if (ivc->buffers.curr) {
>> +		vb2_buffer_done(&ivc->buffers.curr->vb.vb2_buf, state);
>> +		ivc->buffers.curr = NULL;
>> +	}
>> +
>> +	list_for_each_entry_safe(buf, tmp, &ivc->buffers.queue, queue) {
>> +		list_del(&buf->queue);
>> +		vb2_buffer_done(&buf->vb.vb2_buf, state);
>> +	}
>> +}
>> +
>> +static int rzv2h_ivc_start_streaming(struct vb2_queue *q, unsigned int count)
>> +{
>> +	struct rzv2h_ivc *ivc = vb2_get_drv_priv(q);
>> +	int ret;
>> +
>> +	ivc->buffers.sequence = 0;
>> +	ivc->vvalid_ifp = 2;
>> +
>> +	ret = pm_runtime_resume_and_get(ivc->dev);
>> +	if (ret)
>> +		goto err_return_buffers;
>> +
>> +	ret = video_device_pipeline_alloc_start(&ivc->vdev.dev);
>> +	if (ret) {
>> +		dev_err(ivc->dev, "failed to start media pipeline\n");
>> +		goto err_pm_runtime_put;
>> +	}
>> +
>> +	rzv2h_ivc_format_configure(ivc);
>> +
>> +	ret = video_device_pipeline_started(&ivc->vdev.dev);
>> +	if (ret < 0)
>> +		goto err_stop_pipeline;
>> +
>> +	return 0;
>> +
>> +err_stop_pipeline:
>> +	video_device_pipeline_stop(&ivc->vdev.dev);
>> +err_pm_runtime_put:
>> +	pm_runtime_put(ivc->dev);
>> +err_return_buffers:
>> +	rzv2h_ivc_return_buffers(ivc, VB2_BUF_STATE_QUEUED);
>> +
>> +	return ret;
>> +}
>> +
>> +static void rzv2h_ivc_stop_streaming(struct vb2_queue *q)
>> +{
>> +	struct rzv2h_ivc *ivc = vb2_get_drv_priv(q);
>> +
>> +	video_device_pipeline_stopped(&ivc->vdev.dev);
>> +	rzv2h_ivc_return_buffers(ivc, VB2_BUF_STATE_ERROR);
>> +	video_device_pipeline_stop(&ivc->vdev.dev);
>> +	pm_runtime_mark_last_busy(ivc->dev);
>> +	pm_runtime_put_autosuspend(ivc->dev);
>> +}
>> +
>> +static const struct vb2_ops rzv2h_ivc_vb2_ops = {
>> +	.queue_setup		= &rzv2h_ivc_queue_setup,
>> +	.buf_queue		= &rzv2h_ivc_buf_queue,
>> +	.wait_prepare		= vb2_ops_wait_prepare,
>> +	.wait_finish		= vb2_ops_wait_finish,
>> +	.start_streaming	= &rzv2h_ivc_start_streaming,
>> +	.stop_streaming		= &rzv2h_ivc_stop_streaming,
>> +};
>> +
>> +static const struct rzv2h_ivc_format *
>> +rzv2h_ivc_format_from_pixelformat(u32 fourcc)
>> +{
>> +	for (unsigned int i = 0; i < ARRAY_SIZE(rzv2h_ivc_formats); i++)
>> +		if (fourcc == rzv2h_ivc_formats[i].fourcc)
>> +			return &rzv2h_ivc_formats[i];
>> +
>> +	return &rzv2h_ivc_formats[0];
>> +}
>> +
>> +static int rzv2h_ivc_enum_fmt_vid_out(struct file *file, void *fh,
>> +				      struct v4l2_fmtdesc *f)
>> +{
>> +	if (f->index >= ARRAY_SIZE(rzv2h_ivc_formats))
>> +		return -EINVAL;
>> +
>> +	f->pixelformat = rzv2h_ivc_formats[f->index].fourcc;
>> +	return 0;
>> +}
>> +
>> +static int rzv2h_ivc_g_fmt_vid_out(struct file *file, void *fh,
>> +				   struct v4l2_format *f)
>> +{
>> +	struct rzv2h_ivc *ivc = video_drvdata(file);
>> +
>> +	f->fmt.pix_mp = ivc->format.pix;
>> +
>> +	return 0;
>> +}
>> +
>> +static void rzv2h_ivc_try_fmt(struct v4l2_pix_format_mplane *pix,
>> +			      const struct rzv2h_ivc_format *fmt)
>> +{
>> +	pix->pixelformat = fmt->fourcc;
>> +
>> +	pix->width = clamp(pix->width, RZV2H_IVC_MIN_WIDTH,
>> +			   RZV2H_IVC_MAX_WIDTH);
>> +	pix->height = clamp(pix->height, RZV2H_IVC_MIN_HEIGHT,
>> +			    RZV2H_IVC_MAX_HEIGHT);
>> +
>> +	pix->field = V4L2_FIELD_NONE;
>> +	pix->colorspace = V4L2_COLORSPACE_RAW;
>> +	pix->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(pix->colorspace);
>> +	pix->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true,
>> +							  pix->colorspace,
>> +							  pix->ycbcr_enc);
>> +
>> +	v4l2_fill_pixfmt_mp(pix, pix->pixelformat, pix->width, pix->height);
>> +}
>> +
>> +static void rzv2h_ivc_set_format(struct rzv2h_ivc *ivc,
>> +				 struct v4l2_pix_format_mplane *pix)
>> +{
>> +	const struct rzv2h_ivc_format *fmt;
>> +
>> +	fmt = rzv2h_ivc_format_from_pixelformat(pix->pixelformat);
>> +
>> +	rzv2h_ivc_try_fmt(pix, fmt);
>> +	ivc->format.pix = *pix;
>> +	ivc->format.fmt = fmt;
>> +}
>> +
>> +static int rzv2h_ivc_s_fmt_vid_out(struct file *file, void *fh,
>> +				   struct v4l2_format *f)
>> +{
>> +	struct rzv2h_ivc *ivc = video_drvdata(file);
>> +	struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;
>> +
>> +	if (vb2_is_busy(&ivc->vdev.vb2q))
>> +		return -EBUSY;
>> +
>> +	rzv2h_ivc_set_format(ivc, pix);
>> +
>> +	return 0;
>> +}
>> +
>> +static int rzv2h_ivc_try_fmt_vid_out(struct file *file, void *fh,
>> +				     struct v4l2_format *f)
>> +{
>> +	const struct rzv2h_ivc_format *fmt;
>> +
>> +	fmt = rzv2h_ivc_format_from_pixelformat(f->fmt.pix.pixelformat);
>> +	rzv2h_ivc_try_fmt(&f->fmt.pix_mp, fmt);
>> +
>> +	return 0;
>> +}
>> +
>> +static int rzv2h_ivc_querycap(struct file *file, void *fh,
>> +			      struct v4l2_capability *cap)
>> +{
>> +	strscpy(cap->driver, "rzv2h-ivc", sizeof(cap->driver));
>> +	strscpy(cap->card, "Renesas Input Video Control", sizeof(cap->card));
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct v4l2_ioctl_ops rzv2h_ivc_v4l2_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_enum_fmt_vid_out = rzv2h_ivc_enum_fmt_vid_out,
>> +	.vidioc_g_fmt_vid_out_mplane = rzv2h_ivc_g_fmt_vid_out,
>> +	.vidioc_s_fmt_vid_out_mplane = rzv2h_ivc_s_fmt_vid_out,
>> +	.vidioc_try_fmt_vid_out_mplane = rzv2h_ivc_try_fmt_vid_out,
>> +	.vidioc_querycap = rzv2h_ivc_querycap,
>> +	.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
>> +	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
>> +};
>> +
>> +static const struct v4l2_file_operations rzv2h_ivc_v4l2_fops = {
>> +	.owner = THIS_MODULE,
>> +	.unlocked_ioctl = video_ioctl2,
>> +	.open = v4l2_fh_open,
>> +	.release = vb2_fop_release,
>> +	.poll = vb2_fop_poll,
>> +	.mmap = vb2_fop_mmap,
>> +};
>> +
>> +int rzv2h_ivc_init_vdev(struct rzv2h_ivc *ivc, struct v4l2_device *v4l2_dev)
>> +{
>> +	struct v4l2_pix_format_mplane pix = { };
>> +	struct video_device *vdev;
>> +	struct vb2_queue *vb2q;
>> +	int ret;
>> +
>> +	spin_lock_init(&ivc->buffers.lock);
>> +	INIT_LIST_HEAD(&ivc->buffers.queue);
>> +	INIT_WORK(&ivc->buffers.work, rzv2h_ivc_transfer_buffer);
>> +
>> +	ivc->buffers.async_wq = alloc_workqueue("rzv2h-ivc", 0, 0);
>> +	if (!ivc->buffers.async_wq)
>> +		return -EINVAL;
>> +
>> +	/* Initialise vb2 queue */
>> +	vb2q = &ivc->vdev.vb2q;
>> +	vb2q->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
>> +	vb2q->io_modes = VB2_MMAP | VB2_DMABUF;
>> +	vb2q->drv_priv = ivc;
>> +	vb2q->mem_ops = &vb2_dma_contig_memops;
>> +	vb2q->ops = &rzv2h_ivc_vb2_ops;
>> +	vb2q->buf_struct_size = sizeof(struct rzv2h_ivc_buf);
>> +	vb2q->min_queued_buffers = 0;
>> +	vb2q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
>> +	vb2q->lock = &ivc->lock;
>> +	vb2q->dev = ivc->dev;
>> +
>> +	ret = vb2_queue_init(vb2q);
>> +	if (ret) {
>> +		dev_err(ivc->dev, "vb2 queue init failed\n");
>> +		goto err_destroy_workqueue;
>> +	}
>> +
>> +	/* Initialise Video Device */
>> +	vdev = &ivc->vdev.dev;
>> +	strscpy(vdev->name, "rzv2h-ivc", sizeof(vdev->name));
>> +	vdev->release = video_device_release_empty;
>> +	vdev->fops = &rzv2h_ivc_v4l2_fops;
>> +	vdev->ioctl_ops = &rzv2h_ivc_v4l2_ioctl_ops;
>> +	vdev->lock = &ivc->lock;
>> +	vdev->v4l2_dev = v4l2_dev;
>> +	vdev->queue = vb2q;
>> +	vdev->device_caps = V4L2_CAP_VIDEO_OUTPUT_MPLANE | V4L2_CAP_STREAMING;
>> +	vdev->vfl_dir = VFL_DIR_TX;
>> +	video_set_drvdata(vdev, ivc);
>> +
>> +	pix.pixelformat = V4L2_PIX_FMT_SRGGB16;
>> +	pix.width = RZV2H_IVC_DEFAULT_WIDTH;
>> +	pix.height = RZV2H_IVC_DEFAULT_HEIGHT;
>> +	rzv2h_ivc_set_format(ivc, &pix);
>> +
>> +	ivc->vdev.pad.flags = MEDIA_PAD_FL_SOURCE;
>> +	ivc->vdev.dev.entity.ops = &rzv2h_ivc_media_ops;
>> +	ret = media_entity_pads_init(&ivc->vdev.dev.entity, 1, &ivc->vdev.pad);
>> +	if (ret)
>> +		goto err_release_vb2q;
>> +
>> +	ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
>> +	if (ret) {
>> +		dev_err(ivc->dev, "failed to register IVC video device\n");
>> +		goto err_cleanup_vdev_entity;
>> +	}
>> +
>> +	ret = media_create_pad_link(&vdev->entity, 0, &ivc->subdev.sd.entity,
>> +				    RZV2H_IVC_SUBDEV_SINK_PAD,
>> +				    MEDIA_LNK_FL_ENABLED |
>> +				    MEDIA_LNK_FL_IMMUTABLE);
>> +	if (ret) {
>> +		dev_err(ivc->dev, "failed to create media link\n");
>> +		goto err_unregister_vdev;
>> +	}
>> +
>> +	return 0;
>> +
>> +err_unregister_vdev:
>> +	video_unregister_device(vdev);
>> +err_cleanup_vdev_entity:
>> +	media_entity_cleanup(&vdev->entity);
>> +err_release_vb2q:
>> +	vb2_queue_release(vb2q);
>> +err_destroy_workqueue:
>> +	destroy_workqueue(ivc->buffers.async_wq);
>> +
>> +	return ret;
>> +}
>> +
>> +void rzv2h_deinit_video_dev_and_queue(struct rzv2h_ivc *ivc)
>> +{
>> +	struct video_device *vdev = &ivc->vdev.dev;
>> +	struct vb2_queue *vb2q = &ivc->vdev.vb2q;
>> +
>> +	if (!ivc->sched)
>> +		return;
>> +
>> +	vb2_video_unregister_device(vdev);
>> +	media_entity_cleanup(&vdev->entity);
>> +	vb2_queue_release(vb2q);
>> +}
>> diff --git a/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc.h b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc.h
>> new file mode 100644
>> index 0000000000000000000000000000000000000000..709c6a9398fe2484c2acb03d443d58ea4e153a66
>> --- /dev/null
>> +++ b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc.h
>> @@ -0,0 +1,131 @@
>> +/* SPDX-License-Identifier: GPL-2.0 */
>> +/*
>> + * Renesas RZ/V2H Input Video Control Block driver
>> + *
>> + * Copyright (C) 2025 Ideas on Board Oy
>> + */
>> +
>> +#include <linux/clk.h>
>> +#include <linux/list.h>
>> +#include <linux/mutex.h>
>> +#include <linux/reset.h>
>> +#include <linux/spinlock.h>
>> +#include <linux/types.h>
>> +#include <linux/videodev2.h>
>> +#include <linux/workqueue.h>
>> +
>> +#include <media/media-entity.h>
>> +#include <media/v4l2-device.h>
>> +#include <media/v4l2-subdev.h>
>> +#include <media/videobuf2-core.h>
>> +#include <media/videobuf2-v4l2.h>
>> +
>> +#define RZV2H_IVC_REG_AXIRX_PLNUM			0x0000
>> +#define RZV2H_IVC_ONE_EXPOSURE				0x00
>> +#define RZV2H_IVC_TWO_EXPOSURE				0x01
>> +#define RZV2H_IVC_REG_AXIRX_PXFMT			0x0004
>> +#define RZV2H_IVC_INPUT_FMT_MIPI			(0 << 16)
>> +#define RZV2H_IVC_INPUT_FMT_CRU_PACKED			(1 << 16)
>> +#define RZV2H_IVC_PXFMT_DTYPE				GENMASK(7, 0)
>> +#define RZV2H_IVC_REG_AXIRX_SADDL_P0			0x0010
>> +#define RZV2H_IVC_REG_AXIRX_SADDH_P0			0x0014
>> +#define RZV2H_IVC_REG_AXIRX_SADDL_P1			0x0018
>> +#define RZV2H_IVC_REG_AXIRX_SADDH_P1			0x001c
>> +#define RZV2H_IVC_REG_AXIRX_HSIZE			0x0020
>> +#define RZV2H_IVC_REG_AXIRX_VSIZE			0x0024
>> +#define RZV2H_IVC_REG_AXIRX_BLANK			0x0028
>> +#define RZV2H_IVC_VBLANK(x)				((x) << 16)
>> +#define RZV2H_IVC_REG_AXIRX_STRD			0x0030
>> +#define RZV2H_IVC_REG_AXIRX_ISSU			0x0040
>> +#define RZV2H_IVC_REG_AXIRX_ERACT			0x0048
>> +#define RZV2H_IVC_REG_FM_CONTEXT			0x0100
>> +#define RZV2H_IVC_SOFTWARE_CFG				0x00
>> +#define RZV2H_IVC_SINGLE_CONTEXT_SW_HW_CFG		BIT(0)
>> +#define RZV2H_IVC_MULTI_CONTEXT_SW_HW_CFG		BIT(1)
>> +#define RZV2H_IVC_REG_FM_MCON				0x0104
>> +#define RZV2H_IVC_REG_FM_FRCON				0x0108
>> +#define RZV2H_IVC_REG_FM_STOP				0x010c
>> +#define RZV2H_IVC_REG_FM_INT_EN				0x0120
>> +#define RZV2H_IVC_VVAL_IFPE				BIT(0)
>> +#define RZV2H_IVC_REG_FM_INT_STA			0x0124
>> +#define RZV2H_IVC_REG_AXIRX_FIFOCAP0			0x0208
>> +#define RZV2H_IVC_REG_CORE_CAPCON			0x020c
>> +#define RZV2H_IVC_REG_CORE_FIFOCAP0			0x0228
>> +#define RZV2H_IVC_REG_CORE_FIFOCAP1			0x022c
>> +
>> +#define RZV2H_IVC_MIN_WIDTH				640
>> +#define RZV2H_IVC_MAX_WIDTH				4096
>> +#define RZV2H_IVC_MIN_HEIGHT				480
>> +#define RZV2H_IVC_MAX_HEIGHT				4096
>> +#define RZV2H_IVC_DEFAULT_WIDTH				1920
>> +#define RZV2H_IVC_DEFAULT_HEIGHT			1080
>> +
>> +#define RZV2H_IVC_NUM_HW_RESOURCES			3
>> +
>> +struct device;
>> +
>> +enum rzv2h_ivc_subdev_pads {
>> +	RZV2H_IVC_SUBDEV_SINK_PAD,
>> +	RZV2H_IVC_SUBDEV_SOURCE_PAD,
>> +	RZV2H_IVC_NUM_SUBDEV_PADS
>> +};
>> +
>> +struct rzv2h_ivc_format {
>> +	u32 fourcc;
>> +	/*
>> +	 * The CRU packed pixel formats are bayer-order agnostic, so each could
>> +	 * support any one of the 4 possible media bus formats.
>> +	 */
>> +	u32 mbus_codes[4];
>> +	u8 dtype;
>> +};
>> +
>> +struct rzv2h_ivc {
>> +	struct device *dev;
>> +	void __iomem *base;
>> +	struct clk_bulk_data clks[RZV2H_IVC_NUM_HW_RESOURCES];
>> +	struct reset_control_bulk_data resets[RZV2H_IVC_NUM_HW_RESOURCES];
>> +	int irqnum;
>> +	u8 vvalid_ifp;
>> +
>> +	struct {
>> +		struct video_device dev;
>> +		struct vb2_queue vb2q;
>> +		struct media_pad pad;
>> +	} vdev;
>> +
>> +	struct {
>> +		struct v4l2_subdev sd;
>> +		struct media_pad pads[RZV2H_IVC_NUM_SUBDEV_PADS];
>> +	} subdev;
>> +
>> +	struct {
>> +		/* Spinlock to guard buffer queue */
>> +		spinlock_t lock;
>> +		struct workqueue_struct *async_wq;
>> +		struct work_struct work;
>> +		struct list_head queue;
>> +		struct rzv2h_ivc_buf *curr;
>> +		unsigned int sequence;
>> +	} buffers;
>> +
>> +	struct media_job_scheduler *sched;
>> +
>> +	struct {
>> +		struct v4l2_pix_format_mplane pix;
>> +		const struct rzv2h_ivc_format *fmt;
>> +	} format;
>> +
>> +	/* Mutex to provide to vb2 */
>> +	struct mutex lock;
>> +	/* Lock to protect the interrupt counter */
>> +	spinlock_t spinlock;
>> +};
>> +
>> +int rzv2h_ivc_init_vdev(struct rzv2h_ivc *ivc, struct v4l2_device *v4l2_dev);
>> +void rzv2h_deinit_video_dev_and_queue(struct rzv2h_ivc *ivc);
>> +int rzv2h_ivc_initialise_subdevice(struct rzv2h_ivc *ivc);
>> +void rzv2h_ivc_deinit_subdevice(struct rzv2h_ivc *ivc);
>> +void rzv2h_ivc_write(struct rzv2h_ivc *ivc, u32 addr, u32 val);
>> +void rzv2h_ivc_update_bits(struct rzv2h_ivc *ivc, unsigned int addr,
>> +			   u32 mask, u32 val);
>>
>> --
>> 2.34.1
>>
>>

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

* Re: [PATCH v4 1/3] dt-bindings: media: Add bindings for the RZ/V2H IVC block
  2025-07-14 15:19 ` [PATCH v4 1/3] dt-bindings: media: Add bindings for the RZ/V2H IVC block Daniel Scally
@ 2025-07-24 15:55   ` Lad, Prabhakar
  0 siblings, 0 replies; 12+ messages in thread
From: Lad, Prabhakar @ 2025-07-24 15:55 UTC (permalink / raw)
  To: Daniel Scally
  Cc: linux-media, devicetree, linux-renesas-soc, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Geert Uytterhoeven,
	Magnus Damm, Philipp Zabel, jacopo.mondi, biju.das.jz,
	laurent.pinchart, Krzysztof Kozlowski

Hi Daniel,

Thank you for the patch.

On Mon, Jul 14, 2025 at 4:28 PM Daniel Scally
<dan.scally@ideasonboard.com> wrote:
>
> The RZ/V2H SoC has a block called the Input Video Control block which
> feeds image data into the Image Signal Processor. Add dt bindings to
> describe the IVC.
>
> Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
> Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com>
> ---
> Changes in v3:
>
>         - Rename from rzv2h-ivc.yaml to r9a09g057-ivc.yaml
>         - Update clock and reset names
>
> Changes in v2:
>
>         - compatible matches filename
>         - Added power-domains
>         - Aligned clock and reset entries on opening "<"
>         - Removed status = "okay"; from example
> ---
>  .../bindings/media/renesas,r9a09g057-ivc.yaml      | 103 +++++++++++++++++++++
>  1 file changed, 103 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/media/renesas,r9a09g057-ivc.yaml b/Documentation/devicetree/bindings/media/renesas,r9a09g057-ivc.yaml
> new file mode 100644
> index 0000000000000000000000000000000000000000..3ecccf0a4b05ffcf90c130924bfe50065b06f87e
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/media/renesas,r9a09g057-ivc.yaml
> @@ -0,0 +1,103 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/media/renesas,r9a09g057-ivc.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Renesas RZ/V2H Input Video Control Block
> +
s/ RZ/V2H/RZ/V2HP
The 'P' variant is the one which has the Mali-C55 (also in the commit message).

Rest LGTM.

Cheers,
Prabhakar

> +maintainers:
> +  - Daniel Scally <dan.scally@ideasonboard.com>
> +
> +description:
> +  The IVC block is a module that takes video frames from memory and feeds them
> +  to the Image Signal Processor for processing.
> +
> +properties:
> +  compatible:
> +    const: renesas,r9a09g057-ivc
> +
> +  reg:
> +    maxItems: 1
> +
> +  interrupts:
> +    maxItems: 1
> +
> +  clocks:
> +    items:
> +      - description: Input Video Control block register access clock
> +      - description: Video input data AXI bus clock
> +      - description: ISP system clock
> +
> +  clock-names:
> +    items:
> +      - const: reg
> +      - const: axi
> +      - const: isp
> +
> +  power-domains:
> +    maxItems: 1
> +
> +  resets:
> +    items:
> +      - description: Input Video Control block register access reset
> +      - description: Video input data AXI bus reset
> +      - description: ISP core reset
> +
> +  reset-names:
> +    items:
> +      - const: reg
> +      - const: axi
> +      - const: isp
> +
> +  port:
> +    $ref: /schemas/graph.yaml#/properties/port
> +    description: Output parallel video bus
> +
> +    properties:
> +      endpoint:
> +        $ref: /schemas/graph.yaml#/properties/endpoint
> +
> +required:
> +  - compatible
> +  - reg
> +  - interrupts
> +  - clocks
> +  - clock-names
> +  - power-domains
> +  - resets
> +  - reset-names
> +  - port
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    #include <dt-bindings/clock/renesas,r9a09g057-cpg.h>
> +    #include <dt-bindings/interrupt-controller/arm-gic.h>
> +
> +    isp-input@16040000 {
> +      compatible = "renesas,r9a09g057-ivc";
> +      reg = <0x16040000 0x230>;
> +
> +      clocks = <&cpg CPG_MOD 0xe3>,
> +               <&cpg CPG_MOD 0xe4>,
> +               <&cpg CPG_MOD 0xe5>;
> +      clock-names = "reg", "axi", "isp";
> +
> +      power-domains = <&cpg>;
> +
> +      resets = <&cpg 0xd4>,
> +               <&cpg 0xd1>,
> +               <&cpg 0xd3>;
> +      reset-names = "reg", "axi", "isp";
> +
> +      interrupts = <GIC_SPI 861 IRQ_TYPE_EDGE_RISING>;
> +
> +      port {
> +        ivc_out: endpoint {
> +          remote-endpoint = <&isp_in>;
> +        };
> +      };
> +    };
> +...
>
> --
> 2.34.1
>
>

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

* Re: [PATCH v4 2/3] media: platform: Add Renesas Input Video Control block driver
  2025-07-14 15:19 ` [PATCH v4 2/3] media: platform: Add Renesas Input Video Control block driver Daniel Scally
  2025-07-22  9:40   ` Jacopo Mondi
@ 2025-07-24 16:52   ` Lad, Prabhakar
  2025-07-24 18:04     ` Dan Scally
  2025-07-30 14:10   ` Geert Uytterhoeven
  2 siblings, 1 reply; 12+ messages in thread
From: Lad, Prabhakar @ 2025-07-24 16:52 UTC (permalink / raw)
  To: Daniel Scally
  Cc: linux-media, devicetree, linux-renesas-soc, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Geert Uytterhoeven,
	Magnus Damm, Philipp Zabel, jacopo.mondi, biju.das.jz,
	laurent.pinchart

Hi Daniel,

Thank you for the patch.

On Mon, Jul 14, 2025 at 4:25 PM Daniel Scally
<dan.scally@ideasonboard.com> wrote:
>
> Add a driver for the Input Video Control block in an RZ/V2H SoC which
> feeds data into the Arm Mali-C55 ISP.
>
s/V2H/V2HP everywhere.

> Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com>
> ---
> Changes in v5:
>
>         - Fixed .enum_frame_sizes() to properly check that the
>           given mbus_code matches the source pads format.
>         - Tidy up extra space in Kconfig
>         - Revise Kconfig option message
>         - Don't mark functions inline
>         - Fixup misleading comment
>         - select CONFIG_PM
>         - Use the new pm_sleep_ptr() functionality
>         - Minor formatting
>
> Changes in v4:
>
>         - Update the compatible to renesas,r9a09g057-ivc
>         - Dropped the media jobs / scheduler functionality, and re
>           worked the driver to have its own workqueue pushing frames
>         - Fix .enum_mbus_code() to return 20-bit output for source
>           pad.
>         - Fix some alignment issues
>         - Make the forwarding of sink to source pad format a more
>           explicit operation.
>         - Rename rzv2h_initialise_video_device_and_queue()
>         - Reversed order of v4l2_subdev_init_finalize() and
>           v4l2_async_register_subdev() to make sure everything is
>           finished initialising before registering the subdev.
>         - Change function to MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER
>         - Use a parametised macro for min vblank
>         - Minor formatting
>         - Use the DEFAULT macros for quantization / ycbcr_enc values
>         - Switch to using the mplane API
>         - Dropped select RESET_CONTROLLER
>         - Used the new helpers for starting a media pipeline
>         - Switch from threaded irq to normal with driver workqueue
>           and revised startup routine
>
> Changes in v3:
>
>         - Account for the renamed CRU pixel formats
>
> Changes in v2:
>
>         - Added selects and depends statements to Kconfig entry
>         - Fixed copyright year
>         - Stopped including in .c files headers already included in .h
>         - Fixed uninitialized variable in iterator
>         - Only check vvalid member in interrupt function and wait
>           unconditionally elsewhere
>         - __maybe_unused for the PM ops
>         - Initialise the subdevice after setting up PM
>         - Fixed the remove function for the driver to actually do
>           something.
>         - Some minor formatting changes
>         - Fixed the quantization member for the format
>         - Changes accounting for the v2 of the media jobs framework
>         - Change min_queued_buffers to 0
> ---
>  drivers/media/platform/renesas/Kconfig             |   1 +
>  drivers/media/platform/renesas/Makefile            |   1 +
>  drivers/media/platform/renesas/rzv2h-ivc/Kconfig   |  18 +
>  drivers/media/platform/renesas/rzv2h-ivc/Makefile  |   5 +
>  .../platform/renesas/rzv2h-ivc/rzv2h-ivc-dev.c     | 229 +++++++++
>  .../platform/renesas/rzv2h-ivc/rzv2h-ivc-subdev.c  | 376 ++++++++++++++
>  .../platform/renesas/rzv2h-ivc/rzv2h-ivc-video.c   | 568 +++++++++++++++++++++
>  .../media/platform/renesas/rzv2h-ivc/rzv2h-ivc.h   | 131 +++++
>  8 files changed, 1329 insertions(+)
>
> diff --git a/drivers/media/platform/renesas/Kconfig b/drivers/media/platform/renesas/Kconfig
> index 27a54fa7908384f2e8200f0f7283a82b0ae8435c..bd8247c0b8aa734d2b412438e694f3908d910b25 100644
> --- a/drivers/media/platform/renesas/Kconfig
> +++ b/drivers/media/platform/renesas/Kconfig
> @@ -42,6 +42,7 @@ config VIDEO_SH_VOU
>  source "drivers/media/platform/renesas/rcar-isp/Kconfig"
>  source "drivers/media/platform/renesas/rcar-vin/Kconfig"
>  source "drivers/media/platform/renesas/rzg2l-cru/Kconfig"
> +source "drivers/media/platform/renesas/rzv2h-ivc/Kconfig"
>
>  # Mem2mem drivers
>
> diff --git a/drivers/media/platform/renesas/Makefile b/drivers/media/platform/renesas/Makefile
> index 1127259c09d6a51b70803e76c495918e06777f67..b6b4abf01db246aaf8269b8027efee9b0b32083a 100644
> --- a/drivers/media/platform/renesas/Makefile
> +++ b/drivers/media/platform/renesas/Makefile
> @@ -6,6 +6,7 @@
>  obj-y += rcar-isp/
>  obj-y += rcar-vin/
>  obj-y += rzg2l-cru/
> +obj-y += rzv2h-ivc/
>  obj-y += vsp1/
>
>  obj-$(CONFIG_VIDEO_RCAR_CSI2) += rcar-csi2.o
> diff --git a/drivers/media/platform/renesas/rzv2h-ivc/Kconfig b/drivers/media/platform/renesas/rzv2h-ivc/Kconfig
> new file mode 100644
> index 0000000000000000000000000000000000000000..5a4a3c052a3ae0f242e844689132d91a75b8a302
> --- /dev/null
> +++ b/drivers/media/platform/renesas/rzv2h-ivc/Kconfig
> @@ -0,0 +1,18 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +config VIDEO_RZV2H_IVC
> +       tristate "Renesas RZ/V2H Input Video Control block driver"
> +       depends on V4L_PLATFORM_DRIVERS
> +       depends on VIDEO_DEV
> +       depends on ARCH_RENESAS || COMPILE_TEST
> +       depends on OF
> +       select CONFIG_PM
> +       select VIDEOBUF2_DMA_CONTIG
> +       select MEDIA_CONTROLLER
> +       select VIDEO_V4L2_SUBDEV_API
> +       help
> +          Support for the Renesas RZ/V2H Input Video Control Block
> +          (IVC).
> +
> +          To compile this driver as a module, choose M here: the
> +          module will be called rzv2h-ivc.
> diff --git a/drivers/media/platform/renesas/rzv2h-ivc/Makefile b/drivers/media/platform/renesas/rzv2h-ivc/Makefile
> new file mode 100644
> index 0000000000000000000000000000000000000000..080ee3570f09c236d87abeaea5d8dd578fefb6d3
> --- /dev/null
> +++ b/drivers/media/platform/renesas/rzv2h-ivc/Makefile
> @@ -0,0 +1,5 @@
> +# SPDX-License-Identifier: GPL-2.0
> +
> +rzv2h-ivc-y := rzv2h-ivc-dev.o rzv2h-ivc-subdev.o rzv2h-ivc-video.o
> +
> +obj-$(CONFIG_VIDEO_RZV2H_IVC) += rzv2h-ivc.o
> diff --git a/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-dev.c b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-dev.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..ce2e3a3af8d19900241add7d261f7a40f2551265
> --- /dev/null
> +++ b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-dev.c
> @@ -0,0 +1,229 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Renesas RZ/V2H Input Video Control Block driver
> + *
> + * Copyright (C) 2025 Ideas on Board Oy
> + */
> +
> +#include "rzv2h-ivc.h"
> +
> +#include <linux/device.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/reset.h>
> +
> +void rzv2h_ivc_write(struct rzv2h_ivc *ivc, u32 addr, u32 val)
> +{
> +       writel(val, ivc->base + addr);
> +}
> +
> +void rzv2h_ivc_update_bits(struct rzv2h_ivc *ivc, unsigned int addr,
> +                          u32 mask, u32 val)
> +{
> +       u32 orig, new;
> +
> +       orig = readl(ivc->base + addr);
> +
> +       new = orig & ~mask;
> +       new |= val & mask;
> +
> +       if (new != orig)
> +               writel(new, ivc->base + addr);
> +}
> +
> +static int rzv2h_ivc_get_hardware_resources(struct rzv2h_ivc *ivc,
> +                                           struct platform_device *pdev)
> +{
> +       const char * const resource_names[RZV2H_IVC_NUM_HW_RESOURCES] = {
> +               "reg",
> +               "axi",
> +               "isp",
> +       };
> +       struct resource *res;
> +       int ret;
> +
> +       ivc->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
> +       if (IS_ERR(ivc->base))
> +               return dev_err_probe(ivc->dev, PTR_ERR(ivc->base),
> +                                    "failed to map IO memory\n");
> +
> +       for (unsigned int i = 0; i < ARRAY_SIZE(resource_names); i++)
> +               ivc->clks[i].id = resource_names[i];
> +
> +       ret = devm_clk_bulk_get(ivc->dev, ARRAY_SIZE(resource_names), ivc->clks);
> +       if (ret)
> +               return dev_err_probe(ivc->dev, ret, "failed to acquire clks\n");
> +
> +       for (unsigned int i = 0; i < ARRAY_SIZE(resource_names); i++)
> +               ivc->resets[i].id = resource_names[i];
> +
> +       ret = devm_reset_control_bulk_get_optional_shared(
> +               ivc->dev, ARRAY_SIZE(resource_names), ivc->resets);
> +       if (ret)
> +               return dev_err_probe(ivc->dev, ret, "failed to acquire resets\n");
> +
> +       return 0;
> +}
> +
> +static void rzv2h_ivc_global_config(struct rzv2h_ivc *ivc)
> +{
> +       /* Currently we only support single-exposure input */
> +       rzv2h_ivc_write(ivc, RZV2H_IVC_REG_AXIRX_PLNUM, RZV2H_IVC_ONE_EXPOSURE);
> +
> +       /*
> +        * Datasheet says we should disable the interrupts before changing mode
> +        * to avoid spurious IFP interrupt.
> +        */
> +       rzv2h_ivc_write(ivc, RZV2H_IVC_REG_FM_INT_EN, 0x0);
> +
> +       /*
> +        * RZ/V2H documentation says software controlled single context mode is
> +        * is not supported, and currently the driver does not support the
> +        * multi-context mode. That being so we just set single context sw-hw
> +        * mode.
> +        */
> +       rzv2h_ivc_write(ivc, RZV2H_IVC_REG_FM_CONTEXT,
> +                       RZV2H_IVC_SINGLE_CONTEXT_SW_HW_CFG);
> +
> +       /*
> +        * We enable the frame end interrupt so that we know when we should send
> +        * follow-up frames.
> +        */
> +       rzv2h_ivc_write(ivc, RZV2H_IVC_REG_FM_INT_EN, RZV2H_IVC_VVAL_IFPE);
> +}
> +
> +static irqreturn_t rzv2h_ivc_isr(int irq, void *context)
> +{
> +       struct device *dev = context;
> +       struct rzv2h_ivc *ivc = dev_get_drvdata(dev);
> +
> +       guard(spinlock)(&ivc->spinlock);
> +
> +       if (!--ivc->vvalid_ifp)
> +               queue_work(ivc->buffers.async_wq, &ivc->buffers.work);
> +
> +       return IRQ_HANDLED;
> +}
> +
> +static int rzv2h_ivc_runtime_resume(struct device *dev)
> +{
> +       struct rzv2h_ivc *ivc = dev_get_drvdata(dev);
> +       int ret;
> +
> +       ret = request_irq(ivc->irqnum, rzv2h_ivc_isr, 0, dev_driver_string(dev),
> +                         dev);
> +       if (ret) {
> +               dev_err(dev, "failed to request irq\n");
> +               return ret;
> +       }
> +
> +       ret = clk_bulk_prepare_enable(ARRAY_SIZE(ivc->clks), ivc->clks);
> +       if (ret) {
> +               dev_err(ivc->dev, "failed to enable clocks\n");
> +               goto err_free_irqnum;
> +       }
> +
> +       ret = reset_control_bulk_deassert(ARRAY_SIZE(ivc->resets), ivc->resets);
> +       if (ret) {
> +               dev_err(ivc->dev, "failed to deassert resets\n");
> +               goto err_disable_clks;
> +       }
> +
> +       rzv2h_ivc_global_config(ivc);
> +
> +       return 0;
> +
> +err_disable_clks:
> +       clk_bulk_disable_unprepare(ARRAY_SIZE(ivc->clks), ivc->clks);
> +err_free_irqnum:
> +       free_irq(ivc->irqnum, dev);
> +
> +       return ret;
> +}
> +
> +static int rzv2h_ivc_runtime_suspend(struct device *dev)
> +{
> +       struct rzv2h_ivc *ivc = dev_get_drvdata(dev);
> +
> +       reset_control_bulk_assert(ARRAY_SIZE(ivc->resets), ivc->resets);
> +       clk_bulk_disable_unprepare(ARRAY_SIZE(ivc->clks), ivc->clks);
> +       free_irq(ivc->irqnum, dev);
> +
> +       return 0;
> +}
> +
> +static const struct dev_pm_ops rzv2h_ivc_pm_ops = {
> +       SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
> +                               pm_runtime_force_resume)
> +       RUNTIME_PM_OPS(rzv2h_ivc_runtime_suspend, rzv2h_ivc_runtime_resume,
> +                          NULL)
> +};
> +
> +static int rzv2h_ivc_probe(struct platform_device *pdev)
> +{
> +       struct device *dev = &pdev->dev;
> +       struct rzv2h_ivc *ivc;
> +       int ret;
> +
> +       ivc = devm_kzalloc(dev, sizeof(*ivc), GFP_KERNEL);
> +       if (!ivc)
> +               return -ENOMEM;
> +
> +       ivc->dev = dev;
> +       platform_set_drvdata(pdev, ivc);
> +       mutex_init(&ivc->lock);
> +       spin_lock_init(&ivc->spinlock);
> +
> +       ret = rzv2h_ivc_get_hardware_resources(ivc, pdev);
> +       if (ret)
> +               return ret;
> +
> +       pm_runtime_set_autosuspend_delay(dev, 2000);
> +       pm_runtime_use_autosuspend(dev);
> +       pm_runtime_enable(dev);
> +
> +       ivc->irqnum = platform_get_irq(pdev, 0);
> +       if (ivc->irqnum < 0) {
> +               dev_err(dev, "failed to get interrupt\n");
dev_err_probe()

> +               return ret;
> +       }
> +
> +       ret = rzv2h_ivc_initialise_subdevice(ivc);
> +       if (ret)
> +               return ret;
we could drop the below statement and just do `return
rzv2h_ivc_initialise_subdevice(ivc);`

> +
> +       return 0;
> +}
> +
> +static void rzv2h_ivc_remove(struct platform_device *pdev)
> +{
> +       struct rzv2h_ivc *ivc = platform_get_drvdata(pdev);
> +
> +       rzv2h_deinit_video_dev_and_queue(ivc);
> +       rzv2h_ivc_deinit_subdevice(ivc);
> +       mutex_destroy(&ivc->lock);
> +}
> +
> +static const struct of_device_id rzv2h_ivc_of_match[] = {
> +       { .compatible = "renesas,r9a09g057-ivc", },
> +       { /* Sentinel */ },
`,` not needed.

> +};
> +MODULE_DEVICE_TABLE(of, rzv2h_ivc_of_match);
> +
> +static struct platform_driver rzv2h_ivc_driver = {
> +       .driver = {
> +               .name = "rzv2h-ivc",
> +               .of_match_table = rzv2h_ivc_of_match,
> +               .pm = &rzv2h_ivc_pm_ops,
> +       },
> +       .probe = rzv2h_ivc_probe,
> +       .remove = rzv2h_ivc_remove,
> +};
> +
> +module_platform_driver(rzv2h_ivc_driver);
> +
> +MODULE_AUTHOR("Daniel Scally <dan.scally@ideasonboard.com>");
> +MODULE_DESCRIPTION("Renesas RZ/V2H Input Video Control Block driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-subdev.c b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-subdev.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..eb2913153d406fbad2491bb36e1c5ea754bea6f2
> --- /dev/null
> +++ b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-subdev.c
> @@ -0,0 +1,376 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Renesas RZ/V2H Input Video Control Block driver
> + *
> + * Copyright (C) 2025 Ideas on Board Oy
> + */
> +
> +#include "rzv2h-ivc.h"
> +
> +#include <linux/media.h>
> +#include <linux/media-bus-format.h>
> +#include <linux/v4l2-mediabus.h>
> +
> +#include <media/v4l2-async.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-dev.h>
> +#include <media/v4l2-event.h>
> +
> +#define RZV2H_IVC_N_INPUTS_PER_OUTPUT          6
> +
> +/*
> + * We support 8/10/12/14/16/20 bit input in any bayer order, but the output
> + * format is fixed at 20-bits with the same order as the input.
> + */
> +static const struct {
> +       u32 inputs[RZV2H_IVC_N_INPUTS_PER_OUTPUT];
> +       u32 output;
> +} rzv2h_ivc_formats[] = {
> +       {
> +               .inputs = {
> +                       MEDIA_BUS_FMT_SBGGR8_1X8,
> +                       MEDIA_BUS_FMT_SBGGR10_1X10,
> +                       MEDIA_BUS_FMT_SBGGR12_1X12,
> +                       MEDIA_BUS_FMT_SBGGR14_1X14,
> +                       MEDIA_BUS_FMT_SBGGR16_1X16,
> +                       MEDIA_BUS_FMT_SBGGR20_1X20,
> +               },
> +               .output = MEDIA_BUS_FMT_SBGGR20_1X20
> +       },
> +       {
> +               .inputs = {
> +                       MEDIA_BUS_FMT_SGBRG8_1X8,
> +                       MEDIA_BUS_FMT_SGBRG10_1X10,
> +                       MEDIA_BUS_FMT_SGBRG12_1X12,
> +                       MEDIA_BUS_FMT_SGBRG14_1X14,
> +                       MEDIA_BUS_FMT_SGBRG16_1X16,
> +                       MEDIA_BUS_FMT_SGBRG20_1X20,
> +               },
> +               .output = MEDIA_BUS_FMT_SGBRG20_1X20
> +       },
> +       {
> +               .inputs = {
> +                       MEDIA_BUS_FMT_SGRBG8_1X8,
> +                       MEDIA_BUS_FMT_SGRBG10_1X10,
> +                       MEDIA_BUS_FMT_SGRBG12_1X12,
> +                       MEDIA_BUS_FMT_SGRBG14_1X14,
> +                       MEDIA_BUS_FMT_SGRBG16_1X16,
> +                       MEDIA_BUS_FMT_SGRBG20_1X20,
> +               },
> +               .output = MEDIA_BUS_FMT_SGRBG20_1X20
> +       },
> +       {
> +               .inputs = {
> +                       MEDIA_BUS_FMT_SRGGB8_1X8,
> +                       MEDIA_BUS_FMT_SRGGB10_1X10,
> +                       MEDIA_BUS_FMT_SRGGB12_1X12,
> +                       MEDIA_BUS_FMT_SRGGB14_1X14,
> +                       MEDIA_BUS_FMT_SRGGB16_1X16,
> +                       MEDIA_BUS_FMT_SRGGB20_1X20,
> +               },
> +               .output = MEDIA_BUS_FMT_SRGGB20_1X20
> +       },
> +};
> +
> +static u32 rzv2h_ivc_get_mbus_output_from_input(u32 mbus_code)
> +{
> +       unsigned int i, j;
> +
> +       for (i = 0; i < ARRAY_SIZE(rzv2h_ivc_formats); i++) {
> +               for (j = 0; j < RZV2H_IVC_N_INPUTS_PER_OUTPUT; j++) {
> +                       if (rzv2h_ivc_formats[i].inputs[j] == mbus_code)
> +                               return rzv2h_ivc_formats[i].output;
> +               }
> +       }
> +
> +       return 0;
> +}
> +
> +static int rzv2h_ivc_enum_mbus_code(struct v4l2_subdev *sd,
> +                                   struct v4l2_subdev_state *state,
> +                                   struct v4l2_subdev_mbus_code_enum *code)
> +{
> +       const struct v4l2_mbus_framefmt *fmt;
> +       unsigned int order_index;
> +       unsigned int index;
> +
> +       /*
> +        * On the source pad, only the 20-bit format corresponding to the sink
> +        * pad format's bayer order is supported.
> +        */
> +       if (code->pad == RZV2H_IVC_SUBDEV_SOURCE_PAD) {
> +               if (code->index)
> +                       return -EINVAL;
> +
> +               fmt = v4l2_subdev_state_get_format(state,
> +                                                  RZV2H_IVC_SUBDEV_SINK_PAD);
> +               code->code = rzv2h_ivc_get_mbus_output_from_input(fmt->code);
> +
> +               return 0;
> +       }
> +
> +       if (code->index >= ARRAY_SIZE(rzv2h_ivc_formats) *
> +                                     RZV2H_IVC_N_INPUTS_PER_OUTPUT)
> +               return -EINVAL;
> +
> +       order_index = code->index / RZV2H_IVC_N_INPUTS_PER_OUTPUT;
> +       index = code->index % RZV2H_IVC_N_INPUTS_PER_OUTPUT;
> +
> +       code->code = rzv2h_ivc_formats[order_index].inputs[index];
> +
> +       return 0;
> +}
> +
> +static int rzv2h_ivc_enum_frame_size(struct v4l2_subdev *sd,
> +                                    struct v4l2_subdev_state *state,
> +                                    struct v4l2_subdev_frame_size_enum *fse)
> +{
> +       const struct v4l2_mbus_framefmt *fmt;
> +
> +       if (fse->index > 0)
> +               return -EINVAL;
> +
> +       if (fse->pad == RZV2H_IVC_SUBDEV_SOURCE_PAD) {
> +               fmt = v4l2_subdev_state_get_format(state,
> +                                                  RZV2H_IVC_SUBDEV_SINK_PAD);
> +
> +               if (fse->code != rzv2h_ivc_get_mbus_output_from_input(fmt->code))
> +                       return -EINVAL;
> +
> +               fse->min_width = fmt->width;
> +               fse->max_width = fmt->width;
> +               fse->min_height = fmt->height;
> +               fse->max_height = fmt->height;
> +
> +               return 0;
> +       }
> +
> +       if (!rzv2h_ivc_get_mbus_output_from_input(fse->code))
> +               return -EINVAL;
> +
> +       fse->min_width = RZV2H_IVC_MIN_WIDTH;
> +       fse->max_width = RZV2H_IVC_MAX_WIDTH;
> +       fse->min_height = RZV2H_IVC_MIN_HEIGHT;
> +       fse->max_height = RZV2H_IVC_MAX_HEIGHT;
> +
> +       return 0;
> +}
> +
> +static int rzv2h_ivc_set_fmt(struct v4l2_subdev *sd,
> +                            struct v4l2_subdev_state *state,
> +                            struct v4l2_subdev_format *format)
> +{
> +       struct v4l2_mbus_framefmt *fmt = &format->format;
> +       struct v4l2_mbus_framefmt *src_fmt, *sink_fmt;
> +
> +       if (format->pad == RZV2H_IVC_SUBDEV_SOURCE_PAD)
> +               return v4l2_subdev_get_fmt(sd, state, format);
> +
> +       sink_fmt = v4l2_subdev_state_get_format(state,
> +                                               RZV2H_IVC_SUBDEV_SINK_PAD);
> +
> +       sink_fmt->code = rzv2h_ivc_get_mbus_output_from_input(fmt->code) ?
> +                        fmt->code : rzv2h_ivc_formats[0].inputs[0];
> +
> +       sink_fmt->width = clamp(fmt->width, RZV2H_IVC_MIN_WIDTH,
> +                               RZV2H_IVC_MAX_WIDTH);
> +       sink_fmt->height = clamp(fmt->height, RZV2H_IVC_MIN_HEIGHT,
> +                                RZV2H_IVC_MAX_HEIGHT);
> +
> +       *fmt = *sink_fmt;
> +
> +       src_fmt = v4l2_subdev_state_get_format(state,
> +                                              RZV2H_IVC_SUBDEV_SOURCE_PAD);
> +       *src_fmt = *sink_fmt;
> +       src_fmt->code = rzv2h_ivc_get_mbus_output_from_input(sink_fmt->code);
> +
> +       return 0;
> +}
> +
> +static int rzv2h_ivc_enable_streams(struct v4l2_subdev *sd,
> +                                   struct v4l2_subdev_state *state, u32 pad,
> +                                   u64 streams_mask)
> +{
> +       /*
> +        * We have a single source pad, which has a single stream. V4L2 core has
> +        * already validated those things. The actual power-on and programming
> +        * of registers will be done through the video device's .vidioc_streamon
> +        * so there's nothing to actually do here...
> +        */
> +
> +       return 0;
> +}
> +
> +static int rzv2h_ivc_disable_streams(struct v4l2_subdev *sd,
> +                                    struct v4l2_subdev_state *state, u32 pad,
> +                                    u64 streams_mask)
> +{
> +       return 0;
> +}
> +
> +static const struct v4l2_subdev_pad_ops rzv2h_ivc_pad_ops = {
> +       .enum_mbus_code         = rzv2h_ivc_enum_mbus_code,
> +       .enum_frame_size        = rzv2h_ivc_enum_frame_size,
> +       .get_fmt                = v4l2_subdev_get_fmt,
> +       .set_fmt                = rzv2h_ivc_set_fmt,
> +       .enable_streams         = rzv2h_ivc_enable_streams,
> +       .disable_streams        = rzv2h_ivc_disable_streams,
> +};
> +
> +static const struct v4l2_subdev_core_ops rzv2h_ivc_core_ops = {
> +       .subscribe_event = v4l2_ctrl_subdev_subscribe_event,
> +       .unsubscribe_event = v4l2_event_subdev_unsubscribe,
> +};
> +
> +static const struct v4l2_subdev_ops rzv2h_ivc_subdev_ops = {
> +       .core   = &rzv2h_ivc_core_ops,
> +       .pad    = &rzv2h_ivc_pad_ops,
> +};
> +
> +static int rzv2h_ivc_init_state(struct v4l2_subdev *sd,
> +                               struct v4l2_subdev_state *state)
> +{
> +       struct v4l2_mbus_framefmt *sink_fmt, *src_fmt;
> +
> +       sink_fmt = v4l2_subdev_state_get_format(state,
> +                                               RZV2H_IVC_SUBDEV_SINK_PAD);
> +       sink_fmt->width = RZV2H_IVC_DEFAULT_WIDTH;
> +       sink_fmt->height = RZV2H_IVC_DEFAULT_HEIGHT;
> +       sink_fmt->field = V4L2_FIELD_NONE;
> +       sink_fmt->code = MEDIA_BUS_FMT_SRGGB16_1X16;
> +       sink_fmt->colorspace = V4L2_COLORSPACE_RAW;
> +       sink_fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(sink_fmt->colorspace);
> +       sink_fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(sink_fmt->colorspace);
> +       sink_fmt->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(
> +               true, sink_fmt->colorspace, sink_fmt->ycbcr_enc);
> +
> +       src_fmt = v4l2_subdev_state_get_format(state,
> +                                              RZV2H_IVC_SUBDEV_SOURCE_PAD);
> +
> +       *src_fmt = *sink_fmt;
> +       src_fmt->code = MEDIA_BUS_FMT_SRGGB20_1X20;
> +
> +       return 0;
> +}
> +
> +static int rzv2h_ivc_registered(struct v4l2_subdev *sd)
> +{
> +       struct rzv2h_ivc *ivc = container_of(sd, struct rzv2h_ivc, subdev.sd);
> +
> +       return rzv2h_ivc_init_vdev(ivc, sd->v4l2_dev);
> +}
> +
> +static const struct v4l2_subdev_internal_ops rzv2h_ivc_subdev_internal_ops = {
> +       .init_state = rzv2h_ivc_init_state,
> +       .registered = rzv2h_ivc_registered,
> +};
> +
> +static int rzv2h_ivc_link_validate(struct media_link *link)
> +{
> +       struct video_device *vdev =
> +               media_entity_to_video_device(link->source->entity);
> +       struct rzv2h_ivc *ivc = video_get_drvdata(vdev);
> +       struct v4l2_subdev *sd =
> +               media_entity_to_v4l2_subdev(link->sink->entity);
> +       const struct rzv2h_ivc_format *fmt;
> +       const struct v4l2_pix_format_mplane *pix;
> +       struct v4l2_subdev_state *state;
> +       struct v4l2_mbus_framefmt *mf;
> +       unsigned int i;
> +       int ret = 0;
> +
> +       state = v4l2_subdev_lock_and_get_active_state(sd);
> +       mf = v4l2_subdev_state_get_format(state, link->sink->index);
> +
> +       pix = &ivc->format.pix;
> +       fmt = ivc->format.fmt;
> +
> +       if (mf->width != pix->width || mf->height != pix->height) {
> +               dev_dbg(ivc->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,
> +                       mf->width, mf->height,
> +                       pix->width, pix->height);
> +               ret = -EPIPE;
> +       }
> +
> +       for (i = 0; i < ARRAY_SIZE(fmt->mbus_codes); i++)
> +               if (mf->code == fmt->mbus_codes[i])
> +                       break;
> +
> +       if (i == ARRAY_SIZE(fmt->mbus_codes)) {
> +               dev_dbg(ivc->dev,
> +                       "link '%s':%u -> '%s':%u not valid: pixel format %p4cc cannot produce mbus_code 0x%04x\n",
> +                       link->source->entity->name, link->source->index,
> +                       link->sink->entity->name, link->sink->index,
> +                       &pix->pixelformat, mf->code);
> +               ret = -EPIPE;
> +       }
> +
> +       v4l2_subdev_unlock_state(state);
> +
> +       return ret;
> +}
> +
> +static const struct media_entity_operations rzv2h_ivc_media_ops = {
> +       .link_validate = rzv2h_ivc_link_validate,
> +};
> +
> +int rzv2h_ivc_initialise_subdevice(struct rzv2h_ivc *ivc)
> +{
> +       struct v4l2_subdev *sd;
> +       int ret;
> +
> +       /* Initialise subdevice */
> +       sd = &ivc->subdev.sd;
> +       sd->dev = ivc->dev;
> +       v4l2_subdev_init(sd, &rzv2h_ivc_subdev_ops);
> +       sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS;
> +       sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;
> +       sd->internal_ops = &rzv2h_ivc_subdev_internal_ops;
> +       sd->entity.ops = &rzv2h_ivc_media_ops;
> +
> +       ivc->subdev.pads[RZV2H_IVC_SUBDEV_SINK_PAD].flags = MEDIA_PAD_FL_SINK;
> +       ivc->subdev.pads[RZV2H_IVC_SUBDEV_SOURCE_PAD].flags = MEDIA_PAD_FL_SOURCE;
> +
> +       snprintf(sd->name, sizeof(sd->name), "rzv2h ivc block");
> +
> +       ret = media_entity_pads_init(&sd->entity, RZV2H_IVC_NUM_SUBDEV_PADS,
> +                                    ivc->subdev.pads);
> +       if (ret) {
> +               dev_err(ivc->dev, "failed to initialise media entity\n");
> +               return ret;
> +       }
> +
> +       ret = v4l2_subdev_init_finalize(sd);
> +       if (ret) {
> +               dev_err(ivc->dev, "failed to finalize subdev init\n");
> +               goto err_cleanup_subdev_entity;
> +       }
> +
> +       ret = v4l2_async_register_subdev(sd);
> +       if (ret) {
> +               dev_err(ivc->dev, "failed to register subdevice\n");
> +               goto err_cleanup_subdev;
> +       }
> +
> +       return 0;
> +
> +err_cleanup_subdev:
> +       v4l2_subdev_cleanup(sd);
> +err_cleanup_subdev_entity:
> +       media_entity_cleanup(&sd->entity);
> +
> +       return ret;
> +}
> +
> +void rzv2h_ivc_deinit_subdevice(struct rzv2h_ivc *ivc)
> +{
> +       struct v4l2_subdev *sd = &ivc->subdev.sd;
> +
> +       v4l2_subdev_cleanup(sd);
> +       media_entity_remove_links(&sd->entity);
> +       v4l2_async_unregister_subdev(sd);
> +       media_entity_cleanup(&sd->entity);
> +}
> diff --git a/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-video.c b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-video.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..005a5700b5e2351b1e7ba5d99539ce4468f3db8b
> --- /dev/null
> +++ b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-video.c
> @@ -0,0 +1,568 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Renesas RZ/V2H Input Video Control Block driver
> + *
> + * Copyright (C) 2025 Ideas on Board Oy
> + */
> +
> +#include "rzv2h-ivc.h"
> +
> +#include <linux/cleanup.h>
> +#include <linux/iopoll.h>
> +#include <linux/media-bus-format.h>
> +#include <linux/minmax.h>
> +#include <linux/mutex.h>
> +#include <linux/pm_runtime.h>
> +
> +#include <media/mipi-csi2.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-dev.h>
> +#include <media/v4l2-event.h>
> +#include <media/v4l2-fh.h>
> +#include <media/v4l2-ioctl.h>
> +#include <media/videobuf2-dma-contig.h>
> +
> +#define RZV2H_IVC_FIXED_HBLANK                 0x20
> +#define RZV2H_IVC_MIN_VBLANK(hts)              max(0x1b, 15 + (120501 / (hts)))
> +
> +struct rzv2h_ivc_buf {
> +       struct vb2_v4l2_buffer vb;
> +       struct list_head queue;
> +       dma_addr_t addr;
> +};
> +
> +#define to_rzv2h_ivc_buf(vbuf) \
> +       container_of(vbuf, struct rzv2h_ivc_buf, vb)
> +
> +static const struct rzv2h_ivc_format rzv2h_ivc_formats[] = {
> +       {
> +               .fourcc = V4L2_PIX_FMT_SBGGR8,
> +               .mbus_codes = {
> +                       MEDIA_BUS_FMT_SBGGR8_1X8,
> +               },
> +               .dtype = MIPI_CSI2_DT_RAW8,
> +       },
> +       {
> +               .fourcc = V4L2_PIX_FMT_SGBRG8,
> +               .mbus_codes = {
> +                       MEDIA_BUS_FMT_SGBRG8_1X8,
> +               },
> +               .dtype = MIPI_CSI2_DT_RAW8,
> +       },
> +       {
> +               .fourcc = V4L2_PIX_FMT_SGRBG8,
> +               .mbus_codes = {
> +                       MEDIA_BUS_FMT_SGRBG8_1X8,
> +               },
> +               .dtype = MIPI_CSI2_DT_RAW8,
> +       },
> +       {
> +               .fourcc = V4L2_PIX_FMT_SRGGB8,
> +               .mbus_codes = {
> +                       MEDIA_BUS_FMT_SRGGB8_1X8,
> +               },
> +               .dtype = MIPI_CSI2_DT_RAW8,
> +       },
> +       {
> +               .fourcc = V4L2_PIX_FMT_RAW_CRU10,
> +               .mbus_codes = {
> +                       MEDIA_BUS_FMT_SBGGR10_1X10,
> +                       MEDIA_BUS_FMT_SGBRG10_1X10,
> +                       MEDIA_BUS_FMT_SGRBG10_1X10,
> +                       MEDIA_BUS_FMT_SRGGB10_1X10
> +               },
> +               .dtype = MIPI_CSI2_DT_RAW10,
> +       },
> +       {
> +               .fourcc = V4L2_PIX_FMT_RAW_CRU12,
> +               .mbus_codes = {
> +                       MEDIA_BUS_FMT_SBGGR12_1X12,
> +                       MEDIA_BUS_FMT_SGBRG12_1X12,
> +                       MEDIA_BUS_FMT_SGRBG12_1X12,
> +                       MEDIA_BUS_FMT_SRGGB12_1X12
> +               },
> +               .dtype = MIPI_CSI2_DT_RAW12,
> +       },
> +       {
> +               .fourcc = V4L2_PIX_FMT_RAW_CRU14,
> +               .mbus_codes = {
> +                       MEDIA_BUS_FMT_SBGGR14_1X14,
> +                       MEDIA_BUS_FMT_SGBRG14_1X14,
> +                       MEDIA_BUS_FMT_SGRBG14_1X14,
> +                       MEDIA_BUS_FMT_SRGGB14_1X14
> +               },
> +               .dtype = MIPI_CSI2_DT_RAW14,
> +       },
> +       {
> +               .fourcc = V4L2_PIX_FMT_SBGGR16,
> +               .mbus_codes = {
> +                       MEDIA_BUS_FMT_SBGGR16_1X16,
> +               },
> +               .dtype = MIPI_CSI2_DT_RAW16,
> +       },
> +       {
> +               .fourcc = V4L2_PIX_FMT_SGBRG16,
> +               .mbus_codes = {
> +                       MEDIA_BUS_FMT_SGBRG16_1X16,
> +               },
> +               .dtype = MIPI_CSI2_DT_RAW16,
> +       },
> +       {
> +               .fourcc = V4L2_PIX_FMT_SGRBG16,
> +               .mbus_codes = {
> +                       MEDIA_BUS_FMT_SGRBG16_1X16,
> +               },
> +               .dtype = MIPI_CSI2_DT_RAW16,
> +       },
> +       {
> +               .fourcc = V4L2_PIX_FMT_SRGGB16,
> +               .mbus_codes = {
> +                       MEDIA_BUS_FMT_SRGGB16_1X16,
> +               },
> +               .dtype = MIPI_CSI2_DT_RAW16,
> +       },
> +};
> +
> +static void rzv2h_ivc_transfer_buffer(struct work_struct *work)
> +{
> +       struct rzv2h_ivc *ivc = container_of(work, struct rzv2h_ivc,
> +                                            buffers.work);
> +       struct rzv2h_ivc_buf *buf;
> +
> +       scoped_guard(spinlock, &ivc->buffers.lock) {
> +               if (ivc->buffers.curr) {
> +                       ivc->buffers.curr->vb.sequence = ivc->buffers.sequence++;
> +                       vb2_buffer_done(&ivc->buffers.curr->vb.vb2_buf,
> +                                       VB2_BUF_STATE_DONE);
> +                       ivc->buffers.curr = NULL;
> +               }
> +
> +               buf = list_first_entry_or_null(&ivc->buffers.queue,
> +                                              struct rzv2h_ivc_buf, queue);
> +       }
> +
> +       if (!buf)
> +               return;
> +
> +       list_del(&buf->queue);
> +
> +       ivc->buffers.curr = buf;
> +       buf->addr = vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
> +       rzv2h_ivc_write(ivc, RZV2H_IVC_REG_AXIRX_SADDL_P0, buf->addr);
> +
> +       scoped_guard(spinlock_irqsave, &ivc->spinlock) {
> +               ivc->vvalid_ifp = 2;
> +       }
> +       rzv2h_ivc_write(ivc, RZV2H_IVC_REG_FM_FRCON, 0x1);
> +}
> +
> +static int rzv2h_ivc_pipeline_started(struct media_entity *entity)
> +{
> +       struct video_device *vdev = media_entity_to_video_device(entity);
> +       struct rzv2h_ivc *ivc = video_get_drvdata(vdev);
> +
> +       guard(spinlock)(&ivc->buffers.lock);
> +
> +       if (list_empty(&ivc->buffers.queue)) {
> +               /*
> +                * The driver waits for interrupts to send a new frame and
> +                * tracks their receipt in the vvalid_ifp variable. .buf_queue()
> +                * will queue work if vvalid_ifp == 0 to trigger a new frame (an
> +                * event that normally would only occur if no buffer was ready
> +                * when the interrupt arrived). If there are no buffers in the
> +                * queue yet, we set vvalid_ifp to zero so that the next queue
> +                * will trigger the work.
> +                */
> +               scoped_guard(spinlock_irqsave, &ivc->spinlock) {
> +                       ivc->vvalid_ifp = 0;
> +               }
> +       } else {
> +               queue_work(ivc->buffers.async_wq, &ivc->buffers.work);
> +       }
> +
> +       return 0;
> +}
> +
> +static void rzv2h_ivc_pipeline_stopped(struct media_entity *entity)
> +{
> +       struct video_device *vdev = media_entity_to_video_device(entity);
> +       struct rzv2h_ivc *ivc = video_get_drvdata(vdev);
> +       u32 val = 0;
> +
> +       rzv2h_ivc_write(ivc, RZV2H_IVC_REG_FM_STOP, 0x1);
> +       readl_poll_timeout(ivc->base + RZV2H_IVC_REG_FM_STOP,
> +                          val, !val, 10 * USEC_PER_MSEC, 250 * USEC_PER_MSEC);
> +}
> +
> +static const struct media_entity_operations rzv2h_ivc_media_ops = {
> +       .pipeline_started = rzv2h_ivc_pipeline_started,
> +       .pipeline_stopped = rzv2h_ivc_pipeline_stopped,
> +};
> +
> +static int rzv2h_ivc_queue_setup(struct vb2_queue *q, unsigned int *num_buffers,
> +                                unsigned int *num_planes, unsigned int sizes[],
> +                                struct device *alloc_devs[])
> +{
> +       struct rzv2h_ivc *ivc = vb2_get_drv_priv(q);
> +
> +       if (*num_planes && *num_planes > 1)
> +               return -EINVAL;
> +
> +       if (sizes[0] && sizes[0] < ivc->format.pix.plane_fmt[0].sizeimage)
> +               return -EINVAL;
> +
> +       *num_planes = 1;
> +
> +       if (!sizes[0])
> +               sizes[0] = ivc->format.pix.plane_fmt[0].sizeimage;
> +
> +       return 0;
> +}
> +
> +static void rzv2h_ivc_buf_queue(struct vb2_buffer *vb)
> +{
> +       struct rzv2h_ivc *ivc = vb2_get_drv_priv(vb->vb2_queue);
> +       struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
> +       struct rzv2h_ivc_buf *buf = to_rzv2h_ivc_buf(vbuf);
> +
> +       scoped_guard(spinlock, &ivc->buffers.lock) {
> +               list_add_tail(&buf->queue, &ivc->buffers.queue);
> +       }
> +
> +       scoped_guard(spinlock_irqsave, &ivc->spinlock) {
> +               if (vb2_is_streaming(vb->vb2_queue) && !ivc->vvalid_ifp)
> +                       queue_work(ivc->buffers.async_wq, &ivc->buffers.work);
> +       }
> +}
> +
> +static void rzv2h_ivc_format_configure(struct rzv2h_ivc *ivc)
> +{
> +       const struct rzv2h_ivc_format *fmt = ivc->format.fmt;
> +       struct v4l2_pix_format_mplane *pix = &ivc->format.pix;
> +       unsigned int vblank;
> +       unsigned int hts;
> +
> +       /* Currently only CRU packed pixel formats are supported */
> +       rzv2h_ivc_write(ivc, RZV2H_IVC_REG_AXIRX_PXFMT,
> +                       RZV2H_IVC_INPUT_FMT_CRU_PACKED);
> +
> +       rzv2h_ivc_update_bits(ivc, RZV2H_IVC_REG_AXIRX_PXFMT,
> +                             RZV2H_IVC_PXFMT_DTYPE, fmt->dtype);
> +
> +       rzv2h_ivc_write(ivc, RZV2H_IVC_REG_AXIRX_HSIZE, pix->width);
> +       rzv2h_ivc_write(ivc, RZV2H_IVC_REG_AXIRX_VSIZE, pix->height);
> +       rzv2h_ivc_write(ivc, RZV2H_IVC_REG_AXIRX_STRD,
> +                       pix->plane_fmt[0].bytesperline);
> +
> +       /*
> +        * The ISP has minimum vertical blanking requirements that must be
> +        * adhered to by the IVC. The minimum is a function of the Iridix blocks
> +        * clocking requirements and the width of the image and horizontal
> +        * blanking, but if we assume the worst case then it boils down to the
> +        * below (plus one to the numerator to ensure the answer is rounded up)
> +        */
> +
> +       hts = pix->width + RZV2H_IVC_FIXED_HBLANK;
> +       vblank = RZV2H_IVC_MIN_VBLANK(hts);
> +
> +       rzv2h_ivc_write(ivc, RZV2H_IVC_REG_AXIRX_BLANK,
> +                       RZV2H_IVC_VBLANK(vblank));
> +}
> +
> +static void rzv2h_ivc_return_buffers(struct rzv2h_ivc *ivc,
> +                                    enum vb2_buffer_state state)
> +{
> +       struct rzv2h_ivc_buf *buf, *tmp;
> +
> +       guard(spinlock)(&ivc->buffers.lock);
> +
> +       if (ivc->buffers.curr) {
> +               vb2_buffer_done(&ivc->buffers.curr->vb.vb2_buf, state);
> +               ivc->buffers.curr = NULL;
> +       }
> +
> +       list_for_each_entry_safe(buf, tmp, &ivc->buffers.queue, queue) {
> +               list_del(&buf->queue);
> +               vb2_buffer_done(&buf->vb.vb2_buf, state);
> +       }
> +}
> +
> +static int rzv2h_ivc_start_streaming(struct vb2_queue *q, unsigned int count)
> +{
> +       struct rzv2h_ivc *ivc = vb2_get_drv_priv(q);
> +       int ret;
> +
> +       ivc->buffers.sequence = 0;
> +       ivc->vvalid_ifp = 2;
> +
> +       ret = pm_runtime_resume_and_get(ivc->dev);
> +       if (ret)
> +               goto err_return_buffers;
> +
> +       ret = video_device_pipeline_alloc_start(&ivc->vdev.dev);
> +       if (ret) {
> +               dev_err(ivc->dev, "failed to start media pipeline\n");
> +               goto err_pm_runtime_put;
> +       }
> +
> +       rzv2h_ivc_format_configure(ivc);
> +
> +       ret = video_device_pipeline_started(&ivc->vdev.dev);
> +       if (ret < 0)
> +               goto err_stop_pipeline;
> +
> +       return 0;
> +
> +err_stop_pipeline:
> +       video_device_pipeline_stop(&ivc->vdev.dev);
> +err_pm_runtime_put:
> +       pm_runtime_put(ivc->dev);
> +err_return_buffers:
> +       rzv2h_ivc_return_buffers(ivc, VB2_BUF_STATE_QUEUED);
> +
> +       return ret;
> +}
> +
> +static void rzv2h_ivc_stop_streaming(struct vb2_queue *q)
> +{
> +       struct rzv2h_ivc *ivc = vb2_get_drv_priv(q);
> +
> +       video_device_pipeline_stopped(&ivc->vdev.dev);
> +       rzv2h_ivc_return_buffers(ivc, VB2_BUF_STATE_ERROR);
> +       video_device_pipeline_stop(&ivc->vdev.dev);
> +       pm_runtime_mark_last_busy(ivc->dev);
> +       pm_runtime_put_autosuspend(ivc->dev);
> +}
> +
> +static const struct vb2_ops rzv2h_ivc_vb2_ops = {
> +       .queue_setup            = &rzv2h_ivc_queue_setup,
> +       .buf_queue              = &rzv2h_ivc_buf_queue,
> +       .wait_prepare           = vb2_ops_wait_prepare,
> +       .wait_finish            = vb2_ops_wait_finish,
> +       .start_streaming        = &rzv2h_ivc_start_streaming,
> +       .stop_streaming         = &rzv2h_ivc_stop_streaming,
> +};
> +
> +static const struct rzv2h_ivc_format *
> +rzv2h_ivc_format_from_pixelformat(u32 fourcc)
> +{
> +       for (unsigned int i = 0; i < ARRAY_SIZE(rzv2h_ivc_formats); i++)
> +               if (fourcc == rzv2h_ivc_formats[i].fourcc)
> +                       return &rzv2h_ivc_formats[i];
> +
> +       return &rzv2h_ivc_formats[0];
> +}
> +
> +static int rzv2h_ivc_enum_fmt_vid_out(struct file *file, void *fh,
> +                                     struct v4l2_fmtdesc *f)
> +{
> +       if (f->index >= ARRAY_SIZE(rzv2h_ivc_formats))
> +               return -EINVAL;
> +
> +       f->pixelformat = rzv2h_ivc_formats[f->index].fourcc;
> +       return 0;
> +}
> +
> +static int rzv2h_ivc_g_fmt_vid_out(struct file *file, void *fh,
> +                                  struct v4l2_format *f)
> +{
> +       struct rzv2h_ivc *ivc = video_drvdata(file);
> +
> +       f->fmt.pix_mp = ivc->format.pix;
> +
> +       return 0;
> +}
> +
> +static void rzv2h_ivc_try_fmt(struct v4l2_pix_format_mplane *pix,
> +                             const struct rzv2h_ivc_format *fmt)
> +{
> +       pix->pixelformat = fmt->fourcc;
> +
> +       pix->width = clamp(pix->width, RZV2H_IVC_MIN_WIDTH,
> +                          RZV2H_IVC_MAX_WIDTH);
> +       pix->height = clamp(pix->height, RZV2H_IVC_MIN_HEIGHT,
> +                           RZV2H_IVC_MAX_HEIGHT);
> +
> +       pix->field = V4L2_FIELD_NONE;
> +       pix->colorspace = V4L2_COLORSPACE_RAW;
> +       pix->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(pix->colorspace);
> +       pix->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true,
> +                                                         pix->colorspace,
> +                                                         pix->ycbcr_enc);
> +
> +       v4l2_fill_pixfmt_mp(pix, pix->pixelformat, pix->width, pix->height);
> +}
> +
> +static void rzv2h_ivc_set_format(struct rzv2h_ivc *ivc,
> +                                struct v4l2_pix_format_mplane *pix)
> +{
> +       const struct rzv2h_ivc_format *fmt;
> +
> +       fmt = rzv2h_ivc_format_from_pixelformat(pix->pixelformat);
> +
> +       rzv2h_ivc_try_fmt(pix, fmt);
> +       ivc->format.pix = *pix;
> +       ivc->format.fmt = fmt;
> +}
> +
> +static int rzv2h_ivc_s_fmt_vid_out(struct file *file, void *fh,
> +                                  struct v4l2_format *f)
> +{
> +       struct rzv2h_ivc *ivc = video_drvdata(file);
> +       struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;
> +
> +       if (vb2_is_busy(&ivc->vdev.vb2q))
> +               return -EBUSY;
> +
> +       rzv2h_ivc_set_format(ivc, pix);
> +
> +       return 0;
> +}
> +
> +static int rzv2h_ivc_try_fmt_vid_out(struct file *file, void *fh,
> +                                    struct v4l2_format *f)
> +{
> +       const struct rzv2h_ivc_format *fmt;
> +
> +       fmt = rzv2h_ivc_format_from_pixelformat(f->fmt.pix.pixelformat);
> +       rzv2h_ivc_try_fmt(&f->fmt.pix_mp, fmt);
> +
> +       return 0;
> +}
> +
> +static int rzv2h_ivc_querycap(struct file *file, void *fh,
> +                             struct v4l2_capability *cap)
> +{
> +       strscpy(cap->driver, "rzv2h-ivc", sizeof(cap->driver));
> +       strscpy(cap->card, "Renesas Input Video Control", sizeof(cap->card));
> +
> +       return 0;
> +}
> +
> +static const struct v4l2_ioctl_ops rzv2h_ivc_v4l2_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_enum_fmt_vid_out = rzv2h_ivc_enum_fmt_vid_out,
> +       .vidioc_g_fmt_vid_out_mplane = rzv2h_ivc_g_fmt_vid_out,
> +       .vidioc_s_fmt_vid_out_mplane = rzv2h_ivc_s_fmt_vid_out,
> +       .vidioc_try_fmt_vid_out_mplane = rzv2h_ivc_try_fmt_vid_out,
> +       .vidioc_querycap = rzv2h_ivc_querycap,
> +       .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
> +       .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
> +};
> +
> +static const struct v4l2_file_operations rzv2h_ivc_v4l2_fops = {
> +       .owner = THIS_MODULE,
> +       .unlocked_ioctl = video_ioctl2,
> +       .open = v4l2_fh_open,
> +       .release = vb2_fop_release,
> +       .poll = vb2_fop_poll,
> +       .mmap = vb2_fop_mmap,
> +};
> +
> +int rzv2h_ivc_init_vdev(struct rzv2h_ivc *ivc, struct v4l2_device *v4l2_dev)
> +{
> +       struct v4l2_pix_format_mplane pix = { };
> +       struct video_device *vdev;
> +       struct vb2_queue *vb2q;
> +       int ret;
> +
> +       spin_lock_init(&ivc->buffers.lock);
> +       INIT_LIST_HEAD(&ivc->buffers.queue);
> +       INIT_WORK(&ivc->buffers.work, rzv2h_ivc_transfer_buffer);
> +
> +       ivc->buffers.async_wq = alloc_workqueue("rzv2h-ivc", 0, 0);
> +       if (!ivc->buffers.async_wq)
> +               return -EINVAL;
> +
> +       /* Initialise vb2 queue */
> +       vb2q = &ivc->vdev.vb2q;
> +       vb2q->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
> +       vb2q->io_modes = VB2_MMAP | VB2_DMABUF;
> +       vb2q->drv_priv = ivc;
> +       vb2q->mem_ops = &vb2_dma_contig_memops;
> +       vb2q->ops = &rzv2h_ivc_vb2_ops;
> +       vb2q->buf_struct_size = sizeof(struct rzv2h_ivc_buf);
> +       vb2q->min_queued_buffers = 0;
> +       vb2q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
> +       vb2q->lock = &ivc->lock;
> +       vb2q->dev = ivc->dev;
> +
> +       ret = vb2_queue_init(vb2q);
> +       if (ret) {
> +               dev_err(ivc->dev, "vb2 queue init failed\n");
> +               goto err_destroy_workqueue;
> +       }
> +
> +       /* Initialise Video Device */
> +       vdev = &ivc->vdev.dev;
> +       strscpy(vdev->name, "rzv2h-ivc", sizeof(vdev->name));
> +       vdev->release = video_device_release_empty;
> +       vdev->fops = &rzv2h_ivc_v4l2_fops;
> +       vdev->ioctl_ops = &rzv2h_ivc_v4l2_ioctl_ops;
> +       vdev->lock = &ivc->lock;
> +       vdev->v4l2_dev = v4l2_dev;
> +       vdev->queue = vb2q;
> +       vdev->device_caps = V4L2_CAP_VIDEO_OUTPUT_MPLANE | V4L2_CAP_STREAMING;
> +       vdev->vfl_dir = VFL_DIR_TX;
> +       video_set_drvdata(vdev, ivc);
> +
> +       pix.pixelformat = V4L2_PIX_FMT_SRGGB16;
> +       pix.width = RZV2H_IVC_DEFAULT_WIDTH;
> +       pix.height = RZV2H_IVC_DEFAULT_HEIGHT;
> +       rzv2h_ivc_set_format(ivc, &pix);
> +
> +       ivc->vdev.pad.flags = MEDIA_PAD_FL_SOURCE;
> +       ivc->vdev.dev.entity.ops = &rzv2h_ivc_media_ops;
> +       ret = media_entity_pads_init(&ivc->vdev.dev.entity, 1, &ivc->vdev.pad);
> +       if (ret)
> +               goto err_release_vb2q;
> +
> +       ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
> +       if (ret) {
> +               dev_err(ivc->dev, "failed to register IVC video device\n");
> +               goto err_cleanup_vdev_entity;
> +       }
> +
> +       ret = media_create_pad_link(&vdev->entity, 0, &ivc->subdev.sd.entity,
> +                                   RZV2H_IVC_SUBDEV_SINK_PAD,
> +                                   MEDIA_LNK_FL_ENABLED |
> +                                   MEDIA_LNK_FL_IMMUTABLE);
> +       if (ret) {
> +               dev_err(ivc->dev, "failed to create media link\n");
> +               goto err_unregister_vdev;
> +       }
> +
> +       return 0;
> +
> +err_unregister_vdev:
> +       video_unregister_device(vdev);
> +err_cleanup_vdev_entity:
> +       media_entity_cleanup(&vdev->entity);
> +err_release_vb2q:
> +       vb2_queue_release(vb2q);
> +err_destroy_workqueue:
> +       destroy_workqueue(ivc->buffers.async_wq);
> +
> +       return ret;
> +}
> +
> +void rzv2h_deinit_video_dev_and_queue(struct rzv2h_ivc *ivc)
> +{
> +       struct video_device *vdev = &ivc->vdev.dev;
> +       struct vb2_queue *vb2q = &ivc->vdev.vb2q;
> +
> +       if (!ivc->sched)
> +               return;
> +
> +       vb2_video_unregister_device(vdev);
> +       media_entity_cleanup(&vdev->entity);
> +       vb2_queue_release(vb2q);
> +}
> diff --git a/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc.h b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc.h
> new file mode 100644
> index 0000000000000000000000000000000000000000..709c6a9398fe2484c2acb03d443d58ea4e153a66
> --- /dev/null
> +++ b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc.h
> @@ -0,0 +1,131 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Renesas RZ/V2H Input Video Control Block driver
> + *
> + * Copyright (C) 2025 Ideas on Board Oy
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/list.h>
> +#include <linux/mutex.h>
> +#include <linux/reset.h>
> +#include <linux/spinlock.h>
> +#include <linux/types.h>
> +#include <linux/videodev2.h>
> +#include <linux/workqueue.h>
> +
> +#include <media/media-entity.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-subdev.h>
> +#include <media/videobuf2-core.h>
> +#include <media/videobuf2-v4l2.h>
> +
> +#define RZV2H_IVC_REG_AXIRX_PLNUM                      0x0000
> +#define RZV2H_IVC_ONE_EXPOSURE                         0x00
> +#define RZV2H_IVC_TWO_EXPOSURE                         0x01
> +#define RZV2H_IVC_REG_AXIRX_PXFMT                      0x0004
> +#define RZV2H_IVC_INPUT_FMT_MIPI                       (0 << 16)
> +#define RZV2H_IVC_INPUT_FMT_CRU_PACKED                 (1 << 16)
> +#define RZV2H_IVC_PXFMT_DTYPE                          GENMASK(7, 0)
> +#define RZV2H_IVC_REG_AXIRX_SADDL_P0                   0x0010
> +#define RZV2H_IVC_REG_AXIRX_SADDH_P0                   0x0014
> +#define RZV2H_IVC_REG_AXIRX_SADDL_P1                   0x0018
> +#define RZV2H_IVC_REG_AXIRX_SADDH_P1                   0x001c
> +#define RZV2H_IVC_REG_AXIRX_HSIZE                      0x0020
> +#define RZV2H_IVC_REG_AXIRX_VSIZE                      0x0024
> +#define RZV2H_IVC_REG_AXIRX_BLANK                      0x0028
> +#define RZV2H_IVC_VBLANK(x)                            ((x) << 16)
> +#define RZV2H_IVC_REG_AXIRX_STRD                       0x0030
> +#define RZV2H_IVC_REG_AXIRX_ISSU                       0x0040
> +#define RZV2H_IVC_REG_AXIRX_ERACT                      0x0048
> +#define RZV2H_IVC_REG_FM_CONTEXT                       0x0100
> +#define RZV2H_IVC_SOFTWARE_CFG                         0x00
> +#define RZV2H_IVC_SINGLE_CONTEXT_SW_HW_CFG             BIT(0)
> +#define RZV2H_IVC_MULTI_CONTEXT_SW_HW_CFG              BIT(1)
> +#define RZV2H_IVC_REG_FM_MCON                          0x0104
> +#define RZV2H_IVC_REG_FM_FRCON                         0x0108
> +#define RZV2H_IVC_REG_FM_STOP                          0x010c
> +#define RZV2H_IVC_REG_FM_INT_EN                                0x0120
> +#define RZV2H_IVC_VVAL_IFPE                            BIT(0)
> +#define RZV2H_IVC_REG_FM_INT_STA                       0x0124
> +#define RZV2H_IVC_REG_AXIRX_FIFOCAP0                   0x0208
> +#define RZV2H_IVC_REG_CORE_CAPCON                      0x020c
> +#define RZV2H_IVC_REG_CORE_FIFOCAP0                    0x0228
> +#define RZV2H_IVC_REG_CORE_FIFOCAP1                    0x022c
> +
> +#define RZV2H_IVC_MIN_WIDTH                            640
> +#define RZV2H_IVC_MAX_WIDTH                            4096
> +#define RZV2H_IVC_MIN_HEIGHT                           480
> +#define RZV2H_IVC_MAX_HEIGHT                           4096
> +#define RZV2H_IVC_DEFAULT_WIDTH                                1920
> +#define RZV2H_IVC_DEFAULT_HEIGHT                       1080
> +
> +#define RZV2H_IVC_NUM_HW_RESOURCES                     3
> +
> +struct device;
> +
> +enum rzv2h_ivc_subdev_pads {
> +       RZV2H_IVC_SUBDEV_SINK_PAD,
> +       RZV2H_IVC_SUBDEV_SOURCE_PAD,
> +       RZV2H_IVC_NUM_SUBDEV_PADS
> +};
> +
> +struct rzv2h_ivc_format {
> +       u32 fourcc;
> +       /*
> +        * The CRU packed pixel formats are bayer-order agnostic, so each could
> +        * support any one of the 4 possible media bus formats.
> +        */
> +       u32 mbus_codes[4];
> +       u8 dtype;
> +};
> +
> +struct rzv2h_ivc {
> +       struct device *dev;
> +       void __iomem *base;
> +       struct clk_bulk_data clks[RZV2H_IVC_NUM_HW_RESOURCES];
> +       struct reset_control_bulk_data resets[RZV2H_IVC_NUM_HW_RESOURCES];
> +       int irqnum;
> +       u8 vvalid_ifp;
> +
> +       struct {
> +               struct video_device dev;
> +               struct vb2_queue vb2q;
> +               struct media_pad pad;
> +       } vdev;
> +
> +       struct {
> +               struct v4l2_subdev sd;
> +               struct media_pad pads[RZV2H_IVC_NUM_SUBDEV_PADS];
> +       } subdev;
> +
> +       struct {
> +               /* Spinlock to guard buffer queue */
> +               spinlock_t lock;
> +               struct workqueue_struct *async_wq;
> +               struct work_struct work;
> +               struct list_head queue;
> +               struct rzv2h_ivc_buf *curr;
> +               unsigned int sequence;
> +       } buffers;
> +
> +       struct media_job_scheduler *sched;
This is unused, we are just checking if  !=NULL in deinit.

I gave these patch a try on next-20250724.

ISP Probe:
[   11.600383] mali-c55 16080000.isp: Detected Mali-C55 ISP 9000043.31032022.0
[   11.622062] mali-c55 16080000.isp: Runtime PM usage count underflow!

Logs from IVC:
root@rzv2h-evk:~/c55# media-ctl -p
Media controller API version 6.16.0

Media device information
------------------------
driver          mali-c55
model           ARM Mali-C55 ISP
serial
bus info        platform:16080000.isp
hw revision     0x1d982d6
driver version  6.16.0

Device topology
- entity 1: mali-c55 tpg (1 pad, 1 link, 0 routes)
            type V4L2 subdev subtype Sensor flags 0
            device node name /dev/v4l-subdev0
        pad0: SOURCE
                [stream:0 fmt:SRGGB20_1X20/1920x1080 field:none
colorspace:raw xfer:none ycbcr:601 quantization:lim-range]
                -> "mali-c55 isp":0 []

- entity 3: mali-c55 isp (5 pads, 6 links, 0 routes)
            type V4L2 subdev subtype Unknown flags 0
            device node name /dev/v4l-subdev1
        pad0: SINK,MUST_CONNECT
                [stream:0 fmt:SGRBG20_1X20/2304x1296 field:none
colorspace:raw xfer:none ycbcr:601 quantization:lim-range
                 crop:(0,0)/2304x1296]
                <- "mali-c55 tpg":0 []
                <- "rzv2h ivc block":1 [ENABLED]
        pad1: SOURCE
                [stream:0 fmt:RGB121212_1X36/2304x1296 field:none
colorspace:srgb xfer:none ycbcr:601 quantization:lim-range]
                -> "mali-c55 resizer fr":0 [ENABLED,IMMUTABLE]
        pad2: SOURCE
                [stream:0 fmt:SGRBG20_1X20/2304x1296 field:none
colorspace:raw xfer:none ycbcr:601 quantization:lim-range]
                -> "mali-c55 resizer fr":2 [ENABLED,IMMUTABLE]
        pad3: SOURCE
                [stream:0 fmt:unknown/0x0 field:none]
                -> "mali-c55 3a stats":0 []
        pad4: SINK
                [stream:0 fmt:unknown/0x0 field:none]
                <- "mali-c55 3a params":0 []

- entity 9: mali-c55 resizer fr (3 pads, 3 links, 0 routes)
            type V4L2 subdev subtype Unknown flags 0
            device node name /dev/v4l-subdev2
        pad0: SINK
                [stream:0 fmt:RGB121212_1X36/2304x1296 field:none
colorspace:srgb xfer:srgb ycbcr:601 quantization:lim-range
                 crop:(0,0)/2304x1296
                 compose:(0,0)/2304x1296]
                <- "mali-c55 isp":1 [ENABLED,IMMUTABLE]
        pad1: SOURCE
                [stream:0 fmt:RGB121212_1X36/2304x1296 field:none
colorspace:srgb xfer:srgb ycbcr:601 quantization:lim-range]
                -> "mali-c55 fr":0 [ENABLED]
        pad2: SINK
                <- "mali-c55 isp":2 [ENABLED,IMMUTABLE]

- entity 13: mali-c55 fr (1 pad, 1 link)
             type Node subtype V4L flags 0
             device node name /dev/video0
        pad0: SINK
                <- "mali-c55 resizer fr":1 [ENABLED]

- entity 17: mali-c55 3a params (1 pad, 1 link)
             type Node subtype V4L flags 0
             device node name /dev/video1
        pad0: SOURCE
                -> "mali-c55 isp":4 []

- entity 21: mali-c55 3a stats (1 pad, 1 link)
             type Node subtype V4L flags 0
             device node name /dev/video2
        pad0: SINK
                <- "mali-c55 isp":3 []

- entity 37: rzv2h ivc block (2 pads, 2 links, 0 routes)
             type V4L2 subdev subtype Unknown flags 0
             device node name /dev/v4l-subdev3
        pad0: SINK
                [stream:0 fmt:SGRBG10_1X10/2304x1296 field:none
colorspace:raw xfer:none ycbcr:601 quantization:full-range]
                <- "rzv2h-ivc":0 [ENABLED,IMMUTABLE]
        pad1: SOURCE
                [stream:0 fmt:SGRBG20_1X20/2304x1296 field:none
colorspace:raw xfer:none ycbcr:601 quantization:full-range]
                -> "mali-c55 isp":0 [ENABLED]

- entity 40: rzv2h-ivc (1 pad, 1 link)
             type Node subtype V4L flags 0
             device node name /dev/video3
        pad0: SOURCE
                -> "rzv2h ivc block":0 [ENABLED,IMMUTABLE]

root@rzv2h-evk:~/c55#
root@rzv2h-evk:~# v4l2-ctl -d3 --stream-out-mmap
--stream-from=/root/c55/frame-15.bin --stream-loop
>>>> VIDIOC_STREAMON returned -1 (Input/output error)
root@rzv2h-evk:~#

Logs from ISP:
root@rzv2h-evk:~/c55# ./isp.sh
Device /dev/video0 opened.
Device `ARM Mali-C55 ISP' on `platform:16080000.isp' (driver
'mali-c55') supports video, capture, with mplanes.
Video format set: RGB565 (50424752) 2304x1296 field none, 1 planes:
 * Stride 4608, buffer size 5971968
Video format: RGB565 (50424752) 2304x1296 field none, 1 planes:
 * Stride 4608, buffer size 5971968
8 buffers requested.
length: 1 offset: 4017363672 timestamp type/source: mono/EoF
Buffer 0/0 mapped at address 0xffff81f2e000.
length: 1 offset: 4017363672 timestamp type/source: mono/EoF
Buffer 1/0 mapped at address 0xffff8197c000.
length: 1 offset: 4017363672 timestamp type/source: mono/EoF
Buffer 2/0 mapped at address 0xffff813ca000.
length: 1 offset: 4017363672 timestamp type/source: mono/EoF
Buffer 3/0 mapped at address 0xffff80e18000.
length: 1 offset: 4017363672 timestamp type/source: mono/EoF
Buffer 4/0 mapped at address 0xffff80866000.
length: 1 offset: 4017363672 timestamp type/source: mono/EoF
Buffer 5/0 mapped at address 0xffff802b4000.
length: 1 offset: 4017363672 timestamp type/source: mono/EoF
Buffer 6/0 mapped at address 0xffff7fd02000.
length: 1 offset: 4017363672 timestamp type/source: mono/EoF
Buffer 7/0 mapped at address 0xffff7f750000.
[   92.647719] kauditd_printk_skb: 8 callbacks suppressed
[   92.647734] audit: type=1006 audit(1753371566.385:25): pid=407
uid=0 old-auid=4294967295 auid=0 tty=(none) old-ses=4294967295 ses=4
res=1
[   92.665263] audit: type=1300 audit(1753371566.385:25):
arch=c00000b7 syscall=64 success=yes exit=1 a0=7 a1=ffffc4ff5740 a2=1
a3=1 items=0 ppid=1 pid=407 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0
egid=0 sgid=0 fsgid=0 tty=(none) ses=4 comm="sshd"
exe="/usr/sbin/sshd" key=(null)
[   92.689604] audit: type=1327 audit(1753371566.385:25):
proctitle=737368643A20726F6F74205B707269765D

[  100.932191] rz-dmac 11400000.dma-controller: dma_sync_wait: timeout!
[  100.938566] mali-c55 16080000.isp: Failed to DMA xfer ISP config
[  100.944702] mali-c55 16080000.isp: failed to write ISP config
[  100.950562] mali-c55 16080000.isp: Failed to start ISP

Cheers,
Prabhakar

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

* Re: [PATCH v4 2/3] media: platform: Add Renesas Input Video Control block driver
  2025-07-24 16:52   ` Lad, Prabhakar
@ 2025-07-24 18:04     ` Dan Scally
  2025-07-24 20:18       ` Lad, Prabhakar
  0 siblings, 1 reply; 12+ messages in thread
From: Dan Scally @ 2025-07-24 18:04 UTC (permalink / raw)
  To: Lad, Prabhakar
  Cc: linux-media, devicetree, linux-renesas-soc, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Geert Uytterhoeven,
	Magnus Damm, Philipp Zabel, jacopo.mondi, biju.das.jz,
	laurent.pinchart

Hi Prabhakar - thanks for the review

On 24/07/2025 17:52, Lad, Prabhakar wrote:
> Hi Daniel,
>
> Thank you for the patch.
>
> On Mon, Jul 14, 2025 at 4:25 PM Daniel Scally
> <dan.scally@ideasonboard.com> wrote:
>> Add a driver for the Input Video Control block in an RZ/V2H SoC which
>> feeds data into the Arm Mali-C55 ISP.
>>
> s/V2H/V2HP everywhere.
Ack!
>> Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com>
>> ---
>> Changes in v5:
>>
>>          - Fixed .enum_frame_sizes() to properly check that the
>>            given mbus_code matches the source pads format.
>>          - Tidy up extra space in Kconfig
>>          - Revise Kconfig option message
>>          - Don't mark functions inline
>>          - Fixup misleading comment
>>          - select CONFIG_PM
>>          - Use the new pm_sleep_ptr() functionality
>>          - Minor formatting
>>
>> Changes in v4:
>>
>>          - Update the compatible to renesas,r9a09g057-ivc
>>          - Dropped the media jobs / scheduler functionality, and re
>>            worked the driver to have its own workqueue pushing frames
>>          - Fix .enum_mbus_code() to return 20-bit output for source
>>            pad.
>>          - Fix some alignment issues
>>          - Make the forwarding of sink to source pad format a more
>>            explicit operation.
>>          - Rename rzv2h_initialise_video_device_and_queue()
>>          - Reversed order of v4l2_subdev_init_finalize() and
>>            v4l2_async_register_subdev() to make sure everything is
>>            finished initialising before registering the subdev.
>>          - Change function to MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER
>>          - Use a parametised macro for min vblank
>>          - Minor formatting
>>          - Use the DEFAULT macros for quantization / ycbcr_enc values
>>          - Switch to using the mplane API
>>          - Dropped select RESET_CONTROLLER
>>          - Used the new helpers for starting a media pipeline
>>          - Switch from threaded irq to normal with driver workqueue
>>            and revised startup routine
>>
>> Changes in v3:
>>
>>          - Account for the renamed CRU pixel formats
>>
>> Changes in v2:
>>
>>          - Added selects and depends statements to Kconfig entry
>>          - Fixed copyright year
>>          - Stopped including in .c files headers already included in .h
>>          - Fixed uninitialized variable in iterator
>>          - Only check vvalid member in interrupt function and wait
>>            unconditionally elsewhere
>>          - __maybe_unused for the PM ops
>>          - Initialise the subdevice after setting up PM
>>          - Fixed the remove function for the driver to actually do
>>            something.
>>          - Some minor formatting changes
>>          - Fixed the quantization member for the format
>>          - Changes accounting for the v2 of the media jobs framework
>>          - Change min_queued_buffers to 0
>> ---
>>   drivers/media/platform/renesas/Kconfig             |   1 +
>>   drivers/media/platform/renesas/Makefile            |   1 +
>>   drivers/media/platform/renesas/rzv2h-ivc/Kconfig   |  18 +
>>   drivers/media/platform/renesas/rzv2h-ivc/Makefile  |   5 +
>>   .../platform/renesas/rzv2h-ivc/rzv2h-ivc-dev.c     | 229 +++++++++
>>   .../platform/renesas/rzv2h-ivc/rzv2h-ivc-subdev.c  | 376 ++++++++++++++
>>   .../platform/renesas/rzv2h-ivc/rzv2h-ivc-video.c   | 568 +++++++++++++++++++++
>>   .../media/platform/renesas/rzv2h-ivc/rzv2h-ivc.h   | 131 +++++
>>   8 files changed, 1329 insertions(+)
>>
>> diff --git a/drivers/media/platform/renesas/Kconfig b/drivers/media/platform/renesas/Kconfig
>> index 27a54fa7908384f2e8200f0f7283a82b0ae8435c..bd8247c0b8aa734d2b412438e694f3908d910b25 100644
>> --- a/drivers/media/platform/renesas/Kconfig
>> +++ b/drivers/media/platform/renesas/Kconfig
>> @@ -42,6 +42,7 @@ config VIDEO_SH_VOU
>>   source "drivers/media/platform/renesas/rcar-isp/Kconfig"
>>   source "drivers/media/platform/renesas/rcar-vin/Kconfig"
>>   source "drivers/media/platform/renesas/rzg2l-cru/Kconfig"
>> +source "drivers/media/platform/renesas/rzv2h-ivc/Kconfig"
>>
>>   # Mem2mem drivers
>>
>> diff --git a/drivers/media/platform/renesas/Makefile b/drivers/media/platform/renesas/Makefile
>> index 1127259c09d6a51b70803e76c495918e06777f67..b6b4abf01db246aaf8269b8027efee9b0b32083a 100644
>> --- a/drivers/media/platform/renesas/Makefile
>> +++ b/drivers/media/platform/renesas/Makefile
>> @@ -6,6 +6,7 @@
>>   obj-y += rcar-isp/
>>   obj-y += rcar-vin/
>>   obj-y += rzg2l-cru/
>> +obj-y += rzv2h-ivc/
>>   obj-y += vsp1/
>>
>>   obj-$(CONFIG_VIDEO_RCAR_CSI2) += rcar-csi2.o
>> diff --git a/drivers/media/platform/renesas/rzv2h-ivc/Kconfig b/drivers/media/platform/renesas/rzv2h-ivc/Kconfig
>> new file mode 100644
>> index 0000000000000000000000000000000000000000..5a4a3c052a3ae0f242e844689132d91a75b8a302
>> --- /dev/null
>> +++ b/drivers/media/platform/renesas/rzv2h-ivc/Kconfig
>> @@ -0,0 +1,18 @@
>> +# SPDX-License-Identifier: GPL-2.0-only
>> +
>> +config VIDEO_RZV2H_IVC
>> +       tristate "Renesas RZ/V2H Input Video Control block driver"
>> +       depends on V4L_PLATFORM_DRIVERS
>> +       depends on VIDEO_DEV
>> +       depends on ARCH_RENESAS || COMPILE_TEST
>> +       depends on OF
>> +       select CONFIG_PM
>> +       select VIDEOBUF2_DMA_CONTIG
>> +       select MEDIA_CONTROLLER
>> +       select VIDEO_V4L2_SUBDEV_API
>> +       help
>> +          Support for the Renesas RZ/V2H Input Video Control Block
>> +          (IVC).
>> +
>> +          To compile this driver as a module, choose M here: the
>> +          module will be called rzv2h-ivc.
>> diff --git a/drivers/media/platform/renesas/rzv2h-ivc/Makefile b/drivers/media/platform/renesas/rzv2h-ivc/Makefile
>> new file mode 100644
>> index 0000000000000000000000000000000000000000..080ee3570f09c236d87abeaea5d8dd578fefb6d3
>> --- /dev/null
>> +++ b/drivers/media/platform/renesas/rzv2h-ivc/Makefile
>> @@ -0,0 +1,5 @@
>> +# SPDX-License-Identifier: GPL-2.0
>> +
>> +rzv2h-ivc-y := rzv2h-ivc-dev.o rzv2h-ivc-subdev.o rzv2h-ivc-video.o
>> +
>> +obj-$(CONFIG_VIDEO_RZV2H_IVC) += rzv2h-ivc.o
>> diff --git a/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-dev.c b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-dev.c
>> new file mode 100644
>> index 0000000000000000000000000000000000000000..ce2e3a3af8d19900241add7d261f7a40f2551265
>> --- /dev/null
>> +++ b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-dev.c
>> @@ -0,0 +1,229 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Renesas RZ/V2H Input Video Control Block driver
>> + *
>> + * Copyright (C) 2025 Ideas on Board Oy
>> + */
>> +
>> +#include "rzv2h-ivc.h"
>> +
>> +#include <linux/device.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/io.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/pm_runtime.h>
>> +#include <linux/reset.h>
>> +
>> +void rzv2h_ivc_write(struct rzv2h_ivc *ivc, u32 addr, u32 val)
>> +{
>> +       writel(val, ivc->base + addr);
>> +}
>> +
>> +void rzv2h_ivc_update_bits(struct rzv2h_ivc *ivc, unsigned int addr,
>> +                          u32 mask, u32 val)
>> +{
>> +       u32 orig, new;
>> +
>> +       orig = readl(ivc->base + addr);
>> +
>> +       new = orig & ~mask;
>> +       new |= val & mask;
>> +
>> +       if (new != orig)
>> +               writel(new, ivc->base + addr);
>> +}
>> +
>> +static int rzv2h_ivc_get_hardware_resources(struct rzv2h_ivc *ivc,
>> +                                           struct platform_device *pdev)
>> +{
>> +       const char * const resource_names[RZV2H_IVC_NUM_HW_RESOURCES] = {
>> +               "reg",
>> +               "axi",
>> +               "isp",
>> +       };
>> +       struct resource *res;
>> +       int ret;
>> +
>> +       ivc->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
>> +       if (IS_ERR(ivc->base))
>> +               return dev_err_probe(ivc->dev, PTR_ERR(ivc->base),
>> +                                    "failed to map IO memory\n");
>> +
>> +       for (unsigned int i = 0; i < ARRAY_SIZE(resource_names); i++)
>> +               ivc->clks[i].id = resource_names[i];
>> +
>> +       ret = devm_clk_bulk_get(ivc->dev, ARRAY_SIZE(resource_names), ivc->clks);
>> +       if (ret)
>> +               return dev_err_probe(ivc->dev, ret, "failed to acquire clks\n");
>> +
>> +       for (unsigned int i = 0; i < ARRAY_SIZE(resource_names); i++)
>> +               ivc->resets[i].id = resource_names[i];
>> +
>> +       ret = devm_reset_control_bulk_get_optional_shared(
>> +               ivc->dev, ARRAY_SIZE(resource_names), ivc->resets);
>> +       if (ret)
>> +               return dev_err_probe(ivc->dev, ret, "failed to acquire resets\n");
>> +
>> +       return 0;
>> +}
>> +
>> +static void rzv2h_ivc_global_config(struct rzv2h_ivc *ivc)
>> +{
>> +       /* Currently we only support single-exposure input */
>> +       rzv2h_ivc_write(ivc, RZV2H_IVC_REG_AXIRX_PLNUM, RZV2H_IVC_ONE_EXPOSURE);
>> +
>> +       /*
>> +        * Datasheet says we should disable the interrupts before changing mode
>> +        * to avoid spurious IFP interrupt.
>> +        */
>> +       rzv2h_ivc_write(ivc, RZV2H_IVC_REG_FM_INT_EN, 0x0);
>> +
>> +       /*
>> +        * RZ/V2H documentation says software controlled single context mode is
>> +        * is not supported, and currently the driver does not support the
>> +        * multi-context mode. That being so we just set single context sw-hw
>> +        * mode.
>> +        */
>> +       rzv2h_ivc_write(ivc, RZV2H_IVC_REG_FM_CONTEXT,
>> +                       RZV2H_IVC_SINGLE_CONTEXT_SW_HW_CFG);
>> +
>> +       /*
>> +        * We enable the frame end interrupt so that we know when we should send
>> +        * follow-up frames.
>> +        */
>> +       rzv2h_ivc_write(ivc, RZV2H_IVC_REG_FM_INT_EN, RZV2H_IVC_VVAL_IFPE);
>> +}
>> +
>> +static irqreturn_t rzv2h_ivc_isr(int irq, void *context)
>> +{
>> +       struct device *dev = context;
>> +       struct rzv2h_ivc *ivc = dev_get_drvdata(dev);
>> +
>> +       guard(spinlock)(&ivc->spinlock);
>> +
>> +       if (!--ivc->vvalid_ifp)
>> +               queue_work(ivc->buffers.async_wq, &ivc->buffers.work);
>> +
>> +       return IRQ_HANDLED;
>> +}
>> +
>> +static int rzv2h_ivc_runtime_resume(struct device *dev)
>> +{
>> +       struct rzv2h_ivc *ivc = dev_get_drvdata(dev);
>> +       int ret;
>> +
>> +       ret = request_irq(ivc->irqnum, rzv2h_ivc_isr, 0, dev_driver_string(dev),
>> +                         dev);
>> +       if (ret) {
>> +               dev_err(dev, "failed to request irq\n");
>> +               return ret;
>> +       }
>> +
>> +       ret = clk_bulk_prepare_enable(ARRAY_SIZE(ivc->clks), ivc->clks);
>> +       if (ret) {
>> +               dev_err(ivc->dev, "failed to enable clocks\n");
>> +               goto err_free_irqnum;
>> +       }
>> +
>> +       ret = reset_control_bulk_deassert(ARRAY_SIZE(ivc->resets), ivc->resets);
>> +       if (ret) {
>> +               dev_err(ivc->dev, "failed to deassert resets\n");
>> +               goto err_disable_clks;
>> +       }
>> +
>> +       rzv2h_ivc_global_config(ivc);
>> +
>> +       return 0;
>> +
>> +err_disable_clks:
>> +       clk_bulk_disable_unprepare(ARRAY_SIZE(ivc->clks), ivc->clks);
>> +err_free_irqnum:
>> +       free_irq(ivc->irqnum, dev);
>> +
>> +       return ret;
>> +}
>> +
>> +static int rzv2h_ivc_runtime_suspend(struct device *dev)
>> +{
>> +       struct rzv2h_ivc *ivc = dev_get_drvdata(dev);
>> +
>> +       reset_control_bulk_assert(ARRAY_SIZE(ivc->resets), ivc->resets);
>> +       clk_bulk_disable_unprepare(ARRAY_SIZE(ivc->clks), ivc->clks);
>> +       free_irq(ivc->irqnum, dev);
>> +
>> +       return 0;
>> +}
>> +
>> +static const struct dev_pm_ops rzv2h_ivc_pm_ops = {
>> +       SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
>> +                               pm_runtime_force_resume)
>> +       RUNTIME_PM_OPS(rzv2h_ivc_runtime_suspend, rzv2h_ivc_runtime_resume,
>> +                          NULL)
>> +};
>> +
>> +static int rzv2h_ivc_probe(struct platform_device *pdev)
>> +{
>> +       struct device *dev = &pdev->dev;
>> +       struct rzv2h_ivc *ivc;
>> +       int ret;
>> +
>> +       ivc = devm_kzalloc(dev, sizeof(*ivc), GFP_KERNEL);
>> +       if (!ivc)
>> +               return -ENOMEM;
>> +
>> +       ivc->dev = dev;
>> +       platform_set_drvdata(pdev, ivc);
>> +       mutex_init(&ivc->lock);
>> +       spin_lock_init(&ivc->spinlock);
>> +
>> +       ret = rzv2h_ivc_get_hardware_resources(ivc, pdev);
>> +       if (ret)
>> +               return ret;
>> +
>> +       pm_runtime_set_autosuspend_delay(dev, 2000);
>> +       pm_runtime_use_autosuspend(dev);
>> +       pm_runtime_enable(dev);
>> +
>> +       ivc->irqnum = platform_get_irq(pdev, 0);
>> +       if (ivc->irqnum < 0) {
>> +               dev_err(dev, "failed to get interrupt\n");
> dev_err_probe()
>
>> +               return ret;
>> +       }
>> +
>> +       ret = rzv2h_ivc_initialise_subdevice(ivc);
>> +       if (ret)
>> +               return ret;
> we could drop the below statement and just do `return
> rzv2h_ivc_initialise_subdevice(ivc);`


Sure

>
>> +
>> +       return 0;
>> +}
>> +
>> +static void rzv2h_ivc_remove(struct platform_device *pdev)
>> +{
>> +       struct rzv2h_ivc *ivc = platform_get_drvdata(pdev);
>> +
>> +       rzv2h_deinit_video_dev_and_queue(ivc);
>> +       rzv2h_ivc_deinit_subdevice(ivc);
>> +       mutex_destroy(&ivc->lock);
>> +}
>> +
>> +static const struct of_device_id rzv2h_ivc_of_match[] = {
>> +       { .compatible = "renesas,r9a09g057-ivc", },
>> +       { /* Sentinel */ },
> `,` not needed.
>
>> +};
>> +MODULE_DEVICE_TABLE(of, rzv2h_ivc_of_match);
>> +
>> +static struct platform_driver rzv2h_ivc_driver = {
>> +       .driver = {
>> +               .name = "rzv2h-ivc",
>> +               .of_match_table = rzv2h_ivc_of_match,
>> +               .pm = &rzv2h_ivc_pm_ops,
>> +       },
>> +       .probe = rzv2h_ivc_probe,
>> +       .remove = rzv2h_ivc_remove,
>> +};
>> +
>> +module_platform_driver(rzv2h_ivc_driver);
>> +
>> +MODULE_AUTHOR("Daniel Scally <dan.scally@ideasonboard.com>");
>> +MODULE_DESCRIPTION("Renesas RZ/V2H Input Video Control Block driver");
>> +MODULE_LICENSE("GPL");
>> diff --git a/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-subdev.c b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-subdev.c
>> new file mode 100644
>> index 0000000000000000000000000000000000000000..eb2913153d406fbad2491bb36e1c5ea754bea6f2
>> --- /dev/null
>> +++ b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-subdev.c
>> @@ -0,0 +1,376 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Renesas RZ/V2H Input Video Control Block driver
>> + *
>> + * Copyright (C) 2025 Ideas on Board Oy
>> + */
>> +
>> +#include "rzv2h-ivc.h"
>> +
>> +#include <linux/media.h>
>> +#include <linux/media-bus-format.h>
>> +#include <linux/v4l2-mediabus.h>
>> +
>> +#include <media/v4l2-async.h>
>> +#include <media/v4l2-ctrls.h>
>> +#include <media/v4l2-dev.h>
>> +#include <media/v4l2-event.h>
>> +
>> +#define RZV2H_IVC_N_INPUTS_PER_OUTPUT          6
>> +
>> +/*
>> + * We support 8/10/12/14/16/20 bit input in any bayer order, but the output
>> + * format is fixed at 20-bits with the same order as the input.
>> + */
>> +static const struct {
>> +       u32 inputs[RZV2H_IVC_N_INPUTS_PER_OUTPUT];
>> +       u32 output;
>> +} rzv2h_ivc_formats[] = {
>> +       {
>> +               .inputs = {
>> +                       MEDIA_BUS_FMT_SBGGR8_1X8,
>> +                       MEDIA_BUS_FMT_SBGGR10_1X10,
>> +                       MEDIA_BUS_FMT_SBGGR12_1X12,
>> +                       MEDIA_BUS_FMT_SBGGR14_1X14,
>> +                       MEDIA_BUS_FMT_SBGGR16_1X16,
>> +                       MEDIA_BUS_FMT_SBGGR20_1X20,
>> +               },
>> +               .output = MEDIA_BUS_FMT_SBGGR20_1X20
>> +       },
>> +       {
>> +               .inputs = {
>> +                       MEDIA_BUS_FMT_SGBRG8_1X8,
>> +                       MEDIA_BUS_FMT_SGBRG10_1X10,
>> +                       MEDIA_BUS_FMT_SGBRG12_1X12,
>> +                       MEDIA_BUS_FMT_SGBRG14_1X14,
>> +                       MEDIA_BUS_FMT_SGBRG16_1X16,
>> +                       MEDIA_BUS_FMT_SGBRG20_1X20,
>> +               },
>> +               .output = MEDIA_BUS_FMT_SGBRG20_1X20
>> +       },
>> +       {
>> +               .inputs = {
>> +                       MEDIA_BUS_FMT_SGRBG8_1X8,
>> +                       MEDIA_BUS_FMT_SGRBG10_1X10,
>> +                       MEDIA_BUS_FMT_SGRBG12_1X12,
>> +                       MEDIA_BUS_FMT_SGRBG14_1X14,
>> +                       MEDIA_BUS_FMT_SGRBG16_1X16,
>> +                       MEDIA_BUS_FMT_SGRBG20_1X20,
>> +               },
>> +               .output = MEDIA_BUS_FMT_SGRBG20_1X20
>> +       },
>> +       {
>> +               .inputs = {
>> +                       MEDIA_BUS_FMT_SRGGB8_1X8,
>> +                       MEDIA_BUS_FMT_SRGGB10_1X10,
>> +                       MEDIA_BUS_FMT_SRGGB12_1X12,
>> +                       MEDIA_BUS_FMT_SRGGB14_1X14,
>> +                       MEDIA_BUS_FMT_SRGGB16_1X16,
>> +                       MEDIA_BUS_FMT_SRGGB20_1X20,
>> +               },
>> +               .output = MEDIA_BUS_FMT_SRGGB20_1X20
>> +       },
>> +};
>> +
>> +static u32 rzv2h_ivc_get_mbus_output_from_input(u32 mbus_code)
>> +{
>> +       unsigned int i, j;
>> +
>> +       for (i = 0; i < ARRAY_SIZE(rzv2h_ivc_formats); i++) {
>> +               for (j = 0; j < RZV2H_IVC_N_INPUTS_PER_OUTPUT; j++) {
>> +                       if (rzv2h_ivc_formats[i].inputs[j] == mbus_code)
>> +                               return rzv2h_ivc_formats[i].output;
>> +               }
>> +       }
>> +
>> +       return 0;
>> +}
>> +
>> +static int rzv2h_ivc_enum_mbus_code(struct v4l2_subdev *sd,
>> +                                   struct v4l2_subdev_state *state,
>> +                                   struct v4l2_subdev_mbus_code_enum *code)
>> +{
>> +       const struct v4l2_mbus_framefmt *fmt;
>> +       unsigned int order_index;
>> +       unsigned int index;
>> +
>> +       /*
>> +        * On the source pad, only the 20-bit format corresponding to the sink
>> +        * pad format's bayer order is supported.
>> +        */
>> +       if (code->pad == RZV2H_IVC_SUBDEV_SOURCE_PAD) {
>> +               if (code->index)
>> +                       return -EINVAL;
>> +
>> +               fmt = v4l2_subdev_state_get_format(state,
>> +                                                  RZV2H_IVC_SUBDEV_SINK_PAD);
>> +               code->code = rzv2h_ivc_get_mbus_output_from_input(fmt->code);
>> +
>> +               return 0;
>> +       }
>> +
>> +       if (code->index >= ARRAY_SIZE(rzv2h_ivc_formats) *
>> +                                     RZV2H_IVC_N_INPUTS_PER_OUTPUT)
>> +               return -EINVAL;
>> +
>> +       order_index = code->index / RZV2H_IVC_N_INPUTS_PER_OUTPUT;
>> +       index = code->index % RZV2H_IVC_N_INPUTS_PER_OUTPUT;
>> +
>> +       code->code = rzv2h_ivc_formats[order_index].inputs[index];
>> +
>> +       return 0;
>> +}
>> +
>> +static int rzv2h_ivc_enum_frame_size(struct v4l2_subdev *sd,
>> +                                    struct v4l2_subdev_state *state,
>> +                                    struct v4l2_subdev_frame_size_enum *fse)
>> +{
>> +       const struct v4l2_mbus_framefmt *fmt;
>> +
>> +       if (fse->index > 0)
>> +               return -EINVAL;
>> +
>> +       if (fse->pad == RZV2H_IVC_SUBDEV_SOURCE_PAD) {
>> +               fmt = v4l2_subdev_state_get_format(state,
>> +                                                  RZV2H_IVC_SUBDEV_SINK_PAD);
>> +
>> +               if (fse->code != rzv2h_ivc_get_mbus_output_from_input(fmt->code))
>> +                       return -EINVAL;
>> +
>> +               fse->min_width = fmt->width;
>> +               fse->max_width = fmt->width;
>> +               fse->min_height = fmt->height;
>> +               fse->max_height = fmt->height;
>> +
>> +               return 0;
>> +       }
>> +
>> +       if (!rzv2h_ivc_get_mbus_output_from_input(fse->code))
>> +               return -EINVAL;
>> +
>> +       fse->min_width = RZV2H_IVC_MIN_WIDTH;
>> +       fse->max_width = RZV2H_IVC_MAX_WIDTH;
>> +       fse->min_height = RZV2H_IVC_MIN_HEIGHT;
>> +       fse->max_height = RZV2H_IVC_MAX_HEIGHT;
>> +
>> +       return 0;
>> +}
>> +
>> +static int rzv2h_ivc_set_fmt(struct v4l2_subdev *sd,
>> +                            struct v4l2_subdev_state *state,
>> +                            struct v4l2_subdev_format *format)
>> +{
>> +       struct v4l2_mbus_framefmt *fmt = &format->format;
>> +       struct v4l2_mbus_framefmt *src_fmt, *sink_fmt;
>> +
>> +       if (format->pad == RZV2H_IVC_SUBDEV_SOURCE_PAD)
>> +               return v4l2_subdev_get_fmt(sd, state, format);
>> +
>> +       sink_fmt = v4l2_subdev_state_get_format(state,
>> +                                               RZV2H_IVC_SUBDEV_SINK_PAD);
>> +
>> +       sink_fmt->code = rzv2h_ivc_get_mbus_output_from_input(fmt->code) ?
>> +                        fmt->code : rzv2h_ivc_formats[0].inputs[0];
>> +
>> +       sink_fmt->width = clamp(fmt->width, RZV2H_IVC_MIN_WIDTH,
>> +                               RZV2H_IVC_MAX_WIDTH);
>> +       sink_fmt->height = clamp(fmt->height, RZV2H_IVC_MIN_HEIGHT,
>> +                                RZV2H_IVC_MAX_HEIGHT);
>> +
>> +       *fmt = *sink_fmt;
>> +
>> +       src_fmt = v4l2_subdev_state_get_format(state,
>> +                                              RZV2H_IVC_SUBDEV_SOURCE_PAD);
>> +       *src_fmt = *sink_fmt;
>> +       src_fmt->code = rzv2h_ivc_get_mbus_output_from_input(sink_fmt->code);
>> +
>> +       return 0;
>> +}
>> +
>> +static int rzv2h_ivc_enable_streams(struct v4l2_subdev *sd,
>> +                                   struct v4l2_subdev_state *state, u32 pad,
>> +                                   u64 streams_mask)
>> +{
>> +       /*
>> +        * We have a single source pad, which has a single stream. V4L2 core has
>> +        * already validated those things. The actual power-on and programming
>> +        * of registers will be done through the video device's .vidioc_streamon
>> +        * so there's nothing to actually do here...
>> +        */
>> +
>> +       return 0;
>> +}
>> +
>> +static int rzv2h_ivc_disable_streams(struct v4l2_subdev *sd,
>> +                                    struct v4l2_subdev_state *state, u32 pad,
>> +                                    u64 streams_mask)
>> +{
>> +       return 0;
>> +}
>> +
>> +static const struct v4l2_subdev_pad_ops rzv2h_ivc_pad_ops = {
>> +       .enum_mbus_code         = rzv2h_ivc_enum_mbus_code,
>> +       .enum_frame_size        = rzv2h_ivc_enum_frame_size,
>> +       .get_fmt                = v4l2_subdev_get_fmt,
>> +       .set_fmt                = rzv2h_ivc_set_fmt,
>> +       .enable_streams         = rzv2h_ivc_enable_streams,
>> +       .disable_streams        = rzv2h_ivc_disable_streams,
>> +};
>> +
>> +static const struct v4l2_subdev_core_ops rzv2h_ivc_core_ops = {
>> +       .subscribe_event = v4l2_ctrl_subdev_subscribe_event,
>> +       .unsubscribe_event = v4l2_event_subdev_unsubscribe,
>> +};
>> +
>> +static const struct v4l2_subdev_ops rzv2h_ivc_subdev_ops = {
>> +       .core   = &rzv2h_ivc_core_ops,
>> +       .pad    = &rzv2h_ivc_pad_ops,
>> +};
>> +
>> +static int rzv2h_ivc_init_state(struct v4l2_subdev *sd,
>> +                               struct v4l2_subdev_state *state)
>> +{
>> +       struct v4l2_mbus_framefmt *sink_fmt, *src_fmt;
>> +
>> +       sink_fmt = v4l2_subdev_state_get_format(state,
>> +                                               RZV2H_IVC_SUBDEV_SINK_PAD);
>> +       sink_fmt->width = RZV2H_IVC_DEFAULT_WIDTH;
>> +       sink_fmt->height = RZV2H_IVC_DEFAULT_HEIGHT;
>> +       sink_fmt->field = V4L2_FIELD_NONE;
>> +       sink_fmt->code = MEDIA_BUS_FMT_SRGGB16_1X16;
>> +       sink_fmt->colorspace = V4L2_COLORSPACE_RAW;
>> +       sink_fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(sink_fmt->colorspace);
>> +       sink_fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(sink_fmt->colorspace);
>> +       sink_fmt->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(
>> +               true, sink_fmt->colorspace, sink_fmt->ycbcr_enc);
>> +
>> +       src_fmt = v4l2_subdev_state_get_format(state,
>> +                                              RZV2H_IVC_SUBDEV_SOURCE_PAD);
>> +
>> +       *src_fmt = *sink_fmt;
>> +       src_fmt->code = MEDIA_BUS_FMT_SRGGB20_1X20;
>> +
>> +       return 0;
>> +}
>> +
>> +static int rzv2h_ivc_registered(struct v4l2_subdev *sd)
>> +{
>> +       struct rzv2h_ivc *ivc = container_of(sd, struct rzv2h_ivc, subdev.sd);
>> +
>> +       return rzv2h_ivc_init_vdev(ivc, sd->v4l2_dev);
>> +}
>> +
>> +static const struct v4l2_subdev_internal_ops rzv2h_ivc_subdev_internal_ops = {
>> +       .init_state = rzv2h_ivc_init_state,
>> +       .registered = rzv2h_ivc_registered,
>> +};
>> +
>> +static int rzv2h_ivc_link_validate(struct media_link *link)
>> +{
>> +       struct video_device *vdev =
>> +               media_entity_to_video_device(link->source->entity);
>> +       struct rzv2h_ivc *ivc = video_get_drvdata(vdev);
>> +       struct v4l2_subdev *sd =
>> +               media_entity_to_v4l2_subdev(link->sink->entity);
>> +       const struct rzv2h_ivc_format *fmt;
>> +       const struct v4l2_pix_format_mplane *pix;
>> +       struct v4l2_subdev_state *state;
>> +       struct v4l2_mbus_framefmt *mf;
>> +       unsigned int i;
>> +       int ret = 0;
>> +
>> +       state = v4l2_subdev_lock_and_get_active_state(sd);
>> +       mf = v4l2_subdev_state_get_format(state, link->sink->index);
>> +
>> +       pix = &ivc->format.pix;
>> +       fmt = ivc->format.fmt;
>> +
>> +       if (mf->width != pix->width || mf->height != pix->height) {
>> +               dev_dbg(ivc->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,
>> +                       mf->width, mf->height,
>> +                       pix->width, pix->height);
>> +               ret = -EPIPE;
>> +       }
>> +
>> +       for (i = 0; i < ARRAY_SIZE(fmt->mbus_codes); i++)
>> +               if (mf->code == fmt->mbus_codes[i])
>> +                       break;
>> +
>> +       if (i == ARRAY_SIZE(fmt->mbus_codes)) {
>> +               dev_dbg(ivc->dev,
>> +                       "link '%s':%u -> '%s':%u not valid: pixel format %p4cc cannot produce mbus_code 0x%04x\n",
>> +                       link->source->entity->name, link->source->index,
>> +                       link->sink->entity->name, link->sink->index,
>> +                       &pix->pixelformat, mf->code);
>> +               ret = -EPIPE;
>> +       }
>> +
>> +       v4l2_subdev_unlock_state(state);
>> +
>> +       return ret;
>> +}
>> +
>> +static const struct media_entity_operations rzv2h_ivc_media_ops = {
>> +       .link_validate = rzv2h_ivc_link_validate,
>> +};
>> +
>> +int rzv2h_ivc_initialise_subdevice(struct rzv2h_ivc *ivc)
>> +{
>> +       struct v4l2_subdev *sd;
>> +       int ret;
>> +
>> +       /* Initialise subdevice */
>> +       sd = &ivc->subdev.sd;
>> +       sd->dev = ivc->dev;
>> +       v4l2_subdev_init(sd, &rzv2h_ivc_subdev_ops);
>> +       sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS;
>> +       sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;
>> +       sd->internal_ops = &rzv2h_ivc_subdev_internal_ops;
>> +       sd->entity.ops = &rzv2h_ivc_media_ops;
>> +
>> +       ivc->subdev.pads[RZV2H_IVC_SUBDEV_SINK_PAD].flags = MEDIA_PAD_FL_SINK;
>> +       ivc->subdev.pads[RZV2H_IVC_SUBDEV_SOURCE_PAD].flags = MEDIA_PAD_FL_SOURCE;
>> +
>> +       snprintf(sd->name, sizeof(sd->name), "rzv2h ivc block");
>> +
>> +       ret = media_entity_pads_init(&sd->entity, RZV2H_IVC_NUM_SUBDEV_PADS,
>> +                                    ivc->subdev.pads);
>> +       if (ret) {
>> +               dev_err(ivc->dev, "failed to initialise media entity\n");
>> +               return ret;
>> +       }
>> +
>> +       ret = v4l2_subdev_init_finalize(sd);
>> +       if (ret) {
>> +               dev_err(ivc->dev, "failed to finalize subdev init\n");
>> +               goto err_cleanup_subdev_entity;
>> +       }
>> +
>> +       ret = v4l2_async_register_subdev(sd);
>> +       if (ret) {
>> +               dev_err(ivc->dev, "failed to register subdevice\n");
>> +               goto err_cleanup_subdev;
>> +       }
>> +
>> +       return 0;
>> +
>> +err_cleanup_subdev:
>> +       v4l2_subdev_cleanup(sd);
>> +err_cleanup_subdev_entity:
>> +       media_entity_cleanup(&sd->entity);
>> +
>> +       return ret;
>> +}
>> +
>> +void rzv2h_ivc_deinit_subdevice(struct rzv2h_ivc *ivc)
>> +{
>> +       struct v4l2_subdev *sd = &ivc->subdev.sd;
>> +
>> +       v4l2_subdev_cleanup(sd);
>> +       media_entity_remove_links(&sd->entity);
>> +       v4l2_async_unregister_subdev(sd);
>> +       media_entity_cleanup(&sd->entity);
>> +}
>> diff --git a/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-video.c b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-video.c
>> new file mode 100644
>> index 0000000000000000000000000000000000000000..005a5700b5e2351b1e7ba5d99539ce4468f3db8b
>> --- /dev/null
>> +++ b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-video.c
>> @@ -0,0 +1,568 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Renesas RZ/V2H Input Video Control Block driver
>> + *
>> + * Copyright (C) 2025 Ideas on Board Oy
>> + */
>> +
>> +#include "rzv2h-ivc.h"
>> +
>> +#include <linux/cleanup.h>
>> +#include <linux/iopoll.h>
>> +#include <linux/media-bus-format.h>
>> +#include <linux/minmax.h>
>> +#include <linux/mutex.h>
>> +#include <linux/pm_runtime.h>
>> +
>> +#include <media/mipi-csi2.h>
>> +#include <media/v4l2-ctrls.h>
>> +#include <media/v4l2-dev.h>
>> +#include <media/v4l2-event.h>
>> +#include <media/v4l2-fh.h>
>> +#include <media/v4l2-ioctl.h>
>> +#include <media/videobuf2-dma-contig.h>
>> +
>> +#define RZV2H_IVC_FIXED_HBLANK                 0x20
>> +#define RZV2H_IVC_MIN_VBLANK(hts)              max(0x1b, 15 + (120501 / (hts)))
>> +
>> +struct rzv2h_ivc_buf {
>> +       struct vb2_v4l2_buffer vb;
>> +       struct list_head queue;
>> +       dma_addr_t addr;
>> +};
>> +
>> +#define to_rzv2h_ivc_buf(vbuf) \
>> +       container_of(vbuf, struct rzv2h_ivc_buf, vb)
>> +
>> +static const struct rzv2h_ivc_format rzv2h_ivc_formats[] = {
>> +       {
>> +               .fourcc = V4L2_PIX_FMT_SBGGR8,
>> +               .mbus_codes = {
>> +                       MEDIA_BUS_FMT_SBGGR8_1X8,
>> +               },
>> +               .dtype = MIPI_CSI2_DT_RAW8,
>> +       },
>> +       {
>> +               .fourcc = V4L2_PIX_FMT_SGBRG8,
>> +               .mbus_codes = {
>> +                       MEDIA_BUS_FMT_SGBRG8_1X8,
>> +               },
>> +               .dtype = MIPI_CSI2_DT_RAW8,
>> +       },
>> +       {
>> +               .fourcc = V4L2_PIX_FMT_SGRBG8,
>> +               .mbus_codes = {
>> +                       MEDIA_BUS_FMT_SGRBG8_1X8,
>> +               },
>> +               .dtype = MIPI_CSI2_DT_RAW8,
>> +       },
>> +       {
>> +               .fourcc = V4L2_PIX_FMT_SRGGB8,
>> +               .mbus_codes = {
>> +                       MEDIA_BUS_FMT_SRGGB8_1X8,
>> +               },
>> +               .dtype = MIPI_CSI2_DT_RAW8,
>> +       },
>> +       {
>> +               .fourcc = V4L2_PIX_FMT_RAW_CRU10,
>> +               .mbus_codes = {
>> +                       MEDIA_BUS_FMT_SBGGR10_1X10,
>> +                       MEDIA_BUS_FMT_SGBRG10_1X10,
>> +                       MEDIA_BUS_FMT_SGRBG10_1X10,
>> +                       MEDIA_BUS_FMT_SRGGB10_1X10
>> +               },
>> +               .dtype = MIPI_CSI2_DT_RAW10,
>> +       },
>> +       {
>> +               .fourcc = V4L2_PIX_FMT_RAW_CRU12,
>> +               .mbus_codes = {
>> +                       MEDIA_BUS_FMT_SBGGR12_1X12,
>> +                       MEDIA_BUS_FMT_SGBRG12_1X12,
>> +                       MEDIA_BUS_FMT_SGRBG12_1X12,
>> +                       MEDIA_BUS_FMT_SRGGB12_1X12
>> +               },
>> +               .dtype = MIPI_CSI2_DT_RAW12,
>> +       },
>> +       {
>> +               .fourcc = V4L2_PIX_FMT_RAW_CRU14,
>> +               .mbus_codes = {
>> +                       MEDIA_BUS_FMT_SBGGR14_1X14,
>> +                       MEDIA_BUS_FMT_SGBRG14_1X14,
>> +                       MEDIA_BUS_FMT_SGRBG14_1X14,
>> +                       MEDIA_BUS_FMT_SRGGB14_1X14
>> +               },
>> +               .dtype = MIPI_CSI2_DT_RAW14,
>> +       },
>> +       {
>> +               .fourcc = V4L2_PIX_FMT_SBGGR16,
>> +               .mbus_codes = {
>> +                       MEDIA_BUS_FMT_SBGGR16_1X16,
>> +               },
>> +               .dtype = MIPI_CSI2_DT_RAW16,
>> +       },
>> +       {
>> +               .fourcc = V4L2_PIX_FMT_SGBRG16,
>> +               .mbus_codes = {
>> +                       MEDIA_BUS_FMT_SGBRG16_1X16,
>> +               },
>> +               .dtype = MIPI_CSI2_DT_RAW16,
>> +       },
>> +       {
>> +               .fourcc = V4L2_PIX_FMT_SGRBG16,
>> +               .mbus_codes = {
>> +                       MEDIA_BUS_FMT_SGRBG16_1X16,
>> +               },
>> +               .dtype = MIPI_CSI2_DT_RAW16,
>> +       },
>> +       {
>> +               .fourcc = V4L2_PIX_FMT_SRGGB16,
>> +               .mbus_codes = {
>> +                       MEDIA_BUS_FMT_SRGGB16_1X16,
>> +               },
>> +               .dtype = MIPI_CSI2_DT_RAW16,
>> +       },
>> +};
>> +
>> +static void rzv2h_ivc_transfer_buffer(struct work_struct *work)
>> +{
>> +       struct rzv2h_ivc *ivc = container_of(work, struct rzv2h_ivc,
>> +                                            buffers.work);
>> +       struct rzv2h_ivc_buf *buf;
>> +
>> +       scoped_guard(spinlock, &ivc->buffers.lock) {
>> +               if (ivc->buffers.curr) {
>> +                       ivc->buffers.curr->vb.sequence = ivc->buffers.sequence++;
>> +                       vb2_buffer_done(&ivc->buffers.curr->vb.vb2_buf,
>> +                                       VB2_BUF_STATE_DONE);
>> +                       ivc->buffers.curr = NULL;
>> +               }
>> +
>> +               buf = list_first_entry_or_null(&ivc->buffers.queue,
>> +                                              struct rzv2h_ivc_buf, queue);
>> +       }
>> +
>> +       if (!buf)
>> +               return;
>> +
>> +       list_del(&buf->queue);
>> +
>> +       ivc->buffers.curr = buf;
>> +       buf->addr = vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
>> +       rzv2h_ivc_write(ivc, RZV2H_IVC_REG_AXIRX_SADDL_P0, buf->addr);
>> +
>> +       scoped_guard(spinlock_irqsave, &ivc->spinlock) {
>> +               ivc->vvalid_ifp = 2;
>> +       }
>> +       rzv2h_ivc_write(ivc, RZV2H_IVC_REG_FM_FRCON, 0x1);
>> +}
>> +
>> +static int rzv2h_ivc_pipeline_started(struct media_entity *entity)
>> +{
>> +       struct video_device *vdev = media_entity_to_video_device(entity);
>> +       struct rzv2h_ivc *ivc = video_get_drvdata(vdev);
>> +
>> +       guard(spinlock)(&ivc->buffers.lock);
>> +
>> +       if (list_empty(&ivc->buffers.queue)) {
>> +               /*
>> +                * The driver waits for interrupts to send a new frame and
>> +                * tracks their receipt in the vvalid_ifp variable. .buf_queue()
>> +                * will queue work if vvalid_ifp == 0 to trigger a new frame (an
>> +                * event that normally would only occur if no buffer was ready
>> +                * when the interrupt arrived). If there are no buffers in the
>> +                * queue yet, we set vvalid_ifp to zero so that the next queue
>> +                * will trigger the work.
>> +                */
>> +               scoped_guard(spinlock_irqsave, &ivc->spinlock) {
>> +                       ivc->vvalid_ifp = 0;
>> +               }
>> +       } else {
>> +               queue_work(ivc->buffers.async_wq, &ivc->buffers.work);
>> +       }
>> +
>> +       return 0;
>> +}
>> +
>> +static void rzv2h_ivc_pipeline_stopped(struct media_entity *entity)
>> +{
>> +       struct video_device *vdev = media_entity_to_video_device(entity);
>> +       struct rzv2h_ivc *ivc = video_get_drvdata(vdev);
>> +       u32 val = 0;
>> +
>> +       rzv2h_ivc_write(ivc, RZV2H_IVC_REG_FM_STOP, 0x1);
>> +       readl_poll_timeout(ivc->base + RZV2H_IVC_REG_FM_STOP,
>> +                          val, !val, 10 * USEC_PER_MSEC, 250 * USEC_PER_MSEC);
>> +}
>> +
>> +static const struct media_entity_operations rzv2h_ivc_media_ops = {
>> +       .pipeline_started = rzv2h_ivc_pipeline_started,
>> +       .pipeline_stopped = rzv2h_ivc_pipeline_stopped,
>> +};
>> +
>> +static int rzv2h_ivc_queue_setup(struct vb2_queue *q, unsigned int *num_buffers,
>> +                                unsigned int *num_planes, unsigned int sizes[],
>> +                                struct device *alloc_devs[])
>> +{
>> +       struct rzv2h_ivc *ivc = vb2_get_drv_priv(q);
>> +
>> +       if (*num_planes && *num_planes > 1)
>> +               return -EINVAL;
>> +
>> +       if (sizes[0] && sizes[0] < ivc->format.pix.plane_fmt[0].sizeimage)
>> +               return -EINVAL;
>> +
>> +       *num_planes = 1;
>> +
>> +       if (!sizes[0])
>> +               sizes[0] = ivc->format.pix.plane_fmt[0].sizeimage;
>> +
>> +       return 0;
>> +}
>> +
>> +static void rzv2h_ivc_buf_queue(struct vb2_buffer *vb)
>> +{
>> +       struct rzv2h_ivc *ivc = vb2_get_drv_priv(vb->vb2_queue);
>> +       struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
>> +       struct rzv2h_ivc_buf *buf = to_rzv2h_ivc_buf(vbuf);
>> +
>> +       scoped_guard(spinlock, &ivc->buffers.lock) {
>> +               list_add_tail(&buf->queue, &ivc->buffers.queue);
>> +       }
>> +
>> +       scoped_guard(spinlock_irqsave, &ivc->spinlock) {
>> +               if (vb2_is_streaming(vb->vb2_queue) && !ivc->vvalid_ifp)
>> +                       queue_work(ivc->buffers.async_wq, &ivc->buffers.work);
>> +       }
>> +}
>> +
>> +static void rzv2h_ivc_format_configure(struct rzv2h_ivc *ivc)
>> +{
>> +       const struct rzv2h_ivc_format *fmt = ivc->format.fmt;
>> +       struct v4l2_pix_format_mplane *pix = &ivc->format.pix;
>> +       unsigned int vblank;
>> +       unsigned int hts;
>> +
>> +       /* Currently only CRU packed pixel formats are supported */
>> +       rzv2h_ivc_write(ivc, RZV2H_IVC_REG_AXIRX_PXFMT,
>> +                       RZV2H_IVC_INPUT_FMT_CRU_PACKED);
>> +
>> +       rzv2h_ivc_update_bits(ivc, RZV2H_IVC_REG_AXIRX_PXFMT,
>> +                             RZV2H_IVC_PXFMT_DTYPE, fmt->dtype);
>> +
>> +       rzv2h_ivc_write(ivc, RZV2H_IVC_REG_AXIRX_HSIZE, pix->width);
>> +       rzv2h_ivc_write(ivc, RZV2H_IVC_REG_AXIRX_VSIZE, pix->height);
>> +       rzv2h_ivc_write(ivc, RZV2H_IVC_REG_AXIRX_STRD,
>> +                       pix->plane_fmt[0].bytesperline);
>> +
>> +       /*
>> +        * The ISP has minimum vertical blanking requirements that must be
>> +        * adhered to by the IVC. The minimum is a function of the Iridix blocks
>> +        * clocking requirements and the width of the image and horizontal
>> +        * blanking, but if we assume the worst case then it boils down to the
>> +        * below (plus one to the numerator to ensure the answer is rounded up)
>> +        */
>> +
>> +       hts = pix->width + RZV2H_IVC_FIXED_HBLANK;
>> +       vblank = RZV2H_IVC_MIN_VBLANK(hts);
>> +
>> +       rzv2h_ivc_write(ivc, RZV2H_IVC_REG_AXIRX_BLANK,
>> +                       RZV2H_IVC_VBLANK(vblank));
>> +}
>> +
>> +static void rzv2h_ivc_return_buffers(struct rzv2h_ivc *ivc,
>> +                                    enum vb2_buffer_state state)
>> +{
>> +       struct rzv2h_ivc_buf *buf, *tmp;
>> +
>> +       guard(spinlock)(&ivc->buffers.lock);
>> +
>> +       if (ivc->buffers.curr) {
>> +               vb2_buffer_done(&ivc->buffers.curr->vb.vb2_buf, state);
>> +               ivc->buffers.curr = NULL;
>> +       }
>> +
>> +       list_for_each_entry_safe(buf, tmp, &ivc->buffers.queue, queue) {
>> +               list_del(&buf->queue);
>> +               vb2_buffer_done(&buf->vb.vb2_buf, state);
>> +       }
>> +}
>> +
>> +static int rzv2h_ivc_start_streaming(struct vb2_queue *q, unsigned int count)
>> +{
>> +       struct rzv2h_ivc *ivc = vb2_get_drv_priv(q);
>> +       int ret;
>> +
>> +       ivc->buffers.sequence = 0;
>> +       ivc->vvalid_ifp = 2;
>> +
>> +       ret = pm_runtime_resume_and_get(ivc->dev);
>> +       if (ret)
>> +               goto err_return_buffers;
>> +
>> +       ret = video_device_pipeline_alloc_start(&ivc->vdev.dev);
>> +       if (ret) {
>> +               dev_err(ivc->dev, "failed to start media pipeline\n");
>> +               goto err_pm_runtime_put;
>> +       }
>> +
>> +       rzv2h_ivc_format_configure(ivc);
>> +
>> +       ret = video_device_pipeline_started(&ivc->vdev.dev);
>> +       if (ret < 0)
>> +               goto err_stop_pipeline;
>> +
>> +       return 0;
>> +
>> +err_stop_pipeline:
>> +       video_device_pipeline_stop(&ivc->vdev.dev);
>> +err_pm_runtime_put:
>> +       pm_runtime_put(ivc->dev);
>> +err_return_buffers:
>> +       rzv2h_ivc_return_buffers(ivc, VB2_BUF_STATE_QUEUED);
>> +
>> +       return ret;
>> +}
>> +
>> +static void rzv2h_ivc_stop_streaming(struct vb2_queue *q)
>> +{
>> +       struct rzv2h_ivc *ivc = vb2_get_drv_priv(q);
>> +
>> +       video_device_pipeline_stopped(&ivc->vdev.dev);
>> +       rzv2h_ivc_return_buffers(ivc, VB2_BUF_STATE_ERROR);
>> +       video_device_pipeline_stop(&ivc->vdev.dev);
>> +       pm_runtime_mark_last_busy(ivc->dev);
>> +       pm_runtime_put_autosuspend(ivc->dev);
>> +}
>> +
>> +static const struct vb2_ops rzv2h_ivc_vb2_ops = {
>> +       .queue_setup            = &rzv2h_ivc_queue_setup,
>> +       .buf_queue              = &rzv2h_ivc_buf_queue,
>> +       .wait_prepare           = vb2_ops_wait_prepare,
>> +       .wait_finish            = vb2_ops_wait_finish,
>> +       .start_streaming        = &rzv2h_ivc_start_streaming,
>> +       .stop_streaming         = &rzv2h_ivc_stop_streaming,
>> +};
>> +
>> +static const struct rzv2h_ivc_format *
>> +rzv2h_ivc_format_from_pixelformat(u32 fourcc)
>> +{
>> +       for (unsigned int i = 0; i < ARRAY_SIZE(rzv2h_ivc_formats); i++)
>> +               if (fourcc == rzv2h_ivc_formats[i].fourcc)
>> +                       return &rzv2h_ivc_formats[i];
>> +
>> +       return &rzv2h_ivc_formats[0];
>> +}
>> +
>> +static int rzv2h_ivc_enum_fmt_vid_out(struct file *file, void *fh,
>> +                                     struct v4l2_fmtdesc *f)
>> +{
>> +       if (f->index >= ARRAY_SIZE(rzv2h_ivc_formats))
>> +               return -EINVAL;
>> +
>> +       f->pixelformat = rzv2h_ivc_formats[f->index].fourcc;
>> +       return 0;
>> +}
>> +
>> +static int rzv2h_ivc_g_fmt_vid_out(struct file *file, void *fh,
>> +                                  struct v4l2_format *f)
>> +{
>> +       struct rzv2h_ivc *ivc = video_drvdata(file);
>> +
>> +       f->fmt.pix_mp = ivc->format.pix;
>> +
>> +       return 0;
>> +}
>> +
>> +static void rzv2h_ivc_try_fmt(struct v4l2_pix_format_mplane *pix,
>> +                             const struct rzv2h_ivc_format *fmt)
>> +{
>> +       pix->pixelformat = fmt->fourcc;
>> +
>> +       pix->width = clamp(pix->width, RZV2H_IVC_MIN_WIDTH,
>> +                          RZV2H_IVC_MAX_WIDTH);
>> +       pix->height = clamp(pix->height, RZV2H_IVC_MIN_HEIGHT,
>> +                           RZV2H_IVC_MAX_HEIGHT);
>> +
>> +       pix->field = V4L2_FIELD_NONE;
>> +       pix->colorspace = V4L2_COLORSPACE_RAW;
>> +       pix->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(pix->colorspace);
>> +       pix->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true,
>> +                                                         pix->colorspace,
>> +                                                         pix->ycbcr_enc);
>> +
>> +       v4l2_fill_pixfmt_mp(pix, pix->pixelformat, pix->width, pix->height);
>> +}
>> +
>> +static void rzv2h_ivc_set_format(struct rzv2h_ivc *ivc,
>> +                                struct v4l2_pix_format_mplane *pix)
>> +{
>> +       const struct rzv2h_ivc_format *fmt;
>> +
>> +       fmt = rzv2h_ivc_format_from_pixelformat(pix->pixelformat);
>> +
>> +       rzv2h_ivc_try_fmt(pix, fmt);
>> +       ivc->format.pix = *pix;
>> +       ivc->format.fmt = fmt;
>> +}
>> +
>> +static int rzv2h_ivc_s_fmt_vid_out(struct file *file, void *fh,
>> +                                  struct v4l2_format *f)
>> +{
>> +       struct rzv2h_ivc *ivc = video_drvdata(file);
>> +       struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;
>> +
>> +       if (vb2_is_busy(&ivc->vdev.vb2q))
>> +               return -EBUSY;
>> +
>> +       rzv2h_ivc_set_format(ivc, pix);
>> +
>> +       return 0;
>> +}
>> +
>> +static int rzv2h_ivc_try_fmt_vid_out(struct file *file, void *fh,
>> +                                    struct v4l2_format *f)
>> +{
>> +       const struct rzv2h_ivc_format *fmt;
>> +
>> +       fmt = rzv2h_ivc_format_from_pixelformat(f->fmt.pix.pixelformat);
>> +       rzv2h_ivc_try_fmt(&f->fmt.pix_mp, fmt);
>> +
>> +       return 0;
>> +}
>> +
>> +static int rzv2h_ivc_querycap(struct file *file, void *fh,
>> +                             struct v4l2_capability *cap)
>> +{
>> +       strscpy(cap->driver, "rzv2h-ivc", sizeof(cap->driver));
>> +       strscpy(cap->card, "Renesas Input Video Control", sizeof(cap->card));
>> +
>> +       return 0;
>> +}
>> +
>> +static const struct v4l2_ioctl_ops rzv2h_ivc_v4l2_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_enum_fmt_vid_out = rzv2h_ivc_enum_fmt_vid_out,
>> +       .vidioc_g_fmt_vid_out_mplane = rzv2h_ivc_g_fmt_vid_out,
>> +       .vidioc_s_fmt_vid_out_mplane = rzv2h_ivc_s_fmt_vid_out,
>> +       .vidioc_try_fmt_vid_out_mplane = rzv2h_ivc_try_fmt_vid_out,
>> +       .vidioc_querycap = rzv2h_ivc_querycap,
>> +       .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
>> +       .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
>> +};
>> +
>> +static const struct v4l2_file_operations rzv2h_ivc_v4l2_fops = {
>> +       .owner = THIS_MODULE,
>> +       .unlocked_ioctl = video_ioctl2,
>> +       .open = v4l2_fh_open,
>> +       .release = vb2_fop_release,
>> +       .poll = vb2_fop_poll,
>> +       .mmap = vb2_fop_mmap,
>> +};
>> +
>> +int rzv2h_ivc_init_vdev(struct rzv2h_ivc *ivc, struct v4l2_device *v4l2_dev)
>> +{
>> +       struct v4l2_pix_format_mplane pix = { };
>> +       struct video_device *vdev;
>> +       struct vb2_queue *vb2q;
>> +       int ret;
>> +
>> +       spin_lock_init(&ivc->buffers.lock);
>> +       INIT_LIST_HEAD(&ivc->buffers.queue);
>> +       INIT_WORK(&ivc->buffers.work, rzv2h_ivc_transfer_buffer);
>> +
>> +       ivc->buffers.async_wq = alloc_workqueue("rzv2h-ivc", 0, 0);
>> +       if (!ivc->buffers.async_wq)
>> +               return -EINVAL;
>> +
>> +       /* Initialise vb2 queue */
>> +       vb2q = &ivc->vdev.vb2q;
>> +       vb2q->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
>> +       vb2q->io_modes = VB2_MMAP | VB2_DMABUF;
>> +       vb2q->drv_priv = ivc;
>> +       vb2q->mem_ops = &vb2_dma_contig_memops;
>> +       vb2q->ops = &rzv2h_ivc_vb2_ops;
>> +       vb2q->buf_struct_size = sizeof(struct rzv2h_ivc_buf);
>> +       vb2q->min_queued_buffers = 0;
>> +       vb2q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
>> +       vb2q->lock = &ivc->lock;
>> +       vb2q->dev = ivc->dev;
>> +
>> +       ret = vb2_queue_init(vb2q);
>> +       if (ret) {
>> +               dev_err(ivc->dev, "vb2 queue init failed\n");
>> +               goto err_destroy_workqueue;
>> +       }
>> +
>> +       /* Initialise Video Device */
>> +       vdev = &ivc->vdev.dev;
>> +       strscpy(vdev->name, "rzv2h-ivc", sizeof(vdev->name));
>> +       vdev->release = video_device_release_empty;
>> +       vdev->fops = &rzv2h_ivc_v4l2_fops;
>> +       vdev->ioctl_ops = &rzv2h_ivc_v4l2_ioctl_ops;
>> +       vdev->lock = &ivc->lock;
>> +       vdev->v4l2_dev = v4l2_dev;
>> +       vdev->queue = vb2q;
>> +       vdev->device_caps = V4L2_CAP_VIDEO_OUTPUT_MPLANE | V4L2_CAP_STREAMING;
>> +       vdev->vfl_dir = VFL_DIR_TX;
>> +       video_set_drvdata(vdev, ivc);
>> +
>> +       pix.pixelformat = V4L2_PIX_FMT_SRGGB16;
>> +       pix.width = RZV2H_IVC_DEFAULT_WIDTH;
>> +       pix.height = RZV2H_IVC_DEFAULT_HEIGHT;
>> +       rzv2h_ivc_set_format(ivc, &pix);
>> +
>> +       ivc->vdev.pad.flags = MEDIA_PAD_FL_SOURCE;
>> +       ivc->vdev.dev.entity.ops = &rzv2h_ivc_media_ops;
>> +       ret = media_entity_pads_init(&ivc->vdev.dev.entity, 1, &ivc->vdev.pad);
>> +       if (ret)
>> +               goto err_release_vb2q;
>> +
>> +       ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
>> +       if (ret) {
>> +               dev_err(ivc->dev, "failed to register IVC video device\n");
>> +               goto err_cleanup_vdev_entity;
>> +       }
>> +
>> +       ret = media_create_pad_link(&vdev->entity, 0, &ivc->subdev.sd.entity,
>> +                                   RZV2H_IVC_SUBDEV_SINK_PAD,
>> +                                   MEDIA_LNK_FL_ENABLED |
>> +                                   MEDIA_LNK_FL_IMMUTABLE);
>> +       if (ret) {
>> +               dev_err(ivc->dev, "failed to create media link\n");
>> +               goto err_unregister_vdev;
>> +       }
>> +
>> +       return 0;
>> +
>> +err_unregister_vdev:
>> +       video_unregister_device(vdev);
>> +err_cleanup_vdev_entity:
>> +       media_entity_cleanup(&vdev->entity);
>> +err_release_vb2q:
>> +       vb2_queue_release(vb2q);
>> +err_destroy_workqueue:
>> +       destroy_workqueue(ivc->buffers.async_wq);
>> +
>> +       return ret;
>> +}
>> +
>> +void rzv2h_deinit_video_dev_and_queue(struct rzv2h_ivc *ivc)
>> +{
>> +       struct video_device *vdev = &ivc->vdev.dev;
>> +       struct vb2_queue *vb2q = &ivc->vdev.vb2q;
>> +
>> +       if (!ivc->sched)
>> +               return;
>> +
>> +       vb2_video_unregister_device(vdev);
>> +       media_entity_cleanup(&vdev->entity);
>> +       vb2_queue_release(vb2q);
>> +}
>> diff --git a/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc.h b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc.h
>> new file mode 100644
>> index 0000000000000000000000000000000000000000..709c6a9398fe2484c2acb03d443d58ea4e153a66
>> --- /dev/null
>> +++ b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc.h
>> @@ -0,0 +1,131 @@
>> +/* SPDX-License-Identifier: GPL-2.0 */
>> +/*
>> + * Renesas RZ/V2H Input Video Control Block driver
>> + *
>> + * Copyright (C) 2025 Ideas on Board Oy
>> + */
>> +
>> +#include <linux/clk.h>
>> +#include <linux/list.h>
>> +#include <linux/mutex.h>
>> +#include <linux/reset.h>
>> +#include <linux/spinlock.h>
>> +#include <linux/types.h>
>> +#include <linux/videodev2.h>
>> +#include <linux/workqueue.h>
>> +
>> +#include <media/media-entity.h>
>> +#include <media/v4l2-device.h>
>> +#include <media/v4l2-subdev.h>
>> +#include <media/videobuf2-core.h>
>> +#include <media/videobuf2-v4l2.h>
>> +
>> +#define RZV2H_IVC_REG_AXIRX_PLNUM                      0x0000
>> +#define RZV2H_IVC_ONE_EXPOSURE                         0x00
>> +#define RZV2H_IVC_TWO_EXPOSURE                         0x01
>> +#define RZV2H_IVC_REG_AXIRX_PXFMT                      0x0004
>> +#define RZV2H_IVC_INPUT_FMT_MIPI                       (0 << 16)
>> +#define RZV2H_IVC_INPUT_FMT_CRU_PACKED                 (1 << 16)
>> +#define RZV2H_IVC_PXFMT_DTYPE                          GENMASK(7, 0)
>> +#define RZV2H_IVC_REG_AXIRX_SADDL_P0                   0x0010
>> +#define RZV2H_IVC_REG_AXIRX_SADDH_P0                   0x0014
>> +#define RZV2H_IVC_REG_AXIRX_SADDL_P1                   0x0018
>> +#define RZV2H_IVC_REG_AXIRX_SADDH_P1                   0x001c
>> +#define RZV2H_IVC_REG_AXIRX_HSIZE                      0x0020
>> +#define RZV2H_IVC_REG_AXIRX_VSIZE                      0x0024
>> +#define RZV2H_IVC_REG_AXIRX_BLANK                      0x0028
>> +#define RZV2H_IVC_VBLANK(x)                            ((x) << 16)
>> +#define RZV2H_IVC_REG_AXIRX_STRD                       0x0030
>> +#define RZV2H_IVC_REG_AXIRX_ISSU                       0x0040
>> +#define RZV2H_IVC_REG_AXIRX_ERACT                      0x0048
>> +#define RZV2H_IVC_REG_FM_CONTEXT                       0x0100
>> +#define RZV2H_IVC_SOFTWARE_CFG                         0x00
>> +#define RZV2H_IVC_SINGLE_CONTEXT_SW_HW_CFG             BIT(0)
>> +#define RZV2H_IVC_MULTI_CONTEXT_SW_HW_CFG              BIT(1)
>> +#define RZV2H_IVC_REG_FM_MCON                          0x0104
>> +#define RZV2H_IVC_REG_FM_FRCON                         0x0108
>> +#define RZV2H_IVC_REG_FM_STOP                          0x010c
>> +#define RZV2H_IVC_REG_FM_INT_EN                                0x0120
>> +#define RZV2H_IVC_VVAL_IFPE                            BIT(0)
>> +#define RZV2H_IVC_REG_FM_INT_STA                       0x0124
>> +#define RZV2H_IVC_REG_AXIRX_FIFOCAP0                   0x0208
>> +#define RZV2H_IVC_REG_CORE_CAPCON                      0x020c
>> +#define RZV2H_IVC_REG_CORE_FIFOCAP0                    0x0228
>> +#define RZV2H_IVC_REG_CORE_FIFOCAP1                    0x022c
>> +
>> +#define RZV2H_IVC_MIN_WIDTH                            640
>> +#define RZV2H_IVC_MAX_WIDTH                            4096
>> +#define RZV2H_IVC_MIN_HEIGHT                           480
>> +#define RZV2H_IVC_MAX_HEIGHT                           4096
>> +#define RZV2H_IVC_DEFAULT_WIDTH                                1920
>> +#define RZV2H_IVC_DEFAULT_HEIGHT                       1080
>> +
>> +#define RZV2H_IVC_NUM_HW_RESOURCES                     3
>> +
>> +struct device;
>> +
>> +enum rzv2h_ivc_subdev_pads {
>> +       RZV2H_IVC_SUBDEV_SINK_PAD,
>> +       RZV2H_IVC_SUBDEV_SOURCE_PAD,
>> +       RZV2H_IVC_NUM_SUBDEV_PADS
>> +};
>> +
>> +struct rzv2h_ivc_format {
>> +       u32 fourcc;
>> +       /*
>> +        * The CRU packed pixel formats are bayer-order agnostic, so each could
>> +        * support any one of the 4 possible media bus formats.
>> +        */
>> +       u32 mbus_codes[4];
>> +       u8 dtype;
>> +};
>> +
>> +struct rzv2h_ivc {
>> +       struct device *dev;
>> +       void __iomem *base;
>> +       struct clk_bulk_data clks[RZV2H_IVC_NUM_HW_RESOURCES];
>> +       struct reset_control_bulk_data resets[RZV2H_IVC_NUM_HW_RESOURCES];
>> +       int irqnum;
>> +       u8 vvalid_ifp;
>> +
>> +       struct {
>> +               struct video_device dev;
>> +               struct vb2_queue vb2q;
>> +               struct media_pad pad;
>> +       } vdev;
>> +
>> +       struct {
>> +               struct v4l2_subdev sd;
>> +               struct media_pad pads[RZV2H_IVC_NUM_SUBDEV_PADS];
>> +       } subdev;
>> +
>> +       struct {
>> +               /* Spinlock to guard buffer queue */
>> +               spinlock_t lock;
>> +               struct workqueue_struct *async_wq;
>> +               struct work_struct work;
>> +               struct list_head queue;
>> +               struct rzv2h_ivc_buf *curr;
>> +               unsigned int sequence;
>> +       } buffers;
>> +
>> +       struct media_job_scheduler *sched;
> This is unused, we are just checking if  !=NULL in deinit.


Oops, that should have been removed; not enough care on my part.

>
> I gave these patch a try on next-20250724.
>
> ISP Probe:
> [   11.600383] mali-c55 16080000.isp: Detected Mali-C55 ISP 9000043.31032022.0
> [   11.622062] mali-c55 16080000.isp: Runtime PM usage count underflow!
>
> Logs from IVC:
> root@rzv2h-evk:~/c55# media-ctl -p
> Media controller API version 6.16.0
>
> Media device information
> ------------------------
> driver          mali-c55
> model           ARM Mali-C55 ISP
> serial
> bus info        platform:16080000.isp
> hw revision     0x1d982d6
> driver version  6.16.0
>
> Device topology
> - entity 1: mali-c55 tpg (1 pad, 1 link, 0 routes)
>              type V4L2 subdev subtype Sensor flags 0
>              device node name /dev/v4l-subdev0
>          pad0: SOURCE
>                  [stream:0 fmt:SRGGB20_1X20/1920x1080 field:none
> colorspace:raw xfer:none ycbcr:601 quantization:lim-range]
>                  -> "mali-c55 isp":0 []
>
> - entity 3: mali-c55 isp (5 pads, 6 links, 0 routes)
>              type V4L2 subdev subtype Unknown flags 0
>              device node name /dev/v4l-subdev1
>          pad0: SINK,MUST_CONNECT
>                  [stream:0 fmt:SGRBG20_1X20/2304x1296 field:none
> colorspace:raw xfer:none ycbcr:601 quantization:lim-range
>                   crop:(0,0)/2304x1296]
>                  <- "mali-c55 tpg":0 []
>                  <- "rzv2h ivc block":1 [ENABLED]
>          pad1: SOURCE
>                  [stream:0 fmt:RGB121212_1X36/2304x1296 field:none
> colorspace:srgb xfer:none ycbcr:601 quantization:lim-range]
>                  -> "mali-c55 resizer fr":0 [ENABLED,IMMUTABLE]
>          pad2: SOURCE
>                  [stream:0 fmt:SGRBG20_1X20/2304x1296 field:none
> colorspace:raw xfer:none ycbcr:601 quantization:lim-range]
>                  -> "mali-c55 resizer fr":2 [ENABLED,IMMUTABLE]
>          pad3: SOURCE
>                  [stream:0 fmt:unknown/0x0 field:none]
>                  -> "mali-c55 3a stats":0 []
>          pad4: SINK
>                  [stream:0 fmt:unknown/0x0 field:none]
>                  <- "mali-c55 3a params":0 []
>
> - entity 9: mali-c55 resizer fr (3 pads, 3 links, 0 routes)
>              type V4L2 subdev subtype Unknown flags 0
>              device node name /dev/v4l-subdev2
>          pad0: SINK
>                  [stream:0 fmt:RGB121212_1X36/2304x1296 field:none
> colorspace:srgb xfer:srgb ycbcr:601 quantization:lim-range
>                   crop:(0,0)/2304x1296
>                   compose:(0,0)/2304x1296]
>                  <- "mali-c55 isp":1 [ENABLED,IMMUTABLE]
>          pad1: SOURCE
>                  [stream:0 fmt:RGB121212_1X36/2304x1296 field:none
> colorspace:srgb xfer:srgb ycbcr:601 quantization:lim-range]
>                  -> "mali-c55 fr":0 [ENABLED]
>          pad2: SINK
>                  <- "mali-c55 isp":2 [ENABLED,IMMUTABLE]
>
> - entity 13: mali-c55 fr (1 pad, 1 link)
>               type Node subtype V4L flags 0
>               device node name /dev/video0
>          pad0: SINK
>                  <- "mali-c55 resizer fr":1 [ENABLED]
>
> - entity 17: mali-c55 3a params (1 pad, 1 link)
>               type Node subtype V4L flags 0
>               device node name /dev/video1
>          pad0: SOURCE
>                  -> "mali-c55 isp":4 []
>
> - entity 21: mali-c55 3a stats (1 pad, 1 link)
>               type Node subtype V4L flags 0
>               device node name /dev/video2
>          pad0: SINK
>                  <- "mali-c55 isp":3 []
>
> - entity 37: rzv2h ivc block (2 pads, 2 links, 0 routes)
>               type V4L2 subdev subtype Unknown flags 0
>               device node name /dev/v4l-subdev3
>          pad0: SINK
>                  [stream:0 fmt:SGRBG10_1X10/2304x1296 field:none
> colorspace:raw xfer:none ycbcr:601 quantization:full-range]
>                  <- "rzv2h-ivc":0 [ENABLED,IMMUTABLE]
>          pad1: SOURCE
>                  [stream:0 fmt:SGRBG20_1X20/2304x1296 field:none
> colorspace:raw xfer:none ycbcr:601 quantization:full-range]
>                  -> "mali-c55 isp":0 [ENABLED]
>
> - entity 40: rzv2h-ivc (1 pad, 1 link)
>               type Node subtype V4L flags 0
>               device node name /dev/video3
>          pad0: SOURCE
>                  -> "rzv2h ivc block":0 [ENABLED,IMMUTABLE]
>
> root@rzv2h-evk:~/c55#
> root@rzv2h-evk:~# v4l2-ctl -d3 --stream-out-mmap
> --stream-from=/root/c55/frame-15.bin --stream-loop
>>>>> VIDIOC_STREAMON returned -1 (Input/output error)
> root@rzv2h-evk:~#
>
> Logs from ISP:
> root@rzv2h-evk:~/c55# ./isp.sh
> Device /dev/video0 opened.
> Device `ARM Mali-C55 ISP' on `platform:16080000.isp' (driver
> 'mali-c55') supports video, capture, with mplanes.
> Video format set: RGB565 (50424752) 2304x1296 field none, 1 planes:
>   * Stride 4608, buffer size 5971968
> Video format: RGB565 (50424752) 2304x1296 field none, 1 planes:
>   * Stride 4608, buffer size 5971968
> 8 buffers requested.
> length: 1 offset: 4017363672 timestamp type/source: mono/EoF
> Buffer 0/0 mapped at address 0xffff81f2e000.
> length: 1 offset: 4017363672 timestamp type/source: mono/EoF
> Buffer 1/0 mapped at address 0xffff8197c000.
> length: 1 offset: 4017363672 timestamp type/source: mono/EoF
> Buffer 2/0 mapped at address 0xffff813ca000.
> length: 1 offset: 4017363672 timestamp type/source: mono/EoF
> Buffer 3/0 mapped at address 0xffff80e18000.
> length: 1 offset: 4017363672 timestamp type/source: mono/EoF
> Buffer 4/0 mapped at address 0xffff80866000.
> length: 1 offset: 4017363672 timestamp type/source: mono/EoF
> Buffer 5/0 mapped at address 0xffff802b4000.
> length: 1 offset: 4017363672 timestamp type/source: mono/EoF
> Buffer 6/0 mapped at address 0xffff7fd02000.
> length: 1 offset: 4017363672 timestamp type/source: mono/EoF
> Buffer 7/0 mapped at address 0xffff7f750000.
> [   92.647719] kauditd_printk_skb: 8 callbacks suppressed
> [   92.647734] audit: type=1006 audit(1753371566.385:25): pid=407
> uid=0 old-auid=4294967295 auid=0 tty=(none) old-ses=4294967295 ses=4
> res=1
> [   92.665263] audit: type=1300 audit(1753371566.385:25):
> arch=c00000b7 syscall=64 success=yes exit=1 a0=7 a1=ffffc4ff5740 a2=1
> a3=1 items=0 ppid=1 pid=407 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0
> egid=0 sgid=0 fsgid=0 tty=(none) ses=4 comm="sshd"
> exe="/usr/sbin/sshd" key=(null)
> [   92.689604] audit: type=1327 audit(1753371566.385:25):
> proctitle=737368643A20726F6F74205B707269765D
>
> [  100.932191] rz-dmac 11400000.dma-controller: dma_sync_wait: timeout!
> [  100.938566] mali-c55 16080000.isp: Failed to DMA xfer ISP config
> [  100.944702] mali-c55 16080000.isp: failed to write ISP config
> [  100.950562] mali-c55 16080000.isp: Failed to start ISP


Hm, Is this on the EVK? How did you set up devicetree?  It's trying to use the DMA write; I've so 
far just been using CPU write on the RZ/V2H...I'll try to replicate it and see where I get


Thanks

Dan

>
> Cheers,
> Prabhakar

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

* Re: [PATCH v4 2/3] media: platform: Add Renesas Input Video Control block driver
  2025-07-24 18:04     ` Dan Scally
@ 2025-07-24 20:18       ` Lad, Prabhakar
  0 siblings, 0 replies; 12+ messages in thread
From: Lad, Prabhakar @ 2025-07-24 20:18 UTC (permalink / raw)
  To: Dan Scally
  Cc: linux-media, devicetree, linux-renesas-soc, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Geert Uytterhoeven,
	Magnus Damm, Philipp Zabel, jacopo.mondi, biju.das.jz,
	laurent.pinchart

Hi Dan,

On Thu, Jul 24, 2025 at 7:04 PM Dan Scally <dan.scally@ideasonboard.com> wrote:
>
> Hi Prabhakar - thanks for the review
>
> On 24/07/2025 17:52, Lad, Prabhakar wrote:
> > Hi Daniel,
> >
> > Thank you for the patch.
> >
> > On Mon, Jul 14, 2025 at 4:25 PM Daniel Scally
> > <dan.scally@ideasonboard.com> wrote:
> >> Add a driver for the Input Video Control block in an RZ/V2H SoC which
> >> feeds data into the Arm Mali-C55 ISP.
> >>
> > s/V2H/V2HP everywhere.
> Ack!
> >> Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com>
> >> ---
> >> Changes in v5:
> >>
> >>          - Fixed .enum_frame_sizes() to properly check that the
> >>            given mbus_code matches the source pads format.
> >>          - Tidy up extra space in Kconfig
> >>          - Revise Kconfig option message
> >>          - Don't mark functions inline
> >>          - Fixup misleading comment
> >>          - select CONFIG_PM
> >>          - Use the new pm_sleep_ptr() functionality
> >>          - Minor formatting
> >>
> >> Changes in v4:
> >>
> >>          - Update the compatible to renesas,r9a09g057-ivc
> >>          - Dropped the media jobs / scheduler functionality, and re
> >>            worked the driver to have its own workqueue pushing frames
> >>          - Fix .enum_mbus_code() to return 20-bit output for source
> >>            pad.
> >>          - Fix some alignment issues
> >>          - Make the forwarding of sink to source pad format a more
> >>            explicit operation.
> >>          - Rename rzv2h_initialise_video_device_and_queue()
> >>          - Reversed order of v4l2_subdev_init_finalize() and
> >>            v4l2_async_register_subdev() to make sure everything is
> >>            finished initialising before registering the subdev.
> >>          - Change function to MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER
> >>          - Use a parametised macro for min vblank
> >>          - Minor formatting
> >>          - Use the DEFAULT macros for quantization / ycbcr_enc values
> >>          - Switch to using the mplane API
> >>          - Dropped select RESET_CONTROLLER
> >>          - Used the new helpers for starting a media pipeline
> >>          - Switch from threaded irq to normal with driver workqueue
> >>            and revised startup routine
> >>
> >> Changes in v3:
> >>
> >>          - Account for the renamed CRU pixel formats
> >>
> >> Changes in v2:
> >>
> >>          - Added selects and depends statements to Kconfig entry
> >>          - Fixed copyright year
> >>          - Stopped including in .c files headers already included in .h
> >>          - Fixed uninitialized variable in iterator
> >>          - Only check vvalid member in interrupt function and wait
> >>            unconditionally elsewhere
> >>          - __maybe_unused for the PM ops
> >>          - Initialise the subdevice after setting up PM
> >>          - Fixed the remove function for the driver to actually do
> >>            something.
> >>          - Some minor formatting changes
> >>          - Fixed the quantization member for the format
> >>          - Changes accounting for the v2 of the media jobs framework
> >>          - Change min_queued_buffers to 0
> >> ---
> >>   drivers/media/platform/renesas/Kconfig             |   1 +
> >>   drivers/media/platform/renesas/Makefile            |   1 +
> >>   drivers/media/platform/renesas/rzv2h-ivc/Kconfig   |  18 +
> >>   drivers/media/platform/renesas/rzv2h-ivc/Makefile  |   5 +
> >>   .../platform/renesas/rzv2h-ivc/rzv2h-ivc-dev.c     | 229 +++++++++
> >>   .../platform/renesas/rzv2h-ivc/rzv2h-ivc-subdev.c  | 376 ++++++++++++++
<snip>
> > ISP Probe:
> > [   11.600383] mali-c55 16080000.isp: Detected Mali-C55 ISP 9000043.31032022.0
> > [   11.622062] mali-c55 16080000.isp: Runtime PM usage count underflow!
> >
> > Logs from IVC:
> > root@rzv2h-evk:~/c55# media-ctl -p
> > Media controller API version 6.16.0
> >
> > Media device information
> > ------------------------
> > driver          mali-c55
> > model           ARM Mali-C55 ISP
> > serial
> > bus info        platform:16080000.isp
> > hw revision     0x1d982d6
> > driver version  6.16.0
> >
> > Device topology
> > - entity 1: mali-c55 tpg (1 pad, 1 link, 0 routes)
> >              type V4L2 subdev subtype Sensor flags 0
> >              device node name /dev/v4l-subdev0
> >          pad0: SOURCE
> >                  [stream:0 fmt:SRGGB20_1X20/1920x1080 field:none
> > colorspace:raw xfer:none ycbcr:601 quantization:lim-range]
> >                  -> "mali-c55 isp":0 []
> >
> > - entity 3: mali-c55 isp (5 pads, 6 links, 0 routes)
> >              type V4L2 subdev subtype Unknown flags 0
> >              device node name /dev/v4l-subdev1
> >          pad0: SINK,MUST_CONNECT
> >                  [stream:0 fmt:SGRBG20_1X20/2304x1296 field:none
> > colorspace:raw xfer:none ycbcr:601 quantization:lim-range
> >                   crop:(0,0)/2304x1296]
> >                  <- "mali-c55 tpg":0 []
> >                  <- "rzv2h ivc block":1 [ENABLED]
> >          pad1: SOURCE
> >                  [stream:0 fmt:RGB121212_1X36/2304x1296 field:none
> > colorspace:srgb xfer:none ycbcr:601 quantization:lim-range]
> >                  -> "mali-c55 resizer fr":0 [ENABLED,IMMUTABLE]
> >          pad2: SOURCE
> >                  [stream:0 fmt:SGRBG20_1X20/2304x1296 field:none
> > colorspace:raw xfer:none ycbcr:601 quantization:lim-range]
> >                  -> "mali-c55 resizer fr":2 [ENABLED,IMMUTABLE]
> >          pad3: SOURCE
> >                  [stream:0 fmt:unknown/0x0 field:none]
> >                  -> "mali-c55 3a stats":0 []
> >          pad4: SINK
> >                  [stream:0 fmt:unknown/0x0 field:none]
> >                  <- "mali-c55 3a params":0 []
> >
> > - entity 9: mali-c55 resizer fr (3 pads, 3 links, 0 routes)
> >              type V4L2 subdev subtype Unknown flags 0
> >              device node name /dev/v4l-subdev2
> >          pad0: SINK
> >                  [stream:0 fmt:RGB121212_1X36/2304x1296 field:none
> > colorspace:srgb xfer:srgb ycbcr:601 quantization:lim-range
> >                   crop:(0,0)/2304x1296
> >                   compose:(0,0)/2304x1296]
> >                  <- "mali-c55 isp":1 [ENABLED,IMMUTABLE]
> >          pad1: SOURCE
> >                  [stream:0 fmt:RGB121212_1X36/2304x1296 field:none
> > colorspace:srgb xfer:srgb ycbcr:601 quantization:lim-range]
> >                  -> "mali-c55 fr":0 [ENABLED]
> >          pad2: SINK
> >                  <- "mali-c55 isp":2 [ENABLED,IMMUTABLE]
> >
> > - entity 13: mali-c55 fr (1 pad, 1 link)
> >               type Node subtype V4L flags 0
> >               device node name /dev/video0
> >          pad0: SINK
> >                  <- "mali-c55 resizer fr":1 [ENABLED]
> >
> > - entity 17: mali-c55 3a params (1 pad, 1 link)
> >               type Node subtype V4L flags 0
> >               device node name /dev/video1
> >          pad0: SOURCE
> >                  -> "mali-c55 isp":4 []
> >
> > - entity 21: mali-c55 3a stats (1 pad, 1 link)
> >               type Node subtype V4L flags 0
> >               device node name /dev/video2
> >          pad0: SINK
> >                  <- "mali-c55 isp":3 []
> >
> > - entity 37: rzv2h ivc block (2 pads, 2 links, 0 routes)
> >               type V4L2 subdev subtype Unknown flags 0
> >               device node name /dev/v4l-subdev3
> >          pad0: SINK
> >                  [stream:0 fmt:SGRBG10_1X10/2304x1296 field:none
> > colorspace:raw xfer:none ycbcr:601 quantization:full-range]
> >                  <- "rzv2h-ivc":0 [ENABLED,IMMUTABLE]
> >          pad1: SOURCE
> >                  [stream:0 fmt:SGRBG20_1X20/2304x1296 field:none
> > colorspace:raw xfer:none ycbcr:601 quantization:full-range]
> >                  -> "mali-c55 isp":0 [ENABLED]
> >
> > - entity 40: rzv2h-ivc (1 pad, 1 link)
> >               type Node subtype V4L flags 0
> >               device node name /dev/video3
> >          pad0: SOURCE
> >                  -> "rzv2h ivc block":0 [ENABLED,IMMUTABLE]
> >
> > root@rzv2h-evk:~/c55#
> > root@rzv2h-evk:~# v4l2-ctl -d3 --stream-out-mmap
> > --stream-from=/root/c55/frame-15.bin --stream-loop
> >>>>> VIDIOC_STREAMON returned -1 (Input/output error)
> > root@rzv2h-evk:~#
> >
> > Logs from ISP:
> > root@rzv2h-evk:~/c55# ./isp.sh
> > Device /dev/video0 opened.
> > Device `ARM Mali-C55 ISP' on `platform:16080000.isp' (driver
> > 'mali-c55') supports video, capture, with mplanes.
> > Video format set: RGB565 (50424752) 2304x1296 field none, 1 planes:
> >   * Stride 4608, buffer size 5971968
> > Video format: RGB565 (50424752) 2304x1296 field none, 1 planes:
> >   * Stride 4608, buffer size 5971968
> > 8 buffers requested.
> > length: 1 offset: 4017363672 timestamp type/source: mono/EoF
> > Buffer 0/0 mapped at address 0xffff81f2e000.
> > length: 1 offset: 4017363672 timestamp type/source: mono/EoF
> > Buffer 1/0 mapped at address 0xffff8197c000.
> > length: 1 offset: 4017363672 timestamp type/source: mono/EoF
> > Buffer 2/0 mapped at address 0xffff813ca000.
> > length: 1 offset: 4017363672 timestamp type/source: mono/EoF
> > Buffer 3/0 mapped at address 0xffff80e18000.
> > length: 1 offset: 4017363672 timestamp type/source: mono/EoF
> > Buffer 4/0 mapped at address 0xffff80866000.
> > length: 1 offset: 4017363672 timestamp type/source: mono/EoF
> > Buffer 5/0 mapped at address 0xffff802b4000.
> > length: 1 offset: 4017363672 timestamp type/source: mono/EoF
> > Buffer 6/0 mapped at address 0xffff7fd02000.
> > length: 1 offset: 4017363672 timestamp type/source: mono/EoF
> > Buffer 7/0 mapped at address 0xffff7f750000.
> > [   92.647719] kauditd_printk_skb: 8 callbacks suppressed
> > [   92.647734] audit: type=1006 audit(1753371566.385:25): pid=407
> > uid=0 old-auid=4294967295 auid=0 tty=(none) old-ses=4294967295 ses=4
> > res=1
> > [   92.665263] audit: type=1300 audit(1753371566.385:25):
> > arch=c00000b7 syscall=64 success=yes exit=1 a0=7 a1=ffffc4ff5740 a2=1
> > a3=1 items=0 ppid=1 pid=407 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0
> > egid=0 sgid=0 fsgid=0 tty=(none) ses=4 comm="sshd"
> > exe="/usr/sbin/sshd" key=(null)
> > [   92.689604] audit: type=1327 audit(1753371566.385:25):
> > proctitle=737368643A20726F6F74205B707269765D
> >
> > [  100.932191] rz-dmac 11400000.dma-controller: dma_sync_wait: timeout!
> > [  100.938566] mali-c55 16080000.isp: Failed to DMA xfer ISP config
> > [  100.944702] mali-c55 16080000.isp: failed to write ISP config
> > [  100.950562] mali-c55 16080000.isp: Failed to start ISP
>
>
> Hm, Is this on the EVK? How did you set up devicetree?  It's trying to use the DMA write; I've so
> far just been using CPU write on the RZ/V2H...I'll try to replicate it and see where I get
>
>
Yes this is on the EVK. I have the changes [0] done to get it working
(Ive not enabled the DMA).

[0] https://gist.github.com/prabhakarlad/bb1d3649243617d88c9b4e8b386f0803

Cheers,
Prabhakar

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

* Re: [PATCH v4 2/3] media: platform: Add Renesas Input Video Control block driver
  2025-07-14 15:19 ` [PATCH v4 2/3] media: platform: Add Renesas Input Video Control block driver Daniel Scally
  2025-07-22  9:40   ` Jacopo Mondi
  2025-07-24 16:52   ` Lad, Prabhakar
@ 2025-07-30 14:10   ` Geert Uytterhoeven
  2 siblings, 0 replies; 12+ messages in thread
From: Geert Uytterhoeven @ 2025-07-30 14:10 UTC (permalink / raw)
  To: Daniel Scally
  Cc: linux-media, devicetree, linux-renesas-soc, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Magnus Damm, Philipp Zabel,
	jacopo.mondi, biju.das.jz, laurent.pinchart

Hi Daniel,

On Mon, 14 Jul 2025 at 17:19, Daniel Scally <dan.scally@ideasonboard.com> wrote:
> Add a driver for the Input Video Control block in an RZ/V2H SoC which
> feeds data into the Arm Mali-C55 ISP.
>
> Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com>

Thanks for your patch!

> --- /dev/null
> +++ b/drivers/media/platform/renesas/rzv2h-ivc/rzv2h-ivc-dev.c

> +static int rzv2h_ivc_get_hardware_resources(struct rzv2h_ivc *ivc,
> +                                           struct platform_device *pdev)
> +{
> +       const char * const resource_names[RZV2H_IVC_NUM_HW_RESOURCES] = {

static

> +               "reg",
> +               "axi",
> +               "isp",
> +       };

> +static int rzv2h_ivc_probe(struct platform_device *pdev)
> +{
> +       struct device *dev = &pdev->dev;
> +       struct rzv2h_ivc *ivc;
> +       int ret;
> +
> +       ivc = devm_kzalloc(dev, sizeof(*ivc), GFP_KERNEL);
> +       if (!ivc)
> +               return -ENOMEM;
> +
> +       ivc->dev = dev;
> +       platform_set_drvdata(pdev, ivc);
> +       mutex_init(&ivc->lock);
> +       spin_lock_init(&ivc->spinlock);
> +
> +       ret = rzv2h_ivc_get_hardware_resources(ivc, pdev);
> +       if (ret)
> +               return ret;
> +
> +       pm_runtime_set_autosuspend_delay(dev, 2000);
> +       pm_runtime_use_autosuspend(dev);
> +       pm_runtime_enable(dev);
> +
> +       ivc->irqnum = platform_get_irq(pdev, 0);
> +       if (ivc->irqnum < 0) {
> +               dev_err(dev, "failed to get interrupt\n");

No need to print anything, as platform_get_irq() already calls
dev_err_probe() on failure.

> +               return ret;
> +       }
> +
> +       ret = rzv2h_ivc_initialise_subdevice(ivc);
> +       if (ret)
> +               return ret;
> +
> +       return 0;
> +}

Gr{oetje,eeting}s,

                        Geert

-- 
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org

In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
                                -- Linus Torvalds

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

end of thread, other threads:[~2025-07-30 14:10 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-07-14 15:19 [PATCH v4 0/3] Add Input Video Control Block driver for RZ/V2H Daniel Scally
2025-07-14 15:19 ` [PATCH v4 1/3] dt-bindings: media: Add bindings for the RZ/V2H IVC block Daniel Scally
2025-07-24 15:55   ` Lad, Prabhakar
2025-07-14 15:19 ` [PATCH v4 2/3] media: platform: Add Renesas Input Video Control block driver Daniel Scally
2025-07-22  9:40   ` Jacopo Mondi
2025-07-23 21:17     ` Dan Scally
2025-07-24 16:52   ` Lad, Prabhakar
2025-07-24 18:04     ` Dan Scally
2025-07-24 20:18       ` Lad, Prabhakar
2025-07-30 14:10   ` Geert Uytterhoeven
2025-07-14 15:19 ` [PATCH v4 3/3] MAINTAINERS: Add entry for rzv2h-ivc driver Daniel Scally
2025-07-14 16:07   ` Krzysztof Kozlowski

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).