Merge branches 'adv7482', 'rcar-csi2', 'rcar-vin-mc' and 'rcar-local' into rcar-vin-elinux
diff --git a/Documentation/devicetree/bindings/media/rcar-csi2.txt b/Documentation/devicetree/bindings/media/rcar-csi2.txt
new file mode 100644
index 0000000..f6e2027
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/rcar-csi2.txt
@@ -0,0 +1,116 @@
+Renesas R-Car MIPI CSI-2
+------------------------
+
+The rcar-csi2 device provides MIPI CSI-2 capabilities for the Renesas R-Car
+family of devices. It is to be used in conjunction with the R-Car VIN module,
+which provides the video capture capabilities.
+
+ - compatible: Must be one or more of the following
+ - "renesas,r8a7795-csi2" for the R8A7795 device.
+ - "renesas,r8a7796-csi2" for the R8A7796 device.
+ - "renesas,rcar-gen3-csi2" for a generic R-Car Gen3 compatible device.
+
+ When compatible with a generic version nodes must list the
+ SoC-specific version corresponding to the platform first
+ followed by the generic version.
+
+ - reg: the register base and size for the device registers
+ - interrupts: the interrupt for the device
+ - clocks: Reference to the parent clock
+
+The device node should contain two 'port' child nodes according to the
+bindings defined in Documentation/devicetree/bindings/media/
+video-interfaces.txt. Port 0 should connect the node that is the video
+source for to the CSI-2. Port 1 should connect all the R-Car VIN
+modules, which can make use of the CSI-2 module.
+
+- Port 0 - Video source
+ - Reg 0 - sub-node describing the endpoint that is the video source
+
+- Port 1 - VIN instances
+ - Reg 0 - sub-node describing the endpoint that is VIN0
+ - Reg 1 - sub-node describing the endpoint that is VIN1
+ - Reg 2 - sub-node describing the endpoint that is VIN2
+ - Reg 3 - sub-node describing the endpoint that is VIN3
+ - Reg 4 - sub-node describing the endpoint that is VIN4
+ - Reg 5 - sub-node describing the endpoint that is VIN5
+ - Reg 6 - sub-node describing the endpoint that is VIN6
+ - Reg 7 - sub-node describing the endpoint that is VIN7
+
+Example:
+
+/* SoC properties */
+
+ csi20: csi2@fea80000 {
+ compatible = "renesas,r8a7796-csi2";
+ reg = <0 0xfea80000 0 0x10000>;
+ interrupts = <0 184 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cpg CPG_MOD 714>;
+ power-domains = <&sysc R8A7796_PD_ALWAYS_ON>;
+ status = "disabled";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ reg = <1>;
+
+ csi20vin0: endpoint@0 {
+ reg = <0>;
+ remote-endpoint = <&vin0csi20>;
+ };
+ csi20vin1: endpoint@1 {
+ reg = <1>;
+ remote-endpoint = <&vin1csi20>;
+ };
+ csi20vin2: endpoint@2 {
+ reg = <2>;
+ remote-endpoint = <&vin2csi20>;
+ };
+ csi20vin3: endpoint@3 {
+ reg = <3>;
+ remote-endpoint = <&vin3csi20>;
+ };
+ csi20vin4: endpoint@4 {
+ reg = <4>;
+ remote-endpoint = <&vin4csi20>;
+ };
+ csi20vin5: endpoint@5 {
+ reg = <5>;
+ remote-endpoint = <&vin5csi20>;
+ };
+ csi20vin6: endpoint@6 {
+ reg = <6>;
+ remote-endpoint = <&vin6csi20>;
+ };
+ csi20vin7: endpoint@7 {
+ reg = <7>;
+ remote-endpoint = <&vin7csi20>;
+ };
+ };
+ };
+ };
+
+/* Board properties */
+
+ &csi20 {
+ status = "okay";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ csi20_in: endpoint@0 {
+ clock-lanes = <0>;
+ data-lanes = <1>;
+ remote-endpoint = <&adv7482_txb>;
+ };
+ };
+ };
+ };
diff --git a/Documentation/devicetree/bindings/media/rcar_vin.txt b/Documentation/devicetree/bindings/media/rcar_vin.txt
index 6a4e61c..25fc933 100644
--- a/Documentation/devicetree/bindings/media/rcar_vin.txt
+++ b/Documentation/devicetree/bindings/media/rcar_vin.txt
@@ -2,10 +2,15 @@
------------------------------------------
The rcar_vin device provides video input capabilities for the Renesas R-Car
-family of devices. The current blocks are always slaves and suppot one input
-channel which can be either RGB, YUYV or BT656.
+family of devices.
+
+On Gen2 the current blocks are always slaves and support one input channel
+which can be either RGB, YUYV or BT656. On Gen3 the current blocks are
+always slaves and support multiple input channels which can be either RGB,
+YUVU, BT656 or CSI-2.
- compatible: Must be one or more of the following
+ - "renesas,vin-r8a7796" for the R8A7796 device
- "renesas,vin-r8a7795" for the R8A7795 device
- "renesas,vin-r8a7794" for the R8A7794 device
- "renesas,vin-r8a7793" for the R8A7793 device
@@ -28,7 +33,7 @@
Additionally, an alias named vinX will need to be created to specify
which video input device this is.
-The per-board settings:
+The per-board settings Gen2:
- port sub-node describing a single endpoint connected to the vin
as described in video-interfaces.txt[1]. Only the first one will
be considered as each vin interface has one input port.
@@ -36,13 +41,23 @@
These settings are used to work out video input format and widths
into the system.
+The per-board settings Gen3:
-Device node example
--------------------
+- renesas,id - ID number of the VIN
+- ports
+ - port@0 - Digital video source (same as port node on Gen2)
+ - port@1 - CSI-2 video sources
+ -reg 0 - sub-node describing the endpoint which is CSI20
+ -reg 1 - sub-node describing the endpoint which is CSI21
+ -reg 2 - sub-node describing the endpoint which is CSI40
+ -reg 3 - sub-node describing the endpoint which is CSI41
- aliases {
- vin0 = &vin0;
- };
+Device node example Gen2
+------------------------
+
+ aliases {
+ vin0 = &vin0;
+ };
vin0: vin@0xe6ef0000 {
compatible = "renesas,vin-r8a7790", "renesas,rcar-gen2-vin";
@@ -52,8 +67,8 @@
status = "disabled";
};
-Board setup example (vin1 composite video input)
-------------------------------------------------
+Board setup example Gen2 (vin1 composite video input)
+-----------------------------------------------------
&i2c2 {
status = "ok";
@@ -92,6 +107,94 @@
};
};
+Device node example Gen3
+------------------------
+ vin0: video@e6ef0000 {
+ compatible = "renesas,vin-r8a7795";
+ reg = <0 0xe6ef0000 0 0x1000>;
+ interrupts = <GIC_SPI 188 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cpg CPG_MOD 811>;
+ power-domains = <&sysc R8A7795_PD_ALWAYS_ON>;
+ status = "disabled";
+
+ renesas,id = <0>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ reg = <1>;
+
+ vin0csi20: endpoint@0 {
+ reg = <0>;
+ remote-endpoint= <&csi20vin0>;
+ };
+ vin0csi21: endpoint@1 {
+ reg = <1>;
+ remote-endpoint= <&csi21vin0>;
+ };
+ vin0csi40: endpoint@2 {
+ reg = <2>;
+ remote-endpoint= <&csi40vin0>;
+ };
+ };
+ };
+ };
+
+ csi20: csi2@fea80000 {
+ compatible = "renesas,r8a7795-csi2", "renesas,rcar-gen3-csi2";
+ reg = <0 0xfea80000 0 0x10000>;
+ interrupts = <GIC_SPI 184 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cpg CPG_MOD 714>;
+ power-domains = <&sysc R8A7795_PD_ALWAYS_ON>;
+ status = "disabled";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ reg = <1>;
+
+ csi20vin0: endpoint@0 {
+ remote-endpoint = <&vin0csi20>;
+ };
+ };
+ };
+ };
+
+
+Board setup example Gen3 (CSI-2)
+--------------------------------
+
+ &vin0 {
+ status = "okay";
+ };
+
+ csi20 {
+ status = "okay";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ csi20_in: endpoint@0 {
+ clock-lanes = <0>;
+ data-lanes = <1>;
+ remote-endpoint = <&adv7482_txb>;
+ };
+ };
+ };
+ };
[1] video-interfaces.txt common video media interface
diff --git a/Documentation/media/kapi/v4l2-subdev.rst b/Documentation/media/kapi/v4l2-subdev.rst
index e1f0b72..28ae681 100644
--- a/Documentation/media/kapi/v4l2-subdev.rst
+++ b/Documentation/media/kapi/v4l2-subdev.rst
@@ -262,6 +262,26 @@
called. When a subdevice is removed from the system the .unbind() method is
called. All three callbacks are optional.
+Subdevice drivers might in turn register subnotifier objects with an
+array of other subdevice descriptors that the subdevice needs for its
+own operation. Subnotifiers are an extension of the bridge drivers
+notifier to allow for a incremental registering and matching of
+subdevices. This is useful when a driver only have information about
+which subdevice is closet it self and would require knowledge from the
+driver of that subdevice to know which other subdevice(s) lies beyond.
+By registering subnotifiers drivers can incrementally move the subdevice
+matching down the chain of drivers. This is performed using the
+:c:func:`v4l2_async_subnotifier_register` call. To unregister the
+subnotifier the driver has to call
+:c:func:`v4l2_async_subnotifier_unregister`. These functions and its
+arguments behave almost the same as the bridge driver notifiers
+described above and are treated equally by the V4L2 core when matching
+asynchronously registered subdevices. The differences are that the
+subnotifier functions act on :c:type:`v4l2_subdev` instead of
+:c:type:`v4l2_device` and that they should be called from the subdevices
+``.registered()`` and ``.unregistered()``
+:c:type:`v4l2_subdev_internal_ops` callbacks instead of at probe time.
+
V4L2 sub-device userspace API
-----------------------------
diff --git a/arch/arm64/boot/dts/renesas/r8a7795-salvator-x.dts b/arch/arm64/boot/dts/renesas/r8a7795-salvator-x.dts
index 639aa08..1d3896d 100644
--- a/arch/arm64/boot/dts/renesas/r8a7795-salvator-x.dts
+++ b/arch/arm64/boot/dts/renesas/r8a7795-salvator-x.dts
@@ -46,7 +46,7 @@
};
chosen {
- bootargs = "ignore_loglevel rw root=/dev/nfs ip=dhcp";
+ bootargs = "ignore_loglevel rw root=/dev/nfs cma=64M ip=dhcp";
stdout-path = "serial0:115200n8";
};
@@ -400,6 +400,64 @@
};
};
+&i2c4 {
+ status = "okay";
+
+ clock-frequency = <100000>;
+
+ video_receiver@70 {
+ compatible = "adi,adv7482";
+ reg = <0x70>;
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@10 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ reg = <10>;
+
+ adv7482_txa: endpoint@1 {
+ reg = <1>;
+ clock-lanes = <0>;
+ data-lanes = <1 2 3 4>;
+ remote-endpoint = <&csi40_in>;
+ };
+ };
+ };
+
+ /*
+ This is a HACK and is in reality the same dev as 0x70
+ But due to V4L2 limitations we need it to be two subdevices
+ to test VIN + CSI2. There will be two instances of the
+ ADV7482 driver controlling the same chip, but *most* parts
+ are independent of the other and i2c is used in a bad way
+ so it sort of works to test.
+ */
+ video_receiver@34 {
+ compatible = "adi,adv7482";
+ reg = <0x34>;
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@11 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ reg = <11>;
+
+ adv7482_txb: endpoint@1 {
+ reg = <1>;
+ clock-lanes = <0>;
+ data-lanes = <1>;
+ remote-endpoint = <&csi20_in>;
+ };
+ };
+ };
+};
+
&rcar_sound {
pinctrl-0 = <&sound_pins &sound_clk_pins>;
pinctrl-names = "default";
@@ -582,3 +640,81 @@
&pciec1 {
status = "okay";
};
+
+&vin0 {
+ status = "okay";
+};
+
+&vin1 {
+ status = "okay";
+};
+
+&vin2 {
+ status = "okay";
+};
+
+&vin3 {
+ status = "okay";
+};
+
+&vin4 {
+ status = "okay";
+};
+
+&vin5 {
+ status = "okay";
+};
+
+&vin6 {
+ status = "okay";
+};
+
+&vin7 {
+ status = "okay";
+};
+
+&csi20 {
+ status = "okay";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ reg = <0>;
+
+ csi20_in: endpoint@0 {
+ reg = <0>;
+ clock-lanes = <0>;
+ data-lanes = <1>;
+ remote-endpoint = <&adv7482_txb>;
+ };
+ };
+ };
+};
+
+&csi40 {
+ status = "okay";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ reg = <0>;
+
+ csi40_in: endpoint@0 {
+ reg = <0>;
+ clock-lanes = <0>;
+ data-lanes = <1 2 3 4>;
+ remote-endpoint = <&adv7482_txa>;
+ };
+ };
+ };
+};
diff --git a/arch/arm64/boot/dts/renesas/r8a7795.dtsi b/arch/arm64/boot/dts/renesas/r8a7795.dtsi
index e99d644..517da42 100644
--- a/arch/arm64/boot/dts/renesas/r8a7795.dtsi
+++ b/arch/arm64/boot/dts/renesas/r8a7795.dtsi
@@ -1068,6 +1068,480 @@
status = "disabled";
};
+ vin0: video@e6ef0000 {
+ compatible = "renesas,vin-r8a7795";
+ reg = <0 0xe6ef0000 0 0x1000>;
+ interrupts = <GIC_SPI 188 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cpg CPG_MOD 811>;
+ power-domains = <&sysc R8A7795_PD_ALWAYS_ON>;
+ status = "disabled";
+
+ renesas,id = <0>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ reg = <1>;
+
+ vin0csi20: endpoint@0 {
+ reg = <0>;
+ remote-endpoint= <&csi20vin0>;
+ };
+ vin0csi21: endpoint@1 {
+ reg = <1>;
+ remote-endpoint= <&csi21vin0>;
+ };
+ vin0csi40: endpoint@2 {
+ reg = <2>;
+ remote-endpoint= <&csi40vin0>;
+ };
+ };
+ };
+ };
+
+ vin1: video@e6ef1000 {
+ compatible = "renesas,vin-r8a7795";
+ reg = <0 0xe6ef1000 0 0x1000>;
+ interrupts = <GIC_SPI 189 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cpg CPG_MOD 810>;
+ power-domains = <&sysc R8A7795_PD_ALWAYS_ON>;
+ status = "disabled";
+
+ renesas,id = <1>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ reg = <1>;
+
+ vin1csi20: endpoint@0 {
+ reg = <0>;
+ remote-endpoint= <&csi20vin1>;
+ };
+ vin1csi21: endpoint@1 {
+ reg = <1>;
+ remote-endpoint= <&csi21vin1>;
+ };
+ vin1csi40: endpoint@2 {
+ reg = <2>;
+ remote-endpoint= <&csi40vin1>;
+ };
+ };
+ };
+ };
+
+ vin2: video@e6ef2000 {
+ compatible = "renesas,vin-r8a7795";
+ reg = <0 0xe6ef2000 0 0x1000>;
+ interrupts = <GIC_SPI 190 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cpg CPG_MOD 809>;
+ power-domains = <&sysc R8A7795_PD_ALWAYS_ON>;
+ status = "disabled";
+
+ renesas,id = <2>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ reg = <1>;
+
+ vin2csi20: endpoint@0 {
+ reg = <0>;
+ remote-endpoint= <&csi20vin2>;
+ };
+ vin2csi21: endpoint@1 {
+ reg = <1>;
+ remote-endpoint= <&csi21vin2>;
+ };
+ vin2csi40: endpoint@2 {
+ reg = <2>;
+ remote-endpoint= <&csi40vin2>;
+ };
+ };
+ };
+ };
+
+ vin3: video@e6ef3000 {
+ compatible = "renesas,vin-r8a7795";
+ reg = <0 0xe6ef3000 0 0x1000>;
+ interrupts = <GIC_SPI 191 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cpg CPG_MOD 808>;
+ power-domains = <&sysc R8A7795_PD_ALWAYS_ON>;
+ status = "disabled";
+
+ renesas,id = <3>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ reg = <1>;
+
+ vin3csi20: endpoint@0 {
+ reg = <0>;
+ remote-endpoint= <&csi20vin3>;
+ };
+ vin3csi21: endpoint@1 {
+ reg = <1>;
+ remote-endpoint= <&csi21vin3>;
+ };
+ vin3csi40: endpoint@2 {
+ reg = <2>;
+ remote-endpoint= <&csi40vin3>;
+ };
+ };
+ };
+ };
+
+ vin4: video@e6ef4000 {
+ compatible = "renesas,vin-r8a7795";
+ reg = <0 0xe6ef4000 0 0x1000>;
+ interrupts = <GIC_SPI 174 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cpg CPG_MOD 807>;
+ power-domains = <&sysc R8A7795_PD_ALWAYS_ON>;
+ status = "disabled";
+
+ renesas,id = <4>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ reg = <1>;
+
+ vin4csi20: endpoint@0 {
+ reg = <0>;
+ remote-endpoint= <&csi20vin4>;
+ };
+ vin4csi21: endpoint@1 {
+ reg = <1>;
+ remote-endpoint= <&csi21vin4>;
+ };
+ vin4csi41: endpoint@3 {
+ reg = <3>;
+ remote-endpoint= <&csi41vin4>;
+ };
+ };
+ };
+ };
+
+ vin5: video@e6ef5000 {
+ compatible = "renesas,vin-r8a7795";
+ reg = <0 0xe6ef5000 0 0x1000>;
+ interrupts = <GIC_SPI 175 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cpg CPG_MOD 806>;
+ power-domains = <&sysc R8A7795_PD_ALWAYS_ON>;
+ status = "disabled";
+
+ renesas,id = <5>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ reg = <1>;
+
+ vin5csi20: endpoint@0 {
+ reg = <0>;
+ remote-endpoint= <&csi20vin5>;
+ };
+ vin5csi21: endpoint@1 {
+ reg = <1>;
+ remote-endpoint= <&csi21vin5>;
+ };
+ vin5csi41: endpoint@3 {
+ reg = <3>;
+ remote-endpoint= <&csi41vin5>;
+ };
+ };
+ };
+ };
+
+ vin6: video@e6ef6000 {
+ compatible = "renesas,vin-r8a7795";
+ reg = <0 0xe6ef6000 0 0x1000>;
+ interrupts = <GIC_SPI 176 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cpg CPG_MOD 805>;
+ power-domains = <&sysc R8A7795_PD_ALWAYS_ON>;
+ status = "disabled";
+
+ renesas,id = <6>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ reg = <1>;
+
+ vin6csi20: endpoint@0 {
+ reg = <0>;
+ remote-endpoint= <&csi20vin6>;
+ };
+ vin6csi21: endpoint@1 {
+ reg = <1>;
+ remote-endpoint= <&csi21vin6>;
+ };
+ vin6csi41: endpoint@3 {
+ reg = <3>;
+ remote-endpoint= <&csi41vin6>;
+ };
+ };
+ };
+ };
+
+ vin7: video@e6ef7000 {
+ compatible = "renesas,vin-r8a7795";
+ reg = <0 0xe6ef7000 0 0x1000>;
+ interrupts = <GIC_SPI 171 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cpg CPG_MOD 804>;
+ power-domains = <&sysc R8A7795_PD_ALWAYS_ON>;
+ status = "disabled";
+
+ renesas,id = <7>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ reg = <1>;
+
+ vin7csi20: endpoint@0 {
+ reg = <0>;
+ remote-endpoint= <&csi20vin7>;
+ };
+ vin7csi21: endpoint@1 {
+ reg = <1>;
+ remote-endpoint= <&csi21vin7>;
+ };
+ vin7csi41: endpoint@3 {
+ reg = <3>;
+ remote-endpoint= <&csi41vin7>;
+ };
+ };
+ };
+ };
+
+ csi20: csi2@fea80000 {
+ compatible = "renesas,r8a7795-csi2", "renesas,rcar-gen3-csi2";
+ reg = <0 0xfea80000 0 0x10000>;
+ interrupts = <GIC_SPI 184 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cpg CPG_MOD 714>;
+ power-domains = <&sysc R8A7795_PD_ALWAYS_ON>;
+ status = "disabled";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ reg = <1>;
+
+ csi20vin0: endpoint@0 {
+ reg = <0>;
+ remote-endpoint = <&vin0csi20>;
+ };
+ csi20vin1: endpoint@1 {
+ reg = <1>;
+ remote-endpoint = <&vin1csi20>;
+ };
+ csi20vin2: endpoint@2 {
+ reg = <2>;
+ remote-endpoint = <&vin2csi20>;
+ };
+ csi20vin3: endpoint@3 {
+ reg = <3>;
+ remote-endpoint = <&vin3csi20>;
+ };
+ csi20vin4: endpoint@4 {
+ reg = <4>;
+ remote-endpoint = <&vin4csi20>;
+ };
+ csi20vin5: endpoint@5 {
+ reg = <5>;
+ remote-endpoint = <&vin5csi20>;
+ };
+ csi20vin6: endpoint@6 {
+ reg = <6>;
+ remote-endpoint = <&vin6csi20>;
+ };
+ csi20vin7: endpoint@7 {
+ reg = <7>;
+ remote-endpoint = <&vin7csi20>;
+ };
+ };
+ };
+ };
+
+ csi21: csi2@fea90000 {
+ compatible = "renesas,r8a7795-csi2", "renesas,rcar-gen3-csi2";
+ reg = <0 0xfea90000 0 0x10000>;
+ interrupts = <GIC_SPI 185 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cpg CPG_MOD 713>;
+ power-domains = <&sysc R8A7795_PD_ALWAYS_ON>;
+ status = "disabled";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ reg = <1>;
+
+ csi21vin0: endpoint@0 {
+ reg = <0>;
+ remote-endpoint = <&vin0csi21>;
+ };
+ csi21vin1: endpoint@1 {
+ reg = <1>;
+ remote-endpoint = <&vin1csi21>;
+ };
+ csi21vin2: endpoint@2 {
+ reg = <2>;
+ remote-endpoint = <&vin2csi21>;
+ };
+ csi21vin3: endpoint@3 {
+ reg = <3>;
+ remote-endpoint = <&vin3csi21>;
+ };
+ csi21vin4: endpoint@4 {
+ reg = <4>;
+ remote-endpoint = <&vin4csi21>;
+ };
+ csi21vin5: endpoint@5 {
+ reg = <5>;
+ remote-endpoint = <&vin5csi21>;
+ };
+ csi21vin6: endpoint@6 {
+ reg = <6>;
+ remote-endpoint = <&vin6csi21>;
+ };
+ csi21vin7: endpoint@7 {
+ reg = <7>;
+ remote-endpoint = <&vin7csi21>;
+ };
+ };
+ };
+ };
+
+ csi40: csi2@feaa0000 {
+ compatible = "renesas,r8a7795-csi2", "renesas,rcar-gen3-csi2";
+ reg = <0 0xfeaa0000 0 0x10000>;
+ interrupts = <GIC_SPI 246 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cpg CPG_MOD 716>;
+ power-domains = <&sysc R8A7795_PD_ALWAYS_ON>;
+ status = "disabled";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ reg = <1>;
+
+ csi40vin0: endpoint@0 {
+ reg = <0>;
+ remote-endpoint = <&vin0csi40>;
+ };
+ csi40vin1: endpoint@1 {
+ reg = <1>;
+ remote-endpoint = <&vin1csi40>;
+ };
+ csi40vin2: endpoint@2 {
+ reg = <2>;
+ remote-endpoint = <&vin2csi40>;
+ };
+ csi40vin3: endpoint@3 {
+ reg = <3>;
+ remote-endpoint = <&vin3csi40>;
+ };
+ };
+
+ };
+ };
+
+ csi41: csi2@feab0000 {
+ compatible = "renesas,r8a7795-csi2", "renesas,rcar-gen3-csi2";
+ reg = <0 0xfeab0000 0 0x10000>;
+ interrupts = <GIC_SPI 247 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cpg CPG_MOD 715>;
+ power-domains = <&sysc R8A7795_PD_ALWAYS_ON>;
+ status = "disabled";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ reg = <1>;
+
+ csi41vin4: endpoint@0 {
+ reg = <0>;
+ remote-endpoint = <&vin4csi41>;
+ };
+ csi41vin5: endpoint@1 {
+ reg = <1>;
+ remote-endpoint = <&vin5csi41>;
+ };
+ csi41vin6: endpoint@2 {
+ reg = <2>;
+ remote-endpoint = <&vin6csi41>;
+ };
+ csi41vin7: endpoint@3 {
+ reg = <3>;
+ remote-endpoint = <&vin7csi41>;
+ };
+ };
+
+ };
+ };
+
rcar_sound: sound@ec500000 {
/*
* #sound-dai-cells is required
diff --git a/arch/arm64/boot/dts/renesas/r8a7796-salvator-x.dts b/arch/arm64/boot/dts/renesas/r8a7796-salvator-x.dts
index c9f59b6..ca4eb98 100644
--- a/arch/arm64/boot/dts/renesas/r8a7796-salvator-x.dts
+++ b/arch/arm64/boot/dts/renesas/r8a7796-salvator-x.dts
@@ -23,7 +23,7 @@
};
chosen {
- bootargs = "ignore_loglevel";
+ bootargs = "ignore_loglevel rw root=/dev/nfs cma=64M ip=dhcp";
stdout-path = "serial0:115200n8";
};
@@ -267,3 +267,139 @@
&i2c_dvfs {
status = "okay";
};
+
+&i2c4 {
+ status = "okay";
+
+ clock-frequency = <100000>;
+
+ video_receiver@70 {
+ compatible = "adi,adv7482";
+ reg = <0x70>;
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@10 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ reg = <10>;
+
+ adv7482_txa: endpoint@1 {
+ reg = <1>;
+ clock-lanes = <0>;
+ data-lanes = <1 2 3 4>;
+ remote-endpoint = <&csi40_in>;
+ };
+ };
+ };
+
+ /*
+ This is a HACK and is in reality the same dev as 0x70
+ But due to V4L2 limitations we need it to be two subdevices
+ to test VIN + CSI2. There will be two instances of the
+ ADV7482 driver controlling the same chip, but *most* parts
+ are independent of the other and i2c is used in a bad way
+ so it sort of works to test.
+ */
+ video_receiver@34 {
+ compatible = "adi,adv7482";
+ reg = <0x34>;
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@11 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ reg = <11>;
+
+ adv7482_txb: endpoint@1 {
+ reg = <1>;
+ clock-lanes = <0>;
+ data-lanes = <1>;
+ remote-endpoint = <&csi20_in>;
+ };
+ };
+ };
+};
+
+&vin0 {
+ status = "okay";
+};
+
+&vin1 {
+ status = "okay";
+};
+
+&vin2 {
+ status = "okay";
+};
+
+&vin3 {
+ status = "okay";
+};
+
+&vin4 {
+ status = "okay";
+};
+
+&vin5 {
+ status = "okay";
+};
+
+&vin6 {
+ status = "okay";
+};
+
+&vin7 {
+ status = "okay";
+};
+
+&csi20 {
+ status = "okay";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ reg = <0>;
+
+ csi20_in: endpoint@0 {
+ reg = <0>;
+ clock-lanes = <0>;
+ data-lanes = <1>;
+ remote-endpoint = <&adv7482_txb>;
+ };
+ };
+ };
+};
+
+&csi40 {
+ status = "okay";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ reg = <0>;
+
+ csi40_in: endpoint@0 {
+ reg = <0>;
+ clock-lanes = <0>;
+ data-lanes = <1 2 3 4>;
+ remote-endpoint = <&adv7482_txa>;
+ };
+ };
+ };
+};
diff --git a/arch/arm64/boot/dts/renesas/r8a7796.dtsi b/arch/arm64/boot/dts/renesas/r8a7796.dtsi
index 2ec1ed5f..c1cdc15 100644
--- a/arch/arm64/boot/dts/renesas/r8a7796.dtsi
+++ b/arch/arm64/boot/dts/renesas/r8a7796.dtsi
@@ -704,6 +704,371 @@
status = "disabled";
};
+ vin0: video@e6ef0000 {
+ compatible = "renesas,vin-r8a7796";
+ reg = <0 0xe6ef0000 0 0x1000>;
+ interrupts = <GIC_SPI 188 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cpg CPG_MOD 811>;
+ power-domains = <&sysc R8A7796_PD_ALWAYS_ON>;
+ status = "disabled";
+
+ renesas,id = <0>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ reg = <1>;
+
+ vin0csi20: endpoint@0 {
+ reg = <0>;
+ remote-endpoint= <&csi20vin0>;
+ };
+ vin0csi40: endpoint@2 {
+ reg = <2>;
+ remote-endpoint= <&csi40vin0>;
+ };
+ };
+ };
+ };
+
+ vin1: video@e6ef1000 {
+ compatible = "renesas,vin-r8a7796";
+ reg = <0 0xe6ef1000 0 0x1000>;
+ interrupts = <GIC_SPI 189 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cpg CPG_MOD 810>;
+ power-domains = <&sysc R8A7796_PD_ALWAYS_ON>;
+ status = "disabled";
+
+ renesas,id = <1>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ reg = <1>;
+
+ vin1csi20: endpoint@0 {
+ reg = <0>;
+ remote-endpoint= <&csi20vin1>;
+ };
+ vin1csi40: endpoint@2 {
+ reg = <2>;
+ remote-endpoint= <&csi40vin1>;
+ };
+ };
+ };
+ };
+
+ vin2: video@e6ef2000 {
+ compatible = "renesas,vin-r8a7796";
+ reg = <0 0xe6ef2000 0 0x1000>;
+ interrupts = <GIC_SPI 190 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cpg CPG_MOD 809>;
+ power-domains = <&sysc R8A7796_PD_ALWAYS_ON>;
+ status = "disabled";
+
+ renesas,id = <2>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ reg = <1>;
+
+ vin2csi20: endpoint@0 {
+ reg = <0>;
+ remote-endpoint= <&csi20vin2>;
+ };
+ vin2csi40: endpoint@2 {
+ reg = <2>;
+ remote-endpoint= <&csi40vin2>;
+ };
+ };
+ };
+ };
+
+ vin3: video@e6ef3000 {
+ compatible = "renesas,vin-r8a7796";
+ reg = <0 0xe6ef3000 0 0x1000>;
+ interrupts = <GIC_SPI 191 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cpg CPG_MOD 808>;
+ power-domains = <&sysc R8A7796_PD_ALWAYS_ON>;
+ status = "disabled";
+
+ renesas,id = <3>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ reg = <1>;
+
+ vin3csi20: endpoint@0 {
+ reg = <0>;
+ remote-endpoint= <&csi20vin3>;
+ };
+ vin3csi40: endpoint@2 {
+ reg = <2>;
+ remote-endpoint= <&csi40vin3>;
+ };
+ };
+ };
+ };
+
+ vin4: video@e6ef4000 {
+ compatible = "renesas,vin-r8a7796";
+ reg = <0 0xe6ef4000 0 0x1000>;
+ interrupts = <GIC_SPI 174 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cpg CPG_MOD 807>;
+ power-domains = <&sysc R8A7796_PD_ALWAYS_ON>;
+ status = "disabled";
+
+ renesas,id = <4>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ reg = <1>;
+
+ vin4csi20: endpoint@0 {
+ reg = <0>;
+ remote-endpoint= <&csi20vin4>;
+ };
+ vin4csi40: endpoint@2 {
+ reg = <2>;
+ remote-endpoint= <&csi40vin4>;
+ };
+ };
+ };
+ };
+
+ vin5: video@e6ef5000 {
+ compatible = "renesas,vin-r8a7796";
+ reg = <0 0xe6ef5000 0 0x1000>;
+ interrupts = <GIC_SPI 175 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cpg CPG_MOD 806>;
+ power-domains = <&sysc R8A7796_PD_ALWAYS_ON>;
+ status = "disabled";
+
+ renesas,id = <5>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ reg = <1>;
+
+ vin5csi20: endpoint@0 {
+ reg = <0>;
+ remote-endpoint= <&csi20vin5>;
+ };
+ vin5csi40: endpoint@2 {
+ reg = <2>;
+ remote-endpoint= <&csi40vin5>;
+ };
+ };
+ };
+ };
+
+ vin6: video@e6ef6000 {
+ compatible = "renesas,vin-r8a7796";
+ reg = <0 0xe6ef6000 0 0x1000>;
+ interrupts = <GIC_SPI 176 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cpg CPG_MOD 805>;
+ power-domains = <&sysc R8A7796_PD_ALWAYS_ON>;
+ status = "disabled";
+
+ renesas,id = <6>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ reg = <1>;
+
+ vin6csi20: endpoint@0 {
+ reg = <0>;
+ remote-endpoint= <&csi20vin6>;
+ };
+ vin6csi40: endpoint@2 {
+ reg = <2>;
+ remote-endpoint= <&csi40vin6>;
+ };
+ };
+ };
+ };
+
+ vin7: video@e6ef7000 {
+ compatible = "renesas,vin-r8a7796";
+ reg = <0 0xe6ef7000 0 0x1000>;
+ interrupts = <GIC_SPI 171 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cpg CPG_MOD 804>;
+ power-domains = <&sysc R8A7796_PD_ALWAYS_ON>;
+ status = "disabled";
+
+ renesas,id = <7>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ reg = <1>;
+
+ vin7csi20: endpoint@0 {
+ reg = <0>;
+ remote-endpoint= <&csi20vin7>;
+ };
+ vin7csi40: endpoint@2 {
+ reg = <2>;
+ remote-endpoint= <&csi40vin7>;
+ };
+ };
+ };
+ };
+
+ csi20: csi2@fea80000 {
+ compatible = "renesas,r8a7796-csi2", "renesas,rcar-gen3-csi2";
+ reg = <0 0xfea80000 0 0x10000>;
+ interrupts = <GIC_SPI 184 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cpg CPG_MOD 714>;
+ power-domains = <&sysc R8A7796_PD_ALWAYS_ON>;
+ status = "disabled";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ reg = <1>;
+
+ csi20vin0: endpoint@0 {
+ reg = <0>;
+ remote-endpoint = <&vin0csi20>;
+ };
+ csi20vin1: endpoint@1 {
+ reg = <1>;
+ remote-endpoint = <&vin1csi20>;
+ };
+ csi20vin2: endpoint@2 {
+ reg = <2>;
+ remote-endpoint = <&vin2csi20>;
+ };
+ csi20vin3: endpoint@3 {
+ reg = <3>;
+ remote-endpoint = <&vin3csi20>;
+ };
+ csi20vin4: endpoint@4 {
+ reg = <4>;
+ remote-endpoint = <&vin4csi20>;
+ };
+ csi20vin5: endpoint@5 {
+ reg = <5>;
+ remote-endpoint = <&vin5csi20>;
+ };
+ csi20vin6: endpoint@6 {
+ reg = <6>;
+ remote-endpoint = <&vin6csi20>;
+ };
+ csi20vin7: endpoint@7 {
+ reg = <7>;
+ remote-endpoint = <&vin7csi20>;
+ };
+ };
+ };
+ };
+
+ csi40: csi2@feaa0000 {
+ compatible = "renesas,r8a7796-csi2", "renesas,rcar-gen3-csi2";
+ reg = <0 0xfeaa0000 0 0x10000>;
+ interrupts = <GIC_SPI 246 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cpg CPG_MOD 716>;
+ power-domains = <&sysc R8A7796_PD_ALWAYS_ON>;
+ status = "disabled";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ reg = <1>;
+
+ csi40vin0: endpoint@0 {
+ reg = <0>;
+ remote-endpoint = <&vin0csi40>;
+ };
+ csi40vin1: endpoint@1 {
+ reg = <1>;
+ remote-endpoint = <&vin1csi40>;
+ };
+ csi40vin2: endpoint@2 {
+ reg = <2>;
+ remote-endpoint = <&vin2csi40>;
+ };
+ csi40vin3: endpoint@3 {
+ reg = <3>;
+ remote-endpoint = <&vin3csi40>;
+ };
+ csi40vin4: endpoint@4 {
+ reg = <4>;
+ remote-endpoint = <&vin4csi40>;
+ };
+ csi40vin5: endpoint@5 {
+ reg = <5>;
+ remote-endpoint = <&vin5csi40>;
+ };
+ csi40vin6: endpoint@6 {
+ reg = <6>;
+ remote-endpoint = <&vin6csi40>;
+ };
+ csi40vin7: endpoint@7 {
+ reg = <7>;
+ remote-endpoint = <&vin7csi40>;
+ };
+ };
+
+ };
+ };
+
scif2: serial@e6e88000 {
compatible = "renesas,scif-r8a7796",
"renesas,rcar-gen3-scif", "renesas,scif";
diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig
index ce07285..f003c67 100644
--- a/arch/arm64/configs/defconfig
+++ b/arch/arm64/configs/defconfig
@@ -322,7 +322,7 @@
CONFIG_REGULATOR_QCOM_SPMI=y
CONFIG_REGULATOR_RK808=y
CONFIG_REGULATOR_S2MPS11=y
-CONFIG_MEDIA_SUPPORT=m
+CONFIG_MEDIA_SUPPORT=y
CONFIG_MEDIA_CAMERA_SUPPORT=y
CONFIG_MEDIA_ANALOG_TV_SUPPORT=y
CONFIG_MEDIA_DIGITAL_TV_SUPPORT=y
@@ -335,6 +335,11 @@
CONFIG_VIDEO_SAMSUNG_EXYNOS_GSC=m
CONFIG_VIDEO_RENESAS_FCP=m
CONFIG_VIDEO_RENESAS_VSP1=m
+CONFIG_V4L_PLATFORM_DRIVERS=y
+CONFIG_VIDEO_RCAR_VIN=y
+CONFIG_VIDEO_RCAR_CSI2=y
+# CONFIG_MEDIA_SUBDRV_AUTOSELECT is not set
+CONFIG_VIDEO_ADV7482=y
CONFIG_DRM=m
CONFIG_DRM_NOUVEAU=m
CONFIG_DRM_EXYNOS=m
diff --git a/drivers/media/media-entity.c b/drivers/media/media-entity.c
index bc44193..8eeb624 100644
--- a/drivers/media/media-entity.c
+++ b/drivers/media/media-entity.c
@@ -18,6 +18,7 @@
#include <linux/bitmap.h>
#include <linux/module.h>
+#include <linux/property.h>
#include <linux/slab.h>
#include <media/media-entity.h>
#include <media/media-device.h>
@@ -244,6 +245,26 @@
* Graph traversal
*/
+bool media_entity_has_route(struct media_entity *entity, unsigned int pad0,
+ unsigned int pad1)
+{
+ if (pad0 >= entity->num_pads || pad1 >= entity->num_pads)
+ return false;
+
+ if (pad0 == pad1)
+ return true;
+
+ if (!entity->ops || !entity->ops->has_route)
+ return true;
+
+ if (entity->pads[pad0].flags & MEDIA_PAD_FL_SOURCE
+ && entity->pads[pad1].flags & MEDIA_PAD_FL_SINK)
+ swap(pad0, pad1);
+
+ return entity->ops->has_route(entity, pad0, pad1);
+}
+EXPORT_SYMBOL_GPL(media_entity_has_route);
+
static struct media_entity *
media_entity_other(struct media_entity *entity, struct media_link *link)
{
@@ -386,6 +407,44 @@
}
EXPORT_SYMBOL_GPL(media_graph_walk_next);
+int media_entity_pad_from_fwnode(struct media_entity *entity,
+ struct fwnode_handle *fwnode,
+ int direction, unsigned int *pad)
+{
+ struct fwnode_endpoint endpoint;
+ int i, tmp, ret;
+
+ if (!entity->ops || !entity->ops->pad_from_fwnode) {
+ for (i = 0; i < entity->num_pads; i++) {
+ if (entity->pads[i].flags & direction) {
+ *pad = i;
+ return 0;
+ }
+ }
+
+ return -ENXIO;
+ }
+
+ ret = fwnode_graph_parse_endpoint(fwnode, &endpoint);
+ if (ret)
+ return ret;
+
+ ret = entity->ops->pad_from_fwnode(&endpoint, &tmp);
+ if (ret)
+ return ret;
+
+ if (tmp >= entity->num_pads)
+ return -ENXIO;
+
+ if (!(entity->pads[tmp].flags & direction))
+ return -ENXIO;
+
+ *pad = tmp;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(media_entity_pad_from_fwnode);
+
/* -----------------------------------------------------------------------------
* Pipeline management
*/
diff --git a/drivers/media/platform/rcar-vin/Kconfig b/drivers/media/platform/rcar-vin/Kconfig
index af4c98b..3dfeb91 100644
--- a/drivers/media/platform/rcar-vin/Kconfig
+++ b/drivers/media/platform/rcar-vin/Kconfig
@@ -1,3 +1,15 @@
+config VIDEO_RCAR_CSI2
+ tristate "R-Car MIPI CSI-2 Receiver"
+ depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API && OF
+ depends on ARCH_RENESAS || COMPILE_TEST
+ select V4L2_FWNODE
+ ---help---
+ Support for Renesas R-Car MIPI CSI-2 receiver.
+ Supports R-Car Gen3 SoCs.
+
+ To compile this driver as a module, choose M here: the
+ module will be called rcar-csi2.
+
config VIDEO_RCAR_VIN
tristate "R-Car Video Input (VIN) Driver"
depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API && OF && HAS_DMA && MEDIA_CONTROLLER
@@ -6,7 +18,7 @@
select V4L2_FWNODE
---help---
Support for Renesas R-Car Video Input (VIN) driver.
- Supports R-Car Gen2 SoCs.
+ Supports R-Car Gen2 and Gen3 SoCs.
To compile this driver as a module, choose M here: the
module will be called rcar-vin.
diff --git a/drivers/media/platform/rcar-vin/Makefile b/drivers/media/platform/rcar-vin/Makefile
index 48c5632..5ab803d 100644
--- a/drivers/media/platform/rcar-vin/Makefile
+++ b/drivers/media/platform/rcar-vin/Makefile
@@ -1,3 +1,4 @@
rcar-vin-objs = rcar-core.o rcar-dma.o rcar-v4l2.o
+obj-$(CONFIG_VIDEO_RCAR_CSI2) += rcar-csi2.o
obj-$(CONFIG_VIDEO_RCAR_VIN) += rcar-vin.o
diff --git a/drivers/media/platform/rcar-vin/rcar-core.c b/drivers/media/platform/rcar-vin/rcar-core.c
index 264604a..175f138 100644
--- a/drivers/media/platform/rcar-vin/rcar-core.c
+++ b/drivers/media/platform/rcar-vin/rcar-core.c
@@ -20,104 +20,335 @@
#include <linux/of_graph.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
+#include <linux/slab.h>
#include <media/v4l2-fwnode.h>
#include "rcar-vin.h"
/* -----------------------------------------------------------------------------
+ * Media Controller link notification
+ */
+
+static unsigned int rvin_group_csi_pad_to_chan(unsigned int pad)
+{
+ /*
+ * The CSI2 driver is rcar-csi2 and we know it's pad layout are
+ * 0: Source 1-4: Sinks so if we remove one from the pad we
+ * get the rcar-vin internal CSI2 channel number
+ */
+ return pad - 1;
+}
+
+/* group lock should be held when calling this function */
+static int rvin_group_entity_to_vin_num(struct rvin_group *group,
+ struct media_entity *entity)
+{
+ struct video_device *vdev;
+ int i;
+
+ if (!is_media_entity_v4l2_video_device(entity))
+ return -ENODEV;
+
+ vdev = media_entity_to_video_device(entity);
+
+ for (i = 0; i < RCAR_VIN_NUM; i++) {
+ if (!group->vin[i])
+ continue;
+
+ if (&group->vin[i]->vdev == vdev)
+ return i;
+ }
+
+ return -ENODEV;
+}
+
+/* group lock should be held when calling this function */
+static int rvin_group_entity_to_csi_num(struct rvin_group *group,
+ struct media_entity *entity)
+{
+ struct v4l2_subdev *sd;
+ int i;
+
+ if (!is_media_entity_v4l2_subdev(entity))
+ return -ENODEV;
+
+ sd = media_entity_to_v4l2_subdev(entity);
+
+ for (i = 0; i < RVIN_CSI_MAX; i++)
+ if (group->csi[i].subdev == sd)
+ return i;
+
+ return -ENODEV;
+}
+
+/* group lock should be held when calling this function */
+static void __rvin_group_build_link_list(struct rvin_group *group,
+ struct rvin_group_chsel *map,
+ int start, int len)
+{
+ struct media_pad *vin_pad, *remote_pad;
+ unsigned int n;
+
+ for (n = 0; n < len; n++) {
+ map[n].csi = -1;
+ map[n].chan = -1;
+
+ if (!group->vin[start + n])
+ continue;
+
+ vin_pad = &group->vin[start + n]->vdev.entity.pads[0];
+
+ remote_pad = media_entity_remote_pad(vin_pad);
+ if (!remote_pad)
+ continue;
+
+ map[n].csi =
+ rvin_group_entity_to_csi_num(group, remote_pad->entity);
+ map[n].chan = rvin_group_csi_pad_to_chan(remote_pad->index);
+ }
+}
+
+/* group lock should be held when calling this function */
+static int __rvin_group_try_get_chsel(struct rvin_group *group,
+ struct rvin_group_chsel *map,
+ int start, int len)
+{
+ const struct rvin_group_chsel *sel;
+ unsigned int i, n;
+ int chsel;
+
+ for (i = 0; i < group->vin[start]->info->num_chsels; i++) {
+ chsel = i;
+ for (n = 0; n < len; n++) {
+
+ /* If the link is not active it's OK */
+ if (map[n].csi == -1)
+ continue;
+
+ /* Check if chsel match requested link */
+ sel = &group->vin[start]->info->chsels[start + n][i];
+ if (map[n].csi != sel->csi ||
+ map[n].chan != sel->chan) {
+ chsel = -1;
+ break;
+ }
+ }
+
+ /* A chsel which satisfy the links have been found */
+ if (chsel != -1)
+ return chsel;
+ }
+
+ /* No chsel can satisfy the requested links */
+ return -1;
+}
+
+/* group lock should be held when calling this function */
+static bool rvin_group_in_use(struct rvin_group *group)
+{
+ struct media_entity *entity;
+
+ media_device_for_each_entity(entity, &group->mdev)
+ if (entity->use_count)
+ return true;
+
+ return false;
+}
+
+static int rvin_group_link_notify(struct media_link *link, u32 flags,
+ unsigned int notification)
+{
+ struct rvin_group *group = container_of(link->graph_obj.mdev,
+ struct rvin_group, mdev);
+ struct rvin_group_chsel chsel_map[4];
+ int vin_num, vin_master, csi_num, csi_chan;
+ unsigned int chsel;
+
+ mutex_lock(&group->lock);
+
+ vin_num = rvin_group_entity_to_vin_num(group, link->sink->entity);
+ csi_num = rvin_group_entity_to_csi_num(group, link->source->entity);
+ csi_chan = rvin_group_csi_pad_to_chan(link->source->index);
+
+ /*
+ * Figure out which VIN node is the subgroup master.
+ *
+ * VIN0-3 are controlled by VIN0
+ * VIN4-7 are controlled by VIN4
+ */
+ vin_master = vin_num < 4 ? 0 : 4;
+
+ /* If not all devices exists something is horribly wrong */
+ if (vin_num < 0 || csi_num < 0 || !group->vin[vin_master])
+ goto error;
+
+ /* Special checking only needed for links which are to be enabled */
+ if (notification != MEDIA_DEV_NOTIFY_PRE_LINK_CH ||
+ !(flags & MEDIA_LNK_FL_ENABLED))
+ goto out;
+
+ /* If any link in the group are in use, no new link can be enabled */
+ if (rvin_group_in_use(group))
+ goto error;
+
+ /* If the VIN already have a active link it's busy */
+ if (media_entity_remote_pad(&link->sink->entity->pads[0]))
+ goto error;
+
+ /* Build list of active links */
+ __rvin_group_build_link_list(group, chsel_map, vin_master, 4);
+
+ /* Add the new proposed link */
+ chsel_map[vin_num - vin_master].csi = csi_num;
+ chsel_map[vin_num - vin_master].chan = csi_chan;
+
+ /* See if there is a chsel value which match our link selection */
+ chsel = __rvin_group_try_get_chsel(group, chsel_map, vin_master, 4);
+
+ /* No chsel can provide the request links */
+ if (chsel == -1)
+ goto error;
+
+ /* Update chsel value at group master */
+ rvin_set_chsel(group->vin[vin_master], chsel);
+
+out:
+ mutex_unlock(&group->lock);
+
+ return v4l2_pipeline_link_notify(link, flags, notification);
+error:
+ mutex_unlock(&group->lock);
+
+ return -EMLINK;
+}
+
+static const struct media_device_ops rvin_media_ops = {
+ .link_notify = rvin_group_link_notify,
+};
+
+/* -----------------------------------------------------------------------------
+ * Gen3 CSI2 Group Allocator
+ */
+
+static DEFINE_MUTEX(rvin_group_lock);
+static struct rvin_group *rvin_group_data;
+
+static void rvin_group_release(struct kref *kref)
+{
+ struct rvin_group *group =
+ container_of(kref, struct rvin_group, refcount);
+
+ mutex_lock(&rvin_group_lock);
+
+ media_device_unregister(&group->mdev);
+ media_device_cleanup(&group->mdev);
+
+ rvin_group_data = NULL;
+
+ mutex_unlock(&rvin_group_lock);
+
+ kfree(group);
+}
+
+static struct rvin_group *__rvin_group_allocate(struct rvin_dev *vin)
+{
+ struct rvin_group *group;
+
+ if (rvin_group_data) {
+ group = rvin_group_data;
+ kref_get(&group->refcount);
+ vin_dbg(vin, "%s: get group=%p\n", __func__, group);
+ return group;
+ }
+
+ group = kzalloc(sizeof(*group), GFP_KERNEL);
+ if (!group)
+ return NULL;
+
+ kref_init(&group->refcount);
+ rvin_group_data = group;
+
+ vin_dbg(vin, "%s: alloc group=%p\n", __func__, group);
+ return group;
+}
+
+static struct rvin_group *rvin_group_allocate(struct rvin_dev *vin)
+{
+ struct rvin_group *group;
+ struct media_device *mdev;
+ int ret;
+
+ mutex_lock(&rvin_group_lock);
+
+ group = __rvin_group_allocate(vin);
+ if (!group) {
+ mutex_unlock(&rvin_group_lock);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ /* Init group data if its not already initialized */
+ mdev = &group->mdev;
+ if (!mdev->dev) {
+ mutex_init(&group->lock);
+ mdev->dev = vin->dev;
+
+ strlcpy(mdev->driver_name, "Renesas VIN",
+ sizeof(mdev->driver_name));
+ strlcpy(mdev->model, vin->dev->of_node->name,
+ sizeof(mdev->model));
+ strlcpy(mdev->bus_info, of_node_full_name(vin->dev->of_node),
+ sizeof(mdev->bus_info));
+ mdev->driver_version = LINUX_VERSION_CODE;
+ media_device_init(mdev);
+
+ mdev->ops = &rvin_media_ops;
+
+ ret = media_device_register(mdev);
+ if (ret) {
+ vin_err(vin, "Failed to register media device\n");
+ kref_put(&group->refcount, rvin_group_release);
+ mutex_unlock(&rvin_group_lock);
+ return ERR_PTR(ret);
+ }
+ }
+
+ vin->v4l2_dev.mdev = mdev;
+
+ mutex_unlock(&rvin_group_lock);
+
+ return group;
+}
+
+static void rvin_group_delete(struct rvin_dev *vin)
+{
+ vin_dbg(vin, "%s: group=%p\n", __func__, &vin->group);
+ kref_put(&vin->group->refcount, rvin_group_release);
+}
+
+/* -----------------------------------------------------------------------------
* Async notifier
*/
#define notifier_to_vin(n) container_of(n, struct rvin_dev, notifier)
-static bool rvin_mbus_supported(struct rvin_graph_entity *entity)
+static int rvin_find_pad(struct v4l2_subdev *sd, int direction)
{
- struct v4l2_subdev *sd = entity->subdev;
- struct v4l2_subdev_mbus_code_enum code = {
- .which = V4L2_SUBDEV_FORMAT_ACTIVE,
- };
+ unsigned int pad;
- code.index = 0;
- while (!v4l2_subdev_call(sd, pad, enum_mbus_code, NULL, &code)) {
- code.index++;
- switch (code.code) {
- case MEDIA_BUS_FMT_YUYV8_1X16:
- case MEDIA_BUS_FMT_UYVY8_2X8:
- case MEDIA_BUS_FMT_UYVY10_2X10:
- case MEDIA_BUS_FMT_RGB888_1X24:
- entity->code = code.code;
- return true;
- default:
- break;
- }
- }
-
- return false;
-}
-
-static int rvin_digital_notify_complete(struct v4l2_async_notifier *notifier)
-{
- struct rvin_dev *vin = notifier_to_vin(notifier);
- int ret;
-
- /* Verify subdevices mbus format */
- if (!rvin_mbus_supported(&vin->digital)) {
- vin_err(vin, "Unsupported media bus format for %s\n",
- vin->digital.subdev->name);
- return -EINVAL;
- }
-
- vin_dbg(vin, "Found media bus format for %s: %d\n",
- vin->digital.subdev->name, vin->digital.code);
-
- ret = v4l2_device_register_subdev_nodes(&vin->v4l2_dev);
- if (ret < 0) {
- vin_err(vin, "Failed to register subdev nodes\n");
- return ret;
- }
-
- return rvin_v4l2_probe(vin);
-}
-
-static void rvin_digital_notify_unbind(struct v4l2_async_notifier *notifier,
- struct v4l2_subdev *subdev,
- struct v4l2_async_subdev *asd)
-{
- struct rvin_dev *vin = notifier_to_vin(notifier);
-
- if (vin->digital.subdev == subdev) {
- vin_dbg(vin, "unbind digital subdev %s\n", subdev->name);
- rvin_v4l2_remove(vin);
- vin->digital.subdev = NULL;
- return;
- }
-
- vin_err(vin, "no entity for subdev %s to unbind\n", subdev->name);
-}
-
-static int rvin_digital_notify_bound(struct v4l2_async_notifier *notifier,
- struct v4l2_subdev *subdev,
- struct v4l2_async_subdev *asd)
-{
- struct rvin_dev *vin = notifier_to_vin(notifier);
-
- v4l2_set_subdev_hostdata(subdev, vin);
-
- if (vin->digital.asd.match.fwnode.fwnode ==
- of_fwnode_handle(subdev->dev->of_node)) {
- vin_dbg(vin, "bound digital subdev %s\n", subdev->name);
- vin->digital.subdev = subdev;
+ if (sd->entity.num_pads <= 1)
return 0;
- }
- vin_err(vin, "no entity for subdev %s to bind\n", subdev->name);
+ for (pad = 0; pad < sd->entity.num_pads; pad++)
+ if (sd->entity.pads[pad].flags & direction)
+ return pad;
+
return -EINVAL;
}
-static int rvin_digitial_parse_v4l2(struct rvin_dev *vin,
- struct device_node *ep,
- struct v4l2_mbus_config *mbus_cfg)
+static int rvin_parse_v4l2(struct rvin_dev *vin,
+ struct device_node *ep,
+ struct v4l2_mbus_config *mbus_cfg)
{
struct v4l2_fwnode_endpoint v4l2_ep;
int ret;
@@ -130,20 +361,153 @@
mbus_cfg->type = v4l2_ep.bus_type;
- switch (mbus_cfg->type) {
- case V4L2_MBUS_PARALLEL:
- vin_dbg(vin, "Found PARALLEL media bus\n");
- mbus_cfg->flags = v4l2_ep.bus.parallel.flags;
- break;
- case V4L2_MBUS_BT656:
- vin_dbg(vin, "Found BT656 media bus\n");
- mbus_cfg->flags = 0;
- break;
- default:
- vin_err(vin, "Unknown media bus type\n");
+ if (vin->info->chip == RCAR_GEN3) {
+ switch (mbus_cfg->type) {
+ case V4L2_MBUS_CSI2:
+ vin_dbg(vin, "Found CSI-2 media bus\n");
+ mbus_cfg->flags = 0;
+ return 0;
+ default:
+ break;
+ }
+ } else {
+ switch (mbus_cfg->type) {
+ case V4L2_MBUS_PARALLEL:
+ vin_dbg(vin, "Found PARALLEL media bus\n");
+ mbus_cfg->flags = v4l2_ep.bus.parallel.flags;
+ return 0;
+ case V4L2_MBUS_BT656:
+ vin_dbg(vin, "Found BT656 media bus\n");
+ mbus_cfg->flags = 0;
+ return 0;
+ default:
+ break;
+ }
+ }
+
+ vin_err(vin, "Unknown media bus type\n");
+ return -EINVAL;
+}
+
+/* -----------------------------------------------------------------------------
+ * Digital async notifier
+ */
+
+static bool rvin_mbus_supported(struct rvin_dev *vin)
+{
+ struct v4l2_subdev *sd = vin->digital.subdev;
+ struct v4l2_subdev_mbus_code_enum code = {
+ .which = V4L2_SUBDEV_FORMAT_ACTIVE,
+ };
+
+ code.index = 0;
+ code.pad = vin->digital.source_pad;
+ while (!v4l2_subdev_call(sd, pad, enum_mbus_code, NULL, &code)) {
+ code.index++;
+ switch (code.code) {
+ case MEDIA_BUS_FMT_YUYV8_1X16:
+ case MEDIA_BUS_FMT_UYVY8_2X8:
+ case MEDIA_BUS_FMT_UYVY10_2X10:
+ case MEDIA_BUS_FMT_RGB888_1X24:
+ vin->code = code.code;
+ return true;
+ default:
+ break;
+ }
+ }
+
+ return false;
+}
+
+static int rvin_digital_notify_complete(struct v4l2_async_notifier *notifier)
+{
+ struct rvin_dev *vin = notifier_to_vin(notifier);
+ struct v4l2_subdev *sd = vin_to_source(vin);
+ int ret;
+
+ /* Verify subdevices mbus format */
+ if (!rvin_mbus_supported(vin)) {
+ vin_err(vin, "Unsupported media bus format for %s\n",
+ vin->digital.subdev->name);
return -EINVAL;
}
+ vin_dbg(vin, "Found media bus format for %s: %d\n",
+ vin->digital.subdev->name, vin->code);
+
+ ret = v4l2_device_register_subdev_nodes(&vin->v4l2_dev);
+ if (ret < 0) {
+ vin_err(vin, "Failed to register subdev nodes\n");
+ return ret;
+ }
+
+ /* Add the controls */
+ /*
+ * Currently the subdev with the largest number of controls (13) is
+ * ov6550. So let's pick 16 as a hint for the control handler. Note
+ * that this is a hint only: too large and you waste some memory, too
+ * small and there is a (very) small performance hit when looking up
+ * controls in the internal hash.
+ */
+ ret = v4l2_ctrl_handler_init(&vin->ctrl_handler, 16);
+ if (ret < 0)
+ return ret;
+
+ ret = v4l2_ctrl_add_handler(&vin->ctrl_handler, sd->ctrl_handler, NULL);
+ if (ret < 0)
+ return ret;
+
+ ret = v4l2_subdev_call(sd, video, g_tvnorms, &vin->vdev.tvnorms);
+ if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV)
+ return ret;
+
+ if (vin->vdev.tvnorms == 0) {
+ /* Disable the STD API if there are no tvnorms defined */
+ v4l2_disable_ioctl(&vin->vdev, VIDIOC_G_STD);
+ v4l2_disable_ioctl(&vin->vdev, VIDIOC_S_STD);
+ v4l2_disable_ioctl(&vin->vdev, VIDIOC_QUERYSTD);
+ v4l2_disable_ioctl(&vin->vdev, VIDIOC_ENUMSTD);
+ }
+
+ return rvin_reset_format(vin);
+}
+
+static void rvin_digital_notify_unbind(struct v4l2_async_notifier *notifier,
+ struct v4l2_subdev *subdev,
+ struct v4l2_async_subdev *asd)
+{
+ struct rvin_dev *vin = notifier_to_vin(notifier);
+
+ vin_dbg(vin, "unbind digital subdev %s\n", subdev->name);
+ v4l2_ctrl_handler_free(&vin->ctrl_handler);
+ vin->digital.subdev = NULL;
+}
+
+static int rvin_digital_notify_bound(struct v4l2_async_notifier *notifier,
+ struct v4l2_subdev *subdev,
+ struct v4l2_async_subdev *asd)
+{
+ struct rvin_dev *vin = notifier_to_vin(notifier);
+ int ret;
+
+ v4l2_set_subdev_hostdata(subdev, vin);
+
+ /* Find surce and sink pad of remote subdevice */
+
+ ret = rvin_find_pad(subdev, MEDIA_PAD_FL_SOURCE);
+ if (ret < 0)
+ return ret;
+ vin->digital.source_pad = ret;
+
+ ret = rvin_find_pad(subdev, MEDIA_PAD_FL_SINK);
+ vin->digital.sink_pad = ret < 0 ? 0 : ret;
+
+ vin->digital.subdev = subdev;
+
+ vin_dbg(vin, "bound subdev %s source pad: %u sink pad: %u\n",
+ subdev->name, vin->digital.source_pad,
+ vin->digital.sink_pad);
+
return 0;
}
@@ -171,7 +535,7 @@
}
of_node_put(np);
- ret = rvin_digitial_parse_v4l2(vin, ep, &vin->digital.mbus_cfg);
+ ret = rvin_parse_v4l2(vin, ep, &vin->mbus_cfg);
of_node_put(ep);
if (ret)
return ret;
@@ -187,6 +551,10 @@
struct v4l2_async_subdev **subdevs = NULL;
int ret;
+ ret = rvin_v4l2_probe(vin);
+ if (ret)
+ return ret;
+
ret = rvin_digital_graph_parse(vin);
if (ret)
return ret;
@@ -222,17 +590,527 @@
}
/* -----------------------------------------------------------------------------
+ * Group async notifier
+ */
+
+/* group lock should be held when calling this function */
+static int rvin_group_add_link(struct rvin_dev *vin,
+ struct media_entity *source,
+ unsigned int source_idx,
+ struct media_entity *sink,
+ unsigned int sink_idx,
+ u32 flags)
+{
+ struct media_pad *source_pad, *sink_pad;
+ int ret = 0;
+
+ source_pad = &source->pads[source_idx];
+ sink_pad = &sink->pads[sink_idx];
+
+ if (!media_entity_find_link(source_pad, sink_pad))
+ ret = media_create_pad_link(source, source_idx,
+ sink, sink_idx, flags);
+
+ if (ret)
+ vin_err(vin, "Error adding link from %s to %s\n",
+ source->name, sink->name);
+
+ return ret;
+}
+
+static int rvin_group_update_links(struct rvin_dev *vin)
+{
+ struct media_entity *source, *sink;
+ struct rvin_dev *master;
+ unsigned int i, n, idx, chsel, csi;
+ u32 flags;
+ int ret;
+
+ mutex_lock(&vin->group->lock);
+
+ for (n = 0; n < RCAR_VIN_NUM; n++) {
+
+ /* Check that VIN is part of the group */
+ if (!vin->group->vin[n])
+ continue;
+
+ /* Check that subgroup master is part of the group */
+ master = vin->group->vin[n < 4 ? 0 : 4];
+ if (!master)
+ continue;
+
+ chsel = rvin_get_chsel(master);
+
+ for (i = 0; i < vin->info->num_chsels; i++) {
+ csi = vin->info->chsels[n][i].csi;
+
+ /* If the CSI-2 is out of bounds it's a noop, skip */
+ if (csi >= RVIN_CSI_MAX)
+ continue;
+
+ /* Check that CSI-2 are part of the group */
+ if (!vin->group->csi[csi].subdev)
+ continue;
+
+ source = &vin->group->csi[csi].subdev->entity;
+ sink = &vin->group->vin[n]->vdev.entity;
+ idx = vin->info->chsels[n][i].chan + 1;
+ flags = i == chsel ? MEDIA_LNK_FL_ENABLED : 0;
+
+ ret = rvin_group_add_link(vin, source, idx, sink, 0,
+ flags);
+ if (ret)
+ goto out;
+ }
+ }
+out:
+ mutex_unlock(&vin->group->lock);
+
+ return ret;
+}
+
+static int rvin_group_notify_complete(struct v4l2_async_notifier *notifier)
+{
+ struct rvin_dev *vin = notifier_to_vin(notifier);
+ int ret;
+
+ ret = v4l2_device_register_subdev_nodes(&vin->v4l2_dev);
+ if (ret) {
+ vin_err(vin, "Failed to register subdev nodes\n");
+ return ret;
+ }
+
+ return rvin_group_update_links(vin);
+}
+
+static void rvin_group_notify_unbind(struct v4l2_async_notifier *notifier,
+ struct v4l2_subdev *subdev,
+ struct v4l2_async_subdev *asd)
+{
+ struct rvin_dev *vin = notifier_to_vin(notifier);
+ unsigned int i;
+
+ mutex_lock(&vin->group->lock);
+ for (i = 0; i < RVIN_CSI_MAX; i++) {
+ if (&vin->group->csi[i].asd == asd) {
+ vin_dbg(vin, "Unbind CSI-2 %s\n", subdev->name);
+ vin->group->csi[i].subdev = NULL;
+ mutex_unlock(&vin->group->lock);
+ return;
+ }
+ }
+ mutex_unlock(&vin->group->lock);
+
+ vin_err(vin, "No entity for subdev %s to unbind\n", subdev->name);
+}
+
+static int rvin_group_notify_bound(struct v4l2_async_notifier *notifier,
+ struct v4l2_subdev *subdev,
+ struct v4l2_async_subdev *asd)
+{
+ struct rvin_dev *vin = notifier_to_vin(notifier);
+ unsigned int i;
+
+ v4l2_set_subdev_hostdata(subdev, vin);
+
+ mutex_lock(&vin->group->lock);
+ for (i = 0; i < RVIN_CSI_MAX; i++) {
+ if (&vin->group->csi[i].asd == asd) {
+ vin_dbg(vin, "Bound CSI-2 %s\n", subdev->name);
+ vin->group->csi[i].subdev = subdev;
+ mutex_unlock(&vin->group->lock);
+ return 0;
+ }
+ }
+ mutex_unlock(&vin->group->lock);
+
+ vin_err(vin, "No entity for subdev %s to bind\n", subdev->name);
+ return -EINVAL;
+}
+
+static struct device_node *rvin_group_get_csi(struct rvin_dev *vin,
+ struct device_node *node)
+{
+ struct device_node *csi;
+
+ csi = of_graph_get_remote_port_parent(node);
+ if (!csi) {
+ vin_err(vin, "No CSI-2 found %s\n", of_node_full_name(node));
+ return ERR_PTR(-EINVAL);
+ }
+
+ /* Not all CSI-2 are available, this is OK */
+ if (!of_device_is_available(csi)) {
+ vin_dbg(vin, "CSI-2 %s not available\n",
+ of_node_full_name(csi));
+ of_node_put(csi);
+ return NULL;
+ }
+
+ return csi;
+}
+
+/* group lock should be held when calling this function */
+static int rvin_group_graph_parse(struct rvin_dev *vin, unsigned long *bitmap)
+{
+ struct device_node *ep, *csi;
+ unsigned int i;
+ u32 val;
+ int ret;
+
+ *bitmap = 0;
+
+ /* Figure out which VIN we are */
+ ret = of_property_read_u32(vin->dev->of_node, "renesas,id", &val);
+ if (ret) {
+ vin_err(vin, "No renesas,id property found\n");
+ return ret;
+ }
+
+ if (val >= RCAR_VIN_NUM) {
+ vin_err(vin, "Invalid renesas,id '%u'\n", val);
+ return -EINVAL;
+ }
+
+ if (vin->group->vin[val] != NULL) {
+ vin_err(vin, "VIN number %d already occupied\n", val);
+ return -EINVAL;
+ }
+
+ vin_dbg(vin, "I'm VIN number %u", val);
+ vin->group->vin[val] = vin;
+
+ /* Parse all CSI-2 nodes */
+ for (i = 0; i < RVIN_CSI_MAX; i++) {
+
+ /* Check if instance is connected to the CSI-2 */
+ ep = of_graph_get_endpoint_by_regs(vin->dev->of_node, 1, i);
+ if (!ep) {
+ vin_dbg(vin, "CSI-2: %d not connected\n", i);
+ continue;
+ }
+
+ if (vin->group->csi[i].asd.match.fwnode.fwnode) {
+ of_node_put(ep);
+ vin_dbg(vin, "CSI-2: %d handled by other device\n", i);
+ continue;
+ }
+
+ csi = rvin_group_get_csi(vin, ep);
+ of_node_put(ep);
+ if (IS_ERR(csi))
+ return PTR_ERR(csi);
+ if (csi == NULL)
+ continue;
+
+ vin->group->csi[i].asd.match.fwnode.fwnode = of_fwnode_handle(csi);
+ vin->group->csi[i].asd.match_type = V4L2_ASYNC_MATCH_FWNODE;
+
+ *bitmap |= BIT(i);
+
+ vin_dbg(vin, "Handle CSI-2 %s\n", of_node_full_name(csi));
+ }
+
+ /* All our sources are CSI-2 */
+ vin->mbus_cfg.type = V4L2_MBUS_CSI2;
+ vin->mbus_cfg.flags = 0;
+
+ return 0;
+}
+
+/* group lock should be held when calling this function */
+static void rvin_group_graph_revert(struct rvin_dev *vin, unsigned long bitmap)
+{
+ int bit;
+
+ for_each_set_bit(bit, &bitmap, RVIN_CSI_MAX) {
+ vin_dbg(vin, "Reverting graph for %s\n",
+ of_node_full_name(vin->dev->of_node));
+ vin->group->csi[bit].asd.match.fwnode.fwnode = NULL;
+ vin->group->csi[bit].asd.match_type = 0;
+ }
+}
+
+static int rvin_group_graph_init(struct rvin_dev *vin)
+{
+ struct v4l2_async_subdev **subdevs = NULL;
+ unsigned long bitmap;
+ int i, bit, count, ret;
+
+ mutex_lock(&vin->group->lock);
+
+ ret = rvin_group_graph_parse(vin, &bitmap);
+ if (ret) {
+ rvin_group_graph_revert(vin, bitmap);
+ mutex_unlock(&vin->group->lock);
+ return ret;
+ }
+
+ /* Check if instance need to handle subdevices on behalf of the group */
+ count = hweight_long(bitmap);
+ if (!count) {
+ mutex_unlock(&vin->group->lock);
+ return 0;
+ }
+
+ subdevs = devm_kzalloc(vin->dev, sizeof(*subdevs) * count, GFP_KERNEL);
+ if (subdevs == NULL) {
+ rvin_group_graph_revert(vin, bitmap);
+ mutex_unlock(&vin->group->lock);
+ return -ENOMEM;
+ }
+
+ i = 0;
+ for_each_set_bit(bit, &bitmap, RVIN_CSI_MAX) {
+ subdevs[i++] = &vin->group->csi[bit].asd;
+ }
+
+ vin_dbg(vin, "Claimed %d subdevices for group\n", count);
+
+ vin->notifier.num_subdevs = count;
+ vin->notifier.subdevs = subdevs;
+ vin->notifier.bound = rvin_group_notify_bound;
+ vin->notifier.unbind = rvin_group_notify_unbind;
+ vin->notifier.complete = rvin_group_notify_complete;
+
+ mutex_unlock(&vin->group->lock);
+
+ ret = v4l2_async_notifier_register(&vin->v4l2_dev, &vin->notifier);
+ if (ret < 0) {
+ vin_err(vin, "Notifier registration failed\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int rvin_group_init(struct rvin_dev *vin)
+{
+ int ret;
+
+ vin->group = rvin_group_allocate(vin);
+ if (IS_ERR(vin->group))
+ return PTR_ERR(vin->group);
+
+ ret = rvin_v4l2_probe(vin);
+ if (ret)
+ goto error_group;
+
+ vin->pad.flags = MEDIA_PAD_FL_SINK;
+ ret = media_entity_pads_init(&vin->vdev.entity, 1, &vin->pad);
+ if (ret)
+ goto error_v4l2;
+
+ ret = rvin_group_graph_init(vin);
+ if (ret)
+ goto error_v4l2;
+
+ ret = rvin_group_update_links(vin);
+ if (ret)
+ goto error_async;
+
+ return 0;
+
+error_async:
+ v4l2_async_notifier_unregister(&vin->notifier);
+error_v4l2:
+ rvin_v4l2_remove(vin);
+error_group:
+ rvin_group_delete(vin);
+
+ return ret;
+}
+
+/* -----------------------------------------------------------------------------
* Platform Device Driver
*/
+static const struct rvin_info rcar_info_h1 = {
+ .chip = RCAR_H1,
+ .use_mc = false,
+ .max_width = 2048,
+ .max_height = 2048,
+};
+
+static const struct rvin_info rcar_info_m1 = {
+ .chip = RCAR_M1,
+ .use_mc = false,
+ .max_width = 2048,
+ .max_height = 2048,
+};
+
+static const struct rvin_info rcar_info_gen2 = {
+ .chip = RCAR_GEN2,
+ .use_mc = false,
+ .max_width = 2048,
+ .max_height = 2048,
+};
+
+static const struct rvin_info rcar_info_r8a7795 = {
+ .chip = RCAR_GEN3,
+ .use_mc = true,
+ .max_width = 4096,
+ .max_height = 4096,
+
+ .num_chsels = 6,
+ .chsels = {
+ {
+ { .csi = RVIN_CSI40, .chan = 0 },
+ { .csi = RVIN_CSI20, .chan = 0 },
+ { .csi = RVIN_CSI21, .chan = 0 },
+ { .csi = RVIN_CSI40, .chan = 0 },
+ { .csi = RVIN_CSI20, .chan = 0 },
+ { .csi = RVIN_CSI21, .chan = 0 },
+ }, {
+ { .csi = RVIN_CSI20, .chan = 0 },
+ { .csi = RVIN_CSI21, .chan = 0 },
+ { .csi = RVIN_CSI40, .chan = 0 },
+ { .csi = RVIN_CSI40, .chan = 1 },
+ { .csi = RVIN_CSI20, .chan = 1 },
+ { .csi = RVIN_CSI21, .chan = 1 },
+ }, {
+ { .csi = RVIN_CSI21, .chan = 0 },
+ { .csi = RVIN_CSI40, .chan = 0 },
+ { .csi = RVIN_CSI20, .chan = 0 },
+ { .csi = RVIN_CSI40, .chan = 2 },
+ { .csi = RVIN_CSI20, .chan = 2 },
+ { .csi = RVIN_CSI21, .chan = 2 },
+ }, {
+ { .csi = RVIN_CSI40, .chan = 1 },
+ { .csi = RVIN_CSI20, .chan = 1 },
+ { .csi = RVIN_CSI21, .chan = 1 },
+ { .csi = RVIN_CSI40, .chan = 3 },
+ { .csi = RVIN_CSI20, .chan = 3 },
+ { .csi = RVIN_CSI21, .chan = 3 },
+ }, {
+ { .csi = RVIN_CSI41, .chan = 0 },
+ { .csi = RVIN_CSI20, .chan = 0 },
+ { .csi = RVIN_CSI21, .chan = 0 },
+ { .csi = RVIN_CSI41, .chan = 0 },
+ { .csi = RVIN_CSI20, .chan = 0 },
+ { .csi = RVIN_CSI21, .chan = 0 },
+ }, {
+ { .csi = RVIN_CSI20, .chan = 0 },
+ { .csi = RVIN_CSI21, .chan = 0 },
+ { .csi = RVIN_CSI41, .chan = 0 },
+ { .csi = RVIN_CSI41, .chan = 1 },
+ { .csi = RVIN_CSI20, .chan = 1 },
+ { .csi = RVIN_CSI21, .chan = 1 },
+ }, {
+ { .csi = RVIN_CSI21, .chan = 0 },
+ { .csi = RVIN_CSI41, .chan = 0 },
+ { .csi = RVIN_CSI20, .chan = 0 },
+ { .csi = RVIN_CSI41, .chan = 2 },
+ { .csi = RVIN_CSI20, .chan = 2 },
+ { .csi = RVIN_CSI21, .chan = 2 },
+ }, {
+ { .csi = RVIN_CSI41, .chan = 1 },
+ { .csi = RVIN_CSI20, .chan = 1 },
+ { .csi = RVIN_CSI21, .chan = 1 },
+ { .csi = RVIN_CSI41, .chan = 3 },
+ { .csi = RVIN_CSI20, .chan = 3 },
+ { .csi = RVIN_CSI21, .chan = 3 },
+ },
+ },
+};
+
+static const struct rvin_info rcar_info_r8a7796 = {
+ .chip = RCAR_GEN3,
+ .use_mc = true,
+ .max_width = 4096,
+ .max_height = 4096,
+
+ .num_chsels = 5,
+ .chsels = {
+ {
+ { .csi = RVIN_CSI40, .chan = 0 },
+ { .csi = RVIN_CSI20, .chan = 0 },
+ { .csi = RVIN_NC, .chan = 0 },
+ { .csi = RVIN_CSI40, .chan = 0 },
+ { .csi = RVIN_CSI20, .chan = 0 },
+ }, {
+ { .csi = RVIN_CSI20, .chan = 0 },
+ { .csi = RVIN_NC, .chan = 0 },
+ { .csi = RVIN_CSI40, .chan = 0 },
+ { .csi = RVIN_CSI40, .chan = 1 },
+ { .csi = RVIN_CSI20, .chan = 1 },
+ }, {
+ { .csi = RVIN_NC, .chan = 0 },
+ { .csi = RVIN_CSI40, .chan = 0 },
+ { .csi = RVIN_CSI20, .chan = 0 },
+ { .csi = RVIN_CSI40, .chan = 2 },
+ { .csi = RVIN_CSI20, .chan = 2 },
+ }, {
+ { .csi = RVIN_CSI40, .chan = 1 },
+ { .csi = RVIN_CSI20, .chan = 1 },
+ { .csi = RVIN_NC, .chan = 1 },
+ { .csi = RVIN_CSI40, .chan = 3 },
+ { .csi = RVIN_CSI20, .chan = 3 },
+ }, {
+ { .csi = RVIN_CSI40, .chan = 0 },
+ { .csi = RVIN_CSI20, .chan = 0 },
+ { .csi = RVIN_NC, .chan = 0 },
+ { .csi = RVIN_CSI40, .chan = 0 },
+ { .csi = RVIN_CSI20, .chan = 0 },
+ }, {
+ { .csi = RVIN_CSI20, .chan = 0 },
+ { .csi = RVIN_NC, .chan = 0 },
+ { .csi = RVIN_CSI40, .chan = 0 },
+ { .csi = RVIN_CSI40, .chan = 1 },
+ { .csi = RVIN_CSI20, .chan = 1 },
+ }, {
+ { .csi = RVIN_NC, .chan = 0 },
+ { .csi = RVIN_CSI40, .chan = 0 },
+ { .csi = RVIN_CSI20, .chan = 0 },
+ { .csi = RVIN_CSI40, .chan = 2 },
+ { .csi = RVIN_CSI20, .chan = 2 },
+ }, {
+ { .csi = RVIN_CSI40, .chan = 1 },
+ { .csi = RVIN_CSI20, .chan = 1 },
+ { .csi = RVIN_NC, .chan = 1 },
+ { .csi = RVIN_CSI40, .chan = 3 },
+ { .csi = RVIN_CSI20, .chan = 3 },
+ },
+ },
+};
+
static const struct of_device_id rvin_of_id_table[] = {
- { .compatible = "renesas,vin-r8a7794", .data = (void *)RCAR_GEN2 },
- { .compatible = "renesas,vin-r8a7793", .data = (void *)RCAR_GEN2 },
- { .compatible = "renesas,vin-r8a7791", .data = (void *)RCAR_GEN2 },
- { .compatible = "renesas,vin-r8a7790", .data = (void *)RCAR_GEN2 },
- { .compatible = "renesas,vin-r8a7779", .data = (void *)RCAR_H1 },
- { .compatible = "renesas,vin-r8a7778", .data = (void *)RCAR_M1 },
- { .compatible = "renesas,rcar-gen2-vin", .data = (void *)RCAR_GEN2 },
+ {
+ .compatible = "renesas,vin-r8a7796",
+ .data = &rcar_info_r8a7796,
+ },
+ {
+ .compatible = "renesas,vin-r8a7795",
+ .data = &rcar_info_r8a7795,
+ },
+ {
+ .compatible = "renesas,vin-r8a7794",
+ .data = &rcar_info_gen2,
+ },
+ {
+ .compatible = "renesas,vin-r8a7793",
+ .data = &rcar_info_gen2,
+ },
+ {
+ .compatible = "renesas,vin-r8a7791",
+ .data = &rcar_info_gen2,
+ },
+ {
+ .compatible = "renesas,vin-r8a7790",
+ .data = &rcar_info_gen2,
+ },
+ {
+ .compatible = "renesas,vin-r8a7779",
+ .data = &rcar_info_h1,
+ },
+ {
+ .compatible = "renesas,vin-r8a7778",
+ .data = &rcar_info_m1,
+ },
+ {
+ .compatible = "renesas,rcar-gen2-vin",
+ .data = &rcar_info_gen2,
+ },
{ },
};
MODULE_DEVICE_TABLE(of, rvin_of_id_table);
@@ -253,7 +1131,7 @@
return -ENODEV;
vin->dev = &pdev->dev;
- vin->chip = (enum chip_id)match->data;
+ vin->info = match->data;
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (mem == NULL)
@@ -271,9 +1149,12 @@
if (ret)
return ret;
- ret = rvin_digital_graph_init(vin);
+ if (vin->info->use_mc)
+ ret = rvin_group_init(vin);
+ else
+ ret = rvin_digital_graph_init(vin);
if (ret < 0)
- goto error;
+ goto error_dma;
pm_suspend_ignore_children(&pdev->dev, true);
pm_runtime_enable(&pdev->dev);
@@ -281,7 +1162,8 @@
platform_set_drvdata(pdev, vin);
return 0;
-error:
+
+error_dma:
rvin_dma_remove(vin);
return ret;
@@ -295,6 +1177,13 @@
v4l2_async_notifier_unregister(&vin->notifier);
+ /* Checks internaly if handlers have been init or not */
+ v4l2_ctrl_handler_free(&vin->ctrl_handler);
+
+ if (vin->info->use_mc)
+ rvin_group_delete(vin);
+
+ rvin_v4l2_remove(vin);
rvin_dma_remove(vin);
return 0;
diff --git a/drivers/media/platform/rcar-vin/rcar-csi2.c b/drivers/media/platform/rcar-vin/rcar-csi2.c
new file mode 100644
index 0000000..1175f1f
--- /dev/null
+++ b/drivers/media/platform/rcar-vin/rcar-csi2.c
@@ -0,0 +1,867 @@
+/*
+ * Driver for Renesas R-Car MIPI CSI-2 Receiver
+ *
+ * Copyright (C) 2017 Renesas Electronics Corp.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-mc.h>
+#include <media/v4l2-subdev.h>
+
+/* Register offsets and bits */
+
+/* Control Timing Select */
+#define TREF_REG 0x00
+#define TREF_TREF (1 << 0)
+
+/* Software Reset */
+#define SRST_REG 0x04
+#define SRST_SRST (1 << 0)
+
+/* PHY Operation Control */
+#define PHYCNT_REG 0x08
+#define PHYCNT_SHUTDOWNZ (1 << 17)
+#define PHYCNT_RSTZ (1 << 16)
+#define PHYCNT_ENABLECLK (1 << 4)
+#define PHYCNT_ENABLE_3 (1 << 3)
+#define PHYCNT_ENABLE_2 (1 << 2)
+#define PHYCNT_ENABLE_1 (1 << 1)
+#define PHYCNT_ENABLE_0 (1 << 0)
+
+/* Checksum Control */
+#define CHKSUM_REG 0x0c
+#define CHKSUM_ECC_EN (1 << 1)
+#define CHKSUM_CRC_EN (1 << 0)
+
+/*
+ * Channel Data Type Select
+ * VCDT[0-15]: Channel 1 VCDT[16-31]: Channel 2
+ * VCDT2[0-15]: Channel 3 VCDT2[16-31]: Channel 4
+ */
+#define VCDT_REG 0x10
+#define VCDT2_REG 0x14
+#define VCDT_VCDTN_EN (1 << 15)
+#define VCDT_SEL_VC(n) (((n) & 0x3) << 8)
+#define VCDT_SEL_DTN_ON (1 << 6)
+#define VCDT_SEL_DT(n) (((n) & 0x3f) << 0)
+
+/* Frame Data Type Select */
+#define FRDT_REG 0x18
+
+/* Field Detection Control */
+#define FLD_REG 0x1c
+#define FLD_FLD_NUM(n) (((n) & 0xff) << 16)
+#define FLD_FLD_EN4 (1 << 3)
+#define FLD_FLD_EN3 (1 << 2)
+#define FLD_FLD_EN2 (1 << 1)
+#define FLD_FLD_EN (1 << 0)
+
+/* Automatic Standby Control */
+#define ASTBY_REG 0x20
+
+/* Long Data Type Setting 0 */
+#define LNGDT0_REG 0x28
+
+/* Long Data Type Setting 1 */
+#define LNGDT1_REG 0x2c
+
+/* Interrupt Enable */
+#define INTEN_REG 0x30
+
+/* Interrupt Source Mask */
+#define INTCLOSE_REG 0x34
+
+/* Interrupt Status Monitor */
+#define INTSTATE_REG 0x38
+
+/* Interrupt Error Status Monitor */
+#define INTERRSTATE_REG 0x3c
+
+/* Short Packet Data */
+#define SHPDAT_REG 0x40
+
+/* Short Packet Count */
+#define SHPCNT_REG 0x44
+
+/* LINK Operation Control */
+#define LINKCNT_REG 0x48
+#define LINKCNT_MONITOR_EN (1 << 31)
+#define LINKCNT_REG_MONI_PACT_EN (1 << 25)
+#define LINKCNT_ICLK_NONSTOP (1 << 24)
+
+/* Lane Swap */
+#define LSWAP_REG 0x4c
+#define LSWAP_L3SEL(n) (((n) & 0x3) << 6)
+#define LSWAP_L2SEL(n) (((n) & 0x3) << 4)
+#define LSWAP_L1SEL(n) (((n) & 0x3) << 2)
+#define LSWAP_L0SEL(n) (((n) & 0x3) << 0)
+
+/* PHY Test Interface Clear */
+#define PHTC_REG 0x58
+#define PHTC_TESTCLR (1 << 0)
+
+/* PHY Frequency Control */
+#define PHYPLL_REG 0x68
+#define PHYPLL_HSFREQRANGE(n) ((n) << 16)
+
+struct phypll_hsfreqrange {
+ unsigned int mbps;
+ unsigned char reg;
+};
+
+static const struct phypll_hsfreqrange phypll_hsfreqrange_map[] = {
+ { .mbps = 80, .reg = 0x00 },
+ { .mbps = 90, .reg = 0x10 },
+ { .mbps = 100, .reg = 0x20 },
+ { .mbps = 110, .reg = 0x30 },
+ { .mbps = 120, .reg = 0x01 },
+ { .mbps = 130, .reg = 0x11 },
+ { .mbps = 140, .reg = 0x21 },
+ { .mbps = 150, .reg = 0x31 },
+ { .mbps = 160, .reg = 0x02 },
+ { .mbps = 170, .reg = 0x12 },
+ { .mbps = 180, .reg = 0x22 },
+ { .mbps = 190, .reg = 0x32 },
+ { .mbps = 205, .reg = 0x03 },
+ { .mbps = 220, .reg = 0x13 },
+ { .mbps = 235, .reg = 0x23 },
+ { .mbps = 250, .reg = 0x33 },
+ { .mbps = 275, .reg = 0x04 },
+ { .mbps = 300, .reg = 0x14 },
+ { .mbps = 325, .reg = 0x05 },
+ { .mbps = 350, .reg = 0x15 },
+ { .mbps = 400, .reg = 0x25 },
+ { .mbps = 450, .reg = 0x06 },
+ { .mbps = 500, .reg = 0x16 },
+ { .mbps = 550, .reg = 0x07 },
+ { .mbps = 600, .reg = 0x17 },
+ { .mbps = 650, .reg = 0x08 },
+ { .mbps = 700, .reg = 0x18 },
+ { .mbps = 750, .reg = 0x09 },
+ { .mbps = 800, .reg = 0x19 },
+ { .mbps = 850, .reg = 0x29 },
+ { .mbps = 900, .reg = 0x39 },
+ { .mbps = 950, .reg = 0x0A },
+ { .mbps = 1000, .reg = 0x1A },
+ { .mbps = 1050, .reg = 0x2A },
+ { .mbps = 1100, .reg = 0x3A },
+ { .mbps = 1150, .reg = 0x0B },
+ { .mbps = 1200, .reg = 0x1B },
+ { .mbps = 1250, .reg = 0x2B },
+ { .mbps = 1300, .reg = 0x3B },
+ { .mbps = 1350, .reg = 0x0C },
+ { .mbps = 1400, .reg = 0x1C },
+ { .mbps = 1450, .reg = 0x2C },
+ { .mbps = 1500, .reg = 0x3C },
+ /* guard */
+ { .mbps = 0, .reg = 0x00 },
+};
+
+/* PHY ESC Error Monitor */
+#define PHEERM_REG 0x74
+
+/* PHY Clock Lane Monitor */
+#define PHCLM_REG 0x78
+
+/* PHY Data Lane Monitor */
+#define PHDLM_REG 0x7c
+
+struct rcar_csi2_format {
+ unsigned int code;
+ unsigned int datatype;
+ unsigned int bpp;
+};
+
+static const struct rcar_csi2_format rcar_csi2_formats[] = {
+ { .code = MEDIA_BUS_FMT_RGB888_1X24, .datatype = 0x24, .bpp = 24},
+ { .code = MEDIA_BUS_FMT_UYVY8_1X16, .datatype = 0x1e, .bpp = 8 },
+ { .code = MEDIA_BUS_FMT_UYVY8_2X8, .datatype = 0x1e, .bpp = 8 },
+ { .code = MEDIA_BUS_FMT_YUYV10_2X10, .datatype = 0x1e, .bpp = 8 },
+};
+
+static const struct rcar_csi2_format *rcar_csi2_code_to_fmt(unsigned int code)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(rcar_csi2_formats); i++)
+ if (rcar_csi2_formats[i].code == code)
+ return rcar_csi2_formats + i;
+ return NULL;
+}
+
+enum rcar_csi2_pads {
+ RCAR_CSI2_SINK,
+ RCAR_CSI2_SOURCE_VC0,
+ RCAR_CSI2_SOURCE_VC1,
+ RCAR_CSI2_SOURCE_VC2,
+ RCAR_CSI2_SOURCE_VC3,
+ NR_OF_RCAR_CSI2_PAD,
+};
+
+struct rcar_csi2 {
+ struct device *dev;
+ void __iomem *base;
+
+ unsigned short lanes;
+ unsigned char lane_swap[4];
+
+ struct v4l2_subdev subdev;
+ struct media_pad pads[NR_OF_RCAR_CSI2_PAD];
+
+ struct v4l2_mbus_framefmt mf;
+
+ struct mutex lock;
+ int stream_count;
+
+ struct v4l2_async_notifier notifier;
+ struct {
+ struct v4l2_async_subdev asd;
+ struct v4l2_subdev *subdev;
+ struct fwnode_handle *fwnode;
+ unsigned int source_pad;
+ } remote;
+};
+
+static inline struct rcar_csi2 *sd_to_csi2(struct v4l2_subdev *sd)
+{
+ return container_of(sd, struct rcar_csi2, subdev);
+}
+
+static u32 rcar_csi2_read(struct rcar_csi2 *priv, unsigned int reg)
+{
+ return ioread32(priv->base + reg);
+}
+
+static void rcar_csi2_write(struct rcar_csi2 *priv, unsigned int reg, u32 data)
+{
+ iowrite32(data, priv->base + reg);
+}
+
+static void rcar_csi2_reset(struct rcar_csi2 *priv)
+{
+ rcar_csi2_write(priv, SRST_REG, SRST_SRST);
+ rcar_csi2_write(priv, SRST_REG, 0);
+}
+
+static int rcar_csi2_wait_phy_start(struct rcar_csi2 *priv)
+{
+ int timeout;
+
+ /* Wait for the clock and data lanes to enter LP-11 state. */
+ for (timeout = 100; timeout >= 0; timeout--) {
+ const u32 lane_mask = (1 << priv->lanes) - 1;
+
+ if ((rcar_csi2_read(priv, PHCLM_REG) & 1) == 1 &&
+ (rcar_csi2_read(priv, PHDLM_REG) & lane_mask) == lane_mask)
+ return 0;
+
+ msleep(20);
+ }
+
+ dev_err(priv->dev, "Timeout waiting for LP-11 state\n");
+
+ return -ETIMEDOUT;
+}
+
+static int rcar_csi2_calc_phypll(struct rcar_csi2 *priv,
+ struct v4l2_subdev *source,
+ struct v4l2_mbus_framefmt *mf,
+ u32 *phypll)
+{
+ const struct phypll_hsfreqrange *hsfreq;
+ const struct rcar_csi2_format *format;
+ struct v4l2_ext_controls ctrls;
+ struct v4l2_ext_control ctrl;
+ u64 mbps;
+ int ret;
+
+ memset(&ctrls, 0, sizeof(ctrls));
+ memset(&ctrl, 0, sizeof(ctrl));
+
+ ctrl.id = V4L2_CID_PIXEL_RATE;
+
+ ctrls.count = 1;
+ ctrls.controls = &ctrl;
+
+ ret = v4l2_g_ext_ctrls(source->ctrl_handler, &ctrls);
+ if (ret < 0) {
+ dev_err(priv->dev, "no link freq control in subdev %s\n",
+ source->name);
+ return ret;
+ }
+
+ format = rcar_csi2_code_to_fmt(mf->code);
+ if (!format) {
+ dev_err(priv->dev, "Unknown format: %d\n", mf->code);
+ return -EINVAL;
+ }
+
+ mbps = ctrl.value64 * format->bpp;
+ do_div(mbps, priv->lanes * 1000000);
+
+ for (hsfreq = phypll_hsfreqrange_map; hsfreq->mbps != 0; hsfreq++)
+ if (hsfreq->mbps >= mbps)
+ break;
+
+ if (!hsfreq->mbps) {
+ dev_err(priv->dev, "Unsupported PHY speed (%llu Mbps)", mbps);
+ return -ERANGE;
+ }
+
+ dev_dbg(priv->dev, "PHY HSFREQRANGE requested %llu got %u Mbps\n", mbps,
+ hsfreq->mbps);
+
+ *phypll = PHYPLL_HSFREQRANGE(hsfreq->reg);
+
+ return 0;
+}
+
+static int rcar_csi2_start(struct rcar_csi2 *priv)
+{
+ const struct rcar_csi2_format *format;
+ struct v4l2_subdev_format fmt;
+ struct media_pad *source_pad;
+ struct v4l2_subdev *source = NULL;
+ struct v4l2_mbus_framefmt *mf = &fmt.format;
+ u32 phycnt, phypll, tmp;
+ u32 vcdt = 0, vcdt2 = 0;
+ unsigned int i;
+ int ret;
+
+ source_pad =
+ media_entity_remote_pad(&priv->subdev.entity.pads[RCAR_CSI2_SINK]);
+ if (!source_pad) {
+ dev_err(priv->dev, "Could not find remote source pad\n");
+ return -ENODEV;
+ }
+
+ source = media_entity_to_v4l2_subdev(source_pad->entity);
+ if (!source) {
+ dev_err(priv->dev, "Could not find remote subdevice\n");
+ return -ENODEV;
+ }
+
+ dev_dbg(priv->dev, "Using source %s pad: %u\n", source->name,
+ source_pad->index);
+
+ fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+ fmt.pad = source_pad->index;
+ ret = v4l2_subdev_call(source, pad, get_fmt, NULL, &fmt);
+ if (ret)
+ return ret;
+
+ dev_dbg(priv->dev, "Input size (%dx%d%c)\n", mf->width,
+ mf->height, mf->field == V4L2_FIELD_NONE ? 'p' : 'i');
+
+ /*
+ * Enable all Virtual Channels
+ *
+ * NOTE: I'ts not possible to get individual format for each
+ * source virtual channel. Once this is possible in V4L2
+ * it should be used here.
+ */
+ for (i = 0; i < 4; i++) {
+
+ format = rcar_csi2_code_to_fmt(mf->code);
+ if (!format) {
+ dev_err(priv->dev, "Unsupported media bus format: %d\n",
+ mf->code);
+ return -EINVAL;
+ }
+
+ tmp = VCDT_SEL_VC(i) | VCDT_VCDTN_EN | VCDT_SEL_DTN_ON |
+ VCDT_SEL_DT(format->datatype);
+
+ /* Store in correct reg and offset */
+ if (i < 2)
+ vcdt |= tmp << ((i % 2) * 16);
+ else
+ vcdt2 |= tmp << ((i % 2) * 16);
+ }
+
+ switch (priv->lanes) {
+ case 1:
+ phycnt = PHYCNT_ENABLECLK | PHYCNT_ENABLE_0;
+ break;
+ case 2:
+ phycnt = PHYCNT_ENABLECLK | PHYCNT_ENABLE_1 | PHYCNT_ENABLE_0;
+ break;
+ case 4:
+ phycnt = PHYCNT_ENABLECLK | PHYCNT_ENABLE_3 | PHYCNT_ENABLE_2 |
+ PHYCNT_ENABLE_1 | PHYCNT_ENABLE_0;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = rcar_csi2_calc_phypll(priv, source, mf, &phypll);
+ if (ret)
+ return ret;
+
+ /* Init */
+ rcar_csi2_write(priv, TREF_REG, TREF_TREF);
+ rcar_csi2_reset(priv);
+ rcar_csi2_write(priv, PHTC_REG, 0);
+
+ /* Configure */
+ rcar_csi2_write(priv, FLD_REG, FLD_FLD_NUM(2) | FLD_FLD_EN4 |
+ FLD_FLD_EN3 | FLD_FLD_EN2 | FLD_FLD_EN);
+ rcar_csi2_write(priv, VCDT_REG, vcdt);
+ rcar_csi2_write(priv, VCDT2_REG, vcdt2);
+ /* Lanes are zero indexed */
+ rcar_csi2_write(priv, LSWAP_REG,
+ LSWAP_L0SEL(priv->lane_swap[0] - 1) |
+ LSWAP_L1SEL(priv->lane_swap[1] - 1) |
+ LSWAP_L2SEL(priv->lane_swap[2] - 1) |
+ LSWAP_L3SEL(priv->lane_swap[3] - 1));
+
+ /* Start */
+ rcar_csi2_write(priv, PHYPLL_REG, phypll);
+ rcar_csi2_write(priv, PHYCNT_REG, phycnt);
+ rcar_csi2_write(priv, LINKCNT_REG, LINKCNT_MONITOR_EN |
+ LINKCNT_REG_MONI_PACT_EN | LINKCNT_ICLK_NONSTOP);
+ rcar_csi2_write(priv, PHYCNT_REG, phycnt | PHYCNT_SHUTDOWNZ);
+ rcar_csi2_write(priv, PHYCNT_REG, phycnt | PHYCNT_SHUTDOWNZ |
+ PHYCNT_RSTZ);
+
+ return rcar_csi2_wait_phy_start(priv);
+}
+
+static void rcar_csi2_stop(struct rcar_csi2 *priv)
+{
+ rcar_csi2_write(priv, PHYCNT_REG, 0);
+
+ rcar_csi2_reset(priv);
+}
+
+static int rcar_csi2_sd_info(struct rcar_csi2 *priv, struct v4l2_subdev **sd)
+{
+ struct media_pad *pad;
+
+ pad = media_entity_remote_pad(&priv->pads[RCAR_CSI2_SINK]);
+ if (!pad) {
+ dev_err(priv->dev, "Could not find remote pad\n");
+ return -ENODEV;
+ }
+
+ *sd = media_entity_to_v4l2_subdev(pad->entity);
+ if (!*sd) {
+ dev_err(priv->dev, "Could not find remote subdevice\n");
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static int rcar_csi2_s_stream(struct v4l2_subdev *sd, int enable)
+{
+ struct rcar_csi2 *priv = sd_to_csi2(sd);
+ struct v4l2_subdev *nextsd;
+ int ret;
+
+ mutex_lock(&priv->lock);
+
+ ret = rcar_csi2_sd_info(priv, &nextsd);
+ if (ret)
+ goto out;
+
+ priv->stream_count += enable ? 1 : -1;
+
+ if (enable && priv->stream_count == 1) {
+ ret = rcar_csi2_start(priv);
+ if (ret)
+ goto out;
+ ret = v4l2_subdev_call(nextsd, video, s_stream, 1);
+
+ } else if (!enable && !priv->stream_count) {
+ rcar_csi2_stop(priv);
+ ret = v4l2_subdev_call(nextsd, video, s_stream, 0);
+ }
+out:
+ mutex_unlock(&priv->lock);
+
+ return ret;
+}
+
+static int rcar_csi2_s_power(struct v4l2_subdev *sd, int on)
+{
+ struct rcar_csi2 *priv = sd_to_csi2(sd);
+
+ if (on)
+ pm_runtime_get_sync(priv->dev);
+ else
+ pm_runtime_put(priv->dev);
+
+ return 0;
+}
+
+static int rcar_csi2_set_pad_format(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_format *format)
+{
+ struct rcar_csi2 *priv = container_of(sd, struct rcar_csi2, subdev);
+
+ if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE)
+ priv->mf = format->format;
+
+ return 0;
+}
+
+static int rcar_csi2_get_pad_format(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_format *format)
+{
+ struct rcar_csi2 *priv = container_of(sd, struct rcar_csi2, subdev);
+
+ format->format = priv->mf;
+
+ return 0;
+}
+
+static const struct v4l2_subdev_video_ops rcar_csi2_video_ops = {
+ .s_stream = rcar_csi2_s_stream,
+};
+
+static const struct v4l2_subdev_core_ops rcar_csi2_subdev_core_ops = {
+ .s_power = rcar_csi2_s_power,
+};
+
+static const struct v4l2_subdev_pad_ops rcar_csi2_pad_ops = {
+ .set_fmt = rcar_csi2_set_pad_format,
+ .get_fmt = rcar_csi2_get_pad_format,
+};
+
+static const struct v4l2_subdev_ops rcar_csi2_subdev_ops = {
+ .video = &rcar_csi2_video_ops,
+ .core = &rcar_csi2_subdev_core_ops,
+ .pad = &rcar_csi2_pad_ops,
+};
+
+/* -----------------------------------------------------------------------------
+ * Async and registered of subdevices and links
+ */
+
+#define notifier_to_priv(n) container_of(n, struct rcar_csi2, notifier)
+
+static int rcar_csi2_notify_complete(struct v4l2_async_notifier *notifier)
+{
+ struct rcar_csi2 *priv = notifier_to_priv(notifier);
+ int ret;
+
+ ret = v4l2_device_register_subdev_nodes(priv->subdev.v4l2_dev);
+ if (ret) {
+ dev_err(priv->dev, "Failed to register subdev nodes\n");
+ return ret;
+ }
+
+ return media_create_pad_link(&priv->remote.subdev->entity,
+ priv->remote.source_pad,
+ &priv->subdev.entity, 0,
+ MEDIA_LNK_FL_ENABLED |
+ MEDIA_LNK_FL_IMMUTABLE);
+}
+
+static int rcar_csi2_notify_bound(struct v4l2_async_notifier *notifier,
+ struct v4l2_subdev *subdev,
+ struct v4l2_async_subdev *asd)
+{
+ struct rcar_csi2 *priv = notifier_to_priv(notifier);
+ int ret;
+
+ v4l2_set_subdev_hostdata(subdev, priv);
+
+ ret = media_entity_pad_from_fwnode(&subdev->entity,
+ priv->remote.fwnode,
+ MEDIA_PAD_FL_SOURCE,
+ &priv->remote.source_pad);
+ if (ret) {
+ dev_err(priv->dev, "Failed to find pad for %s\n",
+ subdev->name);
+ return ret;
+ }
+
+ dev_dbg(priv->dev, "Bound %s pad: %d\n", subdev->name,
+ priv->remote.source_pad);
+ priv->remote.subdev = subdev;
+
+ return 0;
+}
+static void rcar_csi2_notify_unbind(struct v4l2_async_notifier *notifier,
+ struct v4l2_subdev *subdev,
+ struct v4l2_async_subdev *asd)
+{
+ struct rcar_csi2 *priv = notifier_to_priv(notifier);
+
+ dev_dbg(priv->dev, "Unbind %s\n", subdev->name);
+ priv->remote.subdev = NULL;
+}
+
+static int rcar_csi2_registered(struct v4l2_subdev *sd)
+{
+ struct rcar_csi2 *priv = container_of(sd, struct rcar_csi2, subdev);
+ struct v4l2_async_subdev **subdevs = NULL;
+ int ret;
+
+ subdevs = devm_kzalloc(priv->dev, sizeof(*subdevs), GFP_KERNEL);
+ if (subdevs == NULL)
+ return -ENOMEM;
+
+ subdevs[0] = &priv->remote.asd;
+
+ priv->notifier.num_subdevs = 1;
+ priv->notifier.subdevs = subdevs;
+ priv->notifier.bound = rcar_csi2_notify_bound;
+ priv->notifier.unbind = rcar_csi2_notify_unbind;
+ priv->notifier.complete = rcar_csi2_notify_complete;
+
+ ret = v4l2_async_subnotifier_register(&priv->subdev, &priv->notifier);
+ if (ret < 0) {
+ dev_err(priv->dev, "Notifier registration failed\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static void rcar_csi2_unregistered(struct v4l2_subdev *sd)
+{
+ struct rcar_csi2 *priv = container_of(sd, struct rcar_csi2, subdev);
+
+ v4l2_async_subnotifier_unregister(&priv->notifier);
+}
+
+static const struct v4l2_subdev_internal_ops rcar_csi2_internal_ops = {
+ .registered = rcar_csi2_registered,
+ .unregistered = rcar_csi2_unregistered,
+};
+
+static int rcar_csi2_parse_dt_subdevice(struct rcar_csi2 *priv)
+{
+ struct device_node *remote, *ep;
+ struct v4l2_fwnode_endpoint v4l2_ep;
+ int ret;
+
+ ep = of_graph_get_endpoint_by_regs(priv->dev->of_node, 0, 0);
+ if (!ep) {
+ dev_err(priv->dev, "Not connected to subdevice\n");
+ return -EINVAL;
+ }
+
+ ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep), &v4l2_ep);
+ if (ret) {
+ dev_err(priv->dev, "Could not parse v4l2 endpoint\n");
+ of_node_put(ep);
+ return -EINVAL;
+ }
+
+ if (v4l2_ep.bus_type != V4L2_MBUS_CSI2) {
+ dev_err(priv->dev, "Unknown media bus type: 0x%x\n",
+ v4l2_ep.bus_type);
+ of_node_put(ep);
+ return -EINVAL;
+ }
+
+ priv->remote.fwnode =
+ fwnode_graph_get_remote_endpoint(of_fwnode_handle(ep));
+
+ remote = of_graph_get_remote_port_parent(ep);
+ of_node_put(ep);
+ if (!remote) {
+ dev_err(priv->dev, "No subdevice found for endpoint '%s'\n",
+ of_node_full_name(ep));
+ return -EINVAL;
+ }
+
+ priv->remote.asd.match.fwnode.fwnode = of_fwnode_handle(remote);
+ priv->remote.asd.match_type = V4L2_ASYNC_MATCH_FWNODE;
+
+ dev_dbg(priv->dev, "Found '%s'\n", of_node_full_name(remote));
+
+ return 0;
+}
+
+/* -----------------------------------------------------------------------------
+ * Platform Device Driver
+ */
+
+static const struct of_device_id rcar_csi2_of_table[] = {
+ { .compatible = "renesas,rcar-gen3-csi2" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, rcar_csi2_of_table);
+
+static const struct media_entity_operations rcar_csi2_entity_ops = {
+ .link_validate = v4l2_subdev_link_validate,
+};
+
+static int rcar_csi2_parse_dt_settings(struct rcar_csi2 *priv)
+{
+ struct v4l2_fwnode_endpoint v4l2_ep;
+ struct device_node *ep;
+ unsigned int i;
+ int ret;
+
+ ep = of_graph_get_endpoint_by_regs(priv->dev->of_node, 0, 0);
+ if (!ep)
+ return -EINVAL;
+
+ ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep), &v4l2_ep);
+ of_node_put(ep);
+ if (ret) {
+ dev_err(priv->dev, "Could not parse v4l2 endpoint\n");
+ return -EINVAL;
+ }
+
+ if (v4l2_ep.bus_type != V4L2_MBUS_CSI2) {
+ dev_err(priv->dev, "Unsupported media bus type for %s\n",
+ of_node_full_name(ep));
+ return -EINVAL;
+ }
+
+ priv->lanes = v4l2_ep.bus.mipi_csi2.num_data_lanes;
+ if (priv->lanes != 1 && priv->lanes != 2 && priv->lanes != 4) {
+ dev_err(priv->dev, "Unsupported number of data-lanes: %d\n",
+ priv->lanes);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(priv->lane_swap); i++) {
+ priv->lane_swap[i] = i < priv->lanes ?
+ v4l2_ep.bus.mipi_csi2.data_lanes[i] : i;
+
+ /* Check for valid lane number */
+ if (priv->lane_swap[i] < 1 || priv->lane_swap[i] > 4) {
+ dev_err(priv->dev, "data-lanes must be in 1-4 range\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int rcar_csi2_probe_resources(struct rcar_csi2 *priv,
+ struct platform_device *pdev)
+{
+ struct resource *mem;
+ int irq;
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!mem)
+ return -ENODEV;
+
+ priv->base = devm_ioremap_resource(&pdev->dev, mem);
+ if (IS_ERR(priv->base))
+ return PTR_ERR(priv->base);
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ return 0;
+}
+
+static int rcar_csi2_probe(struct platform_device *pdev)
+{
+ struct rcar_csi2 *priv;
+ unsigned int i;
+ int ret;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->dev = &pdev->dev;
+
+ mutex_init(&priv->lock);
+ priv->stream_count = 0;
+
+ ret = rcar_csi2_parse_dt_settings(priv);
+ if (ret)
+ return ret;
+
+ ret = rcar_csi2_parse_dt_subdevice(priv);
+ if (ret)
+ return ret;
+
+ ret = rcar_csi2_probe_resources(priv, pdev);
+ if (ret) {
+ dev_err(priv->dev, "Failed to get resources\n");
+ return ret;
+ }
+
+ platform_set_drvdata(pdev, priv);
+
+ priv->subdev.owner = THIS_MODULE;
+ priv->subdev.dev = &pdev->dev;
+ v4l2_subdev_init(&priv->subdev, &rcar_csi2_subdev_ops);
+ v4l2_set_subdevdata(&priv->subdev, &pdev->dev);
+ snprintf(priv->subdev.name, V4L2_SUBDEV_NAME_SIZE, "%s %s",
+ KBUILD_MODNAME, dev_name(&pdev->dev));
+ priv->subdev.flags = V4L2_SUBDEV_FL_HAS_DEVNODE;
+ priv->subdev.internal_ops = &rcar_csi2_internal_ops;
+
+ priv->subdev.entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;
+ priv->subdev.entity.ops = &rcar_csi2_entity_ops;
+
+ priv->pads[RCAR_CSI2_SINK].flags = MEDIA_PAD_FL_SINK;
+ for (i = RCAR_CSI2_SOURCE_VC0; i < NR_OF_RCAR_CSI2_PAD; i++)
+ priv->pads[i].flags = MEDIA_PAD_FL_SOURCE;
+
+ ret = media_entity_pads_init(&priv->subdev.entity, NR_OF_RCAR_CSI2_PAD,
+ priv->pads);
+ if (ret)
+ return ret;
+
+ ret = v4l2_async_register_subdev(&priv->subdev);
+ if (ret < 0)
+ return ret;
+
+ pm_runtime_enable(&pdev->dev);
+
+ dev_info(priv->dev, "%d lanes found\n", priv->lanes);
+
+ return 0;
+}
+
+static int rcar_csi2_remove(struct platform_device *pdev)
+{
+ struct rcar_csi2 *priv = platform_get_drvdata(pdev);
+
+ v4l2_async_unregister_subdev(&priv->subdev);
+
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+}
+
+static struct platform_driver __refdata rcar_csi2_pdrv = {
+ .remove = rcar_csi2_remove,
+ .probe = rcar_csi2_probe,
+ .driver = {
+ .name = "rcar-csi2",
+ .of_match_table = of_match_ptr(rcar_csi2_of_table),
+ },
+};
+
+module_platform_driver(rcar_csi2_pdrv);
+
+MODULE_AUTHOR("Niklas Söderlund <niklas.soderlund@ragnatech.se>");
+MODULE_DESCRIPTION("Renesas R-Car MIPI CSI-2 receiver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/media/platform/rcar-vin/rcar-dma.c b/drivers/media/platform/rcar-vin/rcar-dma.c
index 9ccd5ff..a476490 100644
--- a/drivers/media/platform/rcar-vin/rcar-dma.c
+++ b/drivers/media/platform/rcar-vin/rcar-dma.c
@@ -16,6 +16,7 @@
#include <linux/delay.h>
#include <linux/interrupt.h>
+#include <linux/pm_runtime.h>
#include <media/videobuf2-dma-contig.h>
@@ -33,21 +34,23 @@
#define VNELPRC_REG 0x10 /* Video n End Line Pre-Clip Register */
#define VNSPPRC_REG 0x14 /* Video n Start Pixel Pre-Clip Register */
#define VNEPPRC_REG 0x18 /* Video n End Pixel Pre-Clip Register */
-#define VNSLPOC_REG 0x1C /* Video n Start Line Post-Clip Register */
-#define VNELPOC_REG 0x20 /* Video n End Line Post-Clip Register */
-#define VNSPPOC_REG 0x24 /* Video n Start Pixel Post-Clip Register */
-#define VNEPPOC_REG 0x28 /* Video n End Pixel Post-Clip Register */
#define VNIS_REG 0x2C /* Video n Image Stride Register */
#define VNMB_REG(m) (0x30 + ((m) << 2)) /* Video n Memory Base m Register */
#define VNIE_REG 0x40 /* Video n Interrupt Enable Register */
#define VNINTS_REG 0x44 /* Video n Interrupt Status Register */
#define VNSI_REG 0x48 /* Video n Scanline Interrupt Register */
#define VNMTC_REG 0x4C /* Video n Memory Transfer Control Register */
-#define VNYS_REG 0x50 /* Video n Y Scale Register */
-#define VNXS_REG 0x54 /* Video n X Scale Register */
#define VNDMR_REG 0x58 /* Video n Data Mode Register */
#define VNDMR2_REG 0x5C /* Video n Data Mode Register 2 */
#define VNUVAOF_REG 0x60 /* Video n UV Address Offset Register */
+
+/* Register offsets specific for Gen2 */
+#define VNSLPOC_REG 0x1C /* Video n Start Line Post-Clip Register */
+#define VNELPOC_REG 0x20 /* Video n End Line Post-Clip Register */
+#define VNSPPOC_REG 0x24 /* Video n Start Pixel Post-Clip Register */
+#define VNEPPOC_REG 0x28 /* Video n End Pixel Post-Clip Register */
+#define VNYS_REG 0x50 /* Video n Y Scale Register */
+#define VNXS_REG 0x54 /* Video n X Scale Register */
#define VNC1A_REG 0x80 /* Video n Coefficient Set C1A Register */
#define VNC1B_REG 0x84 /* Video n Coefficient Set C1B Register */
#define VNC1C_REG 0x88 /* Video n Coefficient Set C1C Register */
@@ -73,9 +76,13 @@
#define VNC8B_REG 0xF4 /* Video n Coefficient Set C8B Register */
#define VNC8C_REG 0xF8 /* Video n Coefficient Set C8C Register */
+/* Register offsets specific for Gen3 */
+#define VNCSI_IFMD_REG 0x20 /* Video n CSI2 Interface Mode Register */
/* Register bit fields for R-Car VIN */
/* Video n Main Control Register bits */
+#define VNMC_DPINE (1 << 27) /* Gen3 specific */
+#define VNMC_SCLE (1 << 26) /* Gen3 specific */
#define VNMC_FOC (1 << 21)
#define VNMC_YCAL (1 << 19)
#define VNMC_INF_YUV8_BT656 (0 << 16)
@@ -119,6 +126,22 @@
#define VNDMR2_FTEV (1 << 17)
#define VNDMR2_VLV(n) ((n & 0xf) << 12)
+/* Video n CSI2 Interface Mode Register (Gen3) */
+#define VNCSI_IFMD_DES2 (1 << 27)
+#define VNCSI_IFMD_DES1 (1 << 26)
+#define VNCSI_IFMD_DES0 (1 << 25)
+#define VNCSI_IFMD_CSI_CHSEL(n) ((n & 0xf) << 0)
+#define VNCSI_IFMD_CSI_CHSEL_MASK 0xf
+
+struct rvin_buffer {
+ struct vb2_v4l2_buffer vb;
+ struct list_head list;
+};
+
+#define to_buf_list(vb2_buffer) (&container_of(vb2_buffer, \
+ struct rvin_buffer, \
+ vb)->list)
+
static void rvin_write(struct rvin_dev *vin, u32 value, u32 offset)
{
iowrite32(value, vin->base + offset);
@@ -129,254 +152,6 @@
return ioread32(vin->base + offset);
}
-static int rvin_setup(struct rvin_dev *vin)
-{
- u32 vnmc, dmr, dmr2, interrupts;
- v4l2_std_id std;
- bool progressive = false, output_is_yuv = false, input_is_yuv = false;
-
- switch (vin->format.field) {
- case V4L2_FIELD_TOP:
- vnmc = VNMC_IM_ODD;
- break;
- case V4L2_FIELD_BOTTOM:
- vnmc = VNMC_IM_EVEN;
- break;
- case V4L2_FIELD_INTERLACED:
- /* Default to TB */
- vnmc = VNMC_IM_FULL;
- /* Use BT if video standard can be read and is 60 Hz format */
- if (!v4l2_subdev_call(vin_to_source(vin), video, g_std, &std)) {
- if (std & V4L2_STD_525_60)
- vnmc = VNMC_IM_FULL | VNMC_FOC;
- }
- break;
- case V4L2_FIELD_INTERLACED_TB:
- vnmc = VNMC_IM_FULL;
- break;
- case V4L2_FIELD_INTERLACED_BT:
- vnmc = VNMC_IM_FULL | VNMC_FOC;
- break;
- case V4L2_FIELD_ALTERNATE:
- case V4L2_FIELD_NONE:
- if (vin->continuous) {
- vnmc = VNMC_IM_ODD_EVEN;
- progressive = true;
- } else {
- vnmc = VNMC_IM_ODD;
- }
- break;
- default:
- vnmc = VNMC_IM_ODD;
- break;
- }
-
- /*
- * Input interface
- */
- switch (vin->digital.code) {
- case MEDIA_BUS_FMT_YUYV8_1X16:
- /* BT.601/BT.1358 16bit YCbCr422 */
- vnmc |= VNMC_INF_YUV16;
- input_is_yuv = true;
- break;
- case MEDIA_BUS_FMT_UYVY8_2X8:
- /* BT.656 8bit YCbCr422 or BT.601 8bit YCbCr422 */
- vnmc |= vin->digital.mbus_cfg.type == V4L2_MBUS_BT656 ?
- VNMC_INF_YUV8_BT656 : VNMC_INF_YUV8_BT601;
- input_is_yuv = true;
- break;
- case MEDIA_BUS_FMT_RGB888_1X24:
- vnmc |= VNMC_INF_RGB888;
- break;
- case MEDIA_BUS_FMT_UYVY10_2X10:
- /* BT.656 10bit YCbCr422 or BT.601 10bit YCbCr422 */
- vnmc |= vin->digital.mbus_cfg.type == V4L2_MBUS_BT656 ?
- VNMC_INF_YUV10_BT656 : VNMC_INF_YUV10_BT601;
- input_is_yuv = true;
- break;
- default:
- break;
- }
-
- /* Enable VSYNC Field Toogle mode after one VSYNC input */
- dmr2 = VNDMR2_FTEV | VNDMR2_VLV(1);
-
- /* Hsync Signal Polarity Select */
- if (!(vin->digital.mbus_cfg.flags & V4L2_MBUS_HSYNC_ACTIVE_LOW))
- dmr2 |= VNDMR2_HPS;
-
- /* Vsync Signal Polarity Select */
- if (!(vin->digital.mbus_cfg.flags & V4L2_MBUS_VSYNC_ACTIVE_LOW))
- dmr2 |= VNDMR2_VPS;
-
- /*
- * Output format
- */
- switch (vin->format.pixelformat) {
- case V4L2_PIX_FMT_NV16:
- rvin_write(vin,
- ALIGN(vin->format.width * vin->format.height, 0x80),
- VNUVAOF_REG);
- dmr = VNDMR_DTMD_YCSEP;
- output_is_yuv = true;
- break;
- case V4L2_PIX_FMT_YUYV:
- dmr = VNDMR_BPSM;
- output_is_yuv = true;
- break;
- case V4L2_PIX_FMT_UYVY:
- dmr = 0;
- output_is_yuv = true;
- break;
- case V4L2_PIX_FMT_XRGB555:
- dmr = VNDMR_DTMD_ARGB1555;
- break;
- case V4L2_PIX_FMT_RGB565:
- dmr = 0;
- break;
- case V4L2_PIX_FMT_XBGR32:
- /* Note: not supported on M1 */
- dmr = VNDMR_EXRGB;
- break;
- default:
- vin_err(vin, "Invalid pixelformat (0x%x)\n",
- vin->format.pixelformat);
- return -EINVAL;
- }
-
- /* Always update on field change */
- vnmc |= VNMC_VUP;
-
- /* If input and output use the same colorspace, use bypass mode */
- if (input_is_yuv == output_is_yuv)
- vnmc |= VNMC_BPS;
-
- /* Progressive or interlaced mode */
- interrupts = progressive ? VNIE_FIE : VNIE_EFE;
-
- /* Ack interrupts */
- rvin_write(vin, interrupts, VNINTS_REG);
- /* Enable interrupts */
- rvin_write(vin, interrupts, VNIE_REG);
- /* Start capturing */
- rvin_write(vin, dmr, VNDMR_REG);
- rvin_write(vin, dmr2, VNDMR2_REG);
-
- /* Enable module */
- rvin_write(vin, vnmc | VNMC_ME, VNMC_REG);
-
- return 0;
-}
-
-static void rvin_capture_on(struct rvin_dev *vin)
-{
- vin_dbg(vin, "Capture on in %s mode\n",
- vin->continuous ? "continuous" : "single");
-
- if (vin->continuous)
- /* Continuous Frame Capture Mode */
- rvin_write(vin, VNFC_C_FRAME, VNFC_REG);
- else
- /* Single Frame Capture Mode */
- rvin_write(vin, VNFC_S_FRAME, VNFC_REG);
-}
-
-static void rvin_capture_off(struct rvin_dev *vin)
-{
- /* Set continuous & single transfer off */
- rvin_write(vin, 0, VNFC_REG);
-}
-
-static int rvin_capture_start(struct rvin_dev *vin)
-{
- int ret;
-
- rvin_crop_scale_comp(vin);
-
- ret = rvin_setup(vin);
- if (ret)
- return ret;
-
- rvin_capture_on(vin);
-
- return 0;
-}
-
-static void rvin_capture_stop(struct rvin_dev *vin)
-{
- rvin_capture_off(vin);
-
- /* Disable module */
- rvin_write(vin, rvin_read(vin, VNMC_REG) & ~VNMC_ME, VNMC_REG);
-}
-
-static void rvin_disable_interrupts(struct rvin_dev *vin)
-{
- rvin_write(vin, 0, VNIE_REG);
-}
-
-static u32 rvin_get_interrupt_status(struct rvin_dev *vin)
-{
- return rvin_read(vin, VNINTS_REG);
-}
-
-static void rvin_ack_interrupt(struct rvin_dev *vin)
-{
- rvin_write(vin, rvin_read(vin, VNINTS_REG), VNINTS_REG);
-}
-
-static bool rvin_capture_active(struct rvin_dev *vin)
-{
- return rvin_read(vin, VNMS_REG) & VNMS_CA;
-}
-
-static int rvin_get_active_slot(struct rvin_dev *vin, u32 vnms)
-{
- if (vin->continuous)
- return (vnms & VNMS_FBS_MASK) >> VNMS_FBS_SHIFT;
-
- return 0;
-}
-
-static enum v4l2_field rvin_get_active_field(struct rvin_dev *vin, u32 vnms)
-{
- if (vin->format.field == V4L2_FIELD_ALTERNATE) {
- /* If FS is set it's a Even field */
- if (vnms & VNMS_FS)
- return V4L2_FIELD_BOTTOM;
- return V4L2_FIELD_TOP;
- }
-
- return vin->format.field;
-}
-
-static void rvin_set_slot_addr(struct rvin_dev *vin, int slot, dma_addr_t addr)
-{
- const struct rvin_video_format *fmt;
- int offsetx, offsety;
- dma_addr_t offset;
-
- fmt = rvin_format_from_pixel(vin->format.pixelformat);
-
- /*
- * There is no HW support for composition do the beast we can
- * by modifying the buffer offset
- */
- offsetx = vin->compose.left * fmt->bpp;
- offsety = vin->compose.top * vin->format.bytesperline;
- offset = addr + offsetx + offsety;
-
- /*
- * The address needs to be 128 bytes aligned. Driver should never accept
- * settings that do not satisfy this in the first place...
- */
- if (WARN_ON((offsetx | offsety | offset) & HW_BUFFER_MASK))
- return;
-
- rvin_write(vin, offset, VNMB_REG(slot));
-}
-
/* -----------------------------------------------------------------------------
* Crop and Scaling Gen2
*/
@@ -753,28 +528,10 @@
rvin_write(vin, p_set->coeff_set[23], VNC8C_REG);
}
-void rvin_crop_scale_comp(struct rvin_dev *vin)
+static void rvin_crop_scale_comp_gen2(struct rvin_dev *vin)
{
u32 xs, ys;
- /* Set Start/End Pixel/Line Pre-Clip */
- rvin_write(vin, vin->crop.left, VNSPPRC_REG);
- rvin_write(vin, vin->crop.left + vin->crop.width - 1, VNEPPRC_REG);
- switch (vin->format.field) {
- case V4L2_FIELD_INTERLACED:
- case V4L2_FIELD_INTERLACED_TB:
- case V4L2_FIELD_INTERLACED_BT:
- rvin_write(vin, vin->crop.top / 2, VNSLPRC_REG);
- rvin_write(vin, (vin->crop.top + vin->crop.height) / 2 - 1,
- VNELPRC_REG);
- break;
- default:
- rvin_write(vin, vin->crop.top, VNSLPRC_REG);
- rvin_write(vin, vin->crop.top + vin->crop.height - 1,
- VNELPRC_REG);
- break;
- }
-
/* Set scaling coefficient */
ys = 0;
if (vin->crop.height != vin->compose.height)
@@ -812,11 +569,6 @@
break;
}
- if (vin->format.pixelformat == V4L2_PIX_FMT_NV16)
- rvin_write(vin, ALIGN(vin->format.width, 0x20), VNIS_REG);
- else
- rvin_write(vin, ALIGN(vin->format.width, 0x10), VNIS_REG);
-
vin_dbg(vin,
"Pre-Clip: %ux%u@%u:%u YS: %d XS: %d Post-Clip: %ux%u@%u:%u\n",
vin->crop.width, vin->crop.height, vin->crop.left,
@@ -824,29 +576,258 @@
0, 0);
}
-void rvin_scale_try(struct rvin_dev *vin, struct v4l2_pix_format *pix,
- u32 width, u32 height)
+static void rvin_crop_scale_comp(struct rvin_dev *vin)
{
- /* All VIN channels on Gen2 have scalers */
- pix->width = width;
- pix->height = height;
+ /* Set Start/End Pixel/Line Pre-Clip */
+ rvin_write(vin, vin->crop.left, VNSPPRC_REG);
+ rvin_write(vin, vin->crop.left + vin->crop.width - 1, VNEPPRC_REG);
+
+ switch (vin->format.field) {
+ case V4L2_FIELD_INTERLACED:
+ case V4L2_FIELD_INTERLACED_TB:
+ case V4L2_FIELD_INTERLACED_BT:
+ rvin_write(vin, vin->crop.top / 2, VNSLPRC_REG);
+ rvin_write(vin, (vin->crop.top + vin->crop.height) / 2 - 1,
+ VNELPRC_REG);
+ break;
+ default:
+ rvin_write(vin, vin->crop.top, VNSLPRC_REG);
+ rvin_write(vin, vin->crop.top + vin->crop.height - 1,
+ VNELPRC_REG);
+ break;
+ }
+
+ /* TODO: Add support for the UDS scaler. */
+ if (vin->info->chip != RCAR_GEN3)
+ rvin_crop_scale_comp_gen2(vin);
+
+ if (vin->format.pixelformat == V4L2_PIX_FMT_NV16)
+ rvin_write(vin, ALIGN(vin->format.width, 0x20), VNIS_REG);
+ else
+ rvin_write(vin, ALIGN(vin->format.width, 0x10), VNIS_REG);
}
/* -----------------------------------------------------------------------------
- * DMA Functions
+ * Hardware setup
*/
-#define RVIN_TIMEOUT_MS 100
-#define RVIN_RETRIES 10
+static int rvin_setup(struct rvin_dev *vin)
+{
+ u32 vnmc, dmr, dmr2, interrupts;
+ v4l2_std_id std;
+ bool progressive = false, output_is_yuv = false, input_is_yuv = false;
-struct rvin_buffer {
- struct vb2_v4l2_buffer vb;
- struct list_head list;
-};
+ switch (vin->format.field) {
+ case V4L2_FIELD_TOP:
+ vnmc = VNMC_IM_ODD;
+ break;
+ case V4L2_FIELD_BOTTOM:
+ vnmc = VNMC_IM_EVEN;
+ break;
+ case V4L2_FIELD_INTERLACED:
+ /* Default to TB */
+ vnmc = VNMC_IM_FULL;
+ /* Use BT if video standard can be read and is 60 Hz format */
+ if (!vin->info->use_mc &&
+ !v4l2_subdev_call(vin_to_source(vin), video, g_std, &std)) {
+ if (std & V4L2_STD_525_60)
+ vnmc = VNMC_IM_FULL | VNMC_FOC;
+ }
+ break;
+ case V4L2_FIELD_INTERLACED_TB:
+ vnmc = VNMC_IM_FULL;
+ break;
+ case V4L2_FIELD_INTERLACED_BT:
+ vnmc = VNMC_IM_FULL | VNMC_FOC;
+ break;
+ case V4L2_FIELD_ALTERNATE:
+ case V4L2_FIELD_NONE:
+ if (vin->continuous) {
+ vnmc = VNMC_IM_ODD_EVEN;
+ progressive = true;
+ } else {
+ vnmc = VNMC_IM_ODD;
+ }
+ break;
+ default:
+ vnmc = VNMC_IM_ODD;
+ break;
+ }
-#define to_buf_list(vb2_buffer) (&container_of(vb2_buffer, \
- struct rvin_buffer, \
- vb)->list)
+ /*
+ * Input interface
+ */
+ switch (vin->code) {
+ case MEDIA_BUS_FMT_YUYV8_1X16:
+ /* BT.601/BT.1358 16bit YCbCr422 */
+ vnmc |= VNMC_INF_YUV16;
+ input_is_yuv = true;
+ break;
+ case MEDIA_BUS_FMT_UYVY8_2X8:
+ /* BT.656 8bit YCbCr422 or BT.601 8bit YCbCr422 */
+ vnmc |= vin->mbus_cfg.type == V4L2_MBUS_BT656 ?
+ VNMC_INF_YUV8_BT656 : VNMC_INF_YUV8_BT601;
+ input_is_yuv = true;
+ break;
+ case MEDIA_BUS_FMT_RGB888_1X24:
+ vnmc |= VNMC_INF_RGB888;
+ break;
+ case MEDIA_BUS_FMT_UYVY10_2X10:
+ /* BT.656 10bit YCbCr422 or BT.601 10bit YCbCr422 */
+ vnmc |= vin->mbus_cfg.type == V4L2_MBUS_BT656 ?
+ VNMC_INF_YUV10_BT656 : VNMC_INF_YUV10_BT601;
+ input_is_yuv = true;
+ break;
+ default:
+ break;
+ }
+
+ /* Enable VSYNC Field Toogle mode after one VSYNC input */
+ if (vin->info->chip == RCAR_GEN3)
+ dmr2 = VNDMR2_FTEV;
+ else
+ dmr2 = VNDMR2_FTEV | VNDMR2_VLV(1);
+
+ /* Hsync Signal Polarity Select */
+ if (!(vin->mbus_cfg.flags & V4L2_MBUS_HSYNC_ACTIVE_LOW))
+ dmr2 |= VNDMR2_HPS;
+
+ /* Vsync Signal Polarity Select */
+ if (!(vin->mbus_cfg.flags & V4L2_MBUS_VSYNC_ACTIVE_LOW))
+ dmr2 |= VNDMR2_VPS;
+
+ /*
+ * Output format
+ */
+ switch (vin->format.pixelformat) {
+ case V4L2_PIX_FMT_NV16:
+ rvin_write(vin,
+ ALIGN(vin->format.width * vin->format.height, 0x80),
+ VNUVAOF_REG);
+ dmr = VNDMR_DTMD_YCSEP;
+ output_is_yuv = true;
+ break;
+ case V4L2_PIX_FMT_YUYV:
+ dmr = VNDMR_BPSM;
+ output_is_yuv = true;
+ break;
+ case V4L2_PIX_FMT_UYVY:
+ dmr = 0;
+ output_is_yuv = true;
+ break;
+ case V4L2_PIX_FMT_XRGB555:
+ dmr = VNDMR_DTMD_ARGB1555;
+ break;
+ case V4L2_PIX_FMT_RGB565:
+ dmr = 0;
+ break;
+ case V4L2_PIX_FMT_XBGR32:
+ /* Note: not supported on M1 */
+ dmr = VNDMR_EXRGB;
+ break;
+ default:
+ vin_err(vin, "Invalid pixelformat (0x%x)\n",
+ vin->format.pixelformat);
+ return -EINVAL;
+ }
+
+ /* Always update on field change */
+ vnmc |= VNMC_VUP;
+
+ /* If input and output use the same colorspace, use bypass mode */
+ if (input_is_yuv == output_is_yuv)
+ vnmc |= VNMC_BPS;
+
+ if (vin->info->chip == RCAR_GEN3) {
+ /* Select between CSI-2 and Digital input */
+ if (vin->mbus_cfg.type == V4L2_MBUS_CSI2)
+ vnmc &= ~VNMC_DPINE;
+ else
+ vnmc |= VNMC_DPINE;
+ }
+
+ /* Progressive or interlaced mode */
+ interrupts = progressive ? VNIE_FIE : VNIE_EFE;
+
+ /* Ack interrupts */
+ rvin_write(vin, interrupts, VNINTS_REG);
+ /* Enable interrupts */
+ rvin_write(vin, interrupts, VNIE_REG);
+ /* Start capturing */
+ rvin_write(vin, dmr, VNDMR_REG);
+ rvin_write(vin, dmr2, VNDMR2_REG);
+
+ /* Enable module */
+ rvin_write(vin, vnmc | VNMC_ME, VNMC_REG);
+
+ return 0;
+}
+
+static void rvin_disable_interrupts(struct rvin_dev *vin)
+{
+ rvin_write(vin, 0, VNIE_REG);
+}
+
+static u32 rvin_get_interrupt_status(struct rvin_dev *vin)
+{
+ return rvin_read(vin, VNINTS_REG);
+}
+
+static void rvin_ack_interrupt(struct rvin_dev *vin)
+{
+ rvin_write(vin, rvin_read(vin, VNINTS_REG), VNINTS_REG);
+}
+
+static bool rvin_capture_active(struct rvin_dev *vin)
+{
+ return rvin_read(vin, VNMS_REG) & VNMS_CA;
+}
+
+static int rvin_get_active_slot(struct rvin_dev *vin, u32 vnms)
+{
+ if (vin->continuous)
+ return (vnms & VNMS_FBS_MASK) >> VNMS_FBS_SHIFT;
+
+ return 0;
+}
+
+static enum v4l2_field rvin_get_active_field(struct rvin_dev *vin, u32 vnms)
+{
+ if (vin->format.field == V4L2_FIELD_ALTERNATE) {
+ /* If FS is set it's a Even field */
+ if (vnms & VNMS_FS)
+ return V4L2_FIELD_BOTTOM;
+ return V4L2_FIELD_TOP;
+ }
+
+ return vin->format.field;
+}
+
+static void rvin_set_slot_addr(struct rvin_dev *vin, int slot, dma_addr_t addr)
+{
+ const struct rvin_video_format *fmt;
+ int offsetx, offsety;
+ dma_addr_t offset;
+
+ fmt = rvin_format_from_pixel(vin->format.pixelformat);
+
+ /*
+ * There is no HW support for composition do the beast we can
+ * by modifying the buffer offset
+ */
+ offsetx = vin->compose.left * fmt->bpp;
+ offsety = vin->compose.top * vin->format.bytesperline;
+ offset = addr + offsetx + offsety;
+
+ /*
+ * The address needs to be 128 bytes aligned. Driver should never accept
+ * settings that do not satisfy this in the first place...
+ */
+ if (WARN_ON((offsetx | offsety | offset) & HW_BUFFER_MASK))
+ return;
+
+ rvin_write(vin, offset, VNMB_REG(slot));
+}
/* Moves a buffer from the queue to the HW slots */
static bool rvin_fill_hw_slot(struct rvin_dev *vin, int slot)
@@ -888,12 +869,73 @@
return true;
}
+static void rvin_capture_on(struct rvin_dev *vin)
+{
+ vin_dbg(vin, "Capture on in %s mode\n",
+ vin->continuous ? "continuous" : "single");
+
+ if (vin->continuous)
+ /* Continuous Frame Capture Mode */
+ rvin_write(vin, VNFC_C_FRAME, VNFC_REG);
+ else
+ /* Single Frame Capture Mode */
+ rvin_write(vin, VNFC_S_FRAME, VNFC_REG);
+}
+
+static int rvin_capture_start(struct rvin_dev *vin)
+{
+ struct rvin_buffer *buf, *node;
+ int bufs, ret;
+
+ /* Count number of free buffers */
+ bufs = 0;
+ list_for_each_entry_safe(buf, node, &vin->buf_list, list)
+ bufs++;
+
+ /* Continuous capture requires more buffers then there are HW slots */
+ vin->continuous = bufs > HW_BUFFER_NUM;
+
+ if (!rvin_fill_hw(vin)) {
+ vin_err(vin, "HW not ready to start, not enough buffers available\n");
+ return -EINVAL;
+ }
+
+ rvin_crop_scale_comp(vin);
+
+ ret = rvin_setup(vin);
+ if (ret)
+ return ret;
+
+ rvin_capture_on(vin);
+
+ vin->state = RUNNING;
+
+ return 0;
+}
+
+static void rvin_capture_stop(struct rvin_dev *vin)
+{
+ /* Set continuous & single transfer off */
+ rvin_write(vin, 0, VNFC_REG);
+
+ /* Disable module */
+ rvin_write(vin, rvin_read(vin, VNMC_REG) & ~VNMC_ME, VNMC_REG);
+}
+
+
+/* -----------------------------------------------------------------------------
+ * DMA Functions
+ */
+
+#define RVIN_TIMEOUT_MS 100
+#define RVIN_RETRIES 10
+
static irqreturn_t rvin_irq(int irq, void *data)
{
struct rvin_dev *vin = data;
u32 int_status, vnms;
int slot;
- unsigned int sequence, handled = 0;
+ unsigned int i, sequence, handled = 0;
unsigned long flags;
spin_lock_irqsave(&vin->qlock, flags);
@@ -955,8 +997,20 @@
* the VnMBm registers.
*/
if (vin->continuous) {
- rvin_capture_off(vin);
+ rvin_capture_stop(vin);
vin_dbg(vin, "IRQ %02d: hw not ready stop\n", sequence);
+
+ /* Maybe we can continue in single capture mode */
+ for (i = 0; i < HW_BUFFER_NUM; i++) {
+ if (vin->queue_buf[i]) {
+ list_add(to_buf_list(vin->queue_buf[i]),
+ &vin->buf_list);
+ vin->queue_buf[i] = NULL;
+ }
+ }
+
+ if (!list_empty(&vin->buf_list))
+ rvin_capture_start(vin);
}
} else {
/*
@@ -1041,46 +1095,96 @@
* capturing if HW is ready to continue.
*/
if (vin->state == STALLED)
- if (rvin_fill_hw(vin))
- rvin_capture_on(vin);
+ rvin_capture_start(vin);
spin_unlock_irqrestore(&vin->qlock, flags);
}
+static int rvin_set_stream(struct rvin_dev *vin, int on)
+{
+ struct v4l2_subdev_format fmt = {
+ .which = V4L2_SUBDEV_FORMAT_ACTIVE,
+ };
+ struct media_pipeline *pipe;
+ struct v4l2_subdev *sd;
+ struct media_pad *pad;
+ int ret;
+
+ /* Not media controller used, simply pass operation to subdevice */
+ if (!vin->info->use_mc) {
+ ret = v4l2_subdev_call(vin->digital.subdev, video, s_stream,
+ on);
+
+ return ret == -ENOIOCTLCMD ? 0 : ret;
+ }
+
+ pad = media_entity_remote_pad(&vin->pad);
+ if (!pad)
+ return -EPIPE;
+
+ sd = media_entity_to_v4l2_subdev(pad->entity);
+ if (!sd)
+ return -EPIPE;
+
+ if (on) {
+ fmt.pad = pad->index;
+ if (v4l2_subdev_call(sd, pad, get_fmt, NULL, &fmt))
+ return -EPIPE;
+
+ switch (fmt.format.code) {
+ case MEDIA_BUS_FMT_YUYV8_1X16:
+ case MEDIA_BUS_FMT_UYVY8_2X8:
+ case MEDIA_BUS_FMT_UYVY10_2X10:
+ case MEDIA_BUS_FMT_RGB888_1X24:
+ vin->code = fmt.format.code;
+ break;
+ default:
+ return -EPIPE;
+ }
+
+ if (fmt.format.width != vin->format.width ||
+ fmt.format.height != vin->format.height)
+ return -EPIPE;
+
+ pipe = sd->entity.pipe ? sd->entity.pipe : &vin->vdev.pipe;
+ if (media_pipeline_start(&vin->vdev.entity, pipe))
+ return -EPIPE;
+
+ ret = v4l2_subdev_call(sd, video, s_stream, 1);
+ if (ret == -ENOIOCTLCMD)
+ ret = 0;
+ if (ret)
+ media_pipeline_stop(&vin->vdev.entity);
+ } else {
+ media_pipeline_stop(&vin->vdev.entity);
+ ret = v4l2_subdev_call(sd, video, s_stream, 0);
+ }
+
+ return ret;
+}
+
static int rvin_start_streaming(struct vb2_queue *vq, unsigned int count)
{
struct rvin_dev *vin = vb2_get_drv_priv(vq);
- struct v4l2_subdev *sd;
unsigned long flags;
int ret;
- sd = vin_to_source(vin);
- v4l2_subdev_call(sd, video, s_stream, 1);
+ ret = rvin_set_stream(vin, 1);
+ if (ret) {
+ spin_lock_irqsave(&vin->qlock, flags);
+ return_all_buffers(vin, VB2_BUF_STATE_QUEUED);
+ spin_unlock_irqrestore(&vin->qlock, flags);
+ return ret;
+ }
spin_lock_irqsave(&vin->qlock, flags);
- vin->state = RUNNING;
vin->sequence = 0;
- /* Continuous capture requires more buffers then there are HW slots */
- vin->continuous = count > HW_BUFFER_NUM;
-
- /*
- * This should never happen but if we don't have enough
- * buffers for HW bail out
- */
- if (!rvin_fill_hw(vin)) {
- vin_err(vin, "HW not ready to start, not enough buffers available\n");
- ret = -EINVAL;
- goto out;
- }
-
ret = rvin_capture_start(vin);
-out:
- /* Return all buffers if something went wrong */
if (ret) {
return_all_buffers(vin, VB2_BUF_STATE_QUEUED);
- v4l2_subdev_call(sd, video, s_stream, 0);
+ rvin_set_stream(vin, 0);
}
spin_unlock_irqrestore(&vin->qlock, flags);
@@ -1091,7 +1195,6 @@
static void rvin_stop_streaming(struct vb2_queue *vq)
{
struct rvin_dev *vin = vb2_get_drv_priv(vq);
- struct v4l2_subdev *sd;
unsigned long flags;
int retries = 0;
@@ -1130,8 +1233,7 @@
spin_unlock_irqrestore(&vin->qlock, flags);
- sd = vin_to_source(vin);
- v4l2_subdev_call(sd, video, s_stream, 0);
+ rvin_set_stream(vin, 0);
/* disable interrupts */
rvin_disable_interrupts(vin);
@@ -1183,7 +1285,7 @@
q->ops = &rvin_qops;
q->mem_ops = &vb2_dma_contig_memops;
q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
- q->min_buffers_needed = 2;
+ q->min_buffers_needed = 1;
q->dev = vin->dev;
ret = vb2_queue_init(q);
@@ -1206,3 +1308,43 @@
return ret;
}
+
+/* -----------------------------------------------------------------------------
+ * Gen3 CHSEL manipulation
+ */
+
+void rvin_set_chsel(struct rvin_dev *vin, u8 chsel)
+{
+ u32 ifmd;
+
+ pm_runtime_get_sync(vin->dev);
+
+ /*
+ * Undocumented feature: Writing to VNCSI_IFMD_REG will go
+ * through and on read back look correct but won't have
+ * any effect if VNMC_REG is not first set to 0.
+ */
+ rvin_write(vin, 0, VNMC_REG);
+
+ ifmd = VNCSI_IFMD_DES2 | VNCSI_IFMD_DES1 | VNCSI_IFMD_DES0 |
+ VNCSI_IFMD_CSI_CHSEL(chsel);
+
+ rvin_write(vin, ifmd, VNCSI_IFMD_REG);
+
+ vin_dbg(vin, "Set IFMD 0x%x\n", ifmd);
+
+ pm_runtime_put(vin->dev);
+}
+
+int rvin_get_chsel(struct rvin_dev *vin)
+{
+ int chsel;
+
+ pm_runtime_get_sync(vin->dev);
+
+ chsel = rvin_read(vin, VNCSI_IFMD_REG) & VNCSI_IFMD_CSI_CHSEL_MASK;
+
+ pm_runtime_put(vin->dev);
+
+ return chsel;
+}
diff --git a/drivers/media/platform/rcar-vin/rcar-v4l2.c b/drivers/media/platform/rcar-vin/rcar-v4l2.c
index 2bbe6d4..236bf10 100644
--- a/drivers/media/platform/rcar-vin/rcar-v4l2.c
+++ b/drivers/media/platform/rcar-vin/rcar-v4l2.c
@@ -23,8 +23,9 @@
#include "rcar-vin.h"
#define RVIN_DEFAULT_FORMAT V4L2_PIX_FMT_YUYV
-#define RVIN_MAX_WIDTH 2048
-#define RVIN_MAX_HEIGHT 2048
+#define RVIN_DEFAULT_WIDTH 800
+#define RVIN_DEFAULT_HEIGHT 600
+#define RVIN_DEFAULT_COLORSPACE V4L2_COLORSPACE_SRGB
/* -----------------------------------------------------------------------------
* Format Conversions
@@ -88,22 +89,79 @@
return pix->bytesperline * pix->height;
}
+static int rvin_format_align(struct rvin_dev *vin, struct v4l2_pix_format *pix)
+{
+ u32 walign;
+
+ /* If requested format is not supported fallback to the default */
+ if (!rvin_format_from_pixel(pix->pixelformat)) {
+ vin_dbg(vin, "Format 0x%x not found, using default 0x%x\n",
+ pix->pixelformat, RVIN_DEFAULT_FORMAT);
+ pix->pixelformat = RVIN_DEFAULT_FORMAT;
+ }
+
+ switch (pix->field) {
+ case V4L2_FIELD_TOP:
+ case V4L2_FIELD_BOTTOM:
+ case V4L2_FIELD_ALTERNATE:
+ case V4L2_FIELD_NONE:
+ case V4L2_FIELD_INTERLACED_TB:
+ case V4L2_FIELD_INTERLACED_BT:
+ case V4L2_FIELD_INTERLACED:
+ break;
+ default:
+ pix->field = V4L2_FIELD_NONE;
+ break;
+ }
+
+ /* Check that colorspace is resonable, if not keep current */
+ if (!pix->colorspace || pix->colorspace >= 0xff)
+ pix->colorspace = vin->format.colorspace;
+
+ /* HW limit width to a multiple of 32 (2^5) for NV16 else 2 (2^1) */
+ walign = vin->format.pixelformat == V4L2_PIX_FMT_NV16 ? 5 : 1;
+
+ /* Limit to VIN capabilities */
+ v4l_bound_align_image(&pix->width, 2, vin->info->max_width, walign,
+ &pix->height, 4, vin->info->max_height, 2, 0);
+
+ pix->bytesperline = rvin_format_bytesperline(pix);
+ pix->sizeimage = rvin_format_sizeimage(pix);
+
+ if (vin->info->chip == RCAR_M1 &&
+ pix->pixelformat == V4L2_PIX_FMT_XBGR32) {
+ vin_err(vin, "pixel format XBGR32 not supported on M1\n");
+ return -EINVAL;
+ }
+
+ vin_dbg(vin, "Format %ux%u bpl: %d size: %d\n",
+ pix->width, pix->height, pix->bytesperline, pix->sizeimage);
+
+ return 0;
+}
+
/* -----------------------------------------------------------------------------
* V4L2
*/
-static void rvin_reset_crop_compose(struct rvin_dev *vin)
+static int rvin_get_sd_format(struct rvin_dev *vin, struct v4l2_pix_format *pix)
{
- vin->crop.top = vin->crop.left = 0;
- vin->crop.width = vin->source.width;
- vin->crop.height = vin->source.height;
+ struct v4l2_subdev_format fmt = {
+ .which = V4L2_SUBDEV_FORMAT_ACTIVE,
+ .pad = vin->digital.source_pad,
+ };
+ int ret;
- vin->compose.top = vin->compose.left = 0;
- vin->compose.width = vin->format.width;
- vin->compose.height = vin->format.height;
+ ret = v4l2_subdev_call(vin_to_source(vin), pad, get_fmt, NULL, &fmt);
+ if (ret)
+ return ret;
+
+ v4l2_fill_pix_format(pix, &fmt.format);
+
+ return 0;
}
-static int rvin_reset_format(struct rvin_dev *vin)
+int rvin_reset_format(struct rvin_dev *vin)
{
struct v4l2_subdev_format fmt = {
.which = V4L2_SUBDEV_FORMAT_ACTIVE,
@@ -111,7 +169,7 @@
struct v4l2_mbus_framefmt *mf = &fmt.format;
int ret;
- fmt.pad = vin->src_pad_idx;
+ fmt.pad = vin->digital.source_pad;
ret = v4l2_subdev_call(vin_to_source(vin), pad, get_fmt, NULL, &fmt);
if (ret)
@@ -137,8 +195,6 @@
case V4L2_FIELD_TOP:
case V4L2_FIELD_BOTTOM:
case V4L2_FIELD_ALTERNATE:
- vin->format.height /= 2;
- break;
case V4L2_FIELD_NONE:
case V4L2_FIELD_INTERLACED_TB:
case V4L2_FIELD_INTERLACED_BT:
@@ -149,15 +205,22 @@
break;
}
- rvin_reset_crop_compose(vin);
+ vin->crop.top = vin->crop.left = 0;
+ vin->crop.width = mf->width;
+ vin->crop.height = mf->height;
+
+ vin->compose.top = vin->compose.left = 0;
+ vin->compose.width = mf->width;
+ vin->compose.height = mf->height;
+
+ vin->format.bytesperline = rvin_format_bytesperline(&vin->format);
+ vin->format.sizeimage = rvin_format_sizeimage(&vin->format);
return 0;
}
static int __rvin_try_format_source(struct rvin_dev *vin,
- u32 which,
- struct v4l2_pix_format *pix,
- struct rvin_source_fmt *source)
+ u32 which, struct v4l2_pix_format *pix)
{
struct v4l2_subdev *sd;
struct v4l2_subdev_pad_config *pad_cfg;
@@ -165,19 +228,23 @@
.which = which,
};
enum v4l2_field field;
+ u32 width, height;
int ret;
sd = vin_to_source(vin);
- v4l2_fill_mbus_format(&format.format, pix, vin->digital.code);
+ v4l2_fill_mbus_format(&format.format, pix, vin->code);
pad_cfg = v4l2_subdev_alloc_pad_config(sd);
if (pad_cfg == NULL)
return -ENOMEM;
- format.pad = vin->src_pad_idx;
+ format.pad = vin->digital.source_pad;
+ /* Allow the video device to override field and to scale */
field = pix->field;
+ width = pix->width;
+ height = pix->height;
ret = v4l2_subdev_call(sd, pad, set_fmt, pad_cfg, &format);
if (ret < 0 && ret != -ENOIOCTLCMD)
@@ -186,97 +253,28 @@
v4l2_fill_pix_format(pix, &format.format);
pix->field = field;
-
- source->width = pix->width;
- source->height = pix->height;
-
- vin_dbg(vin, "Source resolution: %ux%u\n", source->width,
- source->height);
-
+ pix->width = width;
+ pix->height = height;
done:
v4l2_subdev_free_pad_config(pad_cfg);
return ret;
}
static int __rvin_try_format(struct rvin_dev *vin,
- u32 which,
- struct v4l2_pix_format *pix,
- struct rvin_source_fmt *source)
+ u32 which, struct v4l2_pix_format *pix)
{
- const struct rvin_video_format *info;
- u32 rwidth, rheight, walign;
-
- /* Requested */
- rwidth = pix->width;
- rheight = pix->height;
+ int ret;
/* Keep current field if no specific one is asked for */
if (pix->field == V4L2_FIELD_ANY)
pix->field = vin->format.field;
- /*
- * Retrieve format information and select the current format if the
- * requested format isn't supported.
- */
- info = rvin_format_from_pixel(pix->pixelformat);
- if (!info) {
- vin_dbg(vin, "Format %x not found, keeping %x\n",
- pix->pixelformat, vin->format.pixelformat);
- *pix = vin->format;
- pix->width = rwidth;
- pix->height = rheight;
- }
-
- /* Always recalculate */
- pix->bytesperline = 0;
- pix->sizeimage = 0;
-
/* Limit to source capabilities */
- __rvin_try_format_source(vin, which, pix, source);
+ ret = __rvin_try_format_source(vin, which, pix);
+ if (ret)
+ return ret;
- switch (pix->field) {
- case V4L2_FIELD_TOP:
- case V4L2_FIELD_BOTTOM:
- case V4L2_FIELD_ALTERNATE:
- pix->height /= 2;
- source->height /= 2;
- break;
- case V4L2_FIELD_NONE:
- case V4L2_FIELD_INTERLACED_TB:
- case V4L2_FIELD_INTERLACED_BT:
- case V4L2_FIELD_INTERLACED:
- break;
- default:
- pix->field = V4L2_FIELD_NONE;
- break;
- }
-
- /* If source can't match format try if VIN can scale */
- if (source->width != rwidth || source->height != rheight)
- rvin_scale_try(vin, pix, rwidth, rheight);
-
- /* HW limit width to a multiple of 32 (2^5) for NV16 else 2 (2^1) */
- walign = vin->format.pixelformat == V4L2_PIX_FMT_NV16 ? 5 : 1;
-
- /* Limit to VIN capabilities */
- v4l_bound_align_image(&pix->width, 2, RVIN_MAX_WIDTH, walign,
- &pix->height, 4, RVIN_MAX_HEIGHT, 2, 0);
-
- pix->bytesperline = max_t(u32, pix->bytesperline,
- rvin_format_bytesperline(pix));
- pix->sizeimage = max_t(u32, pix->sizeimage,
- rvin_format_sizeimage(pix));
-
- if (vin->chip == RCAR_M1 && pix->pixelformat == V4L2_PIX_FMT_XBGR32) {
- vin_err(vin, "pixel format XBGR32 not supported on M1\n");
- return -EINVAL;
- }
-
- vin_dbg(vin, "Requested %ux%u Got %ux%u bpl: %d size: %d\n",
- rwidth, rheight, pix->width, pix->height,
- pix->bytesperline, pix->sizeimage);
-
- return 0;
+ return rvin_format_align(vin, pix);
}
static int rvin_querycap(struct file *file, void *priv,
@@ -295,34 +293,25 @@
struct v4l2_format *f)
{
struct rvin_dev *vin = video_drvdata(file);
- struct rvin_source_fmt source;
- return __rvin_try_format(vin, V4L2_SUBDEV_FORMAT_TRY, &f->fmt.pix,
- &source);
+ return __rvin_try_format(vin, V4L2_SUBDEV_FORMAT_TRY, &f->fmt.pix);
}
static int rvin_s_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
{
struct rvin_dev *vin = video_drvdata(file);
- struct rvin_source_fmt source;
int ret;
if (vb2_is_busy(&vin->queue))
return -EBUSY;
- ret = __rvin_try_format(vin, V4L2_SUBDEV_FORMAT_ACTIVE, &f->fmt.pix,
- &source);
+ ret = __rvin_try_format(vin, V4L2_SUBDEV_FORMAT_ACTIVE, &f->fmt.pix);
if (ret)
return ret;
- vin->source.width = source.width;
- vin->source.height = source.height;
-
vin->format = f->fmt.pix;
- rvin_reset_crop_compose(vin);
-
return 0;
}
@@ -351,6 +340,8 @@
struct v4l2_selection *s)
{
struct rvin_dev *vin = video_drvdata(file);
+ struct v4l2_pix_format pix;
+ int ret;
if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
return -EINVAL;
@@ -358,9 +349,12 @@
switch (s->target) {
case V4L2_SEL_TGT_CROP_BOUNDS:
case V4L2_SEL_TGT_CROP_DEFAULT:
+ ret = rvin_get_sd_format(vin, &pix);
+ if (ret)
+ return ret;
s->r.left = s->r.top = 0;
- s->r.width = vin->source.width;
- s->r.height = vin->source.height;
+ s->r.width = pix.width;
+ s->r.height = pix.height;
break;
case V4L2_SEL_TGT_CROP:
s->r = vin->crop;
@@ -386,12 +380,14 @@
{
struct rvin_dev *vin = video_drvdata(file);
const struct rvin_video_format *fmt;
+ struct v4l2_pix_format pix;
struct v4l2_rect r = s->r;
struct v4l2_rect max_rect;
struct v4l2_rect min_rect = {
.width = 6,
.height = 2,
};
+ int ret;
if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
return -EINVAL;
@@ -401,22 +397,25 @@
switch (s->target) {
case V4L2_SEL_TGT_CROP:
/* Can't crop outside of source input */
+ ret = rvin_get_sd_format(vin, &pix);
+ if (ret)
+ return ret;
max_rect.top = max_rect.left = 0;
- max_rect.width = vin->source.width;
- max_rect.height = vin->source.height;
+ max_rect.width = pix.width;
+ max_rect.height = pix.height;
v4l2_rect_map_inside(&r, &max_rect);
- v4l_bound_align_image(&r.width, 2, vin->source.width, 1,
- &r.height, 4, vin->source.height, 2, 0);
+ v4l_bound_align_image(&r.width, 2, pix.width, 1,
+ &r.height, 4, pix.height, 2, 0);
- r.top = clamp_t(s32, r.top, 0, vin->source.height - r.height);
- r.left = clamp_t(s32, r.left, 0, vin->source.width - r.width);
+ r.top = clamp_t(s32, r.top, 0, pix.height - r.height);
+ r.left = clamp_t(s32, r.left, 0, pix.width - r.width);
vin->crop = s->r = r;
vin_dbg(vin, "Cropped %dx%d@%d:%d of %dx%d\n",
r.width, r.height, r.left, r.top,
- vin->source.width, vin->source.height);
+ pix.width, pix.height);
break;
case V4L2_SEL_TGT_COMPOSE:
/* Make sure compose rect fits inside output format */
@@ -447,9 +446,6 @@
return -EINVAL;
}
- /* HW supports modifying configuration while running */
- rvin_crop_scale_comp(vin);
-
return 0;
}
@@ -480,10 +476,14 @@
return ret;
i->type = V4L2_INPUT_TYPE_CAMERA;
- i->std = vin->vdev.tvnorms;
- if (v4l2_subdev_has_op(sd, pad, dv_timings_cap))
+ if (v4l2_subdev_has_op(sd, pad, dv_timings_cap)) {
i->capabilities = V4L2_IN_CAP_DV_TIMINGS;
+ i->std = 0;
+ } else {
+ i->capabilities = V4L2_IN_CAP_STD;
+ i->std = vin->vdev.tvnorms;
+ }
strlcpy(i->name, "Camera", sizeof(i->name));
@@ -547,14 +547,16 @@
{
struct rvin_dev *vin = video_drvdata(file);
struct v4l2_subdev *sd = vin_to_source(vin);
- int pad, ret;
+ int ret;
- pad = timings->pad;
- timings->pad = vin->sink_pad_idx;
+ if (timings->pad)
+ return -EINVAL;
+
+ timings->pad = vin->digital.sink_pad;
ret = v4l2_subdev_call(sd, pad, enum_dv_timings, timings);
- timings->pad = pad;
+ timings->pad = 0;
return ret;
}
@@ -570,12 +572,8 @@
if (ret)
return ret;
- vin->source.width = timings->bt.width;
- vin->source.height = timings->bt.height;
- vin->format.width = timings->bt.width;
- vin->format.height = timings->bt.height;
-
- return 0;
+ /* Changing the timings will change the width/height */
+ return rvin_reset_format(vin);
}
static int rvin_g_dv_timings(struct file *file, void *priv_fh,
@@ -601,14 +599,16 @@
{
struct rvin_dev *vin = video_drvdata(file);
struct v4l2_subdev *sd = vin_to_source(vin);
- int pad, ret;
+ int ret;
- pad = cap->pad;
- cap->pad = vin->sink_pad_idx;
+ if (cap->pad)
+ return -EINVAL;
+
+ cap->pad = vin->digital.sink_pad;
ret = v4l2_subdev_call(sd, pad, dv_timings_cap, cap);
- cap->pad = pad;
+ cap->pad = 0;
return ret;
}
@@ -617,17 +617,16 @@
{
struct rvin_dev *vin = video_drvdata(file);
struct v4l2_subdev *sd = vin_to_source(vin);
- int input, ret;
+ int ret;
if (edid->pad)
return -EINVAL;
- input = edid->pad;
- edid->pad = vin->sink_pad_idx;
+ edid->pad = vin->digital.sink_pad;
ret = v4l2_subdev_call(sd, pad, get_edid, edid);
- edid->pad = input;
+ edid->pad = 0;
return ret;
}
@@ -636,17 +635,16 @@
{
struct rvin_dev *vin = video_drvdata(file);
struct v4l2_subdev *sd = vin_to_source(vin);
- int input, ret;
+ int ret;
if (edid->pad)
return -EINVAL;
- input = edid->pad;
- edid->pad = vin->sink_pad_idx;
+ edid->pad = vin->digital.sink_pad;
ret = v4l2_subdev_call(sd, pad, set_edid, edid);
- edid->pad = input;
+ edid->pad = 0;
return ret;
}
@@ -696,6 +694,84 @@
};
/* -----------------------------------------------------------------------------
+ * V4L2 Media Controller
+ */
+
+static int __rvin_mc_try_format(struct rvin_dev *vin,
+ struct v4l2_pix_format *pix)
+{
+ /* Keep current field if no specific one is asked for */
+ if (pix->field == V4L2_FIELD_ANY)
+ pix->field = vin->format.field;
+
+ return rvin_format_align(vin, pix);
+}
+
+static int rvin_mc_try_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct rvin_dev *vin = video_drvdata(file);
+
+ return __rvin_mc_try_format(vin, &f->fmt.pix);
+}
+
+static int rvin_mc_s_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct rvin_dev *vin = video_drvdata(file);
+ int ret;
+
+ if (vb2_is_busy(&vin->queue))
+ return -EBUSY;
+
+ ret = __rvin_mc_try_format(vin, &f->fmt.pix);
+ if (ret)
+ return ret;
+
+ vin->format = f->fmt.pix;
+
+ return 0;
+}
+
+static int rvin_mc_enum_input(struct file *file, void *priv,
+ struct v4l2_input *i)
+{
+ if (i->index != 0)
+ return -EINVAL;
+
+ i->type = V4L2_INPUT_TYPE_CAMERA;
+ strlcpy(i->name, "Camera", sizeof(i->name));
+
+ return 0;
+}
+
+static const struct v4l2_ioctl_ops rvin_mc_ioctl_ops = {
+ .vidioc_querycap = rvin_querycap,
+ .vidioc_try_fmt_vid_cap = rvin_mc_try_fmt_vid_cap,
+ .vidioc_g_fmt_vid_cap = rvin_g_fmt_vid_cap,
+ .vidioc_s_fmt_vid_cap = rvin_mc_s_fmt_vid_cap,
+ .vidioc_enum_fmt_vid_cap = rvin_enum_fmt_vid_cap,
+
+ .vidioc_enum_input = rvin_mc_enum_input,
+ .vidioc_g_input = rvin_g_input,
+ .vidioc_s_input = rvin_s_input,
+
+ .vidioc_reqbufs = vb2_ioctl_reqbufs,
+ .vidioc_create_bufs = vb2_ioctl_create_bufs,
+ .vidioc_querybuf = vb2_ioctl_querybuf,
+ .vidioc_qbuf = vb2_ioctl_qbuf,
+ .vidioc_dqbuf = vb2_ioctl_dqbuf,
+ .vidioc_expbuf = vb2_ioctl_expbuf,
+ .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
+ .vidioc_streamon = vb2_ioctl_streamon,
+ .vidioc_streamoff = vb2_ioctl_streamoff,
+
+ .vidioc_log_status = v4l2_ctrl_log_status,
+ .vidioc_subscribe_event = rvin_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+/* -----------------------------------------------------------------------------
* File Operations
*/
@@ -780,6 +856,11 @@
mutex_lock(&vin->lock);
+ if (!vin->digital.subdev) {
+ ret = -ENODEV;
+ goto unlock;
+ }
+
file->private_data = vin;
ret = v4l2_fh_open(file);
@@ -838,14 +919,69 @@
.read = vb2_fop_read,
};
+/* -----------------------------------------------------------------------------
+ * Media controller file Operations
+ */
+
+static int rvin_mc_open(struct file *file)
+{
+ struct rvin_dev *vin = video_drvdata(file);
+ int ret;
+
+ mutex_lock(&vin->lock);
+
+ file->private_data = vin;
+
+ ret = v4l2_fh_open(file);
+ if (ret)
+ goto unlock;
+
+ pm_runtime_get_sync(vin->dev);
+ v4l2_pipeline_pm_use(&vin->vdev.entity, 1);
+
+unlock:
+ mutex_unlock(&vin->lock);
+
+ return ret;
+}
+
+static int rvin_mc_release(struct file *file)
+{
+ struct rvin_dev *vin = video_drvdata(file);
+ bool fh_singular;
+ int ret;
+
+ mutex_lock(&vin->lock);
+
+ /* Save the singular status before we call the clean-up helper */
+ fh_singular = v4l2_fh_is_singular_file(file);
+
+ /* the release helper will cleanup any on-going streaming */
+ ret = _vb2_fop_release(file, NULL);
+
+ v4l2_pipeline_pm_use(&vin->vdev.entity, 0);
+ pm_runtime_put(vin->dev);
+
+ mutex_unlock(&vin->lock);
+
+ return ret;
+}
+
+static const struct v4l2_file_operations rvin_mc_fops = {
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = video_ioctl2,
+ .open = rvin_mc_open,
+ .release = rvin_mc_release,
+ .poll = vb2_fop_poll,
+ .mmap = vb2_fop_mmap,
+ .read = vb2_fop_read,
+};
+
void rvin_v4l2_remove(struct rvin_dev *vin)
{
v4l2_info(&vin->v4l2_dev, "Removing %s\n",
video_device_node_name(&vin->vdev));
- /* Checks internaly if handlers have been init or not */
- v4l2_ctrl_handler_free(&vin->ctrl_handler);
-
/* Checks internaly if vdev have been init or not */
video_unregister_device(&vin->vdev);
}
@@ -868,71 +1004,39 @@
int rvin_v4l2_probe(struct rvin_dev *vin)
{
struct video_device *vdev = &vin->vdev;
- struct v4l2_subdev *sd = vin_to_source(vin);
- int pad_idx, ret;
-
- v4l2_set_subdev_hostdata(sd, vin);
+ int ret;
vin->v4l2_dev.notify = rvin_notify;
- ret = v4l2_subdev_call(sd, video, g_tvnorms, &vin->vdev.tvnorms);
- if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV)
- return ret;
-
- if (vin->vdev.tvnorms == 0) {
- /* Disable the STD API if there are no tvnorms defined */
- v4l2_disable_ioctl(&vin->vdev, VIDIOC_G_STD);
- v4l2_disable_ioctl(&vin->vdev, VIDIOC_S_STD);
- v4l2_disable_ioctl(&vin->vdev, VIDIOC_QUERYSTD);
- v4l2_disable_ioctl(&vin->vdev, VIDIOC_ENUMSTD);
- }
-
- /* Add the controls */
- /*
- * Currently the subdev with the largest number of controls (13) is
- * ov6550. So let's pick 16 as a hint for the control handler. Note
- * that this is a hint only: too large and you waste some memory, too
- * small and there is a (very) small performance hit when looking up
- * controls in the internal hash.
- */
- ret = v4l2_ctrl_handler_init(&vin->ctrl_handler, 16);
- if (ret < 0)
- return ret;
-
- ret = v4l2_ctrl_add_handler(&vin->ctrl_handler, sd->ctrl_handler, NULL);
- if (ret < 0)
- return ret;
-
/* video node */
- vdev->fops = &rvin_fops;
vdev->v4l2_dev = &vin->v4l2_dev;
vdev->queue = &vin->queue;
- strlcpy(vdev->name, KBUILD_MODNAME, sizeof(vdev->name));
+ snprintf(vdev->name, sizeof(vdev->name), "%s %s", KBUILD_MODNAME,
+ dev_name(vin->dev));
vdev->release = video_device_release_empty;
- vdev->ioctl_ops = &rvin_ioctl_ops;
vdev->lock = &vin->lock;
- vdev->ctrl_handler = &vin->ctrl_handler;
vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING |
V4L2_CAP_READWRITE;
- vin->src_pad_idx = 0;
- for (pad_idx = 0; pad_idx < sd->entity.num_pads; pad_idx++)
- if (sd->entity.pads[pad_idx].flags == MEDIA_PAD_FL_SOURCE)
- break;
- if (pad_idx >= sd->entity.num_pads)
- return -EINVAL;
-
- vin->src_pad_idx = pad_idx;
-
- vin->sink_pad_idx = 0;
- for (pad_idx = 0; pad_idx < sd->entity.num_pads; pad_idx++)
- if (sd->entity.pads[pad_idx].flags == MEDIA_PAD_FL_SINK) {
- vin->sink_pad_idx = pad_idx;
- break;
- }
-
vin->format.pixelformat = RVIN_DEFAULT_FORMAT;
- rvin_reset_format(vin);
+
+ if (vin->info->use_mc) {
+ vdev->fops = &rvin_mc_fops;
+ vdev->ioctl_ops = &rvin_mc_ioctl_ops;
+
+ /* Set some form of default format */
+ vin->format.pixelformat = RVIN_DEFAULT_FORMAT;
+ vin->format.width = RVIN_DEFAULT_WIDTH;
+ vin->format.height = RVIN_DEFAULT_HEIGHT;
+ vin->format.colorspace = RVIN_DEFAULT_COLORSPACE;
+ ret = __rvin_mc_try_format(vin, &vin->format);
+ if (ret)
+ return ret;
+ } else {
+ vdev->fops = &rvin_fops;
+ vdev->ioctl_ops = &rvin_ioctl_ops;
+ vdev->ctrl_handler = &vin->ctrl_handler;
+ }
ret = video_register_device(&vin->vdev, VFL_TYPE_GRABBER, -1);
if (ret) {
diff --git a/drivers/media/platform/rcar-vin/rcar-vin.h b/drivers/media/platform/rcar-vin/rcar-vin.h
index 727e215..e7e600f 100644
--- a/drivers/media/platform/rcar-vin/rcar-vin.h
+++ b/drivers/media/platform/rcar-vin/rcar-vin.h
@@ -17,10 +17,13 @@
#ifndef __RCAR_VIN__
#define __RCAR_VIN__
+#include <linux/kref.h>
+
#include <media/v4l2-async.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-dev.h>
#include <media/v4l2-device.h>
+#include <media/v4l2-mc.h>
#include <media/videobuf2-v4l2.h>
/* Number of HW buffers */
@@ -29,10 +32,26 @@
/* Address alignment mask for HW buffers */
#define HW_BUFFER_MASK 0x7f
+/* Max number on VIN instances that can be in a system */
+#define RCAR_VIN_NUM 8
+
+/* Max number of CHSEL values for any Gen3 SoC */
+#define RCAR_CHSEL_MAX 6
+
enum chip_id {
RCAR_H1,
RCAR_M1,
RCAR_GEN2,
+ RCAR_GEN3,
+};
+
+enum rvin_csi_id {
+ RVIN_CSI20,
+ RVIN_CSI21,
+ RVIN_CSI40,
+ RVIN_CSI41,
+ RVIN_CSI_MAX,
+ RVIN_NC, /* Not Connected */
};
/**
@@ -49,16 +68,6 @@
};
/**
- * struct rvin_source_fmt - Source information
- * @width: Width from source
- * @height: Height from source
- */
-struct rvin_source_fmt {
- u32 width;
- u32 height;
-};
-
-/**
* struct rvin_video_format - Data format stored in memory
* @fourcc: Pixelformat
* @bpp: Bytes per pixel
@@ -72,31 +81,69 @@
* struct rvin_graph_entity - Video endpoint from async framework
* @asd: sub-device descriptor for async framework
* @subdev: subdevice matched using async framework
- * @code: Media bus format from source
- * @mbus_cfg: Media bus format from DT
+ * @source_pad: source pad of remote subdevice
+ * @sink_pad: sink pad of remote subdevice
*/
struct rvin_graph_entity {
struct v4l2_async_subdev asd;
struct v4l2_subdev *subdev;
- u32 code;
- struct v4l2_mbus_config mbus_cfg;
+ unsigned int source_pad;
+ unsigned int sink_pad;
+};
+
+struct rvin_group;
+
+
+/** struct rvin_group_chsel - Map a CSI2 device and channel for a CHSEL value
+ * @csi: VIN internal number for CSI2 device
+ * @chan: CSI-2 channel number on remote. Note that channel
+ * is not the same as VC. The CSI-2 hardware have 4
+ * channels it can output on but which VC is outputted
+ * on which channel is configurable inside the CSI-2.
+ */
+struct rvin_group_chsel {
+ enum rvin_csi_id csi;
+ unsigned int chan;
+};
+
+/**
+ * struct rvin_info- Information about the particular VIN implementation
+ * @chip: type of VIN chip
+ * @use_mc: use media controller instead of controlling subdevice
+ *
+ * max_width: max input width the VIN supports
+ * max_height: max input height the VIN supports
+ *
+ * num_chsels: number of possible chsel values for this VIN
+ * chsels: routing table VIN <-> CSI-2 for the chsel values
+ */
+struct rvin_info {
+ enum chip_id chip;
+ bool use_mc;
+
+ unsigned int max_width;
+ unsigned int max_height;
+
+ unsigned int num_chsels;
+ struct rvin_group_chsel chsels[RCAR_VIN_NUM][RCAR_CHSEL_MAX];
};
/**
* struct rvin_dev - Renesas VIN device structure
* @dev: (OF) device
* @base: device I/O register space remapped to virtual memory
- * @chip: type of VIN chip
+ * @info: info about VIN instance
*
* @vdev: V4L2 video device associated with VIN
* @v4l2_dev: V4L2 device
- * @src_pad_idx: source pad index for media controller drivers
- * @sink_pad_idx: sink pad index for media controller drivers
* @ctrl_handler: V4L2 control handler
* @notifier: V4L2 asynchronous subdevs notifier
* @digital: entity in the DT for local digital subdevice
*
+ * @group: Gen3 CSI group
+ * @pad: pad for media controller
+ *
* @lock: protects @queue
* @queue: vb2 buffers queue
*
@@ -108,7 +155,8 @@
* @sequence: V4L2 buffers sequence number
* @state: keeps track of operation state
*
- * @source: active format from the video source
+ * @mbus_cfg: media bus format from DT
+ * @code: media bus coide from subdevice
* @format: active V4L2 pixel format
*
* @crop: active cropping
@@ -117,16 +165,17 @@
struct rvin_dev {
struct device *dev;
void __iomem *base;
- enum chip_id chip;
+ const struct rvin_info *info;
struct video_device vdev;
struct v4l2_device v4l2_dev;
- int src_pad_idx;
- int sink_pad_idx;
struct v4l2_ctrl_handler ctrl_handler;
struct v4l2_async_notifier notifier;
struct rvin_graph_entity digital;
+ struct rvin_group *group;
+ struct media_pad pad;
+
struct mutex lock;
struct vb2_queue queue;
@@ -137,7 +186,8 @@
unsigned int sequence;
enum rvin_dma_state state;
- struct rvin_source_fmt source;
+ struct v4l2_mbus_config mbus_cfg;
+ u32 code;
struct v4l2_pix_format format;
struct v4l2_rect crop;
@@ -152,17 +202,36 @@
#define vin_warn(d, fmt, arg...) dev_warn(d->dev, fmt, ##arg)
#define vin_err(d, fmt, arg...) dev_err(d->dev, fmt, ##arg)
+/**
+ * struct rvin_group - VIN CSI2 group information
+ * @refcount: number of VIN instances using the group
+ *
+ * @mdev: media device which represents the group
+ *
+ * @lock: protects the vin and csi members
+ * @vin: VIN instances which are part of the group
+ * @csi: CSI-2 entities that are part of the group
+ */
+struct rvin_group {
+ struct kref refcount;
+
+ struct media_device mdev;
+
+ struct mutex lock;
+ struct rvin_dev *vin[RCAR_VIN_NUM];
+ struct rvin_graph_entity csi[RVIN_CSI_MAX];
+};
+
int rvin_dma_probe(struct rvin_dev *vin, int irq);
void rvin_dma_remove(struct rvin_dev *vin);
int rvin_v4l2_probe(struct rvin_dev *vin);
void rvin_v4l2_remove(struct rvin_dev *vin);
+int rvin_reset_format(struct rvin_dev *vin);
const struct rvin_video_format *rvin_format_from_pixel(u32 pixelformat);
-/* Cropping, composing and scaling */
-void rvin_scale_try(struct rvin_dev *vin, struct v4l2_pix_format *pix,
- u32 width, u32 height);
-void rvin_crop_scale_comp(struct rvin_dev *vin);
+void rvin_set_chsel(struct rvin_dev *vin, u8 chsel);
+int rvin_get_chsel(struct rvin_dev *vin);
#endif
diff --git a/drivers/media/v4l2-core/v4l2-async.c b/drivers/media/v4l2-core/v4l2-async.c
index cbd919d..e1e181d 100644
--- a/drivers/media/v4l2-core/v4l2-async.c
+++ b/drivers/media/v4l2-core/v4l2-async.c
@@ -141,14 +141,17 @@
sd->dev = NULL;
}
-int v4l2_async_notifier_register(struct v4l2_device *v4l2_dev,
- struct v4l2_async_notifier *notifier)
+static int v4l2_async_do_notifier_register(struct v4l2_device *v4l2_dev,
+ struct v4l2_async_notifier *notifier,
+ bool subnotifier)
{
struct v4l2_subdev *sd, *tmp;
struct v4l2_async_subdev *asd;
+ bool found;
int i;
- if (!notifier->num_subdevs || notifier->num_subdevs > V4L2_MAX_SUBDEVS)
+ if (!v4l2_dev || !notifier->num_subdevs ||
+ notifier->num_subdevs > V4L2_MAX_SUBDEVS)
return -EINVAL;
notifier->v4l2_dev = v4l2_dev;
@@ -173,32 +176,65 @@
list_add_tail(&asd->list, ¬ifier->waiting);
}
- mutex_lock(&list_lock);
+ if (subnotifier)
+ lockdep_assert_held(&list_lock);
+ else
+ mutex_lock(&list_lock);
- list_for_each_entry_safe(sd, tmp, &subdev_list, async_list) {
- int ret;
+ /*
+ * This function can be called recursively so the list
+ * might be modified in a recursive call. Start from the
+ * top of the list each iteration.
+ */
+ found = true;
+ while (found) {
+ found = false;
- asd = v4l2_async_belongs(notifier, sd);
- if (!asd)
- continue;
+ list_for_each_entry_safe(sd, tmp, &subdev_list, async_list) {
+ int ret;
- ret = v4l2_async_test_notify(notifier, sd, asd);
- if (ret < 0) {
- mutex_unlock(&list_lock);
- return ret;
+ asd = v4l2_async_belongs(notifier, sd);
+ if (!asd)
+ continue;
+
+ ret = v4l2_async_test_notify(notifier, sd, asd);
+ if (ret < 0) {
+ if (!subnotifier)
+ mutex_unlock(&list_lock);
+ return ret;
+ }
+
+ found = true;
+ break;
}
}
/* Keep also completed notifiers on the list */
list_add(¬ifier->list, ¬ifier_list);
- mutex_unlock(&list_lock);
+ if (!subnotifier)
+ mutex_unlock(&list_lock);
return 0;
}
+
+int v4l2_async_subnotifier_register(struct v4l2_subdev *sd,
+ struct v4l2_async_notifier *notifier)
+{
+ return v4l2_async_do_notifier_register(sd->v4l2_dev, notifier, true);
+}
+EXPORT_SYMBOL(v4l2_async_subnotifier_register);
+
+int v4l2_async_notifier_register(struct v4l2_device *v4l2_dev,
+ struct v4l2_async_notifier *notifier)
+{
+ return v4l2_async_do_notifier_register(v4l2_dev, notifier, false);
+}
EXPORT_SYMBOL(v4l2_async_notifier_register);
-void v4l2_async_notifier_unregister(struct v4l2_async_notifier *notifier)
+static void
+v4l2_async_do_notifier_unregister(struct v4l2_async_notifier *notifier,
+ bool subnotifier)
{
struct v4l2_subdev *sd, *tmp;
unsigned int notif_n_subdev = notifier->num_subdevs;
@@ -215,7 +251,10 @@
"Failed to allocate device cache!\n");
}
- mutex_lock(&list_lock);
+ if (subnotifier)
+ lockdep_assert_held(&list_lock);
+ else
+ mutex_lock(&list_lock);
list_del(¬ifier->list);
@@ -242,15 +281,20 @@
put_device(d);
}
- mutex_unlock(&list_lock);
+ if (!subnotifier)
+ mutex_unlock(&list_lock);
/*
* Call device_attach() to reprobe devices
*
* NOTE: If dev allocation fails, i is 0, and the whole loop won't be
* executed.
+ * TODO: If we are unregistering a subdevice notifier we can't reprobe
+ * since the lock_list is held by the master device and attaching that
+ * device would call v4l2_async_register_subdev() and end in a deadlock
+ * on list_lock.
*/
- while (i--) {
+ while (i-- && !subnotifier) {
struct device *d = dev[i];
if (d && device_attach(d) < 0) {
@@ -274,6 +318,17 @@
* upon notifier registration.
*/
}
+
+void v4l2_async_subnotifier_unregister(struct v4l2_async_notifier *notifier)
+{
+ v4l2_async_do_notifier_unregister(notifier, true);
+}
+EXPORT_SYMBOL(v4l2_async_subnotifier_unregister);
+
+void v4l2_async_notifier_unregister(struct v4l2_async_notifier *notifier)
+{
+ v4l2_async_do_notifier_unregister(notifier, false);
+}
EXPORT_SYMBOL(v4l2_async_notifier_unregister);
int v4l2_async_register_subdev(struct v4l2_subdev *sd)
diff --git a/include/media/media-entity.h b/include/media/media-entity.h
index c7c254c..829442d 100644
--- a/include/media/media-entity.h
+++ b/include/media/media-entity.h
@@ -21,6 +21,7 @@
#include <linux/bitmap.h>
#include <linux/bug.h>
+#include <linux/fwnode.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/media.h>
@@ -171,12 +172,18 @@
/**
* struct media_entity_operations - Media entity operations
+ * @pad_from_fwnode: Return the pad number based on a fwnode endpoint.
+ * This operation can be used to map a fwnode to a
+ * media pad number. Optional.
* @link_setup: Notify the entity of link changes. The operation can
* return an error, in which case link setup will be
* cancelled. Optional.
* @link_validate: Return whether a link is valid from the entity point of
* view. The media_pipeline_start() function
* validates all links by calling this operation. Optional.
+ * @has_route: Return whether a route exists inside the entity between
+ * two given pads. Optional. If the operation isn't
+ * implemented all pads will be considered as connected.
*
* .. note::
*
@@ -184,10 +191,14 @@
* mutex held.
*/
struct media_entity_operations {
+ int (*pad_from_fwnode)(struct fwnode_endpoint *endpoint,
+ unsigned int *pad);
int (*link_setup)(struct media_entity *entity,
const struct media_pad *local,
const struct media_pad *remote, u32 flags);
int (*link_validate)(struct media_link *link);
+ bool (*has_route)(struct media_entity *entity, unsigned int pad0,
+ unsigned int pad1);
};
/**
@@ -816,6 +827,28 @@
struct media_entity *media_entity_get(struct media_entity *entity);
/**
+ * media_entity_pad_from_fwnode - Get pad number from fwnode
+ *
+ * @entity: The entity
+ * @fwnode: Pointer to fwnode_handle which should be used to find pad
+ * @direction: Expected direction of the pad
+ * @pad: Pointer to pad which will should be filled in
+ *
+ * This function can be used to resolve the media pad number from
+ * a fwnode. This is useful for devices which uses more complex
+ * mappings of media pads.
+ *
+ * If the entity do not implement the pad_from_fwnode() operation
+ * this function searches the entity for the first pad that matches
+ * the @direction.
+ *
+ * Return: return 0 on success.
+ */
+int media_entity_pad_from_fwnode(struct media_entity *entity,
+ struct fwnode_handle *fwnode,
+ int direction, unsigned int *pad);
+
+/**
* media_graph_walk_init - Allocate resources used by graph walk.
*
* @graph: Media graph structure that will be used to walk the graph
@@ -825,6 +858,22 @@
struct media_graph *graph, struct media_device *mdev);
/**
+ * media_entity_has_route - Check if two entity pads are connected internally
+ *
+ * @entity: The entity
+ * @pad0: The first pad index
+ * @pad1: The second pad index
+ *
+ * This function can be used to check whether two pads of an entity are
+ * connected internally in the entity.
+ *
+ * The caller must hold entity->graph_obj.mdev->mutex.
+ *
+ * Return: true if the pads are connected internally and false otherwise.
+ */
+bool media_entity_has_route(struct media_entity *entity, unsigned int pad0,
+ unsigned int pad1);
+/**
* media_graph_walk_cleanup - Release resources used by graph walk.
*
* @graph: Media graph structure that will be used to walk the graph
diff --git a/include/media/v4l2-async.h b/include/media/v4l2-async.h
index c69d8c8..7d55a5b 100644
--- a/include/media/v4l2-async.h
+++ b/include/media/v4l2-async.h
@@ -105,6 +105,18 @@
};
/**
+ * v4l2_async_notifier_register - registers a subdevice asynchronous subnotifier
+ *
+ * @sd: pointer to &struct v4l2_subdev
+ * @notifier: pointer to &struct v4l2_async_notifier
+ *
+ * This function assumes the async list_lock is already locked, allowing
+ * it to be used from struct v4l2_subdev_internal_ops registered() callback.
+ */
+int v4l2_async_subnotifier_register(struct v4l2_subdev *sd,
+ struct v4l2_async_notifier *notifier);
+
+/**
* v4l2_async_notifier_register - registers a subdevice asynchronous notifier
*
* @v4l2_dev: pointer to &struct v4l2_device
@@ -114,6 +126,16 @@
struct v4l2_async_notifier *notifier);
/**
+ * v4l2_async_subnotifier_unregister - unregisters a asynchronous subnotifier
+ *
+ * @notifier: pointer to &struct v4l2_async_notifier
+ *
+ * This function assumes the async list_lock is already locked, allowing
+ * it to be used from struct v4l2_subdev_internal_ops unregistered() callback.
+ */
+void v4l2_async_subnotifier_unregister(struct v4l2_async_notifier *notifier);
+
+/**
* v4l2_async_notifier_unregister - unregisters a subdevice asynchronous notifier
*
* @notifier: pointer to &struct v4l2_async_notifier