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