devicetree.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v3 0/4] Add support for MAX96714F and MAX96717F GMSL2 ser/des
@ 2024-01-11 13:03 Julien Massot
  2024-01-11 13:03 ` [PATCH v3 1/4] dt-bindings: media: add Maxim MAX96717F GMSL2 Serializer Julien Massot
                   ` (4 more replies)
  0 siblings, 5 replies; 18+ messages in thread
From: Julien Massot @ 2024-01-11 13:03 UTC (permalink / raw)
  To: linux-media
  Cc: kernel, devicetree, linux-kernel, mchehab, robh+dt,
	krzysztof.kozlowski+dt, conor+dt, Julien Massot

Change since v2:
- Convert drivers to use CCI helpers
- Use generic node name
- Use 'powerdown' as gpio name instead of 'enable'
- Add pattern generator support for MAX96714

These patches add support for Maxim MAX96714F deserializer and
MAX96717F serializer.

MAX96714F has one GMSL2 input port and one CSI2 4 lanes output port,
MAX96717F has one CSI2 input port and one GMSL2 output port.

The drivers support the tunnel mode where all the
CSI2 traffic coming from an imager is replicated through the deserializer
output port.

Both MAX96714F and MAX96717F are limited to a 3Gbps forward link rate
leaving a maximum of 2.6Gbps for the video payload.

Julien Massot (4):
  dt-bindings: media: add Maxim MAX96717F GMSL2 Serializer
  dt-bindings: media: add Maxim MAX96714F GMSL2 Deserializer
  media: i2c: add MAX96717 driver
  media: i2c: add MAX96714 driver

 .../bindings/media/i2c/maxim,max96714f.yaml   |  171 +++
 .../bindings/media/i2c/maxim,max96717f.yaml   |  147 +++
 MAINTAINERS                                   |   14 +
 drivers/media/i2c/Kconfig                     |   26 +
 drivers/media/i2c/Makefile                    |    2 +
 drivers/media/i2c/max96714.c                  | 1077 +++++++++++++++++
 drivers/media/i2c/max96717.c                  |  956 +++++++++++++++
 7 files changed, 2393 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/media/i2c/maxim,max96714f.yaml
 create mode 100644 Documentation/devicetree/bindings/media/i2c/maxim,max96717f.yaml
 create mode 100644 drivers/media/i2c/max96714.c
 create mode 100644 drivers/media/i2c/max96717.c

-- 
2.43.0


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

* [PATCH v3 1/4] dt-bindings: media: add Maxim MAX96717F GMSL2 Serializer
  2024-01-11 13:03 [PATCH v3 0/4] Add support for MAX96714F and MAX96717F GMSL2 ser/des Julien Massot
@ 2024-01-11 13:03 ` Julien Massot
  2024-01-12  8:13   ` Krzysztof Kozlowski
  2024-02-09  9:28   ` Sakari Ailus
  2024-01-11 13:03 ` [PATCH v3 2/4] dt-bindings: media: add Maxim MAX96714F GMSL2 Deserializer Julien Massot
                   ` (3 subsequent siblings)
  4 siblings, 2 replies; 18+ messages in thread
From: Julien Massot @ 2024-01-11 13:03 UTC (permalink / raw)
  To: linux-media
  Cc: kernel, devicetree, linux-kernel, mchehab, robh+dt,
	krzysztof.kozlowski+dt, conor+dt, Julien Massot

Add DT bindings for Maxim MAX96717F GMSL2 Serializer.

Signed-off-by: Julien Massot <julien.massot@collabora.com>
---
Change since v2:
 - remove reg description
 - add data lanes min/maxItems
 - Use generic node name 

---
 .../bindings/media/i2c/maxim,max96717f.yaml   | 147 ++++++++++++++++++
 1 file changed, 147 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/media/i2c/maxim,max96717f.yaml

diff --git a/Documentation/devicetree/bindings/media/i2c/maxim,max96717f.yaml b/Documentation/devicetree/bindings/media/i2c/maxim,max96717f.yaml
new file mode 100644
index 000000000000..f31517b1dbc8
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/i2c/maxim,max96717f.yaml
@@ -0,0 +1,147 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+# Copyright (C) 2024 Collabora Ltd.
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/i2c/maxim,max96717f.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: MAX96717 CSI-2 to GMSL2 Serializer
+
+maintainers:
+  - Julien Massot <julien.massot@collabora.com>
+
+description: |
+  The MAX96717F serializer converts MIPI CSI-2 D-PHY or C-PHY formatted input
+  into GMSL2 serial outputs. The device allows the GMSL2 link to
+  simultaneously transmit bidirectional control-channel data while forward
+  video transmissions are in progress. The MAX96717F can connect to one
+  remotely located deserializer using industry-standard coax or STP
+  interconnects. The device cans operate in pixel or tunnel mode. In pixel mode
+  the MAX96717F can select the MIPI datatype, while the tunnel mode forward all the MIPI
+  data received by the serializer.
+  The MAX96717F supports Reference Over Reverse (channel),
+  to generate a clock output for the sensor from the GMSL reverse channel.
+
+  The GMSL2 serial link operates at a fixed rate of 3Gbps in the
+  forward direction and 187.5Mbps in the reverse direction.
+
+properties:
+  compatible:
+    const: maxim,max96717f
+
+  '#gpio-cells':
+    const: 2
+    description:
+      First cell is the GPIO pin number, second cell is the flags. The GPIO pin
+      number must be in range of [0, 10].
+
+  gpio-controller: true
+
+  '#clock-cells':
+    const: 0
+
+  reg:
+    maxItems: 1
+
+  ports:
+    $ref: /schemas/graph.yaml#/properties/ports
+
+    properties:
+      port@0:
+        $ref: /schemas/graph.yaml#/$defs/port-base
+        unevaluatedProperties: false
+        description: CSI-2 Input port
+
+        properties:
+          endpoint:
+            $ref: /schemas/media/video-interfaces.yaml#
+            unevaluatedProperties: false
+
+            properties:
+              data-lanes:
+                minItems: 1
+                maxItems: 4
+
+            required:
+              - data-lanes
+
+      port@1:
+        $ref: /schemas/graph.yaml#/properties/port
+        unevaluatedProperties: false
+        description: GMSL Output port
+
+    required:
+      - port@1
+
+  i2c-gate:
+    $ref: /schemas/i2c/i2c-controller.yaml
+    unevaluatedProperties: false
+    description: |
+      The MAX96717F will forward the I2C requests from the
+      incoming GMSL2 link. Therefore, it supports an i2c-gate
+      subnode to configure a sensor.
+
+required:
+  - compatible
+  - reg
+  - ports
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+    #include <dt-bindings/media/video-interfaces.h>
+
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+        serializer: serializer@40 {
+            compatible = "maxim,max96717f";
+            reg = <0x40>;
+            gpio-controller;
+            #gpio-cells = <2>;
+            #clock-cells = <0>;
+
+            ports {
+                #address-cells = <1>;
+                #size-cells = <0>;
+
+                port@0 {
+                    reg = <0>;
+                    max96717f_csi_in: endpoint {
+                        data-lanes = <1 2 3 4>;
+                        remote-endpoint = <&sensor_out>;
+                    };
+                };
+
+                port@1 {
+                    reg = <1>;
+                    max96917f_gmsl_out: endpoint {
+                        remote-endpoint = <&deser_gmsl_in>;
+                    };
+                };
+            };
+
+            i2c-gate {
+                #address-cells = <1>;
+                #size-cells = <0>;
+                sensor@10 {
+                    compatible = "st,st-vgxy61";
+                    reg = <0x10>;
+                    reset-gpios = <&serializer 0 GPIO_ACTIVE_LOW>;
+                    clocks = <&serializer>;
+                    VCORE-supply = <&v1v2>;
+                    VDDIO-supply = <&v1v8>;
+                    VANA-supply = <&v2v8>;
+                    port {
+                        sensor_out: endpoint {
+                            data-lanes = <1 2 3 4>;
+                            remote-endpoint = <&max96717f_csi_in>;
+                        };
+                    };
+                };
+            };
+        };
+    };
+...
-- 
2.43.0


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

* [PATCH v3 2/4] dt-bindings: media: add Maxim MAX96714F GMSL2 Deserializer
  2024-01-11 13:03 [PATCH v3 0/4] Add support for MAX96714F and MAX96717F GMSL2 ser/des Julien Massot
  2024-01-11 13:03 ` [PATCH v3 1/4] dt-bindings: media: add Maxim MAX96717F GMSL2 Serializer Julien Massot
@ 2024-01-11 13:03 ` Julien Massot
  2024-01-12  8:15   ` Krzysztof Kozlowski
  2024-02-09  9:48   ` Sakari Ailus
  2024-01-11 13:03 ` [PATCH v3 3/4] media: i2c: add MAX96717 driver Julien Massot
                   ` (2 subsequent siblings)
  4 siblings, 2 replies; 18+ messages in thread
From: Julien Massot @ 2024-01-11 13:03 UTC (permalink / raw)
  To: linux-media
  Cc: kernel, devicetree, linux-kernel, mchehab, robh+dt,
	krzysztof.kozlowski+dt, conor+dt, Julien Massot

Add DT bindings for Maxim MAX96714F GMSL2 Deserializer.

Signed-off-by: Julien Massot <julien.massot@collabora.com>

---
Change since v2:
 - remove reg description
 - rename enable gpio to powerdown
 - use generic node name: i2c, serializer, deserializer
---
 .../bindings/media/i2c/maxim,max96714f.yaml   | 171 ++++++++++++++++++
 1 file changed, 171 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/media/i2c/maxim,max96714f.yaml

diff --git a/Documentation/devicetree/bindings/media/i2c/maxim,max96714f.yaml b/Documentation/devicetree/bindings/media/i2c/maxim,max96714f.yaml
new file mode 100644
index 000000000000..2423d166c954
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/i2c/maxim,max96714f.yaml
@@ -0,0 +1,171 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+# Copyright (C) 2024 Collabora Ltd.
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/i2c/maxim,max96714f.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Maxim MAX96714 GMSL2 to CSI-2 Deserializer
+
+maintainers:
+  - Julien Massot <julien.massot@collabora.com>
+
+description: |
+  The MAX96714F deserializer converts GMSL2 serial inputs into MIPI
+  CSI-2 D-PHY or C-PHY formatted output. The device allows the GMSL2 link to
+  simultaneously transmit bidirectional control-channel data while forward
+  video transmissions are in progress. The MAX96714F can connect to one
+  remotely located serializer using industry-standard coax or STP
+  interconnects. The device cans operate in pixel or tunnel mode. In pixel mode
+  the MAX96714F can select individual video stream, while the tunnel mode forward all
+  the MIPI data received by the serializer.
+
+  The GMSL2 serial link operates at a fixed rate of 3Gbps in the
+  forward direction and 187.5Mbps in the reverse direction.
+
+properties:
+  compatible:
+    const: maxim,max96714f
+
+  reg:
+    maxItems: 1
+
+  powerdown-gpios:
+    maxItems: 1
+    description:
+      Specifier for the GPIO connected to the PWDNB pin.
+
+  ports:
+    $ref: /schemas/graph.yaml#/properties/ports
+
+    properties:
+      port@0:
+        $ref: /schemas/graph.yaml#/properties/port
+        unevaluatedProperties: false
+        description: GMSL Input
+        properties:
+          endpoint:
+            $ref: /schemas/media/video-interfaces.yaml#
+            unevaluatedProperties: false
+            description:
+              Endpoint for GMSL2-Link port.
+
+      port@1:
+        $ref: /schemas/graph.yaml#/$defs/port-base
+        unevaluatedProperties: false
+        description: CSI-2 Output port
+
+        properties:
+          endpoint:
+            $ref: /schemas/media/video-interfaces.yaml#
+            unevaluatedProperties: false
+
+            properties:
+              data-lanes:
+                minItems: 1
+                maxItems: 4
+
+              link-frequencies:
+                maxItems: 1
+
+              bus-type:
+                enum:
+                  - 4 # MEDIA_BUS_TYPE_CSI2_DPHY
+
+            required:
+              - data-lanes
+              - bus-type
+
+    required:
+      - port@1
+
+  i2c-gate:
+    $ref: /schemas/i2c/i2c-controller.yaml
+    unevaluatedProperties: false
+    description: |
+      The MAX96714 will pass through and forward the I2C requests from the
+      incoming I2C bus over the GMSL2 link. Therefore it supports an i2c-gate
+      subnode to configure a serializer.
+
+  port0-poc-supply:
+    description: Regulator providing Power over Coax for the GMSL port
+
+required:
+  - compatible
+  - reg
+  - ports
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+    #include <dt-bindings/media/video-interfaces.h>
+
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        deserializer@28 {
+            compatible = "maxim,max96714f";
+            reg = <0x28>;
+            powerdown-gpios = <&main_gpio0 37 GPIO_ACTIVE_LOW>;
+
+            ports {
+                #address-cells = <1>;
+                #size-cells = <0>;
+                port@0 {
+                    reg = <0>;
+                    max96714_gmsl_in: endpoint {
+                        remote-endpoint = <&max96917f_gmsl_out>;
+                    };
+                };
+
+                port@1 {
+                    reg = <1>;
+                    max96714_csi_out: endpoint {
+                        bus-type = <MEDIA_BUS_TYPE_CSI2_DPHY>;
+                        clock-lanes = <0>;
+                        data-lanes = <1 2 3 4>;
+                        link-frequencies = /bits/ 64 <400000000>;
+                        remote-endpoint = <&csi_in>;
+                    };
+                };
+            };
+
+            i2c-gate {
+                #address-cells = <1>;
+                #size-cells = <0>;
+
+                serializer@40 {
+                    compatible = "maxim,max96717f";
+                    reg = <0x40>;
+                    gpio-controller;
+                    #gpio-cells = <2>;
+                    #clock-cells = <0>;
+
+                    ports {
+                        #address-cells = <1>;
+                        #size-cells = <0>;
+
+                        port@0 {
+                            reg = <0>;
+                            max96717f_csi_in: endpoint {
+                                data-lanes = <1 2>;
+                                lane-polarities = <1 0 1>;
+                                remote-endpoint = <&sensor_out>;
+                            };
+                        };
+
+                        port@1 {
+                            reg = <1>;
+                            max96917f_gmsl_out: endpoint {
+                                remote-endpoint = <&max96714_gmsl_in>;
+                            };
+                        };
+                    };
+                };
+            };
+        };
+    };
+...
-- 
2.43.0


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

* [PATCH v3 3/4] media: i2c: add MAX96717 driver
  2024-01-11 13:03 [PATCH v3 0/4] Add support for MAX96714F and MAX96717F GMSL2 ser/des Julien Massot
  2024-01-11 13:03 ` [PATCH v3 1/4] dt-bindings: media: add Maxim MAX96717F GMSL2 Serializer Julien Massot
  2024-01-11 13:03 ` [PATCH v3 2/4] dt-bindings: media: add Maxim MAX96714F GMSL2 Deserializer Julien Massot
@ 2024-01-11 13:03 ` Julien Massot
  2024-02-09 15:26   ` Sakari Ailus
  2024-01-11 13:03 ` [PATCH v3 4/4] media: i2c: add MAX96714 driver Julien Massot
  2024-01-12  8:10 ` [PATCH v3 0/4] Add support for MAX96714F and MAX96717F GMSL2 ser/des Krzysztof Kozlowski
  4 siblings, 1 reply; 18+ messages in thread
From: Julien Massot @ 2024-01-11 13:03 UTC (permalink / raw)
  To: linux-media
  Cc: kernel, devicetree, linux-kernel, mchehab, robh+dt,
	krzysztof.kozlowski+dt, conor+dt, Julien Massot

This driver handle the MAX96717 serializer in tunnel mode.
All incoming CSI traffic will be tunneled through the GMSL2
link.

Signed-off-by: Julien Massot <julien.massot@collabora.com>
---
Change since v2:
 - Use CCI helpers instead of recoding register access
 - add missing bitfield header
---
 MAINTAINERS                  |   7 +
 drivers/media/i2c/Kconfig    |  13 +
 drivers/media/i2c/Makefile   |   1 +
 drivers/media/i2c/max96717.c | 956 +++++++++++++++++++++++++++++++++++
 4 files changed, 977 insertions(+)
 create mode 100644 drivers/media/i2c/max96717.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 675e5d63a25b..a64a7932fe76 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13039,6 +13039,13 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/media/i2c/maxim,max96712.yaml
 F:	drivers/staging/media/max96712/max96712.c
 
+MAX96717 GMSL2 SERIALIZER DRIVER
+M:	Julien Massot <julien.massot@collabora.com>
+L:	linux-media@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/media/i2c/maxim,max96717f.yaml
+F:	drivers/media/i2c/max96717.c
+
 MAX9860 MONO AUDIO VOICE CODEC DRIVER
 M:	Peter Rosin <peda@axentia.se>
 L:	alsa-devel@alsa-project.org (moderated for non-subscribers)
diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index 78a87331686e..64f6a35f4481 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -1570,6 +1570,19 @@ config VIDEO_DS90UB960
 	  Device driver for the Texas Instruments DS90UB960
 	  FPD-Link III Deserializer and DS90UB9702 FPD-Link IV Deserializer.
 
+config VIDEO_MAX96717
+	tristate "Maxim MAX96717 GMSL2 Serializer support"
+	depends on OF && I2C && VIDEO_DEV && COMMON_CLK
+	select I2C_MUX
+	select GPIOLIB
+	select V4L2_CCI_I2C
+	help
+	  Device driver for the Maxim MAX96717
+	  GMSL2 Serializer.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called max96717.
+
 endmenu
 
 endif # VIDEO_DEV
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
index dfbe6448b549..9e007116f929 100644
--- a/drivers/media/i2c/Makefile
+++ b/drivers/media/i2c/Makefile
@@ -64,6 +64,7 @@ obj-$(CONFIG_VIDEO_LM3646) += lm3646.o
 obj-$(CONFIG_VIDEO_M52790) += m52790.o
 obj-$(CONFIG_VIDEO_MAX9271_LIB) += max9271.o
 obj-$(CONFIG_VIDEO_MAX9286) += max9286.o
+obj-$(CONFIG_VIDEO_MAX96717) += max96717.o
 obj-$(CONFIG_VIDEO_ML86V7667) += ml86v7667.o
 obj-$(CONFIG_VIDEO_MSP3400) += msp3400.o
 obj-$(CONFIG_VIDEO_MT9M001) += mt9m001.o
diff --git a/drivers/media/i2c/max96717.c b/drivers/media/i2c/max96717.c
new file mode 100644
index 000000000000..700e50894250
--- /dev/null
+++ b/drivers/media/i2c/max96717.c
@@ -0,0 +1,956 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Maxim GMSL2 Serializer Driver
+ *
+ * Copyright (C) 2024 Collabora Ltd.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/fwnode.h>
+#include <linux/gpio/driver.h>
+#include <linux/i2c-mux.h>
+#include <linux/i2c.h>
+#include <linux/regmap.h>
+
+#include <media/v4l2-cci.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-subdev.h>
+
+#define MAX96717F_DEVICE_ID 0xc8
+#define MAX96717_PORTS      2
+#define MAX96717_PAD_SINK   0
+#define MAX96717_PAD_SOURCE 1
+
+#define MAX96717_DEFAULT_CLKOUT_RATE	24000000UL
+
+/* DEV */
+#define REG3             CCI_REG8(0x3)
+#define MAX96717_RCLKSEL GENMASK(1, 0)
+#define RCLKSEL_REF_PLL  CCI_REG8(0x3)
+#define MAX96717_REG6    CCI_REG8(0x6)
+#define RCLKEN           BIT(5)
+#define MAX96717_DEV_ID  CCI_REG8(0xd)
+#define MAX96717_DEV_REV CCI_REG8(0xe)
+#define MAX96717_DEV_REV_MASK GENMASK(3, 0)
+
+/* VID_TX Z */
+#define MAX96717_VIDEO_TX2 CCI_REG8(0x112)
+#define MAX96717_VIDEO_PCLKDET BIT(7)
+
+/* GPIO */
+#define MAX96717_NUM_GPIO         11
+#define MAX96717_GPIO_REG_A(gpio) CCI_REG8(0x2be + (gpio) * 3)
+#define MAX96717_GPIO_OUT         BIT(4)
+#define MAX96717_GPIO_IN          BIT(3)
+#define MAX96717_GPIO_RX_EN       BIT(2)
+#define MAX96717_GPIO_TX_EN       BIT(1)
+#define MAX96717_GPIO_OUT_DIS     BIT(0)
+
+/* FRONTTOP */
+/* MAX96717 only have CSI port 'B' */
+#define MAX96717_FRONTOP0     CCI_REG8(0x308)
+#define MAX96717_START_PORT_B BIT(5)
+
+/* MIPI_RX */
+#define MAX96717_MIPI_RX1       CCI_REG8(0x331)
+#define MAX96717_MIPI_LANES_CNT GENMASK(5, 4)
+#define MAX96717_MIPI_RX2       CCI_REG8(0x332) /* phy1 Lanes map */
+#define MAX96717_PHY2_LANES_MAP GENMASK(7, 4)
+#define MAX96717_MIPI_RX3       CCI_REG8(0x333) /* phy2 Lanes map */
+#define MAX96717_PHY1_LANES_MAP GENMASK(3, 0)
+#define MAX96717_MIPI_RX4       CCI_REG8(0x334) /* phy1 lane polarities */
+#define MAX96717_PHY1_LANES_POL GENMASK(6, 4)
+#define MAX96717_MIPI_RX5       CCI_REG8(0x335) /* phy2 lane polarities */
+#define MAX96717_PHY2_LANES_POL GENMASK(2, 0)
+
+/* MIPI_RX_EXT */
+#define MAX96717_MIPI_RX_EXT11 CCI_REG8(0x383)
+#define MAX96717_TUN_MODE      BIT(7)
+
+/* REF_VTG */
+#define REF_VTG0                CCI_REG8(0x3f0)
+#define REFGEN_PREDEF_EN        BIT(6)
+#define REFGEN_PREDEF_FREQ_MASK GENMASK(5, 4)
+#define REFGEN_PREDEF_FREQ_ALT  BIT(3)
+#define REFGEN_RST              BIT(1)
+#define REFGEN_EN               BIT(0)
+
+/* MISC */
+#define PIO_SLEW_1 CCI_REG8(0x570)
+
+struct max96717_hw_data {
+	const char *model;
+	u8 device_id;
+};
+
+static const struct max96717_hw_data max96717f_data = {
+	.model = "max96717f",
+	.device_id = MAX96717F_DEVICE_ID,
+};
+
+struct max96717_priv {
+	const struct max96717_hw_data *data;
+	struct i2c_client             *client;
+	struct regmap                 *regmap;
+	struct i2c_mux_core           *mux;
+	struct v4l2_fwnode_endpoint   vep;
+	struct v4l2_subdev            sd;
+	struct media_pad              pads[MAX96717_PORTS];
+	struct v4l2_async_notifier    notifier;
+	struct v4l2_subdev            *source_sd;
+	u16                           source_sd_pad;
+	u64			      enabled_source_streams;
+	u8                            pll_predef_index;
+	struct clk_hw                 clk_hw;
+	struct gpio_chip              gpio_chip;
+};
+
+static inline struct max96717_priv *sd_to_max96717(struct v4l2_subdev *sd)
+{
+	return container_of(sd, struct max96717_priv, sd);
+}
+
+static inline struct max96717_priv *clk_hw_to_max96717(struct clk_hw *hw)
+{
+	return container_of(hw, struct max96717_priv, clk_hw);
+}
+
+static int max96717_i2c_mux_select(struct i2c_mux_core *mux, u32 chan)
+{
+	return 0;
+}
+
+static int max96717_i2c_mux_init(struct max96717_priv *priv)
+{
+	priv->mux = i2c_mux_alloc(priv->client->adapter, &priv->client->dev,
+				  1, 0, I2C_MUX_LOCKED | I2C_MUX_GATE,
+				  max96717_i2c_mux_select, NULL);
+	if (!priv->mux)
+		return -ENOMEM;
+
+	return i2c_mux_add_adapter(priv->mux, 0, 0, 0);
+}
+
+static inline int max96717_start_csi(struct max96717_priv *priv, bool start)
+{
+	return cci_update_bits(priv->regmap, MAX96717_FRONTOP0,
+			       MAX96717_START_PORT_B,
+			       start ? MAX96717_START_PORT_B : 0, NULL);
+}
+
+static int max96717_gpiochip_get(struct gpio_chip *gpiochip,
+				 unsigned int offset)
+{
+	struct max96717_priv *priv = gpiochip_get_data(gpiochip);
+	u64 val;
+	int ret;
+
+	ret = cci_read(priv->regmap, MAX96717_GPIO_REG_A(offset),
+		       &val, NULL);
+	if (ret)
+		return ret;
+
+	if (val & MAX96717_GPIO_OUT_DIS)
+		return !!(val & MAX96717_GPIO_IN);
+	else
+		return !!(val & MAX96717_GPIO_OUT);
+}
+
+static void max96717_gpiochip_set(struct gpio_chip *gpiochip,
+				  unsigned int offset, int value)
+{
+	struct max96717_priv *priv = gpiochip_get_data(gpiochip);
+
+	cci_update_bits(priv->regmap, MAX96717_GPIO_REG_A(offset),
+			MAX96717_GPIO_OUT, MAX96717_GPIO_OUT, NULL);
+}
+
+static int max96717_gpio_get_direction(struct gpio_chip *gpiochip,
+				       unsigned int offset)
+{
+	struct max96717_priv *priv = gpiochip_get_data(gpiochip);
+	u64 val;
+	int ret;
+
+	ret = cci_read(priv->regmap, MAX96717_GPIO_REG_A(offset), &val, NULL);
+	if (ret < 0)
+		return ret;
+
+	return !!(val & MAX96717_GPIO_OUT_DIS);
+}
+
+static int max96717_gpio_direction_out(struct gpio_chip *gpiochip,
+				       unsigned int offset, int value)
+{
+	struct max96717_priv *priv = gpiochip_get_data(gpiochip);
+
+	return cci_update_bits(priv->regmap, MAX96717_GPIO_REG_A(offset),
+			       MAX96717_GPIO_OUT_DIS | MAX96717_GPIO_OUT,
+			       value ? MAX96717_GPIO_OUT : 0, NULL);
+}
+
+static int max96717_gpio_direction_in(struct gpio_chip *gpiochip,
+				      unsigned int offset)
+{
+	struct max96717_priv *priv = gpiochip_get_data(gpiochip);
+
+	return cci_update_bits(priv->regmap, MAX96717_GPIO_REG_A(offset),
+			       MAX96717_GPIO_OUT_DIS, MAX96717_GPIO_OUT_DIS,
+			       NULL);
+}
+
+static int max96717_gpiochip_probe(struct max96717_priv *priv)
+{
+	struct device *dev = &priv->client->dev;
+	struct gpio_chip *gc = &priv->gpio_chip;
+	int ret, i;
+
+	gc->label = dev_name(dev);
+	gc->parent = dev;
+	gc->owner = THIS_MODULE;
+	gc->ngpio = MAX96717_NUM_GPIO;
+	gc->base = -1;
+	gc->can_sleep = true;
+	gc->get_direction = max96717_gpio_get_direction;
+	gc->direction_input = max96717_gpio_direction_in;
+	gc->direction_output = max96717_gpio_direction_out;
+	gc->set = max96717_gpiochip_set;
+	gc->get = max96717_gpiochip_get;
+	gc->of_gpio_n_cells = 2;
+
+	/* Disable GPIO forwarding */
+	for (i = 0; i < gc->ngpio; i++)
+		cci_update_bits(priv->regmap, MAX96717_GPIO_REG_A(i),
+				MAX96717_GPIO_RX_EN | MAX96717_GPIO_TX_EN,
+				0, &ret);
+
+	if (ret)
+		return ret;
+
+	ret = devm_gpiochip_add_data(dev, gc, priv);
+	if (ret) {
+		dev_err(dev, "Unable to create gpio_chip\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int _max96717_set_routing(struct v4l2_subdev *sd,
+				 struct v4l2_subdev_state *state,
+				 struct v4l2_subdev_krouting *routing)
+{
+	static const struct v4l2_mbus_framefmt format = {
+		.width = 1280,
+		.height = 1080,
+		.code = MEDIA_BUS_FMT_Y8_1X8,
+		.field = V4L2_FIELD_NONE,
+	};
+	int ret;
+
+	ret = v4l2_subdev_routing_validate(sd, routing,
+					   V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
+	if (ret)
+		return ret;
+
+	ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int max96717_set_routing(struct v4l2_subdev *sd,
+				struct v4l2_subdev_state *state,
+				enum v4l2_subdev_format_whence which,
+				struct v4l2_subdev_krouting *routing)
+{
+	struct max96717_priv *priv = sd_to_max96717(sd);
+
+	if (which == V4L2_SUBDEV_FORMAT_ACTIVE && priv->enabled_source_streams)
+		return -EBUSY;
+
+	return _max96717_set_routing(sd, state, routing);
+}
+
+static int max96717_set_fmt(struct v4l2_subdev *sd,
+			    struct v4l2_subdev_state *state,
+			    struct v4l2_subdev_format *format)
+{
+	struct max96717_priv *priv = sd_to_max96717(sd);
+	struct v4l2_mbus_framefmt *fmt;
+	u64 stream_source_mask;
+
+	if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE &&
+	    priv->enabled_source_streams)
+		return -EBUSY;
+
+	/* No transcoding, source and sink formats must match. */
+	if (format->pad == MAX96717_PAD_SOURCE)
+		return v4l2_subdev_get_fmt(sd, state, format);
+
+	/* Set sink format */
+	fmt = v4l2_subdev_state_get_format(state, format->pad, format->stream);
+	if (!fmt)
+		return -EINVAL;
+
+	*fmt = format->format;
+
+	/* Propagate to source format */
+	fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad,
+							   format->stream);
+	if (!fmt)
+		return -EINVAL;
+	*fmt = format->format;
+
+	stream_source_mask = BIT(format->stream);
+
+	return v4l2_subdev_state_xlate_streams(state, MAX96717_PAD_SOURCE,
+					       MAX96717_PAD_SINK,
+					       &stream_source_mask);
+}
+
+static int max96717_init_state(struct v4l2_subdev *sd,
+			       struct v4l2_subdev_state *state)
+{
+	struct v4l2_subdev_route routes[] = {
+		{
+			.sink_pad = MAX96717_PAD_SINK,
+			.sink_stream = 0,
+			.source_pad = MAX96717_PAD_SOURCE,
+			.source_stream = 0,
+			.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
+		},
+	};
+
+	struct v4l2_subdev_krouting routing = {
+		.num_routes = ARRAY_SIZE(routes),
+		.routes = routes,
+	};
+
+	return _max96717_set_routing(sd, state, &routing);
+}
+
+static bool max96717_pipe_pclkdet(struct max96717_priv *priv)
+{
+	u64 val = 0;
+
+	cci_read(priv->regmap, MAX96717_VIDEO_TX2, &val, NULL);
+
+	return val & MAX96717_VIDEO_PCLKDET;
+}
+
+static int max96717_log_status(struct v4l2_subdev *sd)
+{
+	struct max96717_priv *priv = sd_to_max96717(sd);
+	struct device *dev = &priv->client->dev;
+
+	dev_info(dev, "Serializer: %s\n", priv->data->model);
+	dev_info(dev, "Pipe: pclkdet:%d\n", max96717_pipe_pclkdet(priv));
+
+	return 0;
+}
+
+static int max96717_enable_streams(struct v4l2_subdev *sd,
+				   struct v4l2_subdev_state *state, u32 pad,
+				   u64 streams_mask)
+{
+	struct max96717_priv *priv = sd_to_max96717(sd);
+	struct device *dev = &priv->client->dev;
+	u64 sink_streams;
+	int ret;
+
+	sink_streams = v4l2_subdev_state_xlate_streams(state,
+						       MAX96717_PAD_SOURCE,
+						       MAX96717_PAD_SINK,
+						       &streams_mask);
+
+	if (!priv->enabled_source_streams)
+		max96717_start_csi(priv, true);
+
+	ret = v4l2_subdev_enable_streams(priv->source_sd, priv->source_sd_pad,
+					 sink_streams);
+	if (ret) {
+		dev_err(dev, "Fail to start streams:%llu on remote subdev\n",
+			sink_streams);
+		goto stop_csi;
+	}
+
+	priv->enabled_source_streams |= streams_mask;
+
+	return 0;
+
+stop_csi:
+	if (!priv->enabled_source_streams)
+		max96717_start_csi(priv, false);
+	return ret;
+}
+
+static int max96717_disable_streams(struct v4l2_subdev *sd,
+				    struct v4l2_subdev_state *state, u32 pad,
+				    u64 streams_mask)
+{
+	struct max96717_priv *priv = sd_to_max96717(sd);
+	u64 sink_streams;
+	int ret;
+
+	sink_streams = v4l2_subdev_state_xlate_streams(state,
+						       MAX96717_PAD_SOURCE,
+						       MAX96717_PAD_SINK,
+						       &streams_mask);
+
+	ret = v4l2_subdev_disable_streams(priv->source_sd, priv->source_sd_pad,
+					  sink_streams);
+	if (ret)
+		return ret;
+
+	priv->enabled_source_streams &= ~streams_mask;
+
+	if (!priv->enabled_source_streams)
+		max96717_start_csi(priv, false);
+
+	return 0;
+}
+
+static const struct v4l2_subdev_pad_ops max96717_pad_ops = {
+	.enable_streams = max96717_enable_streams,
+	.disable_streams = max96717_disable_streams,
+	.set_routing = max96717_set_routing,
+	.get_fmt = v4l2_subdev_get_fmt,
+	.set_fmt = max96717_set_fmt,
+};
+
+static const struct v4l2_subdev_core_ops max96717_subdev_core_ops = {
+	.log_status = max96717_log_status,
+};
+
+static const struct v4l2_subdev_internal_ops max96717_internal_ops = {
+	.init_state = max96717_init_state,
+};
+
+static const struct v4l2_subdev_ops max96717_subdev_ops = {
+	.core = &max96717_subdev_core_ops,
+	.pad = &max96717_pad_ops,
+};
+
+static const struct media_entity_operations max96717_entity_ops = {
+	.link_validate = v4l2_subdev_link_validate,
+};
+
+static int max96717_notify_bound(struct v4l2_async_notifier *notifier,
+				 struct v4l2_subdev *source_subdev,
+				 struct v4l2_async_connection *asd)
+{
+	struct max96717_priv *priv = sd_to_max96717(notifier->sd);
+	struct device *dev = &priv->client->dev;
+	int ret;
+
+	ret = media_entity_get_fwnode_pad(&source_subdev->entity,
+					  source_subdev->fwnode,
+					  MEDIA_PAD_FL_SOURCE);
+	if (ret < 0) {
+		dev_err(dev, "Failed to find pad for %s\n",
+			source_subdev->name);
+		return ret;
+	}
+
+	priv->source_sd = source_subdev;
+	priv->source_sd_pad = ret;
+
+	ret = media_create_pad_link(&source_subdev->entity, priv->source_sd_pad,
+				    &priv->sd.entity, 0,
+				    MEDIA_LNK_FL_ENABLED |
+				    MEDIA_LNK_FL_IMMUTABLE);
+	if (ret) {
+		dev_err(dev, "Unable to link %s:%u -> %s:0\n",
+			source_subdev->name, priv->source_sd_pad,
+			priv->sd.name);
+		return ret;
+	}
+
+	return 0;
+}
+
+static const struct v4l2_async_notifier_operations max96717_notify_ops = {
+	.bound = max96717_notify_bound,
+};
+
+static int max96717_v4l2_notifier_register(struct max96717_priv *priv)
+{
+	struct device *dev = &priv->client->dev;
+	struct v4l2_async_connection *asd;
+	struct fwnode_handle *ep_fwnode;
+	int ret;
+
+	ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
+						    MAX96717_PAD_SINK, 0, 0);
+	if (!ep_fwnode) {
+		dev_err(dev, "No graph endpoint\n");
+		return -ENODEV;
+	}
+
+	v4l2_async_subdev_nf_init(&priv->notifier, &priv->sd);
+
+	asd = v4l2_async_nf_add_fwnode_remote(&priv->notifier, ep_fwnode,
+					      struct v4l2_async_connection);
+
+	fwnode_handle_put(ep_fwnode);
+
+	if (IS_ERR(asd)) {
+		dev_err(dev, "Failed to add subdev: %ld", PTR_ERR(asd));
+		v4l2_async_nf_cleanup(&priv->notifier);
+		return PTR_ERR(asd);
+	}
+
+	priv->notifier.ops = &max96717_notify_ops;
+
+	ret = v4l2_async_nf_register(&priv->notifier);
+	if (ret) {
+		dev_err(dev, "Failed to register subdev_notifier");
+		v4l2_async_nf_cleanup(&priv->notifier);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void max96717_v4l2_notifier_unregister(struct max96717_priv *priv)
+{
+	v4l2_async_nf_unregister(&priv->notifier);
+	v4l2_async_nf_cleanup(&priv->notifier);
+}
+
+static int max96717_subdev_init(struct max96717_priv *priv)
+{
+	struct device *dev = &priv->client->dev;
+	int ret;
+
+	v4l2_i2c_subdev_init(&priv->sd, priv->client, &max96717_subdev_ops);
+	priv->sd.internal_ops = &max96717_internal_ops;
+
+	priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS;
+	priv->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
+	priv->sd.entity.ops = &max96717_entity_ops;
+
+	priv->pads[0].flags = MEDIA_PAD_FL_SINK;
+	priv->pads[1].flags = MEDIA_PAD_FL_SOURCE;
+
+	ret = media_entity_pads_init(&priv->sd.entity, 2, priv->pads);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to init pads\n");
+
+	ret = v4l2_subdev_init_finalize(&priv->sd);
+	if (ret) {
+		dev_err_probe(dev, ret,
+			      "v4l2 subdev init finalized failed\n");
+		goto err_fwnode_put;
+	}
+	ret = max96717_v4l2_notifier_register(priv);
+	if (ret) {
+		dev_err_probe(dev, ret,
+			      "v4l2 subdev notifier register failed\n");
+		goto err_free_state;
+	}
+
+	ret = v4l2_async_register_subdev(&priv->sd);
+	if (ret) {
+		dev_err_probe(dev, ret, "v4l2_async_register_subdev error\n");
+		goto err_unreg_notif;
+	}
+
+	return 0;
+
+err_unreg_notif:
+	max96717_v4l2_notifier_unregister(priv);
+err_free_state:
+	v4l2_subdev_cleanup(&priv->sd);
+err_fwnode_put:
+	fwnode_handle_put(priv->sd.fwnode);
+	media_entity_cleanup(&priv->sd.entity);
+
+	return ret;
+}
+
+static void max96717_subdev_uninit(struct max96717_priv *priv)
+{
+	v4l2_async_unregister_subdev(&priv->sd);
+	max96717_v4l2_notifier_unregister(priv);
+	v4l2_subdev_cleanup(&priv->sd);
+	fwnode_handle_put(priv->sd.fwnode);
+	media_entity_cleanup(&priv->sd.entity);
+}
+
+struct max96717_pll_predef_freq {
+	unsigned long freq;
+	bool is_alt;
+	u8 val;
+};
+
+static const struct max96717_pll_predef_freq max96717_predef_freqs[] = {
+	{ 13500000, true,  0 }, { 19200000, false, 0 },
+	{ 24000000, true,  1 }, { 27000000, false, 1 },
+	{ 37125000, false, 2 }, { 74250000, false, 3 },
+};
+
+static unsigned long
+max96717_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
+{
+	struct max96717_priv *priv = clk_hw_to_max96717(hw);
+
+	return max96717_predef_freqs[priv->pll_predef_index].freq;
+}
+
+static unsigned int max96717_clk_find_best_index(struct max96717_priv *priv,
+						 unsigned long rate)
+{
+	u8 i, idx;
+	unsigned long diff_new, diff_old;
+
+	diff_old = U32_MAX;
+	idx = 0;
+
+	for (i = 0; i < ARRAY_SIZE(max96717_predef_freqs); i++) {
+		diff_new = abs(rate - max96717_predef_freqs[i].freq);
+		if (diff_new < diff_old) {
+			diff_old = diff_new;
+			idx = i;
+		}
+	}
+
+	return idx;
+}
+
+static long max96717_clk_round_rate(struct clk_hw *hw, unsigned long rate,
+				    unsigned long *parent_rate)
+{
+	struct max96717_priv *priv = clk_hw_to_max96717(hw);
+	struct device *dev = &priv->client->dev;
+	u8 idx;
+
+	idx = max96717_clk_find_best_index(priv, rate);
+
+	if (rate != max96717_predef_freqs[idx].freq) {
+		dev_warn(dev, "Request CLK freq:%lu, found CLK freq:%lu\n",
+			 rate, max96717_predef_freqs[idx].freq);
+	}
+
+	return max96717_predef_freqs[idx].freq;
+}
+
+static int max96717_clk_set_rate(struct clk_hw *hw, unsigned long rate,
+				 unsigned long parent_rate)
+{
+	struct max96717_priv *priv = clk_hw_to_max96717(hw);
+	int ret = 0;
+	u8 val, idx;
+
+	idx = max96717_clk_find_best_index(priv, rate);
+
+	val = FIELD_PREP(REFGEN_PREDEF_FREQ_MASK,
+			 max96717_predef_freqs[idx].val);
+
+	if (max96717_predef_freqs[idx].is_alt)
+		val |= REFGEN_PREDEF_FREQ_ALT;
+
+	val |= REFGEN_RST | REFGEN_PREDEF_EN;
+
+	cci_write(priv->regmap, REF_VTG0, val, &ret);
+	cci_update_bits(priv->regmap, REF_VTG0, REFGEN_RST | REFGEN_EN,
+			REFGEN_EN, &ret);
+	if (ret)
+		return ret;
+
+	priv->pll_predef_index = idx;
+
+	return 0;
+}
+
+static int max96717_clk_prepare(struct clk_hw *hw)
+{
+	struct max96717_priv *priv = clk_hw_to_max96717(hw);
+
+	return cci_update_bits(priv->regmap, MAX96717_REG6, RCLKEN,
+			       RCLKEN, NULL);
+}
+
+static void max96717_clk_unprepare(struct clk_hw *hw)
+{
+	struct max96717_priv *priv = clk_hw_to_max96717(hw);
+
+	cci_update_bits(priv->regmap, MAX96717_REG6, RCLKEN, 0, NULL);
+}
+
+static const struct clk_ops max96717_clk_ops = {
+	.prepare     = max96717_clk_prepare,
+	.unprepare   = max96717_clk_unprepare,
+	.set_rate    = max96717_clk_set_rate,
+	.recalc_rate = max96717_clk_recalc_rate,
+	.round_rate  = max96717_clk_round_rate,
+};
+
+static int max96717_register_clkout(struct max96717_priv *priv)
+{
+	struct device *dev = &priv->client->dev;
+	int ret;
+
+	const struct clk_init_data init = {
+		.name = kasprintf(GFP_KERNEL, "%s.%s.clk_out",
+				  priv->data->model,
+				  dev_name(dev)),
+		.ops = &max96717_clk_ops,
+	};
+
+	if (!init.name)
+		return -ENOMEM;
+
+	/* RCLKSEL Reference PLL output */
+	ret = cci_update_bits(priv->regmap, REG3, MAX96717_RCLKSEL,
+			      RCLKSEL_REF_PLL, NULL);
+	/* MFP4 fastest slew rate */
+	cci_update_bits(priv->regmap, PIO_SLEW_1, BIT(5) | BIT(4), 0, &ret);
+	if (ret)
+		goto free_init_name;
+
+	priv->clk_hw.init = &init;
+
+	/* Initialize to 24 MHz */
+	ret = max96717_clk_set_rate(&priv->clk_hw,
+				    MAX96717_DEFAULT_CLKOUT_RATE, 0);
+	if (ret < 0)
+		goto free_init_name;
+
+	ret = devm_clk_hw_register(dev, &priv->clk_hw);
+	kfree(init.name);
+	if (ret)
+		return dev_err_probe(dev, ret, "Cannot register clock HW\n");
+
+	ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get,
+					  &priv->clk_hw);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "Cannot add OF clock provider\n");
+
+	return 0;
+
+free_init_name:
+	kfree(init.name);
+	return ret;
+}
+
+static int max96717_init_csi_lanes(struct max96717_priv *priv)
+{
+	struct v4l2_mbus_config_mipi_csi2 *mipi = &priv->vep.bus.mipi_csi2;
+	unsigned long lanes_used = 0;
+	u8 nlanes, lane, val = 0;
+	int ret;
+
+	nlanes = mipi->num_data_lanes;
+
+	ret = cci_update_bits(priv->regmap, MAX96717_MIPI_RX1,
+			      MAX96717_MIPI_LANES_CNT,
+			      FIELD_PREP(MAX96717_MIPI_LANES_CNT,
+					 nlanes - 1), NULL);
+
+	/* lanes polarity */
+	for (lane = 0; lane < nlanes + 1; lane++) {
+		if (!mipi->lane_polarities[lane])
+			continue;
+		/* Clock lane */
+		if (lane == 0)
+			val |= BIT(2);
+		else if (lane < 3)
+			val |= BIT(lane - 1);
+		else
+			val |= BIT(lane);
+	}
+
+	cci_update_bits(priv->regmap, MAX96717_MIPI_RX5,
+			MAX96717_PHY2_LANES_POL,
+			FIELD_PREP(MAX96717_PHY2_LANES_POL, val), &ret);
+
+	cci_update_bits(priv->regmap, MAX96717_MIPI_RX4,
+			MAX96717_PHY1_LANES_POL,
+			FIELD_PREP(MAX96717_PHY1_LANES_POL,
+				   val >> 3), &ret);
+	/* lanes mapping */
+	val = 0;
+	for (lane = 0; lane < nlanes; lane++) {
+		val |= (mipi->data_lanes[lane] - 1) << (lane * 2);
+		lanes_used |= BIT(mipi->data_lanes[lane] - 1);
+	}
+
+	/* Unused lanes need to be mapped as well to not have
+	 * the same lanes mapped twice.
+	 */
+	for (; lane < 4; lane++) {
+		unsigned int idx = find_first_zero_bit(&lanes_used, 4);
+
+		val |= idx << (lane * 2);
+		lanes_used |= BIT(idx);
+	}
+
+	cci_update_bits(priv->regmap, MAX96717_MIPI_RX3,
+			MAX96717_PHY1_LANES_MAP,
+			FIELD_PREP(MAX96717_PHY1_LANES_MAP, val), &ret);
+
+	return cci_update_bits(priv->regmap, MAX96717_MIPI_RX2,
+			       MAX96717_PHY2_LANES_MAP,
+			       FIELD_PREP(MAX96717_PHY2_LANES_MAP, val >> 4),
+			       &ret);
+}
+
+static int max96717_hw_init(struct max96717_priv *priv)
+{
+	struct device *dev = &priv->client->dev;
+	u64 val;
+	int ret;
+
+	ret = cci_read(priv->regmap, MAX96717_DEV_ID, &val, NULL);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "Fail to read the device id\n");
+
+	if (val != priv->data->device_id)
+		return dev_err_probe(dev, -EOPNOTSUPP,
+				     "Unsupported device id expected %x got %x\n",
+				     priv->data->device_id, (u8)val);
+
+	ret = cci_read(priv->regmap, MAX96717_DEV_REV, &val, NULL);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "Fail to read device revision");
+
+	dev_dbg(dev, "Found %x (rev %lx)\n", priv->data->device_id,
+		(u8)val & MAX96717_DEV_REV_MASK);
+
+	ret = cci_read(priv->regmap, MAX96717_MIPI_RX_EXT11, &val, NULL);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "Fail to read mipi rx extension");
+
+	if (!(val & MAX96717_TUN_MODE))
+		return dev_err_probe(dev, -EOPNOTSUPP,
+				     "Only supporting tunnel mode");
+
+	return max96717_init_csi_lanes(priv);
+}
+
+static int max96717_parse_dt(struct max96717_priv *priv)
+{
+	struct device *dev = &priv->client->dev;
+	struct fwnode_handle *ep_fwnode;
+	unsigned char num_data_lanes;
+	int ret;
+
+	priv->vep.bus_type = V4L2_MBUS_CSI2_DPHY;
+
+	ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
+						    MAX96717_PAD_SINK, 0, 0);
+	if (!ep_fwnode)
+		return dev_err_probe(dev, -ENOENT, "no endpoint found\n");
+
+	ret = v4l2_fwnode_endpoint_parse(ep_fwnode, &priv->vep);
+
+	fwnode_handle_put(ep_fwnode);
+
+	if (ret < 0)
+		return dev_err_probe(dev, ret, "Failed to parse sink endpoint");
+
+	num_data_lanes = priv->vep.bus.mipi_csi2.num_data_lanes;
+	if (num_data_lanes < 1 || num_data_lanes > 4)
+		return dev_err_probe(dev, -EINVAL,
+				     "Invalid data lanes should be 1 to 4\n");
+
+	return 0;
+}
+
+static int max96717_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	struct max96717_priv *priv;
+	int ret;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->client = client;
+
+	priv->data = of_device_get_match_data(&client->dev);
+
+	i2c_set_clientdata(client, priv);
+
+	priv->regmap = devm_cci_regmap_init_i2c(client, 16);
+	if (IS_ERR(priv->regmap)) {
+		ret = PTR_ERR(priv->regmap);
+		return dev_err_probe(dev, ret, "Failed to init regmap\n");
+	}
+
+	ret = max96717_parse_dt(priv);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to parse the dt\n");
+
+	ret = max96717_hw_init(priv);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "Failed to initialize the hardware\n");
+
+	ret = max96717_gpiochip_probe(priv);
+	if (ret) {
+		dev_err(&client->dev, "Failed to init gpiochip\n");
+		return ret;
+	}
+
+	ret = max96717_register_clkout(priv);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to register clkout\n");
+
+	ret = max96717_subdev_init(priv);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "Failed to initialize v4l2 subdev\n");
+
+	ret = max96717_i2c_mux_init(priv);
+	if (ret) {
+		dev_err_probe(dev, ret, "failed to add remote i2c adapter\n");
+		goto err_subdev_uninit;
+	}
+
+	return 0;
+
+err_subdev_uninit:
+	max96717_subdev_uninit(priv);
+	return ret;
+}
+
+static void max96717_remove(struct i2c_client *client)
+{
+	struct max96717_priv *priv = i2c_get_clientdata(client);
+
+	max96717_subdev_uninit(priv);
+	i2c_mux_del_adapters(priv->mux);
+}
+
+static const struct of_device_id max96717_of_ids[] = {
+	{ .compatible = "maxim,max96717f", .data = &max96717f_data },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, max96717_of_ids);
+
+static struct i2c_driver max96717_i2c_driver = {
+	.driver	= {
+		.name		= "max96717",
+		.of_match_table	= max96717_of_ids,
+	},
+	.probe		= max96717_probe,
+	.remove		= max96717_remove,
+};
+
+module_i2c_driver(max96717_i2c_driver);
+
+MODULE_DESCRIPTION("Maxim GMSL2 MAX96717 Serializer Driver");
+MODULE_AUTHOR("Julien Massot <julien.massot@collabora.com>");
+MODULE_LICENSE("GPL");
-- 
2.43.0


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

* [PATCH v3 4/4] media: i2c: add MAX96714 driver
  2024-01-11 13:03 [PATCH v3 0/4] Add support for MAX96714F and MAX96717F GMSL2 ser/des Julien Massot
                   ` (2 preceding siblings ...)
  2024-01-11 13:03 ` [PATCH v3 3/4] media: i2c: add MAX96717 driver Julien Massot
@ 2024-01-11 13:03 ` Julien Massot
  2024-01-13 11:31   ` kernel test robot
  2024-02-09 15:49   ` Sakari Ailus
  2024-01-12  8:10 ` [PATCH v3 0/4] Add support for MAX96714F and MAX96717F GMSL2 ser/des Krzysztof Kozlowski
  4 siblings, 2 replies; 18+ messages in thread
From: Julien Massot @ 2024-01-11 13:03 UTC (permalink / raw)
  To: linux-media
  Cc: kernel, devicetree, linux-kernel, mchehab, robh+dt,
	krzysztof.kozlowski+dt, conor+dt, Julien Massot

This driver handle the MAX96714 deserializer in tunnel mode.
The CSI output will replicate all the CSI traffic forwarded by
the remote serializer.

Signed-off-by: Julien Massot <julien.massot@collabora.com>
---
Change since v2:
 - Use CCI helpers instead of recoding register access
 - add missing bitfield header
 - Add pattern generator so the deserializer can be tested without a serializer/sensor
---
 MAINTAINERS                  |    7 +
 drivers/media/i2c/Kconfig    |   13 +
 drivers/media/i2c/Makefile   |    1 +
 drivers/media/i2c/max96714.c | 1077 ++++++++++++++++++++++++++++++++++
 4 files changed, 1098 insertions(+)
 create mode 100644 drivers/media/i2c/max96714.c

diff --git a/MAINTAINERS b/MAINTAINERS
index a64a7932fe76..a7691d38513a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13039,6 +13039,13 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/media/i2c/maxim,max96712.yaml
 F:	drivers/staging/media/max96712/max96712.c
 
+MAX96714 GMSL2 DESERIALIZER DRIVER
+M:	Julien Massot <julien.massot@collabora.com>
+L:	linux-media@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/media/i2c/maxim,max96714f.yaml
+F:	drivers/media/i2c/max96714.c
+
 MAX96717 GMSL2 SERIALIZER DRIVER
 M:	Julien Massot <julien.massot@collabora.com>
 L:	linux-media@vger.kernel.org
diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index 64f6a35f4481..d0dfab513154 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -1570,6 +1570,19 @@ config VIDEO_DS90UB960
 	  Device driver for the Texas Instruments DS90UB960
 	  FPD-Link III Deserializer and DS90UB9702 FPD-Link IV Deserializer.
 
+config VIDEO_MAX96714
+	tristate "Maxim MAX96714 GMSL2 deserializer"
+	depends on OF && I2C && VIDEO_DEV
+	select I2C_MUX
+	select GPIOLIB
+	select V4L2_CCI_I2C
+	help
+	  Device driver for the Maxim MAX96714
+	  GMSL2 Deserializer.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called max96714.
+
 config VIDEO_MAX96717
 	tristate "Maxim MAX96717 GMSL2 Serializer support"
 	depends on OF && I2C && VIDEO_DEV && COMMON_CLK
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
index 9e007116f929..7c794441eaff 100644
--- a/drivers/media/i2c/Makefile
+++ b/drivers/media/i2c/Makefile
@@ -64,6 +64,7 @@ obj-$(CONFIG_VIDEO_LM3646) += lm3646.o
 obj-$(CONFIG_VIDEO_M52790) += m52790.o
 obj-$(CONFIG_VIDEO_MAX9271_LIB) += max9271.o
 obj-$(CONFIG_VIDEO_MAX9286) += max9286.o
+obj-$(CONFIG_VIDEO_MAX96714) += max96714.o
 obj-$(CONFIG_VIDEO_MAX96717) += max96717.o
 obj-$(CONFIG_VIDEO_ML86V7667) += ml86v7667.o
 obj-$(CONFIG_VIDEO_MSP3400) += msp3400.o
diff --git a/drivers/media/i2c/max96714.c b/drivers/media/i2c/max96714.c
new file mode 100644
index 000000000000..8bf1f5babf5d
--- /dev/null
+++ b/drivers/media/i2c/max96714.c
@@ -0,0 +1,1077 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Maxim GMSL2 Deserializer Driver
+ *
+ * Copyright (C) 2024 Collabora Ltd.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/fwnode.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/i2c-mux.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+
+#include <media/v4l2-cci.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-subdev.h>
+
+#define MAX96714F_DEVICE_ID 0xca
+#define MAX96714_NPORTS     2
+#define MAX96714_PAD_SINK   0
+#define MAX96714_PAD_SOURCE 1
+
+/* DEV */
+#define MAX96714_REG13            CCI_REG8(0x0d)
+#define MAX96714_DEV_REV          CCI_REG8(0x0e)
+#define MAX96714_DEV_REV_MASK     GENMASK(3, 0)
+#define MAX96714_LINK_LOCK        CCI_REG8(0x13)
+#define MAX96714_LINK_LOCK_BIT    BIT(3)
+#define MAX96714_IO_CHK0          CCI_REG8(0x38)
+#define MAX96714_PATTERN_CLK_FREQ GENMASK(1, 0)
+/* VID_RX */
+#define MAX96714_VIDEO_RX8        CCI_REG8(0x11a)
+#define MAX96714_VID_LOCK         BIT(6)
+
+/* VRX_PATGEN_0 */
+#define MAX96714_PATGEN_0              CCI_REG8(0x240)
+#define MAX96714_PATGEN_1              CCI_REG8(0x241)
+#define MAX96714_PATGEN_MODE           GENMASK(5, 4)
+#define MAX96714_PATGEN_VS_DLY         CCI_REG24(0x242)
+#define MAX96714_PATGEN_VS_HIGH        CCI_REG24(0x245)
+#define MAX96714_PATGEN_VS_LOW         CCI_REG24(0x248)
+#define MAX96714_PATGEN_V2H            CCI_REG24(0x24b)
+#define MAX96714_PATGEN_HS_HIGH        CCI_REG16(0x24e)
+#define MAX96714_PATGEN_HS_LOW	       CCI_REG16(0x250)
+#define MAX96714_PATGEN_HS_CNT	       CCI_REG16(0x252)
+#define MAX96714_PATGEN_V2D            CCI_REG24(0x254)
+#define MAX96714_PATGEN_DE_HIGH        CCI_REG16(0x257)
+#define MAX96714_PATGEN_DE_LOW         CCI_REG16(0x259)
+#define MAX96714_PATGEN_DE_CNT         CCI_REG16(0x25B)
+#define MAX96714_PATGEN_GRAD_INC       CCI_REG8(0x25d)
+#define MAX96714_PATGEN_CHKB_COLOR_A   CCI_REG24(0x25E)
+#define MAX96714_PATGEN_CHKB_COLOR_B   CCI_REG24(0x261)
+#define MAX96714_PATGEN_CHKB_RPT_CNT_A CCI_REG8(0x264)
+#define MAX96714_PATGEN_CHKB_RPT_CNT_B CCI_REG8(0x265)
+#define MAX96714_PATGEN_CHKB_ALT       CCI_REG8(0x266)
+/* BACKTOP */
+#define MAX96714_BACKTOP25 CCI_REG8(0x320)
+#define CSI_DPLL_FREQ_MASK GENMASK(4, 0)
+
+/* MIPI_PHY */
+#define MAX96714_MIPI_PHY0       CCI_REG8(0x330)
+#define MAX96714_FORCE_CSI_OUT   BIT(7)
+#define MAX96714_MIPI_STDBY_N    CCI_REG8(0x332)
+#define MAX96714_MIPI_STDBY_MASK GENMASK(5, 4)
+#define MAX96714_MIPI_LANE_MAP   CCI_REG8(0x333)
+#define MAX96714_MIPI_POLARITY   CCI_REG8(0x335)
+#define MAX96714_MIPI_POLARITY_MASK GENMASK(5, 0)
+
+/* MIPI_TX */
+#define MAX96714_MIPI_LANE_CNT CCI_REG8(0x44a)
+#define MAX96714_CSI2_LANE_CNT_MASK GENMASK(7, 6)
+#define MAX96714_MIPI_TX52 CCI_REG8(0x474)
+#define MAX96714_TUN_EN BIT(0)
+
+#define MHZ(v) ((u32)((v)  * 1000000U))
+
+enum max96714_vpg_mode {
+	MAX96714_VPG_DISABLED = 0,
+	MAX96714_VPG_CHECKERBOARD = 1,
+	MAX96714_VPG_GRADIENT = 2,
+};
+
+struct max96714_rxport {
+	struct {
+		struct v4l2_subdev   *sd;
+		u16 pad;
+		struct fwnode_handle *ep_fwnode;
+	} source;
+	struct regulator	     *poc;
+};
+
+struct max96714_txport {
+	struct v4l2_fwnode_endpoint vep;
+};
+
+struct max96714_priv {
+	struct i2c_client *client;
+	struct regmap     *regmap;
+	struct gpio_desc  *pd_gpio;
+	struct max96714_rxport rxport;
+	struct i2c_mux_core *mux;
+	u64 enabled_source_streams;
+	struct v4l2_subdev	sd;
+	struct media_pad	pads[MAX96714_NPORTS];
+	struct v4l2_fwnode_endpoint vep;
+	struct v4l2_ctrl_handler   ctrl_handler;
+	struct v4l2_async_notifier notifier;
+	s64 tx_link_freq;
+	enum max96714_vpg_mode pattern;
+};
+
+static struct regmap_config max96714_regmap_config = {
+	.name = "max96714",
+	.reg_bits = 16,
+	.val_bits = 8,
+	.max_register = 0xFFFF,
+};
+
+static inline struct max96714_priv *sd_to_max96714(struct v4l2_subdev *sd)
+{
+	return container_of(sd, struct max96714_priv, sd);
+}
+
+static int max96714_enable_tx_port(struct max96714_priv *priv)
+{
+	return cci_update_bits(priv->regmap, MAX96714_MIPI_STDBY_N,
+			       MAX96714_MIPI_STDBY_MASK,
+			       MAX96714_MIPI_STDBY_MASK, NULL);
+}
+
+static int max96714_disable_tx_port(struct max96714_priv *priv)
+{
+	return cci_update_bits(priv->regmap, MAX96714_MIPI_STDBY_N,
+			       MAX96714_MIPI_STDBY_MASK, 0, NULL);
+}
+
+static bool max96714_tx_port_enabled(struct max96714_priv *priv)
+{
+	u64 val;
+
+	cci_read(priv->regmap, MAX96714_MIPI_STDBY_N, &val, NULL);
+
+	return val & MAX96714_MIPI_STDBY_MASK;
+}
+
+static int max96714_apply_patgen_timing(struct max96714_priv *priv,
+					struct v4l2_subdev_state *state)
+{
+	struct v4l2_mbus_framefmt *fmt =
+		v4l2_subdev_state_get_format(state, MAX96714_PAD_SOURCE);
+	const u32 h_active = fmt->width;
+	const u32 h_fp = 88;
+	const u32 h_sw = 44;
+	const u32 h_bp = 148;
+	u32 h_tot;
+
+	const u32 v_active = fmt->height;
+	const u32 v_fp = 4;
+	const u32 v_sw = 5;
+	const u32 v_bp = 36;
+	u32 v_tot;
+	int ret = 0;
+
+	h_tot = h_active + h_fp + h_sw + h_bp;
+	v_tot = v_active + v_fp + v_sw + v_bp;
+
+	/* 75 Mhz pixel clock */
+	cci_update_bits(priv->regmap, MAX96714_IO_CHK0,
+			MAX96714_PATTERN_CLK_FREQ, 1, &ret);
+
+	dev_info(&priv->client->dev, "height: %d width: %d\n", fmt->height,
+		 fmt->width);
+
+	cci_write(priv->regmap, MAX96714_PATGEN_VS_DLY, 0, &ret);
+	cci_write(priv->regmap, MAX96714_PATGEN_VS_HIGH, v_sw * h_tot, &ret);
+	cci_write(priv->regmap, MAX96714_PATGEN_VS_LOW,
+		  (v_active + v_fp + v_bp) * h_tot, &ret);
+	cci_write(priv->regmap, MAX96714_PATGEN_HS_HIGH, h_sw, &ret);
+	cci_write(priv->regmap, MAX96714_PATGEN_HS_LOW, h_active + h_fp + h_bp,
+		  &ret);
+	cci_write(priv->regmap, MAX96714_PATGEN_V2D,
+		  h_tot * (v_sw + v_bp) + (h_sw + h_bp), &ret);
+	cci_write(priv->regmap, MAX96714_PATGEN_HS_CNT, v_tot, &ret);
+	cci_write(priv->regmap, MAX96714_PATGEN_DE_HIGH, h_active, &ret);
+	cci_write(priv->regmap, MAX96714_PATGEN_DE_LOW, h_fp + h_sw + h_bp,
+		  &ret);
+	cci_write(priv->regmap, MAX96714_PATGEN_DE_CNT, v_active, &ret);
+	/* B G R */
+	cci_write(priv->regmap, MAX96714_PATGEN_CHKB_COLOR_A, 0xfecc00, &ret);
+	/* B G R */
+	cci_write(priv->regmap, MAX96714_PATGEN_CHKB_COLOR_B, 0x006aa7, &ret);
+	cci_write(priv->regmap, MAX96714_PATGEN_CHKB_RPT_CNT_A, 0x3c, &ret);
+	cci_write(priv->regmap, MAX96714_PATGEN_CHKB_RPT_CNT_B, 0x3c, &ret);
+	cci_write(priv->regmap, MAX96714_PATGEN_CHKB_ALT, 0x3c, &ret);
+	cci_write(priv->regmap, MAX96714_PATGEN_GRAD_INC, 0x10, &ret);
+
+	return ret;
+}
+
+static int max96714_apply_patgen(struct max96714_priv *priv,
+				 struct v4l2_subdev_state *state)
+{
+	int ret = 0;
+	u8 val;
+
+	if (priv->pattern)
+		ret = max96714_apply_patgen_timing(priv, state);
+
+	cci_write(priv->regmap, MAX96714_PATGEN_0, priv->pattern ? 0xfb : 0,
+		  &ret);
+
+	val = FIELD_PREP(MAX96714_PATGEN_MODE, priv->pattern);
+	cci_update_bits(priv->regmap, MAX96714_PATGEN_1, MAX96714_PATGEN_MODE,
+			val, &ret);
+	return ret;
+}
+
+static int max96714_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct max96714_priv *priv =
+		container_of(ctrl->handler, struct max96714_priv, ctrl_handler);
+	int ret;
+
+	switch (ctrl->id) {
+	case V4L2_CID_TEST_PATTERN:
+		priv->pattern = ctrl->val;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	ret = cci_update_bits(priv->regmap, MAX96714_MIPI_PHY0,
+			      MAX96714_FORCE_CSI_OUT,
+			      priv->pattern ? MAX96714_FORCE_CSI_OUT : 0, NULL);
+
+	/* Pattern generator doesn't work with tunnel mode */
+	return cci_update_bits(priv->regmap, MAX96714_MIPI_TX52,
+			       MAX96714_TUN_EN,
+			       priv->pattern ? 0 : MAX96714_TUN_EN, &ret);
+}
+
+static const char * const max96714_test_pattern[] = {
+	"Disabled",
+	"Checkerboard",
+	"Gradient"
+};
+
+static const struct v4l2_ctrl_ops max96714_ctrl_ops = {
+	.s_ctrl = max96714_s_ctrl,
+};
+
+static int max96714_enable_streams(struct v4l2_subdev *sd,
+				   struct v4l2_subdev_state *state,
+				   u32 source_pad, u64 streams_mask)
+{
+	struct max96714_priv *priv = sd_to_max96714(sd);
+	u64 sink_streams;
+	int ret;
+
+	if (!priv->enabled_source_streams)
+		max96714_enable_tx_port(priv);
+
+	ret = max96714_apply_patgen(priv, state);
+	if (ret)
+		goto err;
+
+	if (!priv->pattern) {
+		if (!priv->rxport.source.sd) {
+			ret = -ENODEV;
+			goto err;
+		}
+
+		sink_streams =
+			v4l2_subdev_state_xlate_streams(state,
+							MAX96714_PAD_SOURCE,
+							MAX96714_PAD_SINK,
+							&streams_mask);
+
+		ret = v4l2_subdev_enable_streams(priv->rxport.source.sd,
+						 priv->rxport.source.pad,
+						 sink_streams);
+		if (ret)
+			goto err;
+	}
+
+	priv->enabled_source_streams |= streams_mask;
+
+	return 0;
+
+err:
+	if (!priv->enabled_source_streams)
+		max96714_disable_tx_port(priv);
+
+	return ret;
+}
+
+static int max96714_disable_streams(struct v4l2_subdev *sd,
+				    struct v4l2_subdev_state *state,
+				    u32 source_pad, u64 streams_mask)
+{
+	struct max96714_priv *priv = sd_to_max96714(sd);
+	u64 sink_streams;
+	int ret;
+
+	if (!priv->pattern && priv->rxport.source.sd) {
+		sink_streams =
+			v4l2_subdev_state_xlate_streams(state,
+							MAX96714_PAD_SOURCE,
+							MAX96714_PAD_SINK,
+							&streams_mask);
+
+		ret = v4l2_subdev_disable_streams(priv->rxport.source.sd,
+						  priv->rxport.source.pad,
+						  sink_streams);
+		if (ret)
+			return ret;
+	}
+
+	priv->enabled_source_streams &= ~streams_mask;
+
+	if (!priv->enabled_source_streams)
+		max96714_disable_tx_port(priv);
+
+	return 0;
+}
+
+static int max96714_s_stream(struct v4l2_subdev *subdev, int enable)
+{
+	int ret;
+	u64 streams_mask = BIT(0);
+
+	if (enable)
+		ret = v4l2_subdev_enable_streams(subdev, MAX96714_PAD_SOURCE,
+						 streams_mask);
+	else
+		ret = v4l2_subdev_disable_streams(subdev, MAX96714_PAD_SOURCE,
+						  streams_mask);
+	return ret;
+}
+
+static int max96714_set_fmt(struct v4l2_subdev *sd,
+			    struct v4l2_subdev_state *state,
+			    struct v4l2_subdev_format *format)
+{
+	struct max96714_priv *priv = sd_to_max96714(sd);
+	struct v4l2_mbus_framefmt *fmt;
+
+	if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE &&
+	    priv->enabled_source_streams)
+		return -EBUSY;
+
+	/* No transcoding, source and sink formats must match. */
+	if (format->pad == MAX96714_PAD_SOURCE)
+		return v4l2_subdev_get_fmt(sd, state, format);
+
+	fmt = v4l2_subdev_state_get_format(state, format->pad, format->stream);
+	if (!fmt)
+		return -EINVAL;
+
+	*fmt = format->format;
+
+	fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad,
+							   format->stream);
+	if (!fmt)
+		return -EINVAL;
+
+	*fmt = format->format;
+
+	return 0;
+}
+
+static int _max96714_set_routing(struct v4l2_subdev *sd,
+				 struct v4l2_subdev_state *state,
+				 enum v4l2_subdev_format_whence which,
+				 struct v4l2_subdev_krouting *routing)
+{
+	int ret;
+
+	static const struct v4l2_mbus_framefmt format = {
+		.width = 1280,
+		.height = 1080,
+		.code = MEDIA_BUS_FMT_Y8_1X8,
+		.field = V4L2_FIELD_NONE,
+	};
+
+	/*
+	 * Note: we can only support up to V4L2_FRAME_DESC_ENTRY_MAX, until
+	 * frame desc is made dynamically allocated.
+	 */
+	if (routing->num_routes > V4L2_FRAME_DESC_ENTRY_MAX)
+		return -EINVAL;
+
+	ret = v4l2_subdev_routing_validate(sd, routing,
+					   V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
+	if (ret)
+		return ret;
+
+	ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int max96714_set_routing(struct v4l2_subdev *sd,
+				struct v4l2_subdev_state *state,
+				enum v4l2_subdev_format_whence which,
+				struct v4l2_subdev_krouting *routing)
+{
+	struct max96714_priv *priv = sd_to_max96714(sd);
+
+	if (which == V4L2_SUBDEV_FORMAT_ACTIVE && priv->enabled_source_streams)
+		return -EBUSY;
+
+	return _max96714_set_routing(sd, state, which, routing);
+}
+
+static int max96714_init_state(struct v4l2_subdev *sd,
+			       struct v4l2_subdev_state *state)
+{
+	struct v4l2_subdev_route routes[] = {
+		{
+			.sink_pad = MAX96714_PAD_SINK,
+			.sink_stream = 0,
+			.source_pad = MAX96714_PAD_SOURCE,
+			.source_stream = 0,
+			.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
+		}
+	};
+
+	struct v4l2_subdev_krouting routing = {
+		.num_routes = ARRAY_SIZE(routes),
+		.routes = routes,
+	};
+
+	return _max96714_set_routing(sd, state, V4L2_SUBDEV_FORMAT_ACTIVE,
+				     &routing);
+}
+
+static const struct v4l2_subdev_pad_ops max96714_pad_ops = {
+	.enable_streams = max96714_enable_streams,
+	.disable_streams = max96714_disable_streams,
+
+	.set_routing = max96714_set_routing,
+	.get_fmt = v4l2_subdev_get_fmt,
+	.set_fmt = max96714_set_fmt,
+};
+
+static bool max96714_link_locked(struct max96714_priv *priv)
+{
+	u64 val = 0;
+
+	cci_read(priv->regmap, MAX96714_LINK_LOCK, &val, NULL);
+
+	return val & MAX96714_LINK_LOCK_BIT;
+}
+
+static void max96714_link_status(struct max96714_priv *priv)
+{
+	struct device *dev = &priv->client->dev;
+
+	dev_info(dev, "Link locked:%d\n", max96714_link_locked(priv));
+}
+
+static bool max96714_pipe_locked(struct max96714_priv *priv)
+{
+	u64 val;
+
+	cci_read(priv->regmap, MAX96714_VIDEO_RX8, &val, NULL);
+
+	return val & MAX96714_VID_LOCK;
+}
+
+static void max96714_pipe_status(struct max96714_priv *priv)
+{
+	struct device *dev = &priv->client->dev;
+
+	dev_info(dev, "Pipe vidlock:%d\n", max96714_pipe_locked(priv));
+}
+
+static void max96714_csi_status(struct max96714_priv *priv)
+{
+	struct device *dev = &priv->client->dev;
+	u64 freq = 0;
+
+	cci_read(priv->regmap, MAX96714_BACKTOP25, &freq, NULL);
+	freq = FIELD_GET(CSI_DPLL_FREQ_MASK, freq);
+
+	dev_info(dev, "CSI controller DPLL freq:%u00MHz CSIPHY enabled:%d\n",
+		 (u8)freq, max96714_tx_port_enabled(priv));
+}
+
+static int max96714_log_status(struct v4l2_subdev *sd)
+{
+	struct max96714_priv *priv = sd_to_max96714(sd);
+	struct device *dev = &priv->client->dev;
+
+	dev_info(dev, "Deserializer: max96714\n");
+
+	max96714_link_status(priv);
+	max96714_pipe_status(priv);
+	max96714_csi_status(priv);
+
+	return 0;
+}
+
+static const struct v4l2_subdev_core_ops max96714_subdev_core_ops = {
+	.log_status = max96714_log_status,
+};
+
+static const struct v4l2_subdev_video_ops max96714_video_ops = {
+	.s_stream	= max96714_s_stream,
+};
+
+static const struct v4l2_subdev_internal_ops max96714_internal_ops = {
+	.init_state = max96714_init_state,
+};
+
+static const struct v4l2_subdev_ops max96714_subdev_ops = {
+	.video = &max96714_video_ops,
+	.core = &max96714_subdev_core_ops,
+	.pad = &max96714_pad_ops,
+};
+
+static const struct media_entity_operations max96714_entity_ops = {
+	.link_validate = v4l2_subdev_link_validate,
+};
+
+static int max96714_notify_bound(struct v4l2_async_notifier *notifier,
+				 struct v4l2_subdev *subdev,
+				 struct v4l2_async_connection *asd)
+{
+	struct max96714_priv *priv = sd_to_max96714(notifier->sd);
+	struct device *dev = &priv->client->dev;
+	int ret;
+
+	ret = media_entity_get_fwnode_pad(&subdev->entity,
+					  priv->rxport.source.ep_fwnode,
+					  MEDIA_PAD_FL_SOURCE);
+	if (ret < 0) {
+		dev_err(dev, "Failed to find pad for %s\n", subdev->name);
+		return ret;
+	}
+
+	priv->rxport.source.sd = subdev;
+	priv->rxport.source.pad = ret;
+
+	ret = media_create_pad_link(&priv->rxport.source.sd->entity,
+				    priv->rxport.source.pad, &priv->sd.entity,
+				    MAX96714_PAD_SINK,
+				    MEDIA_LNK_FL_ENABLED |
+				    MEDIA_LNK_FL_IMMUTABLE);
+	if (ret) {
+		dev_err(dev, "Unable to link %s:%u -> %s:%u\n",
+			priv->rxport.source.sd->name, priv->rxport.source.pad,
+			priv->sd.name, MAX96714_PAD_SINK);
+		return ret;
+	}
+
+	return 0;
+}
+
+static const struct v4l2_async_notifier_operations max96714_notify_ops = {
+	.bound = max96714_notify_bound,
+};
+
+static int max96714_v4l2_notifier_register(struct max96714_priv *priv)
+{
+	struct device *dev = &priv->client->dev;
+	struct max96714_rxport *rxport = &priv->rxport;
+	struct v4l2_async_connection *asd;
+	int ret;
+
+	if (!rxport->source.ep_fwnode)
+		return 0;
+
+	v4l2_async_subdev_nf_init(&priv->notifier, &priv->sd);
+
+	asd = v4l2_async_nf_add_fwnode(&priv->notifier,
+				       rxport->source.ep_fwnode,
+				       struct v4l2_async_connection);
+	if (IS_ERR(asd)) {
+		dev_err(dev, "Failed to add subdev: %pe", asd);
+		v4l2_async_nf_cleanup(&priv->notifier);
+		return PTR_ERR(asd);
+	}
+
+	priv->notifier.ops = &max96714_notify_ops;
+
+	ret = v4l2_async_nf_register(&priv->notifier);
+	if (ret) {
+		dev_err(dev, "Failed to register subdev_notifier");
+		v4l2_async_nf_cleanup(&priv->notifier);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void max96714_v4l2_notifier_unregister(struct max96714_priv *priv)
+{
+	v4l2_async_nf_unregister(&priv->notifier);
+	v4l2_async_nf_cleanup(&priv->notifier);
+}
+
+static int max96714_create_subdev(struct max96714_priv *priv)
+{
+	struct device *dev = &priv->client->dev;
+	int ret;
+
+	v4l2_i2c_subdev_init(&priv->sd, priv->client, &max96714_subdev_ops);
+	priv->sd.internal_ops = &max96714_internal_ops;
+
+	v4l2_ctrl_handler_init(&priv->ctrl_handler, 1);
+	priv->sd.ctrl_handler = &priv->ctrl_handler;
+
+	priv->tx_link_freq = priv->vep.link_frequencies[0];
+	if (priv->tx_link_freq < 0)
+		return priv->tx_link_freq;
+
+	v4l2_ctrl_new_int_menu(&priv->ctrl_handler, NULL, V4L2_CID_LINK_FREQ,
+			       0, 0, &priv->tx_link_freq);
+	if (priv->ctrl_handler.error) {
+		ret = priv->ctrl_handler.error;
+		goto err_free_ctrl;
+	}
+
+	v4l2_ctrl_new_std_menu_items(&priv->ctrl_handler,
+				     &max96714_ctrl_ops,
+				     V4L2_CID_TEST_PATTERN,
+				     ARRAY_SIZE(max96714_test_pattern) - 1,
+				     0, 0, max96714_test_pattern);
+	if (priv->ctrl_handler.error) {
+		ret = priv->ctrl_handler.error;
+		goto err_free_ctrl;
+	}
+
+	priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS;
+	priv->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
+	priv->sd.entity.ops = &max96714_entity_ops;
+
+	priv->pads[MAX96714_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+	priv->pads[MAX96714_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
+
+	ret = media_entity_pads_init(&priv->sd.entity,
+				     MAX96714_NPORTS,
+				     priv->pads);
+	if (ret)
+		goto err_free_ctrl;
+
+	priv->sd.state_lock = priv->sd.ctrl_handler->lock;
+
+	ret = v4l2_subdev_init_finalize(&priv->sd);
+	if (ret)
+		goto err_entity_cleanup;
+
+	ret = max96714_v4l2_notifier_register(priv);
+	if (ret) {
+		dev_err(dev, "v4l2 subdev notifier register failed: %d\n", ret);
+		goto err_subdev_cleanup;
+	}
+
+	ret = v4l2_async_register_subdev(&priv->sd);
+	if (ret) {
+		dev_err(dev, "v4l2_async_register_subdev error: %d\n", ret);
+		goto err_unreg_notif;
+	}
+
+	return 0;
+
+err_unreg_notif:
+	max96714_v4l2_notifier_unregister(priv);
+err_subdev_cleanup:
+	v4l2_subdev_cleanup(&priv->sd);
+err_entity_cleanup:
+	media_entity_cleanup(&priv->sd.entity);
+err_free_ctrl:
+	v4l2_ctrl_handler_free(&priv->ctrl_handler);
+
+	return ret;
+};
+
+static void max96714_destroy_subdev(struct max96714_priv *priv)
+{
+	max96714_v4l2_notifier_unregister(priv);
+	v4l2_async_unregister_subdev(&priv->sd);
+
+	v4l2_subdev_cleanup(&priv->sd);
+
+	media_entity_cleanup(&priv->sd.entity);
+	v4l2_ctrl_handler_free(&priv->ctrl_handler);
+}
+
+static int max96714_i2c_mux_select(struct i2c_mux_core *mux, u32 chan)
+{
+	return 0;
+}
+
+static int max96714_i2c_mux_init(struct max96714_priv *priv)
+{
+	priv->mux = i2c_mux_alloc(priv->client->adapter, &priv->client->dev,
+				  1, 0, I2C_MUX_LOCKED | I2C_MUX_GATE,
+				  max96714_i2c_mux_select, NULL);
+	if (!priv->mux)
+		return -ENOMEM;
+
+	return i2c_mux_add_adapter(priv->mux, 0, 0, 0);
+}
+
+static int max96714_init_tx_port(struct max96714_priv *priv)
+{
+	struct v4l2_mbus_config_mipi_csi2 *mipi;
+	unsigned long lanes_used = 0;
+	u8 val, lane;
+	int ret;
+
+	ret = max96714_disable_tx_port(priv);
+
+	mipi = &priv->vep.bus.mipi_csi2;
+	val = *priv->vep.link_frequencies * 2 / MHZ(100);
+
+	cci_update_bits(priv->regmap, MAX96714_BACKTOP25,
+			CSI_DPLL_FREQ_MASK, val, &ret);
+
+	val = FIELD_PREP(MAX96714_CSI2_LANE_CNT_MASK, mipi->num_data_lanes - 1);
+	cci_update_bits(priv->regmap, MAX96714_MIPI_LANE_CNT,
+			MAX96714_CSI2_LANE_CNT_MASK, val, &ret);
+
+	/* lanes polarity */
+	val = 0;
+	for (lane = 0; lane < mipi->num_data_lanes + 1; lane++) {
+		if (!mipi->lane_polarities[lane])
+			continue;
+		if (lane == 0)
+			/* clock lane */
+			val |= BIT(5);
+		else if (lane < 3)
+			/* Lane D0 and D1 */
+			val |= BIT(lane - 1);
+		else
+			/* D2 and D3 */
+			val |= BIT(lane);
+	}
+
+	cci_update_bits(priv->regmap, MAX96714_MIPI_POLARITY,
+			MAX96714_MIPI_POLARITY_MASK, val, &ret);
+
+	/* lanes mapping */
+	val = 0;
+	for (lane = 0; lane < mipi->num_data_lanes; lane++) {
+		val |= (mipi->data_lanes[lane] - 1) << (lane * 2);
+		lanes_used |= BIT(mipi->data_lanes[lane] - 1);
+	}
+
+	/* Unused lanes need to be mapped as well to not have
+	 * the same lanes mapped twice.
+	 */
+	for (; lane < 4; lane++) {
+		unsigned int idx = find_first_zero_bit(&lanes_used, 4);
+
+		val |= idx << (lane * 2);
+		lanes_used |= BIT(idx);
+	}
+
+	return cci_write(priv->regmap, MAX96714_MIPI_LANE_MAP, val, &ret);
+}
+
+static int max96714_rxport_enable_poc(struct max96714_priv *priv)
+{
+	struct max96714_rxport *rxport = &priv->rxport;
+
+	if (!rxport->poc)
+		return 0;
+
+	return regulator_enable(rxport->poc);
+}
+
+static int max96714_rxport_disable_poc(struct max96714_priv *priv)
+{
+	struct max96714_rxport *rxport = &priv->rxport;
+
+	if (!rxport->poc)
+		return 0;
+
+	return regulator_disable(rxport->poc);
+}
+
+static int max96714_parse_dt_txport(struct max96714_priv *priv)
+{
+	struct device *dev = &priv->client->dev;
+	struct fwnode_handle *ep_fwnode;
+	u32 num_data_lanes;
+	s64 dpll_freq;
+	int ret;
+
+	ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
+						    MAX96714_PAD_SOURCE, 0, 0);
+	if (!ep_fwnode)
+		return -EINVAL;
+
+	priv->vep.bus_type = V4L2_MBUS_UNKNOWN;
+
+	ret = v4l2_fwnode_endpoint_alloc_parse(ep_fwnode, &priv->vep);
+	fwnode_handle_put(ep_fwnode);
+	if (ret) {
+		dev_err(dev, "tx: failed to parse endpoint data\n");
+		return -EINVAL;
+	}
+
+	if (priv->vep.bus_type != V4L2_MBUS_CSI2_DPHY) {
+		dev_err(&priv->client->dev, "Unsupported bus-type %u\n",
+			priv->vep.bus_type);
+		return -EINVAL;
+	}
+
+	if (priv->vep.nr_of_link_frequencies != 1) {
+		ret = -EINVAL;
+		goto err_free_vep;
+	}
+
+	/* DPLL freq is twice the link frequency */
+	dpll_freq = priv->vep.link_frequencies[0] * 2;
+
+	/* 100Mbps step, Min 100Mbps, Max 2500Mbps */
+	if (dpll_freq % MHZ(100) || dpll_freq < MHZ(100) ||
+	    dpll_freq > MHZ(2500)) {
+		dev_err(dev, "tx: invalid link frequency\n");
+		ret = -EINVAL;
+		goto err_free_vep;
+	}
+
+	num_data_lanes = priv->vep.bus.mipi_csi2.num_data_lanes;
+	if (num_data_lanes < 1 || num_data_lanes > 4) {
+		dev_err(dev,
+			"tx: invalid number of data lanes should be 1 to 4\n");
+		ret = -EINVAL;
+		goto err_free_vep;
+	}
+
+	return 0;
+
+err_free_vep:
+	v4l2_fwnode_endpoint_free(&priv->vep);
+
+	return ret;
+};
+
+static int max96714_parse_dt_rxport(struct max96714_priv *priv)
+{
+	static const char *poc_name = "port0-poc";
+	struct max96714_rxport *rxport = &priv->rxport;
+	struct device *dev = &priv->client->dev;
+	struct fwnode_handle *ep_fwnode;
+	int ret;
+
+	ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
+						    MAX96714_PAD_SINK,
+						    0, 0);
+	if (!ep_fwnode)
+		return -ENOENT;
+
+	rxport->source.ep_fwnode = fwnode_graph_get_remote_endpoint(ep_fwnode);
+	fwnode_handle_put(ep_fwnode);
+
+	if (!rxport->source.ep_fwnode) {
+		dev_err(dev, "rx: no remote endpoint\n");
+		return -EINVAL;
+	}
+
+	rxport->poc = devm_regulator_get_optional(dev, poc_name);
+	if (IS_ERR(rxport->poc)) {
+		ret = PTR_ERR(rxport->poc);
+		if (ret == -ENODEV) {
+			rxport->poc = NULL;
+		} else {
+			dev_err(dev, "rx: failed to get POC supply: %d\n", ret);
+			goto err_put_source_ep_fwnode;
+		}
+	}
+
+	return 0;
+
+err_put_source_ep_fwnode:
+	fwnode_handle_put(rxport->source.ep_fwnode);
+	return ret;
+}
+
+static int max96714_parse_dt(struct max96714_priv *priv)
+{
+	int ret;
+
+	ret = max96714_parse_dt_rxport(priv);
+	/* The deserializer can create a test pattern even if the
+	 * rx port is not connected to a serializer.
+	 */
+	if (ret && ret != -ENOENT)
+		return ret;
+
+	ret = max96714_parse_dt_txport(priv);
+	if (ret)
+		goto err_put_fwnode;
+
+	return 0;
+
+err_put_fwnode:
+	fwnode_handle_put(priv->rxport.source.ep_fwnode);
+
+	return ret;
+}
+
+static int max96714_enable_core_hw(struct max96714_priv *priv)
+{
+	struct device *dev = &priv->client->dev;
+	u64 val;
+	int ret;
+
+	if (priv->pd_gpio) {
+		/* wait min 2 ms for reset to complete */
+		gpiod_set_value_cansleep(priv->pd_gpio, 1);
+		fsleep(2000);
+		gpiod_set_value_cansleep(priv->pd_gpio, 0);
+		/* wait min 2 ms for power up to finish */
+		fsleep(2000);
+	}
+
+	ret = cci_read(priv->regmap, MAX96714_REG13, &val, NULL);
+	if (ret) {
+		dev_err_probe(dev, ret, "Cannot read first register, abort\n");
+		goto err_pd_gpio;
+	}
+
+	if (val != MAX96714F_DEVICE_ID) {
+		dev_err(dev, "Unsupported device id expected %x got %x\n",
+			MAX96714F_DEVICE_ID, (u8)val);
+		ret = -EOPNOTSUPP;
+		goto err_pd_gpio;
+	}
+
+	ret = cci_read(priv->regmap, MAX96714_DEV_REV, &val, NULL);
+	if (ret)
+		goto err_pd_gpio;
+
+	dev_dbg(dev, "Found %x (rev %lx)\n", MAX96714F_DEVICE_ID,
+		(u8)val & MAX96714_DEV_REV_MASK);
+
+	ret = cci_read(priv->regmap, MAX96714_MIPI_TX52, &val, NULL);
+	if (ret)
+		goto err_pd_gpio;
+
+	if (!(val & MAX96714_TUN_EN)) {
+		dev_err(dev, "Only supporting tunnel mode");
+		ret = -EOPNOTSUPP;
+		goto err_pd_gpio;
+	}
+
+	return 0;
+
+err_pd_gpio:
+	gpiod_set_value_cansleep(priv->pd_gpio, 1);
+	return ret;
+}
+
+static void max96714_disable_core_hw(struct max96714_priv *priv)
+{
+	gpiod_set_value_cansleep(priv->pd_gpio, 1);
+}
+
+static int max96714_get_hw_resources(struct max96714_priv *priv)
+{
+	struct device *dev = &priv->client->dev;
+
+	priv->regmap = devm_cci_regmap_init_i2c(priv->client, 16);
+	if (IS_ERR(priv->regmap))
+		return PTR_ERR(priv->regmap);
+
+	priv->pd_gpio =
+		devm_gpiod_get_optional(dev, "powerdown", GPIOD_OUT_HIGH);
+	if (IS_ERR(priv->pd_gpio))
+		return dev_err_probe(dev, PTR_ERR(priv->pd_gpio),
+				     "Cannot get powerdown GPIO\n");
+	return 0;
+}
+
+static int max96714_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	struct max96714_priv *priv;
+	int ret;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->client = client;
+	priv->regmap = devm_regmap_init_i2c(priv->client,
+					    &max96714_regmap_config);
+
+	ret = max96714_get_hw_resources(priv);
+	if (ret)
+		return ret;
+
+	ret = max96714_enable_core_hw(priv);
+	if (ret)
+		return ret;
+
+	ret = max96714_parse_dt(priv);
+	if (ret)
+		goto err_disable_core_hw;
+
+	max96714_init_tx_port(priv);
+
+	ret = max96714_rxport_enable_poc(priv);
+	if (ret)
+		goto err_free_ports;
+
+	ret = max96714_i2c_mux_init(priv);
+	if (ret)
+		goto err_disable_poc;
+
+	ret = max96714_create_subdev(priv);
+	if (ret)
+		goto err_del_mux;
+
+	return 0;
+
+err_del_mux:
+	i2c_mux_del_adapters(priv->mux);
+err_disable_poc:
+	max96714_rxport_disable_poc(priv);
+err_free_ports:
+	fwnode_handle_put(priv->rxport.source.ep_fwnode);
+	v4l2_fwnode_endpoint_free(&priv->vep);
+err_disable_core_hw:
+	max96714_disable_core_hw(priv);
+
+	return ret;
+}
+
+static void max96714_remove(struct i2c_client *client)
+{
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct max96714_priv *priv = sd_to_max96714(sd);
+
+	max96714_destroy_subdev(priv);
+	i2c_mux_del_adapters(priv->mux);
+	max96714_rxport_disable_poc(priv);
+	fwnode_handle_put(priv->rxport.source.ep_fwnode);
+	v4l2_fwnode_endpoint_free(&priv->vep);
+	max96714_disable_core_hw(priv);
+	gpiod_set_value_cansleep(priv->pd_gpio, 1);
+}
+
+static const struct of_device_id max96714_of_ids[] = {
+	{ .compatible = "maxim,max96714f" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, max96714_of_ids);
+
+static struct i2c_driver max96714_i2c_driver = {
+	.driver	= {
+		.name		= "max96714",
+		.of_match_table	= max96714_of_ids,
+	},
+	.probe		= max96714_probe,
+	.remove		= max96714_remove,
+};
+
+module_i2c_driver(max96714_i2c_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Maxim Integrated GMSL2 Deserializers Driver");
+MODULE_AUTHOR("Julien Massot <julien.massot@collabora.com>");
-- 
2.43.0


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

* Re: [PATCH v3 0/4] Add support for MAX96714F and MAX96717F GMSL2 ser/des
  2024-01-11 13:03 [PATCH v3 0/4] Add support for MAX96714F and MAX96717F GMSL2 ser/des Julien Massot
                   ` (3 preceding siblings ...)
  2024-01-11 13:03 ` [PATCH v3 4/4] media: i2c: add MAX96714 driver Julien Massot
@ 2024-01-12  8:10 ` Krzysztof Kozlowski
  4 siblings, 0 replies; 18+ messages in thread
From: Krzysztof Kozlowski @ 2024-01-12  8:10 UTC (permalink / raw)
  To: Julien Massot, linux-media
  Cc: kernel, devicetree, linux-kernel, mchehab, robh+dt,
	krzysztof.kozlowski+dt, conor+dt

On 11/01/2024 14:03, Julien Massot wrote:
> Change since v2:
> - Convert drivers to use CCI helpers
> - Use generic node name
> - Use 'powerdown' as gpio name instead of 'enable'
> - Add pattern generator support for MAX96714

It's incomplete changelog or I miss something. b4 diff shows me huge
amount of changes, including in examples, many properties, copyrights
and so on. It does not make it easier for review.

Best regards,
Krzysztof


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

* Re: [PATCH v3 1/4] dt-bindings: media: add Maxim MAX96717F GMSL2 Serializer
  2024-01-11 13:03 ` [PATCH v3 1/4] dt-bindings: media: add Maxim MAX96717F GMSL2 Serializer Julien Massot
@ 2024-01-12  8:13   ` Krzysztof Kozlowski
  2024-02-09  9:28   ` Sakari Ailus
  1 sibling, 0 replies; 18+ messages in thread
From: Krzysztof Kozlowski @ 2024-01-12  8:13 UTC (permalink / raw)
  To: Julien Massot, linux-media
  Cc: kernel, devicetree, linux-kernel, mchehab, robh+dt,
	krzysztof.kozlowski+dt, conor+dt

On 11/01/2024 14:03, Julien Massot wrote:
> Add DT bindings for Maxim MAX96717F GMSL2 Serializer.
> 
> Signed-off-by: Julien Massot <julien.massot@collabora.com>
> ---
> Change since v2:
>  - remove reg description
>  - add data lanes min/maxItems
>  - Use generic node name 
> 


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

Best regards,
Krzysztof


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

* Re: [PATCH v3 2/4] dt-bindings: media: add Maxim MAX96714F GMSL2 Deserializer
  2024-01-11 13:03 ` [PATCH v3 2/4] dt-bindings: media: add Maxim MAX96714F GMSL2 Deserializer Julien Massot
@ 2024-01-12  8:15   ` Krzysztof Kozlowski
  2024-02-09  9:48   ` Sakari Ailus
  1 sibling, 0 replies; 18+ messages in thread
From: Krzysztof Kozlowski @ 2024-01-12  8:15 UTC (permalink / raw)
  To: Julien Massot, linux-media
  Cc: kernel, devicetree, linux-kernel, mchehab, robh+dt,
	krzysztof.kozlowski+dt, conor+dt

On 11/01/2024 14:03, Julien Massot wrote:
> Add DT bindings for Maxim MAX96714F GMSL2 Deserializer.
> 
> Signed-off-by: Julien Massot <julien.massot@collabora.com>
> 

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

Best regards,
Krzysztof


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

* Re: [PATCH v3 4/4] media: i2c: add MAX96714 driver
  2024-01-11 13:03 ` [PATCH v3 4/4] media: i2c: add MAX96714 driver Julien Massot
@ 2024-01-13 11:31   ` kernel test robot
  2024-02-09 15:49   ` Sakari Ailus
  1 sibling, 0 replies; 18+ messages in thread
From: kernel test robot @ 2024-01-13 11:31 UTC (permalink / raw)
  To: Julien Massot, linux-media
  Cc: oe-kbuild-all, kernel, devicetree, linux-kernel, mchehab, robh+dt,
	krzysztof.kozlowski+dt, conor+dt, Julien Massot

Hi Julien,

kernel test robot noticed the following build errors:

[auto build test ERROR on media-tree/master]
[also build test ERROR on linuxtv-media-stage/master linus/master next-20240112]
[cannot apply to sailus-media-tree/streams]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Julien-Massot/dt-bindings-media-add-Maxim-MAX96717F-GMSL2-Serializer/20240111-210740
base:   git://linuxtv.org/media_tree.git master
patch link:    https://lore.kernel.org/r/20240111130349.2776699-5-julien.massot%40collabora.com
patch subject: [PATCH v3 4/4] media: i2c: add MAX96714 driver
config: i386-randconfig-r071-20240112 (https://download.01.org/0day-ci/archive/20240113/202401131916.MpdR8A2O-lkp@intel.com/config)
compiler: gcc-12 (Debian 12.2.0-14) 12.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20240113/202401131916.MpdR8A2O-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202401131916.MpdR8A2O-lkp@intel.com/

All errors (new ones prefixed by >>):

   ld: drivers/media/i2c/max96714.o: in function `max96714_parse_dt_txport':
>> drivers/media/i2c/max96714.c:827: undefined reference to `__moddi3'
   ld: drivers/media/i2c/max96714.o: in function `max96714_init_tx_port':
>> drivers/media/i2c/max96714.c:725: undefined reference to `__udivdi3'


vim +827 drivers/media/i2c/max96714.c

   714	
   715	static int max96714_init_tx_port(struct max96714_priv *priv)
   716	{
   717		struct v4l2_mbus_config_mipi_csi2 *mipi;
   718		unsigned long lanes_used = 0;
   719		u8 val, lane;
   720		int ret;
   721	
   722		ret = max96714_disable_tx_port(priv);
   723	
   724		mipi = &priv->vep.bus.mipi_csi2;
 > 725		val = *priv->vep.link_frequencies * 2 / MHZ(100);
   726	
   727		cci_update_bits(priv->regmap, MAX96714_BACKTOP25,
   728				CSI_DPLL_FREQ_MASK, val, &ret);
   729	
   730		val = FIELD_PREP(MAX96714_CSI2_LANE_CNT_MASK, mipi->num_data_lanes - 1);
   731		cci_update_bits(priv->regmap, MAX96714_MIPI_LANE_CNT,
   732				MAX96714_CSI2_LANE_CNT_MASK, val, &ret);
   733	
   734		/* lanes polarity */
   735		val = 0;
   736		for (lane = 0; lane < mipi->num_data_lanes + 1; lane++) {
   737			if (!mipi->lane_polarities[lane])
   738				continue;
   739			if (lane == 0)
   740				/* clock lane */
   741				val |= BIT(5);
   742			else if (lane < 3)
   743				/* Lane D0 and D1 */
   744				val |= BIT(lane - 1);
   745			else
   746				/* D2 and D3 */
   747				val |= BIT(lane);
   748		}
   749	
   750		cci_update_bits(priv->regmap, MAX96714_MIPI_POLARITY,
   751				MAX96714_MIPI_POLARITY_MASK, val, &ret);
   752	
   753		/* lanes mapping */
   754		val = 0;
   755		for (lane = 0; lane < mipi->num_data_lanes; lane++) {
   756			val |= (mipi->data_lanes[lane] - 1) << (lane * 2);
   757			lanes_used |= BIT(mipi->data_lanes[lane] - 1);
   758		}
   759	
   760		/* Unused lanes need to be mapped as well to not have
   761		 * the same lanes mapped twice.
   762		 */
   763		for (; lane < 4; lane++) {
   764			unsigned int idx = find_first_zero_bit(&lanes_used, 4);
   765	
   766			val |= idx << (lane * 2);
   767			lanes_used |= BIT(idx);
   768		}
   769	
   770		return cci_write(priv->regmap, MAX96714_MIPI_LANE_MAP, val, &ret);
   771	}
   772	
   773	static int max96714_rxport_enable_poc(struct max96714_priv *priv)
   774	{
   775		struct max96714_rxport *rxport = &priv->rxport;
   776	
   777		if (!rxport->poc)
   778			return 0;
   779	
   780		return regulator_enable(rxport->poc);
   781	}
   782	
   783	static int max96714_rxport_disable_poc(struct max96714_priv *priv)
   784	{
   785		struct max96714_rxport *rxport = &priv->rxport;
   786	
   787		if (!rxport->poc)
   788			return 0;
   789	
   790		return regulator_disable(rxport->poc);
   791	}
   792	
   793	static int max96714_parse_dt_txport(struct max96714_priv *priv)
   794	{
   795		struct device *dev = &priv->client->dev;
   796		struct fwnode_handle *ep_fwnode;
   797		u32 num_data_lanes;
   798		s64 dpll_freq;
   799		int ret;
   800	
   801		ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
   802							    MAX96714_PAD_SOURCE, 0, 0);
   803		if (!ep_fwnode)
   804			return -EINVAL;
   805	
   806		priv->vep.bus_type = V4L2_MBUS_UNKNOWN;
   807	
   808		ret = v4l2_fwnode_endpoint_alloc_parse(ep_fwnode, &priv->vep);
   809		fwnode_handle_put(ep_fwnode);
   810		if (ret) {
   811			dev_err(dev, "tx: failed to parse endpoint data\n");
   812			return -EINVAL;
   813		}
   814	
   815		if (priv->vep.bus_type != V4L2_MBUS_CSI2_DPHY) {
   816			dev_err(&priv->client->dev, "Unsupported bus-type %u\n",
   817				priv->vep.bus_type);
   818			return -EINVAL;
   819		}
   820	
   821		if (priv->vep.nr_of_link_frequencies != 1) {
   822			ret = -EINVAL;
   823			goto err_free_vep;
   824		}
   825	
   826		/* DPLL freq is twice the link frequency */
 > 827		dpll_freq = priv->vep.link_frequencies[0] * 2;
   828	
   829		/* 100Mbps step, Min 100Mbps, Max 2500Mbps */
   830		if (dpll_freq % MHZ(100) || dpll_freq < MHZ(100) ||
   831		    dpll_freq > MHZ(2500)) {
   832			dev_err(dev, "tx: invalid link frequency\n");
   833			ret = -EINVAL;
   834			goto err_free_vep;
   835		}
   836	
   837		num_data_lanes = priv->vep.bus.mipi_csi2.num_data_lanes;
   838		if (num_data_lanes < 1 || num_data_lanes > 4) {
   839			dev_err(dev,
   840				"tx: invalid number of data lanes should be 1 to 4\n");
   841			ret = -EINVAL;
   842			goto err_free_vep;
   843		}
   844	
   845		return 0;
   846	
   847	err_free_vep:
   848		v4l2_fwnode_endpoint_free(&priv->vep);
   849	
   850		return ret;
   851	};
   852	

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

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

* Re: [PATCH v3 1/4] dt-bindings: media: add Maxim MAX96717F GMSL2 Serializer
  2024-01-11 13:03 ` [PATCH v3 1/4] dt-bindings: media: add Maxim MAX96717F GMSL2 Serializer Julien Massot
  2024-01-12  8:13   ` Krzysztof Kozlowski
@ 2024-02-09  9:28   ` Sakari Ailus
  2024-02-21 10:12     ` Julien Massot
  1 sibling, 1 reply; 18+ messages in thread
From: Sakari Ailus @ 2024-02-09  9:28 UTC (permalink / raw)
  To: Julien Massot
  Cc: linux-media, kernel, devicetree, linux-kernel, mchehab, robh+dt,
	krzysztof.kozlowski+dt, conor+dt

Hi Julien,

On Thu, Jan 11, 2024 at 02:03:46PM +0100, Julien Massot wrote:
> Add DT bindings for Maxim MAX96717F GMSL2 Serializer.
> 
> Signed-off-by: Julien Massot <julien.massot@collabora.com>
> ---
> Change since v2:
>  - remove reg description
>  - add data lanes min/maxItems
>  - Use generic node name 
> 
> ---
>  .../bindings/media/i2c/maxim,max96717f.yaml   | 147 ++++++++++++++++++
>  1 file changed, 147 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/media/i2c/maxim,max96717f.yaml
> 
> diff --git a/Documentation/devicetree/bindings/media/i2c/maxim,max96717f.yaml b/Documentation/devicetree/bindings/media/i2c/maxim,max96717f.yaml
> new file mode 100644
> index 000000000000..f31517b1dbc8
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/media/i2c/maxim,max96717f.yaml
> @@ -0,0 +1,147 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +# Copyright (C) 2024 Collabora Ltd.
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/media/i2c/maxim,max96717f.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: MAX96717 CSI-2 to GMSL2 Serializer
> +
> +maintainers:
> +  - Julien Massot <julien.massot@collabora.com>
> +
> +description: |
> +  The MAX96717F serializer converts MIPI CSI-2 D-PHY or C-PHY formatted input

Presumably this will need to be configured on the device? You should thus
require the bus-type property in the endpoint.

> +  into GMSL2 serial outputs. The device allows the GMSL2 link to
> +  simultaneously transmit bidirectional control-channel data while forward
> +  video transmissions are in progress. The MAX96717F can connect to one
> +  remotely located deserializer using industry-standard coax or STP
> +  interconnects. The device cans operate in pixel or tunnel mode. In pixel mode
> +  the MAX96717F can select the MIPI datatype, while the tunnel mode forward all the MIPI
> +  data received by the serializer.
> +  The MAX96717F supports Reference Over Reverse (channel),
> +  to generate a clock output for the sensor from the GMSL reverse channel.
> +
> +  The GMSL2 serial link operates at a fixed rate of 3Gbps in the
> +  forward direction and 187.5Mbps in the reverse direction.
> +
> +properties:
> +  compatible:
> +    const: maxim,max96717f
> +
> +  '#gpio-cells':
> +    const: 2
> +    description:
> +      First cell is the GPIO pin number, second cell is the flags. The GPIO pin
> +      number must be in range of [0, 10].
> +
> +  gpio-controller: true
> +
> +  '#clock-cells':
> +    const: 0
> +
> +  reg:
> +    maxItems: 1
> +
> +  ports:
> +    $ref: /schemas/graph.yaml#/properties/ports
> +
> +    properties:
> +      port@0:
> +        $ref: /schemas/graph.yaml#/$defs/port-base
> +        unevaluatedProperties: false
> +        description: CSI-2 Input port
> +
> +        properties:
> +          endpoint:
> +            $ref: /schemas/media/video-interfaces.yaml#
> +            unevaluatedProperties: false
> +
> +            properties:
> +              data-lanes:
> +                minItems: 1
> +                maxItems: 4
> +
> +            required:
> +              - data-lanes
> +
> +      port@1:
> +        $ref: /schemas/graph.yaml#/properties/port
> +        unevaluatedProperties: false
> +        description: GMSL Output port
> +
> +    required:
> +      - port@1
> +
> +  i2c-gate:
> +    $ref: /schemas/i2c/i2c-controller.yaml
> +    unevaluatedProperties: false
> +    description: |

I think you can remove ' |'.

> +      The MAX96717F will forward the I2C requests from the
> +      incoming GMSL2 link. Therefore, it supports an i2c-gate
> +      subnode to configure a sensor.
> +
> +required:
> +  - compatible
> +  - reg
> +  - ports
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    #include <dt-bindings/gpio/gpio.h>
> +    #include <dt-bindings/media/video-interfaces.h>
> +
> +    i2c {
> +        #address-cells = <1>;
> +        #size-cells = <0>;
> +        serializer: serializer@40 {
> +            compatible = "maxim,max96717f";
> +            reg = <0x40>;
> +            gpio-controller;
> +            #gpio-cells = <2>;
> +            #clock-cells = <0>;
> +
> +            ports {
> +                #address-cells = <1>;
> +                #size-cells = <0>;
> +
> +                port@0 {
> +                    reg = <0>;
> +                    max96717f_csi_in: endpoint {
> +                        data-lanes = <1 2 3 4>;
> +                        remote-endpoint = <&sensor_out>;
> +                    };
> +                };
> +
> +                port@1 {
> +                    reg = <1>;
> +                    max96917f_gmsl_out: endpoint {
> +                        remote-endpoint = <&deser_gmsl_in>;
> +                    };
> +                };
> +            };
> +
> +            i2c-gate {
> +                #address-cells = <1>;
> +                #size-cells = <0>;
> +                sensor@10 {
> +                    compatible = "st,st-vgxy61";
> +                    reg = <0x10>;
> +                    reset-gpios = <&serializer 0 GPIO_ACTIVE_LOW>;
> +                    clocks = <&serializer>;
> +                    VCORE-supply = <&v1v2>;
> +                    VDDIO-supply = <&v1v8>;
> +                    VANA-supply = <&v2v8>;
> +                    port {
> +                        sensor_out: endpoint {
> +                            data-lanes = <1 2 3 4>;
> +                            remote-endpoint = <&max96717f_csi_in>;
> +                        };
> +                    };
> +                };
> +            };
> +        };
> +    };
> +...

-- 
Kind regards,

Sakari Ailus

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

* Re: [PATCH v3 2/4] dt-bindings: media: add Maxim MAX96714F GMSL2 Deserializer
  2024-01-11 13:03 ` [PATCH v3 2/4] dt-bindings: media: add Maxim MAX96714F GMSL2 Deserializer Julien Massot
  2024-01-12  8:15   ` Krzysztof Kozlowski
@ 2024-02-09  9:48   ` Sakari Ailus
  2024-02-21 10:10     ` Julien Massot
  1 sibling, 1 reply; 18+ messages in thread
From: Sakari Ailus @ 2024-02-09  9:48 UTC (permalink / raw)
  To: Julien Massot
  Cc: linux-media, kernel, devicetree, linux-kernel, mchehab, robh+dt,
	krzysztof.kozlowski+dt, conor+dt

Hi Julien,

On Thu, Jan 11, 2024 at 02:03:47PM +0100, Julien Massot wrote:
> Add DT bindings for Maxim MAX96714F GMSL2 Deserializer.
> 
> Signed-off-by: Julien Massot <julien.massot@collabora.com>
> 
> ---
> Change since v2:
>  - remove reg description
>  - rename enable gpio to powerdown
>  - use generic node name: i2c, serializer, deserializer
> ---
>  .../bindings/media/i2c/maxim,max96714f.yaml   | 171 ++++++++++++++++++
>  1 file changed, 171 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/media/i2c/maxim,max96714f.yaml
> 
> diff --git a/Documentation/devicetree/bindings/media/i2c/maxim,max96714f.yaml b/Documentation/devicetree/bindings/media/i2c/maxim,max96714f.yaml
> new file mode 100644
> index 000000000000..2423d166c954
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/media/i2c/maxim,max96714f.yaml
> @@ -0,0 +1,171 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +# Copyright (C) 2024 Collabora Ltd.
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/media/i2c/maxim,max96714f.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Maxim MAX96714 GMSL2 to CSI-2 Deserializer
> +
> +maintainers:
> +  - Julien Massot <julien.massot@collabora.com>
> +
> +description: |
> +  The MAX96714F deserializer converts GMSL2 serial inputs into MIPI
> +  CSI-2 D-PHY or C-PHY formatted output. The device allows the GMSL2 link to
> +  simultaneously transmit bidirectional control-channel data while forward
> +  video transmissions are in progress. The MAX96714F can connect to one
> +  remotely located serializer using industry-standard coax or STP
> +  interconnects. The device cans operate in pixel or tunnel mode. In pixel mode
> +  the MAX96714F can select individual video stream, while the tunnel mode forward all
> +  the MIPI data received by the serializer.
> +
> +  The GMSL2 serial link operates at a fixed rate of 3Gbps in the
> +  forward direction and 187.5Mbps in the reverse direction.
> +
> +properties:
> +  compatible:
> +    const: maxim,max96714f
> +
> +  reg:
> +    maxItems: 1
> +
> +  powerdown-gpios:
> +    maxItems: 1
> +    description:
> +      Specifier for the GPIO connected to the PWDNB pin.
> +
> +  ports:
> +    $ref: /schemas/graph.yaml#/properties/ports
> +
> +    properties:
> +      port@0:
> +        $ref: /schemas/graph.yaml#/properties/port
> +        unevaluatedProperties: false
> +        description: GMSL Input
> +        properties:
> +          endpoint:
> +            $ref: /schemas/media/video-interfaces.yaml#
> +            unevaluatedProperties: false
> +            description:
> +              Endpoint for GMSL2-Link port.
> +
> +      port@1:
> +        $ref: /schemas/graph.yaml#/$defs/port-base
> +        unevaluatedProperties: false
> +        description: CSI-2 Output port
> +
> +        properties:
> +          endpoint:
> +            $ref: /schemas/media/video-interfaces.yaml#
> +            unevaluatedProperties: false
> +
> +            properties:
> +              data-lanes:
> +                minItems: 1
> +                maxItems: 4
> +
> +              link-frequencies:
> +                maxItems: 1
> +
> +              bus-type:
> +                enum:
> +                  - 4 # MEDIA_BUS_TYPE_CSI2_DPHY

MEDIA_BUS_TYPE_CSI2_CPHY (1) is missing. Even if the driver doesn't support
it, it would be best to list it here.

> +
> +            required:
> +              - data-lanes
> +              - bus-type
> +
> +    required:
> +      - port@1

Why port@1 but not port@0?

> +
> +  i2c-gate:
> +    $ref: /schemas/i2c/i2c-controller.yaml
> +    unevaluatedProperties: false
> +    description: |
> +      The MAX96714 will pass through and forward the I2C requests from the
> +      incoming I2C bus over the GMSL2 link. Therefore it supports an i2c-gate
> +      subnode to configure a serializer.
> +
> +  port0-poc-supply:
> +    description: Regulator providing Power over Coax for the GMSL port
> +
> +required:
> +  - compatible
> +  - reg
> +  - ports
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    #include <dt-bindings/gpio/gpio.h>
> +    #include <dt-bindings/media/video-interfaces.h>
> +
> +    i2c {
> +        #address-cells = <1>;
> +        #size-cells = <0>;
> +
> +        deserializer@28 {
> +            compatible = "maxim,max96714f";
> +            reg = <0x28>;
> +            powerdown-gpios = <&main_gpio0 37 GPIO_ACTIVE_LOW>;
> +
> +            ports {
> +                #address-cells = <1>;
> +                #size-cells = <0>;
> +                port@0 {
> +                    reg = <0>;
> +                    max96714_gmsl_in: endpoint {
> +                        remote-endpoint = <&max96917f_gmsl_out>;
> +                    };
> +                };
> +
> +                port@1 {
> +                    reg = <1>;
> +                    max96714_csi_out: endpoint {
> +                        bus-type = <MEDIA_BUS_TYPE_CSI2_DPHY>;
> +                        clock-lanes = <0>;

clock-lanes isn't listed in bindings. You can drop it from here.

> +                        data-lanes = <1 2 3 4>;
> +                        link-frequencies = /bits/ 64 <400000000>;
> +                        remote-endpoint = <&csi_in>;
> +                    };
> +                };
> +            };
> +
> +            i2c-gate {
> +                #address-cells = <1>;
> +                #size-cells = <0>;
> +
> +                serializer@40 {
> +                    compatible = "maxim,max96717f";
> +                    reg = <0x40>;
> +                    gpio-controller;
> +                    #gpio-cells = <2>;
> +                    #clock-cells = <0>;
> +
> +                    ports {
> +                        #address-cells = <1>;
> +                        #size-cells = <0>;
> +
> +                        port@0 {
> +                            reg = <0>;
> +                            max96717f_csi_in: endpoint {
> +                                data-lanes = <1 2>;
> +                                lane-polarities = <1 0 1>;

Shouldn't lane-polarities be mentioned in bindings, too?

> +                                remote-endpoint = <&sensor_out>;
> +                            };
> +                        };
> +
> +                        port@1 {
> +                            reg = <1>;
> +                            max96917f_gmsl_out: endpoint {
> +                                remote-endpoint = <&max96714_gmsl_in>;
> +                            };
> +                        };
> +                    };
> +                };
> +            };
> +        };
> +    };
> +...

-- 
Regards,

Sakari Ailus

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

* Re: [PATCH v3 3/4] media: i2c: add MAX96717 driver
  2024-01-11 13:03 ` [PATCH v3 3/4] media: i2c: add MAX96717 driver Julien Massot
@ 2024-02-09 15:26   ` Sakari Ailus
  2024-02-21 10:34     ` Julien Massot
  0 siblings, 1 reply; 18+ messages in thread
From: Sakari Ailus @ 2024-02-09 15:26 UTC (permalink / raw)
  To: Julien Massot
  Cc: linux-media, kernel, devicetree, linux-kernel, mchehab, robh+dt,
	krzysztof.kozlowski+dt, conor+dt

Hi Julien,

On Thu, Jan 11, 2024 at 02:03:48PM +0100, Julien Massot wrote:
> This driver handle the MAX96717 serializer in tunnel mode.
> All incoming CSI traffic will be tunneled through the GMSL2
> link.
> 
> Signed-off-by: Julien Massot <julien.massot@collabora.com>
> ---
> Change since v2:
>  - Use CCI helpers instead of recoding register access
>  - add missing bitfield header
> ---
>  MAINTAINERS                  |   7 +
>  drivers/media/i2c/Kconfig    |  13 +
>  drivers/media/i2c/Makefile   |   1 +
>  drivers/media/i2c/max96717.c | 956 +++++++++++++++++++++++++++++++++++
>  4 files changed, 977 insertions(+)
>  create mode 100644 drivers/media/i2c/max96717.c
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 675e5d63a25b..a64a7932fe76 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -13039,6 +13039,13 @@ S:	Maintained
>  F:	Documentation/devicetree/bindings/media/i2c/maxim,max96712.yaml
>  F:	drivers/staging/media/max96712/max96712.c
>  
> +MAX96717 GMSL2 SERIALIZER DRIVER
> +M:	Julien Massot <julien.massot@collabora.com>
> +L:	linux-media@vger.kernel.org
> +S:	Maintained
> +F:	Documentation/devicetree/bindings/media/i2c/maxim,max96717f.yaml

What's that "f" for? It's in bindings but not in the name of the driver.
Not a typo I suppose? :-)

> +F:	drivers/media/i2c/max96717.c
> +
>  MAX9860 MONO AUDIO VOICE CODEC DRIVER
>  M:	Peter Rosin <peda@axentia.se>
>  L:	alsa-devel@alsa-project.org (moderated for non-subscribers)
> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
> index 78a87331686e..64f6a35f4481 100644
> --- a/drivers/media/i2c/Kconfig
> +++ b/drivers/media/i2c/Kconfig
> @@ -1570,6 +1570,19 @@ config VIDEO_DS90UB960
>  	  Device driver for the Texas Instruments DS90UB960
>  	  FPD-Link III Deserializer and DS90UB9702 FPD-Link IV Deserializer.
>  
> +config VIDEO_MAX96717
> +	tristate "Maxim MAX96717 GMSL2 Serializer support"
> +	depends on OF && I2C && VIDEO_DEV && COMMON_CLK
> +	select I2C_MUX
> +	select GPIOLIB
> +	select V4L2_CCI_I2C
> +	help
> +	  Device driver for the Maxim MAX96717
> +	  GMSL2 Serializer.

Fits on the previous line.

> +
> +	  To compile this driver as a module, choose M here: the
> +	  module will be called max96717.
> +
>  endmenu
>  
>  endif # VIDEO_DEV
> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
> index dfbe6448b549..9e007116f929 100644
> --- a/drivers/media/i2c/Makefile
> +++ b/drivers/media/i2c/Makefile
> @@ -64,6 +64,7 @@ obj-$(CONFIG_VIDEO_LM3646) += lm3646.o
>  obj-$(CONFIG_VIDEO_M52790) += m52790.o
>  obj-$(CONFIG_VIDEO_MAX9271_LIB) += max9271.o
>  obj-$(CONFIG_VIDEO_MAX9286) += max9286.o
> +obj-$(CONFIG_VIDEO_MAX96717) += max96717.o
>  obj-$(CONFIG_VIDEO_ML86V7667) += ml86v7667.o
>  obj-$(CONFIG_VIDEO_MSP3400) += msp3400.o
>  obj-$(CONFIG_VIDEO_MT9M001) += mt9m001.o
> diff --git a/drivers/media/i2c/max96717.c b/drivers/media/i2c/max96717.c
> new file mode 100644
> index 000000000000..700e50894250
> --- /dev/null
> +++ b/drivers/media/i2c/max96717.c
> @@ -0,0 +1,956 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Maxim GMSL2 Serializer Driver
> + *
> + * Copyright (C) 2024 Collabora Ltd.
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/clk.h>
> +#include <linux/clk-provider.h>
> +#include <linux/delay.h>
> +#include <linux/fwnode.h>
> +#include <linux/gpio/driver.h>
> +#include <linux/i2c-mux.h>
> +#include <linux/i2c.h>
> +#include <linux/regmap.h>
> +
> +#include <media/v4l2-cci.h>
> +#include <media/v4l2-fwnode.h>
> +#include <media/v4l2-subdev.h>
> +
> +#define MAX96717F_DEVICE_ID 0xc8
> +#define MAX96717_PORTS      2
> +#define MAX96717_PAD_SINK   0
> +#define MAX96717_PAD_SOURCE 1
> +
> +#define MAX96717_DEFAULT_CLKOUT_RATE	24000000UL
> +
> +/* DEV */
> +#define REG3             CCI_REG8(0x3)
> +#define MAX96717_RCLKSEL GENMASK(1, 0)
> +#define RCLKSEL_REF_PLL  CCI_REG8(0x3)
> +#define MAX96717_REG6    CCI_REG8(0x6)
> +#define RCLKEN           BIT(5)
> +#define MAX96717_DEV_ID  CCI_REG8(0xd)
> +#define MAX96717_DEV_REV CCI_REG8(0xe)
> +#define MAX96717_DEV_REV_MASK GENMASK(3, 0)
> +
> +/* VID_TX Z */
> +#define MAX96717_VIDEO_TX2 CCI_REG8(0x112)
> +#define MAX96717_VIDEO_PCLKDET BIT(7)
> +
> +/* GPIO */
> +#define MAX96717_NUM_GPIO         11
> +#define MAX96717_GPIO_REG_A(gpio) CCI_REG8(0x2be + (gpio) * 3)
> +#define MAX96717_GPIO_OUT         BIT(4)
> +#define MAX96717_GPIO_IN          BIT(3)
> +#define MAX96717_GPIO_RX_EN       BIT(2)
> +#define MAX96717_GPIO_TX_EN       BIT(1)
> +#define MAX96717_GPIO_OUT_DIS     BIT(0)
> +
> +/* FRONTTOP */
> +/* MAX96717 only have CSI port 'B' */
> +#define MAX96717_FRONTOP0     CCI_REG8(0x308)
> +#define MAX96717_START_PORT_B BIT(5)
> +
> +/* MIPI_RX */
> +#define MAX96717_MIPI_RX1       CCI_REG8(0x331)
> +#define MAX96717_MIPI_LANES_CNT GENMASK(5, 4)
> +#define MAX96717_MIPI_RX2       CCI_REG8(0x332) /* phy1 Lanes map */
> +#define MAX96717_PHY2_LANES_MAP GENMASK(7, 4)
> +#define MAX96717_MIPI_RX3       CCI_REG8(0x333) /* phy2 Lanes map */
> +#define MAX96717_PHY1_LANES_MAP GENMASK(3, 0)
> +#define MAX96717_MIPI_RX4       CCI_REG8(0x334) /* phy1 lane polarities */
> +#define MAX96717_PHY1_LANES_POL GENMASK(6, 4)
> +#define MAX96717_MIPI_RX5       CCI_REG8(0x335) /* phy2 lane polarities */
> +#define MAX96717_PHY2_LANES_POL GENMASK(2, 0)
> +
> +/* MIPI_RX_EXT */
> +#define MAX96717_MIPI_RX_EXT11 CCI_REG8(0x383)
> +#define MAX96717_TUN_MODE      BIT(7)
> +
> +/* REF_VTG */
> +#define REF_VTG0                CCI_REG8(0x3f0)
> +#define REFGEN_PREDEF_EN        BIT(6)
> +#define REFGEN_PREDEF_FREQ_MASK GENMASK(5, 4)
> +#define REFGEN_PREDEF_FREQ_ALT  BIT(3)
> +#define REFGEN_RST              BIT(1)
> +#define REFGEN_EN               BIT(0)
> +
> +/* MISC */
> +#define PIO_SLEW_1 CCI_REG8(0x570)
> +
> +struct max96717_hw_data {
> +	const char *model;
> +	u8 device_id;
> +};
> +
> +static const struct max96717_hw_data max96717f_data = {
> +	.model = "max96717f",
> +	.device_id = MAX96717F_DEVICE_ID,
> +};
> +
> +struct max96717_priv {
> +	const struct max96717_hw_data *data;
> +	struct i2c_client             *client;
> +	struct regmap                 *regmap;
> +	struct i2c_mux_core           *mux;
> +	struct v4l2_fwnode_endpoint   vep;
> +	struct v4l2_subdev            sd;
> +	struct media_pad              pads[MAX96717_PORTS];
> +	struct v4l2_async_notifier    notifier;
> +	struct v4l2_subdev            *source_sd;
> +	u16                           source_sd_pad;
> +	u64			      enabled_source_streams;
> +	u8                            pll_predef_index;
> +	struct clk_hw                 clk_hw;
> +	struct gpio_chip              gpio_chip;
> +};
> +
> +static inline struct max96717_priv *sd_to_max96717(struct v4l2_subdev *sd)
> +{
> +	return container_of(sd, struct max96717_priv, sd);
> +}
> +
> +static inline struct max96717_priv *clk_hw_to_max96717(struct clk_hw *hw)
> +{
> +	return container_of(hw, struct max96717_priv, clk_hw);
> +}
> +
> +static int max96717_i2c_mux_select(struct i2c_mux_core *mux, u32 chan)
> +{
> +	return 0;
> +}
> +
> +static int max96717_i2c_mux_init(struct max96717_priv *priv)
> +{
> +	priv->mux = i2c_mux_alloc(priv->client->adapter, &priv->client->dev,
> +				  1, 0, I2C_MUX_LOCKED | I2C_MUX_GATE,
> +				  max96717_i2c_mux_select, NULL);
> +	if (!priv->mux)
> +		return -ENOMEM;
> +
> +	return i2c_mux_add_adapter(priv->mux, 0, 0, 0);
> +}
> +
> +static inline int max96717_start_csi(struct max96717_priv *priv, bool start)
> +{
> +	return cci_update_bits(priv->regmap, MAX96717_FRONTOP0,
> +			       MAX96717_START_PORT_B,
> +			       start ? MAX96717_START_PORT_B : 0, NULL);
> +}
> +
> +static int max96717_gpiochip_get(struct gpio_chip *gpiochip,
> +				 unsigned int offset)
> +{
> +	struct max96717_priv *priv = gpiochip_get_data(gpiochip);
> +	u64 val;
> +	int ret;
> +
> +	ret = cci_read(priv->regmap, MAX96717_GPIO_REG_A(offset),
> +		       &val, NULL);
> +	if (ret)
> +		return ret;
> +
> +	if (val & MAX96717_GPIO_OUT_DIS)
> +		return !!(val & MAX96717_GPIO_IN);
> +	else
> +		return !!(val & MAX96717_GPIO_OUT);
> +}
> +
> +static void max96717_gpiochip_set(struct gpio_chip *gpiochip,
> +				  unsigned int offset, int value)
> +{
> +	struct max96717_priv *priv = gpiochip_get_data(gpiochip);
> +
> +	cci_update_bits(priv->regmap, MAX96717_GPIO_REG_A(offset),
> +			MAX96717_GPIO_OUT, MAX96717_GPIO_OUT, NULL);
> +}
> +
> +static int max96717_gpio_get_direction(struct gpio_chip *gpiochip,
> +				       unsigned int offset)
> +{
> +	struct max96717_priv *priv = gpiochip_get_data(gpiochip);
> +	u64 val;
> +	int ret;
> +
> +	ret = cci_read(priv->regmap, MAX96717_GPIO_REG_A(offset), &val, NULL);
> +	if (ret < 0)
> +		return ret;
> +
> +	return !!(val & MAX96717_GPIO_OUT_DIS);
> +}
> +
> +static int max96717_gpio_direction_out(struct gpio_chip *gpiochip,
> +				       unsigned int offset, int value)
> +{
> +	struct max96717_priv *priv = gpiochip_get_data(gpiochip);
> +
> +	return cci_update_bits(priv->regmap, MAX96717_GPIO_REG_A(offset),
> +			       MAX96717_GPIO_OUT_DIS | MAX96717_GPIO_OUT,
> +			       value ? MAX96717_GPIO_OUT : 0, NULL);
> +}
> +
> +static int max96717_gpio_direction_in(struct gpio_chip *gpiochip,
> +				      unsigned int offset)
> +{
> +	struct max96717_priv *priv = gpiochip_get_data(gpiochip);
> +
> +	return cci_update_bits(priv->regmap, MAX96717_GPIO_REG_A(offset),
> +			       MAX96717_GPIO_OUT_DIS, MAX96717_GPIO_OUT_DIS,
> +			       NULL);
> +}
> +
> +static int max96717_gpiochip_probe(struct max96717_priv *priv)
> +{
> +	struct device *dev = &priv->client->dev;
> +	struct gpio_chip *gc = &priv->gpio_chip;
> +	int ret, i;
> +
> +	gc->label = dev_name(dev);
> +	gc->parent = dev;
> +	gc->owner = THIS_MODULE;
> +	gc->ngpio = MAX96717_NUM_GPIO;
> +	gc->base = -1;
> +	gc->can_sleep = true;
> +	gc->get_direction = max96717_gpio_get_direction;
> +	gc->direction_input = max96717_gpio_direction_in;
> +	gc->direction_output = max96717_gpio_direction_out;
> +	gc->set = max96717_gpiochip_set;
> +	gc->get = max96717_gpiochip_get;
> +	gc->of_gpio_n_cells = 2;
> +
> +	/* Disable GPIO forwarding */
> +	for (i = 0; i < gc->ngpio; i++)
> +		cci_update_bits(priv->regmap, MAX96717_GPIO_REG_A(i),
> +				MAX96717_GPIO_RX_EN | MAX96717_GPIO_TX_EN,
> +				0, &ret);
> +
> +	if (ret)
> +		return ret;
> +
> +	ret = devm_gpiochip_add_data(dev, gc, priv);
> +	if (ret) {
> +		dev_err(dev, "Unable to create gpio_chip\n");
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int _max96717_set_routing(struct v4l2_subdev *sd,
> +				 struct v4l2_subdev_state *state,
> +				 struct v4l2_subdev_krouting *routing)
> +{
> +	static const struct v4l2_mbus_framefmt format = {
> +		.width = 1280,
> +		.height = 1080,
> +		.code = MEDIA_BUS_FMT_Y8_1X8,
> +		.field = V4L2_FIELD_NONE,
> +	};
> +	int ret;
> +
> +	ret = v4l2_subdev_routing_validate(sd, routing,
> +					   V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
> +	if (ret)
> +		return ret;
> +
> +	ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int max96717_set_routing(struct v4l2_subdev *sd,
> +				struct v4l2_subdev_state *state,
> +				enum v4l2_subdev_format_whence which,
> +				struct v4l2_subdev_krouting *routing)
> +{
> +	struct max96717_priv *priv = sd_to_max96717(sd);
> +
> +	if (which == V4L2_SUBDEV_FORMAT_ACTIVE && priv->enabled_source_streams)
> +		return -EBUSY;
> +
> +	return _max96717_set_routing(sd, state, routing);
> +}
> +
> +static int max96717_set_fmt(struct v4l2_subdev *sd,
> +			    struct v4l2_subdev_state *state,
> +			    struct v4l2_subdev_format *format)
> +{
> +	struct max96717_priv *priv = sd_to_max96717(sd);
> +	struct v4l2_mbus_framefmt *fmt;
> +	u64 stream_source_mask;
> +
> +	if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE &&
> +	    priv->enabled_source_streams)
> +		return -EBUSY;
> +
> +	/* No transcoding, source and sink formats must match. */
> +	if (format->pad == MAX96717_PAD_SOURCE)
> +		return v4l2_subdev_get_fmt(sd, state, format);
> +
> +	/* Set sink format */
> +	fmt = v4l2_subdev_state_get_format(state, format->pad, format->stream);
> +	if (!fmt)
> +		return -EINVAL;
> +
> +	*fmt = format->format;
> +
> +	/* Propagate to source format */
> +	fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad,
> +							   format->stream);
> +	if (!fmt)
> +		return -EINVAL;
> +	*fmt = format->format;
> +
> +	stream_source_mask = BIT(format->stream);
> +
> +	return v4l2_subdev_state_xlate_streams(state, MAX96717_PAD_SOURCE,
> +					       MAX96717_PAD_SINK,
> +					       &stream_source_mask);
> +}
> +
> +static int max96717_init_state(struct v4l2_subdev *sd,
> +			       struct v4l2_subdev_state *state)
> +{
> +	struct v4l2_subdev_route routes[] = {
> +		{
> +			.sink_pad = MAX96717_PAD_SINK,
> +			.sink_stream = 0,
> +			.source_pad = MAX96717_PAD_SOURCE,
> +			.source_stream = 0,
> +			.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
> +		},
> +	};
> +

Extra newline.

> +	struct v4l2_subdev_krouting routing = {
> +		.num_routes = ARRAY_SIZE(routes),
> +		.routes = routes,
> +	};
> +
> +	return _max96717_set_routing(sd, state, &routing);
> +}
> +
> +static bool max96717_pipe_pclkdet(struct max96717_priv *priv)
> +{
> +	u64 val = 0;
> +
> +	cci_read(priv->regmap, MAX96717_VIDEO_TX2, &val, NULL);
> +
> +	return val & MAX96717_VIDEO_PCLKDET;
> +}
> +
> +static int max96717_log_status(struct v4l2_subdev *sd)
> +{
> +	struct max96717_priv *priv = sd_to_max96717(sd);
> +	struct device *dev = &priv->client->dev;
> +
> +	dev_info(dev, "Serializer: %s\n", priv->data->model);
> +	dev_info(dev, "Pipe: pclkdet:%d\n", max96717_pipe_pclkdet(priv));
> +
> +	return 0;
> +}
> +
> +static int max96717_enable_streams(struct v4l2_subdev *sd,
> +				   struct v4l2_subdev_state *state, u32 pad,
> +				   u64 streams_mask)
> +{
> +	struct max96717_priv *priv = sd_to_max96717(sd);
> +	struct device *dev = &priv->client->dev;
> +	u64 sink_streams;
> +	int ret;
> +
> +	sink_streams = v4l2_subdev_state_xlate_streams(state,
> +						       MAX96717_PAD_SOURCE,
> +						       MAX96717_PAD_SINK,
> +						       &streams_mask);
> +
> +	if (!priv->enabled_source_streams)
> +		max96717_start_csi(priv, true);
> +
> +	ret = v4l2_subdev_enable_streams(priv->source_sd, priv->source_sd_pad,
> +					 sink_streams);
> +	if (ret) {
> +		dev_err(dev, "Fail to start streams:%llu on remote subdev\n",
> +			sink_streams);
> +		goto stop_csi;
> +	}
> +
> +	priv->enabled_source_streams |= streams_mask;
> +
> +	return 0;
> +
> +stop_csi:
> +	if (!priv->enabled_source_streams)
> +		max96717_start_csi(priv, false);
> +	return ret;
> +}
> +
> +static int max96717_disable_streams(struct v4l2_subdev *sd,
> +				    struct v4l2_subdev_state *state, u32 pad,
> +				    u64 streams_mask)
> +{
> +	struct max96717_priv *priv = sd_to_max96717(sd);
> +	u64 sink_streams;
> +	int ret;
> +
> +	sink_streams = v4l2_subdev_state_xlate_streams(state,
> +						       MAX96717_PAD_SOURCE,
> +						       MAX96717_PAD_SINK,
> +						       &streams_mask);
> +
> +	ret = v4l2_subdev_disable_streams(priv->source_sd, priv->source_sd_pad,
> +					  sink_streams);
> +	if (ret)
> +		return ret;
> +
> +	priv->enabled_source_streams &= ~streams_mask;
> +
> +	if (!priv->enabled_source_streams)
> +		max96717_start_csi(priv, false);
> +
> +	return 0;
> +}
> +
> +static const struct v4l2_subdev_pad_ops max96717_pad_ops = {
> +	.enable_streams = max96717_enable_streams,
> +	.disable_streams = max96717_disable_streams,
> +	.set_routing = max96717_set_routing,
> +	.get_fmt = v4l2_subdev_get_fmt,
> +	.set_fmt = max96717_set_fmt,
> +};
> +
> +static const struct v4l2_subdev_core_ops max96717_subdev_core_ops = {
> +	.log_status = max96717_log_status,
> +};
> +
> +static const struct v4l2_subdev_internal_ops max96717_internal_ops = {
> +	.init_state = max96717_init_state,
> +};
> +
> +static const struct v4l2_subdev_ops max96717_subdev_ops = {
> +	.core = &max96717_subdev_core_ops,
> +	.pad = &max96717_pad_ops,
> +};
> +
> +static const struct media_entity_operations max96717_entity_ops = {
> +	.link_validate = v4l2_subdev_link_validate,
> +};
> +
> +static int max96717_notify_bound(struct v4l2_async_notifier *notifier,
> +				 struct v4l2_subdev *source_subdev,
> +				 struct v4l2_async_connection *asd)
> +{
> +	struct max96717_priv *priv = sd_to_max96717(notifier->sd);
> +	struct device *dev = &priv->client->dev;
> +	int ret;
> +
> +	ret = media_entity_get_fwnode_pad(&source_subdev->entity,
> +					  source_subdev->fwnode,
> +					  MEDIA_PAD_FL_SOURCE);
> +	if (ret < 0) {
> +		dev_err(dev, "Failed to find pad for %s\n",
> +			source_subdev->name);
> +		return ret;
> +	}
> +
> +	priv->source_sd = source_subdev;
> +	priv->source_sd_pad = ret;
> +
> +	ret = media_create_pad_link(&source_subdev->entity, priv->source_sd_pad,
> +				    &priv->sd.entity, 0,
> +				    MEDIA_LNK_FL_ENABLED |
> +				    MEDIA_LNK_FL_IMMUTABLE);
> +	if (ret) {
> +		dev_err(dev, "Unable to link %s:%u -> %s:0\n",
> +			source_subdev->name, priv->source_sd_pad,
> +			priv->sd.name);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct v4l2_async_notifier_operations max96717_notify_ops = {
> +	.bound = max96717_notify_bound,
> +};
> +
> +static int max96717_v4l2_notifier_register(struct max96717_priv *priv)
> +{
> +	struct device *dev = &priv->client->dev;
> +	struct v4l2_async_connection *asd;
> +	struct fwnode_handle *ep_fwnode;
> +	int ret;
> +
> +	ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
> +						    MAX96717_PAD_SINK, 0, 0);
> +	if (!ep_fwnode) {
> +		dev_err(dev, "No graph endpoint\n");
> +		return -ENODEV;
> +	}
> +
> +	v4l2_async_subdev_nf_init(&priv->notifier, &priv->sd);
> +
> +	asd = v4l2_async_nf_add_fwnode_remote(&priv->notifier, ep_fwnode,
> +					      struct v4l2_async_connection);
> +
> +	fwnode_handle_put(ep_fwnode);
> +
> +	if (IS_ERR(asd)) {
> +		dev_err(dev, "Failed to add subdev: %ld", PTR_ERR(asd));
> +		v4l2_async_nf_cleanup(&priv->notifier);
> +		return PTR_ERR(asd);
> +	}
> +
> +	priv->notifier.ops = &max96717_notify_ops;
> +
> +	ret = v4l2_async_nf_register(&priv->notifier);
> +	if (ret) {
> +		dev_err(dev, "Failed to register subdev_notifier");
> +		v4l2_async_nf_cleanup(&priv->notifier);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static void max96717_v4l2_notifier_unregister(struct max96717_priv *priv)
> +{
> +	v4l2_async_nf_unregister(&priv->notifier);
> +	v4l2_async_nf_cleanup(&priv->notifier);
> +}
> +
> +static int max96717_subdev_init(struct max96717_priv *priv)
> +{
> +	struct device *dev = &priv->client->dev;
> +	int ret;
> +
> +	v4l2_i2c_subdev_init(&priv->sd, priv->client, &max96717_subdev_ops);
> +	priv->sd.internal_ops = &max96717_internal_ops;
> +
> +	priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS;
> +	priv->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
> +	priv->sd.entity.ops = &max96717_entity_ops;
> +
> +	priv->pads[0].flags = MEDIA_PAD_FL_SINK;
> +	priv->pads[1].flags = MEDIA_PAD_FL_SOURCE;

You have MAX96717_PAD_SINK and MAX96717_PAD_SOURCE as well.

> +
> +	ret = media_entity_pads_init(&priv->sd.entity, 2, priv->pads);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "Failed to init pads\n");
> +
> +	ret = v4l2_subdev_init_finalize(&priv->sd);
> +	if (ret) {
> +		dev_err_probe(dev, ret,
> +			      "v4l2 subdev init finalized failed\n");
> +		goto err_fwnode_put;
> +	}
> +	ret = max96717_v4l2_notifier_register(priv);
> +	if (ret) {
> +		dev_err_probe(dev, ret,
> +			      "v4l2 subdev notifier register failed\n");
> +		goto err_free_state;
> +	}
> +
> +	ret = v4l2_async_register_subdev(&priv->sd);
> +	if (ret) {
> +		dev_err_probe(dev, ret, "v4l2_async_register_subdev error\n");
> +		goto err_unreg_notif;
> +	}
> +
> +	return 0;
> +
> +err_unreg_notif:
> +	max96717_v4l2_notifier_unregister(priv);
> +err_free_state:
> +	v4l2_subdev_cleanup(&priv->sd);
> +err_fwnode_put:
> +	fwnode_handle_put(priv->sd.fwnode);

Hmm. Does this belong here? Or... should it be done at all?

> +	media_entity_cleanup(&priv->sd.entity);
> +
> +	return ret;
> +}
> +
> +static void max96717_subdev_uninit(struct max96717_priv *priv)
> +{
> +	v4l2_async_unregister_subdev(&priv->sd);
> +	max96717_v4l2_notifier_unregister(priv);
> +	v4l2_subdev_cleanup(&priv->sd);
> +	fwnode_handle_put(priv->sd.fwnode);

Same here.

> +	media_entity_cleanup(&priv->sd.entity);
> +}
> +
> +struct max96717_pll_predef_freq {
> +	unsigned long freq;
> +	bool is_alt;
> +	u8 val;
> +};
> +
> +static const struct max96717_pll_predef_freq max96717_predef_freqs[] = {
> +	{ 13500000, true,  0 }, { 19200000, false, 0 },
> +	{ 24000000, true,  1 }, { 27000000, false, 1 },
> +	{ 37125000, false, 2 }, { 74250000, false, 3 },
> +};
> +
> +static unsigned long
> +max96717_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
> +{
> +	struct max96717_priv *priv = clk_hw_to_max96717(hw);
> +
> +	return max96717_predef_freqs[priv->pll_predef_index].freq;
> +}
> +
> +static unsigned int max96717_clk_find_best_index(struct max96717_priv *priv,
> +						 unsigned long rate)
> +{
> +	u8 i, idx;

unsigned int please, and declare these after the diff* below.

> +	unsigned long diff_new, diff_old;
> +
> +	diff_old = U32_MAX;
> +	idx = 0;
> +
> +	for (i = 0; i < ARRAY_SIZE(max96717_predef_freqs); i++) {
> +		diff_new = abs(rate - max96717_predef_freqs[i].freq);
> +		if (diff_new < diff_old) {
> +			diff_old = diff_new;
> +			idx = i;
> +		}
> +	}
> +
> +	return idx;
> +}
> +
> +static long max96717_clk_round_rate(struct clk_hw *hw, unsigned long rate,
> +				    unsigned long *parent_rate)
> +{
> +	struct max96717_priv *priv = clk_hw_to_max96717(hw);
> +	struct device *dev = &priv->client->dev;
> +	u8 idx;

unsigned int.

> +
> +	idx = max96717_clk_find_best_index(priv, rate);
> +
> +	if (rate != max96717_predef_freqs[idx].freq) {
> +		dev_warn(dev, "Request CLK freq:%lu, found CLK freq:%lu\n",
> +			 rate, max96717_predef_freqs[idx].freq);
> +	}
> +
> +	return max96717_predef_freqs[idx].freq;
> +}
> +
> +static int max96717_clk_set_rate(struct clk_hw *hw, unsigned long rate,
> +				 unsigned long parent_rate)
> +{
> +	struct max96717_priv *priv = clk_hw_to_max96717(hw);
> +	int ret = 0;
> +	u8 val, idx;

Ditto.

> +
> +	idx = max96717_clk_find_best_index(priv, rate);
> +
> +	val = FIELD_PREP(REFGEN_PREDEF_FREQ_MASK,
> +			 max96717_predef_freqs[idx].val);
> +
> +	if (max96717_predef_freqs[idx].is_alt)
> +		val |= REFGEN_PREDEF_FREQ_ALT;
> +
> +	val |= REFGEN_RST | REFGEN_PREDEF_EN;
> +
> +	cci_write(priv->regmap, REF_VTG0, val, &ret);
> +	cci_update_bits(priv->regmap, REF_VTG0, REFGEN_RST | REFGEN_EN,
> +			REFGEN_EN, &ret);
> +	if (ret)
> +		return ret;
> +
> +	priv->pll_predef_index = idx;
> +
> +	return 0;
> +}
> +
> +static int max96717_clk_prepare(struct clk_hw *hw)
> +{
> +	struct max96717_priv *priv = clk_hw_to_max96717(hw);
> +
> +	return cci_update_bits(priv->regmap, MAX96717_REG6, RCLKEN,
> +			       RCLKEN, NULL);
> +}
> +
> +static void max96717_clk_unprepare(struct clk_hw *hw)
> +{
> +	struct max96717_priv *priv = clk_hw_to_max96717(hw);
> +
> +	cci_update_bits(priv->regmap, MAX96717_REG6, RCLKEN, 0, NULL);
> +}
> +
> +static const struct clk_ops max96717_clk_ops = {
> +	.prepare     = max96717_clk_prepare,
> +	.unprepare   = max96717_clk_unprepare,
> +	.set_rate    = max96717_clk_set_rate,
> +	.recalc_rate = max96717_clk_recalc_rate,
> +	.round_rate  = max96717_clk_round_rate,
> +};
> +
> +static int max96717_register_clkout(struct max96717_priv *priv)
> +{
> +	struct device *dev = &priv->client->dev;
> +	int ret;
> +

Extra newline.

> +	const struct clk_init_data init = {
> +		.name = kasprintf(GFP_KERNEL, "%s.%s.clk_out",
> +				  priv->data->model,
> +				  dev_name(dev)),

Please move kasprintf() outside declaration. It's not nice to do things
there that can fail.

> +		.ops = &max96717_clk_ops,
> +	};

This would be nicer declared before rett.

> +
> +	if (!init.name)
> +		return -ENOMEM;
> +
> +	/* RCLKSEL Reference PLL output */
> +	ret = cci_update_bits(priv->regmap, REG3, MAX96717_RCLKSEL,
> +			      RCLKSEL_REF_PLL, NULL);
> +	/* MFP4 fastest slew rate */
> +	cci_update_bits(priv->regmap, PIO_SLEW_1, BIT(5) | BIT(4), 0, &ret);
> +	if (ret)
> +		goto free_init_name;
> +
> +	priv->clk_hw.init = &init;
> +
> +	/* Initialize to 24 MHz */
> +	ret = max96717_clk_set_rate(&priv->clk_hw,
> +				    MAX96717_DEFAULT_CLKOUT_RATE, 0);
> +	if (ret < 0)
> +		goto free_init_name;
> +
> +	ret = devm_clk_hw_register(dev, &priv->clk_hw);
> +	kfree(init.name);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "Cannot register clock HW\n");
> +
> +	ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get,
> +					  &priv->clk_hw);
> +	if (ret)
> +		return dev_err_probe(dev, ret,
> +				     "Cannot add OF clock provider\n");
> +
> +	return 0;
> +
> +free_init_name:
> +	kfree(init.name);
> +	return ret;
> +}
> +
> +static int max96717_init_csi_lanes(struct max96717_priv *priv)
> +{
> +	struct v4l2_mbus_config_mipi_csi2 *mipi = &priv->vep.bus.mipi_csi2;
> +	unsigned long lanes_used = 0;
> +	u8 nlanes, lane, val = 0;

unsigned int.

> +	int ret;
> +
> +	nlanes = mipi->num_data_lanes;
> +
> +	ret = cci_update_bits(priv->regmap, MAX96717_MIPI_RX1,
> +			      MAX96717_MIPI_LANES_CNT,
> +			      FIELD_PREP(MAX96717_MIPI_LANES_CNT,
> +					 nlanes - 1), NULL);
> +
> +	/* lanes polarity */
> +	for (lane = 0; lane < nlanes + 1; lane++) {
> +		if (!mipi->lane_polarities[lane])
> +			continue;
> +		/* Clock lane */
> +		if (lane == 0)
> +			val |= BIT(2);
> +		else if (lane < 3)
> +			val |= BIT(lane - 1);
> +		else
> +			val |= BIT(lane);
> +	}
> +
> +	cci_update_bits(priv->regmap, MAX96717_MIPI_RX5,
> +			MAX96717_PHY2_LANES_POL,
> +			FIELD_PREP(MAX96717_PHY2_LANES_POL, val), &ret);
> +
> +	cci_update_bits(priv->regmap, MAX96717_MIPI_RX4,
> +			MAX96717_PHY1_LANES_POL,
> +			FIELD_PREP(MAX96717_PHY1_LANES_POL,
> +				   val >> 3), &ret);
> +	/* lanes mapping */
> +	val = 0;
> +	for (lane = 0; lane < nlanes; lane++) {

You can initialise val in loop initialisation (i.e. lane = 0, val = 0).

> +		val |= (mipi->data_lanes[lane] - 1) << (lane * 2);
> +		lanes_used |= BIT(mipi->data_lanes[lane] - 1);
> +	}
> +
> +	/* Unused lanes need to be mapped as well to not have

/*
 * Multi-line
 * comment.
 */

> +	 * the same lanes mapped twice.
> +	 */
> +	for (; lane < 4; lane++) {
> +		unsigned int idx = find_first_zero_bit(&lanes_used, 4);
> +
> +		val |= idx << (lane * 2);
> +		lanes_used |= BIT(idx);
> +	}
> +
> +	cci_update_bits(priv->regmap, MAX96717_MIPI_RX3,
> +			MAX96717_PHY1_LANES_MAP,
> +			FIELD_PREP(MAX96717_PHY1_LANES_MAP, val), &ret);
> +
> +	return cci_update_bits(priv->regmap, MAX96717_MIPI_RX2,
> +			       MAX96717_PHY2_LANES_MAP,
> +			       FIELD_PREP(MAX96717_PHY2_LANES_MAP, val >> 4),
> +			       &ret);
> +}
> +
> +static int max96717_hw_init(struct max96717_priv *priv)
> +{
> +	struct device *dev = &priv->client->dev;
> +	u64 val;
> +	int ret;
> +
> +	ret = cci_read(priv->regmap, MAX96717_DEV_ID, &val, NULL);
> +	if (ret)
> +		return dev_err_probe(dev, ret,
> +				     "Fail to read the device id\n");
> +
> +	if (val != priv->data->device_id)
> +		return dev_err_probe(dev, -EOPNOTSUPP,
> +				     "Unsupported device id expected %x got %x\n",
> +				     priv->data->device_id, (u8)val);
> +
> +	ret = cci_read(priv->regmap, MAX96717_DEV_REV, &val, NULL);
> +	if (ret)
> +		return dev_err_probe(dev, ret,
> +				     "Fail to read device revision");
> +
> +	dev_dbg(dev, "Found %x (rev %lx)\n", priv->data->device_id,
> +		(u8)val & MAX96717_DEV_REV_MASK);
> +
> +	ret = cci_read(priv->regmap, MAX96717_MIPI_RX_EXT11, &val, NULL);
> +	if (ret)
> +		return dev_err_probe(dev, ret,
> +				     "Fail to read mipi rx extension");
> +
> +	if (!(val & MAX96717_TUN_MODE))
> +		return dev_err_probe(dev, -EOPNOTSUPP,
> +				     "Only supporting tunnel mode");
> +
> +	return max96717_init_csi_lanes(priv);
> +}
> +
> +static int max96717_parse_dt(struct max96717_priv *priv)
> +{
> +	struct device *dev = &priv->client->dev;
> +	struct fwnode_handle *ep_fwnode;
> +	unsigned char num_data_lanes;
> +	int ret;
> +
> +	priv->vep.bus_type = V4L2_MBUS_CSI2_DPHY;
> +
> +	ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
> +						    MAX96717_PAD_SINK, 0, 0);
> +	if (!ep_fwnode)
> +		return dev_err_probe(dev, -ENOENT, "no endpoint found\n");
> +
> +	ret = v4l2_fwnode_endpoint_parse(ep_fwnode, &priv->vep);
> +
> +	fwnode_handle_put(ep_fwnode);
> +
> +	if (ret < 0)
> +		return dev_err_probe(dev, ret, "Failed to parse sink endpoint");
> +
> +	num_data_lanes = priv->vep.bus.mipi_csi2.num_data_lanes;

If all you need from the endpoint is the number of lanes and their
polarities, then I'd store just that, not the entire endpoint.

> +	if (num_data_lanes < 1 || num_data_lanes > 4)
> +		return dev_err_probe(dev, -EINVAL,
> +				     "Invalid data lanes should be 1 to 4\n");

s/should/must/
s/be \K/from /

> +
> +	return 0;
> +}
> +
> +static int max96717_probe(struct i2c_client *client)
> +{
> +	struct device *dev = &client->dev;
> +	struct max96717_priv *priv;
> +	int ret;
> +
> +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	priv->client = client;
> +

Extra newline.

> +	priv->data = of_device_get_match_data(&client->dev);

We have device_get_match_data(), too. Up to you.

> +
> +	i2c_set_clientdata(client, priv);
> +
> +	priv->regmap = devm_cci_regmap_init_i2c(client, 16);
> +	if (IS_ERR(priv->regmap)) {
> +		ret = PTR_ERR(priv->regmap);
> +		return dev_err_probe(dev, ret, "Failed to init regmap\n");
> +	}
> +
> +	ret = max96717_parse_dt(priv);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "Failed to parse the dt\n");
> +
> +	ret = max96717_hw_init(priv);
> +	if (ret)
> +		return dev_err_probe(dev, ret,
> +				     "Failed to initialize the hardware\n");
> +
> +	ret = max96717_gpiochip_probe(priv);
> +	if (ret) {
> +		dev_err(&client->dev, "Failed to init gpiochip\n");
> +		return ret;
> +	}
> +
> +	ret = max96717_register_clkout(priv);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "Failed to register clkout\n");
> +
> +	ret = max96717_subdev_init(priv);
> +	if (ret)
> +		return dev_err_probe(dev, ret,
> +				     "Failed to initialize v4l2 subdev\n");
> +
> +	ret = max96717_i2c_mux_init(priv);
> +	if (ret) {
> +		dev_err_probe(dev, ret, "failed to add remote i2c adapter\n");
> +		goto err_subdev_uninit;

No need for goto as you use the lable in a single location only. Move
max96717_subdev_uninit() here.

> +	}
> +
> +	return 0;
> +
> +err_subdev_uninit:
> +	max96717_subdev_uninit(priv);
> +	return ret;
> +}
> +
> +static void max96717_remove(struct i2c_client *client)
> +{
> +	struct max96717_priv *priv = i2c_get_clientdata(client);
> +
> +	max96717_subdev_uninit(priv);
> +	i2c_mux_del_adapters(priv->mux);
> +}
> +
> +static const struct of_device_id max96717_of_ids[] = {
> +	{ .compatible = "maxim,max96717f", .data = &max96717f_data },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, max96717_of_ids);
> +
> +static struct i2c_driver max96717_i2c_driver = {
> +	.driver	= {
> +		.name		= "max96717",
> +		.of_match_table	= max96717_of_ids,
> +	},
> +	.probe		= max96717_probe,
> +	.remove		= max96717_remove,
> +};
> +
> +module_i2c_driver(max96717_i2c_driver);
> +
> +MODULE_DESCRIPTION("Maxim GMSL2 MAX96717 Serializer Driver");
> +MODULE_AUTHOR("Julien Massot <julien.massot@collabora.com>");
> +MODULE_LICENSE("GPL");

-- 
Kind regards,

Sakari Ailus

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

* Re: [PATCH v3 4/4] media: i2c: add MAX96714 driver
  2024-01-11 13:03 ` [PATCH v3 4/4] media: i2c: add MAX96714 driver Julien Massot
  2024-01-13 11:31   ` kernel test robot
@ 2024-02-09 15:49   ` Sakari Ailus
  2024-02-21 10:45     ` Julien Massot
  1 sibling, 1 reply; 18+ messages in thread
From: Sakari Ailus @ 2024-02-09 15:49 UTC (permalink / raw)
  To: Julien Massot
  Cc: linux-media, kernel, devicetree, linux-kernel, mchehab, robh+dt,
	krzysztof.kozlowski+dt, conor+dt

Hi Julien,

On Thu, Jan 11, 2024 at 02:03:49PM +0100, Julien Massot wrote:
> This driver handle the MAX96714 deserializer in tunnel mode.
> The CSI output will replicate all the CSI traffic forwarded by
> the remote serializer.
> 
> Signed-off-by: Julien Massot <julien.massot@collabora.com>
> ---
> Change since v2:
>  - Use CCI helpers instead of recoding register access
>  - add missing bitfield header
>  - Add pattern generator so the deserializer can be tested without a serializer/sensor
> ---
>  MAINTAINERS                  |    7 +
>  drivers/media/i2c/Kconfig    |   13 +
>  drivers/media/i2c/Makefile   |    1 +
>  drivers/media/i2c/max96714.c | 1077 ++++++++++++++++++++++++++++++++++
>  4 files changed, 1098 insertions(+)
>  create mode 100644 drivers/media/i2c/max96714.c
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index a64a7932fe76..a7691d38513a 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -13039,6 +13039,13 @@ S:	Maintained
>  F:	Documentation/devicetree/bindings/media/i2c/maxim,max96712.yaml
>  F:	drivers/staging/media/max96712/max96712.c
>  
> +MAX96714 GMSL2 DESERIALIZER DRIVER
> +M:	Julien Massot <julien.massot@collabora.com>
> +L:	linux-media@vger.kernel.org
> +S:	Maintained
> +F:	Documentation/devicetree/bindings/media/i2c/maxim,max96714f.yaml
> +F:	drivers/media/i2c/max96714.c
> +
>  MAX96717 GMSL2 SERIALIZER DRIVER
>  M:	Julien Massot <julien.massot@collabora.com>
>  L:	linux-media@vger.kernel.org
> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
> index 64f6a35f4481..d0dfab513154 100644
> --- a/drivers/media/i2c/Kconfig
> +++ b/drivers/media/i2c/Kconfig
> @@ -1570,6 +1570,19 @@ config VIDEO_DS90UB960
>  	  Device driver for the Texas Instruments DS90UB960
>  	  FPD-Link III Deserializer and DS90UB9702 FPD-Link IV Deserializer.
>  
> +config VIDEO_MAX96714
> +	tristate "Maxim MAX96714 GMSL2 deserializer"
> +	depends on OF && I2C && VIDEO_DEV
> +	select I2C_MUX
> +	select GPIOLIB
> +	select V4L2_CCI_I2C
> +	help
> +	  Device driver for the Maxim MAX96714
> +	  GMSL2 Deserializer.
> +
> +	  To compile this driver as a module, choose M here: the
> +	  module will be called max96714.
> +
>  config VIDEO_MAX96717
>  	tristate "Maxim MAX96717 GMSL2 Serializer support"
>  	depends on OF && I2C && VIDEO_DEV && COMMON_CLK
> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
> index 9e007116f929..7c794441eaff 100644
> --- a/drivers/media/i2c/Makefile
> +++ b/drivers/media/i2c/Makefile
> @@ -64,6 +64,7 @@ obj-$(CONFIG_VIDEO_LM3646) += lm3646.o
>  obj-$(CONFIG_VIDEO_M52790) += m52790.o
>  obj-$(CONFIG_VIDEO_MAX9271_LIB) += max9271.o
>  obj-$(CONFIG_VIDEO_MAX9286) += max9286.o
> +obj-$(CONFIG_VIDEO_MAX96714) += max96714.o
>  obj-$(CONFIG_VIDEO_MAX96717) += max96717.o
>  obj-$(CONFIG_VIDEO_ML86V7667) += ml86v7667.o
>  obj-$(CONFIG_VIDEO_MSP3400) += msp3400.o
> diff --git a/drivers/media/i2c/max96714.c b/drivers/media/i2c/max96714.c
> new file mode 100644
> index 000000000000..8bf1f5babf5d
> --- /dev/null
> +++ b/drivers/media/i2c/max96714.c
> @@ -0,0 +1,1077 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Maxim GMSL2 Deserializer Driver
> + *
> + * Copyright (C) 2024 Collabora Ltd.
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/bitops.h>
> +#include <linux/fwnode.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/i2c.h>
> +#include <linux/i2c-mux.h>
> +#include <linux/module.h>
> +#include <linux/regmap.h>
> +#include <linux/regulator/consumer.h>
> +
> +#include <media/v4l2-cci.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-fwnode.h>
> +#include <media/v4l2-subdev.h>
> +
> +#define MAX96714F_DEVICE_ID 0xca
> +#define MAX96714_NPORTS     2
> +#define MAX96714_PAD_SINK   0
> +#define MAX96714_PAD_SOURCE 1
> +
> +/* DEV */
> +#define MAX96714_REG13            CCI_REG8(0x0d)
> +#define MAX96714_DEV_REV          CCI_REG8(0x0e)
> +#define MAX96714_DEV_REV_MASK     GENMASK(3, 0)
> +#define MAX96714_LINK_LOCK        CCI_REG8(0x13)
> +#define MAX96714_LINK_LOCK_BIT    BIT(3)
> +#define MAX96714_IO_CHK0          CCI_REG8(0x38)
> +#define MAX96714_PATTERN_CLK_FREQ GENMASK(1, 0)
> +/* VID_RX */
> +#define MAX96714_VIDEO_RX8        CCI_REG8(0x11a)
> +#define MAX96714_VID_LOCK         BIT(6)
> +
> +/* VRX_PATGEN_0 */
> +#define MAX96714_PATGEN_0              CCI_REG8(0x240)
> +#define MAX96714_PATGEN_1              CCI_REG8(0x241)
> +#define MAX96714_PATGEN_MODE           GENMASK(5, 4)
> +#define MAX96714_PATGEN_VS_DLY         CCI_REG24(0x242)
> +#define MAX96714_PATGEN_VS_HIGH        CCI_REG24(0x245)
> +#define MAX96714_PATGEN_VS_LOW         CCI_REG24(0x248)
> +#define MAX96714_PATGEN_V2H            CCI_REG24(0x24b)
> +#define MAX96714_PATGEN_HS_HIGH        CCI_REG16(0x24e)
> +#define MAX96714_PATGEN_HS_LOW	       CCI_REG16(0x250)
> +#define MAX96714_PATGEN_HS_CNT	       CCI_REG16(0x252)
> +#define MAX96714_PATGEN_V2D            CCI_REG24(0x254)
> +#define MAX96714_PATGEN_DE_HIGH        CCI_REG16(0x257)
> +#define MAX96714_PATGEN_DE_LOW         CCI_REG16(0x259)
> +#define MAX96714_PATGEN_DE_CNT         CCI_REG16(0x25B)
> +#define MAX96714_PATGEN_GRAD_INC       CCI_REG8(0x25d)
> +#define MAX96714_PATGEN_CHKB_COLOR_A   CCI_REG24(0x25E)
> +#define MAX96714_PATGEN_CHKB_COLOR_B   CCI_REG24(0x261)
> +#define MAX96714_PATGEN_CHKB_RPT_CNT_A CCI_REG8(0x264)
> +#define MAX96714_PATGEN_CHKB_RPT_CNT_B CCI_REG8(0x265)
> +#define MAX96714_PATGEN_CHKB_ALT       CCI_REG8(0x266)
> +/* BACKTOP */
> +#define MAX96714_BACKTOP25 CCI_REG8(0x320)
> +#define CSI_DPLL_FREQ_MASK GENMASK(4, 0)
> +
> +/* MIPI_PHY */
> +#define MAX96714_MIPI_PHY0       CCI_REG8(0x330)
> +#define MAX96714_FORCE_CSI_OUT   BIT(7)
> +#define MAX96714_MIPI_STDBY_N    CCI_REG8(0x332)
> +#define MAX96714_MIPI_STDBY_MASK GENMASK(5, 4)
> +#define MAX96714_MIPI_LANE_MAP   CCI_REG8(0x333)
> +#define MAX96714_MIPI_POLARITY   CCI_REG8(0x335)
> +#define MAX96714_MIPI_POLARITY_MASK GENMASK(5, 0)
> +
> +/* MIPI_TX */
> +#define MAX96714_MIPI_LANE_CNT CCI_REG8(0x44a)
> +#define MAX96714_CSI2_LANE_CNT_MASK GENMASK(7, 6)
> +#define MAX96714_MIPI_TX52 CCI_REG8(0x474)
> +#define MAX96714_TUN_EN BIT(0)

It'd be nice to align the macro expansions above.

> +
> +#define MHZ(v) ((u32)((v)  * 1000000U))
> +
> +enum max96714_vpg_mode {
> +	MAX96714_VPG_DISABLED = 0,
> +	MAX96714_VPG_CHECKERBOARD = 1,
> +	MAX96714_VPG_GRADIENT = 2,
> +};
> +
> +struct max96714_rxport {
> +	struct {
> +		struct v4l2_subdev   *sd;
> +		u16 pad;
> +		struct fwnode_handle *ep_fwnode;
> +	} source;
> +	struct regulator	     *poc;
> +};
> +
> +struct max96714_txport {
> +	struct v4l2_fwnode_endpoint vep;
> +};
> +
> +struct max96714_priv {
> +	struct i2c_client *client;
> +	struct regmap     *regmap;
> +	struct gpio_desc  *pd_gpio;
> +	struct max96714_rxport rxport;
> +	struct i2c_mux_core *mux;
> +	u64 enabled_source_streams;
> +	struct v4l2_subdev	sd;
> +	struct media_pad	pads[MAX96714_NPORTS];
> +	struct v4l2_fwnode_endpoint vep;
> +	struct v4l2_ctrl_handler   ctrl_handler;
> +	struct v4l2_async_notifier notifier;
> +	s64 tx_link_freq;
> +	enum max96714_vpg_mode pattern;
> +};
> +
> +static struct regmap_config max96714_regmap_config = {
> +	.name = "max96714",
> +	.reg_bits = 16,
> +	.val_bits = 8,
> +	.max_register = 0xFFFF,
> +};
> +
> +static inline struct max96714_priv *sd_to_max96714(struct v4l2_subdev *sd)
> +{
> +	return container_of(sd, struct max96714_priv, sd);
> +}
> +
> +static int max96714_enable_tx_port(struct max96714_priv *priv)
> +{
> +	return cci_update_bits(priv->regmap, MAX96714_MIPI_STDBY_N,
> +			       MAX96714_MIPI_STDBY_MASK,
> +			       MAX96714_MIPI_STDBY_MASK, NULL);
> +}
> +
> +static int max96714_disable_tx_port(struct max96714_priv *priv)
> +{
> +	return cci_update_bits(priv->regmap, MAX96714_MIPI_STDBY_N,
> +			       MAX96714_MIPI_STDBY_MASK, 0, NULL);
> +}
> +
> +static bool max96714_tx_port_enabled(struct max96714_priv *priv)
> +{
> +	u64 val;
> +
> +	cci_read(priv->regmap, MAX96714_MIPI_STDBY_N, &val, NULL);
> +
> +	return val & MAX96714_MIPI_STDBY_MASK;
> +}
> +
> +static int max96714_apply_patgen_timing(struct max96714_priv *priv,
> +					struct v4l2_subdev_state *state)
> +{
> +	struct v4l2_mbus_framefmt *fmt =
> +		v4l2_subdev_state_get_format(state, MAX96714_PAD_SOURCE);
> +	const u32 h_active = fmt->width;
> +	const u32 h_fp = 88;
> +	const u32 h_sw = 44;
> +	const u32 h_bp = 148;
> +	u32 h_tot;
> +
> +	const u32 v_active = fmt->height;
> +	const u32 v_fp = 4;
> +	const u32 v_sw = 5;
> +	const u32 v_bp = 36;
> +	u32 v_tot;
> +	int ret = 0;
> +
> +	h_tot = h_active + h_fp + h_sw + h_bp;
> +	v_tot = v_active + v_fp + v_sw + v_bp;
> +
> +	/* 75 Mhz pixel clock */
> +	cci_update_bits(priv->regmap, MAX96714_IO_CHK0,
> +			MAX96714_PATTERN_CLK_FREQ, 1, &ret);
> +
> +	dev_info(&priv->client->dev, "height: %d width: %d\n", fmt->height,
> +		 fmt->width);
> +
> +	cci_write(priv->regmap, MAX96714_PATGEN_VS_DLY, 0, &ret);
> +	cci_write(priv->regmap, MAX96714_PATGEN_VS_HIGH, v_sw * h_tot, &ret);
> +	cci_write(priv->regmap, MAX96714_PATGEN_VS_LOW,
> +		  (v_active + v_fp + v_bp) * h_tot, &ret);
> +	cci_write(priv->regmap, MAX96714_PATGEN_HS_HIGH, h_sw, &ret);
> +	cci_write(priv->regmap, MAX96714_PATGEN_HS_LOW, h_active + h_fp + h_bp,
> +		  &ret);
> +	cci_write(priv->regmap, MAX96714_PATGEN_V2D,
> +		  h_tot * (v_sw + v_bp) + (h_sw + h_bp), &ret);
> +	cci_write(priv->regmap, MAX96714_PATGEN_HS_CNT, v_tot, &ret);
> +	cci_write(priv->regmap, MAX96714_PATGEN_DE_HIGH, h_active, &ret);
> +	cci_write(priv->regmap, MAX96714_PATGEN_DE_LOW, h_fp + h_sw + h_bp,
> +		  &ret);
> +	cci_write(priv->regmap, MAX96714_PATGEN_DE_CNT, v_active, &ret);
> +	/* B G R */
> +	cci_write(priv->regmap, MAX96714_PATGEN_CHKB_COLOR_A, 0xfecc00, &ret);
> +	/* B G R */
> +	cci_write(priv->regmap, MAX96714_PATGEN_CHKB_COLOR_B, 0x006aa7, &ret);
> +	cci_write(priv->regmap, MAX96714_PATGEN_CHKB_RPT_CNT_A, 0x3c, &ret);
> +	cci_write(priv->regmap, MAX96714_PATGEN_CHKB_RPT_CNT_B, 0x3c, &ret);
> +	cci_write(priv->regmap, MAX96714_PATGEN_CHKB_ALT, 0x3c, &ret);
> +	cci_write(priv->regmap, MAX96714_PATGEN_GRAD_INC, 0x10, &ret);
> +
> +	return ret;
> +}
> +
> +static int max96714_apply_patgen(struct max96714_priv *priv,
> +				 struct v4l2_subdev_state *state)
> +{
> +	int ret = 0;
> +	u8 val;
> +
> +	if (priv->pattern)
> +		ret = max96714_apply_patgen_timing(priv, state);
> +
> +	cci_write(priv->regmap, MAX96714_PATGEN_0, priv->pattern ? 0xfb : 0,
> +		  &ret);
> +
> +	val = FIELD_PREP(MAX96714_PATGEN_MODE, priv->pattern);
> +	cci_update_bits(priv->regmap, MAX96714_PATGEN_1, MAX96714_PATGEN_MODE,
> +			val, &ret);
> +	return ret;
> +}
> +
> +static int max96714_s_ctrl(struct v4l2_ctrl *ctrl)
> +{
> +	struct max96714_priv *priv =
> +		container_of(ctrl->handler, struct max96714_priv, ctrl_handler);
> +	int ret;
> +
> +	switch (ctrl->id) {
> +	case V4L2_CID_TEST_PATTERN:
> +		priv->pattern = ctrl->val;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	ret = cci_update_bits(priv->regmap, MAX96714_MIPI_PHY0,
> +			      MAX96714_FORCE_CSI_OUT,
> +			      priv->pattern ? MAX96714_FORCE_CSI_OUT : 0, NULL);
> +
> +	/* Pattern generator doesn't work with tunnel mode */
> +	return cci_update_bits(priv->regmap, MAX96714_MIPI_TX52,
> +			       MAX96714_TUN_EN,
> +			       priv->pattern ? 0 : MAX96714_TUN_EN, &ret);
> +}
> +
> +static const char * const max96714_test_pattern[] = {
> +	"Disabled",
> +	"Checkerboard",
> +	"Gradient"
> +};
> +
> +static const struct v4l2_ctrl_ops max96714_ctrl_ops = {
> +	.s_ctrl = max96714_s_ctrl,
> +};
> +
> +static int max96714_enable_streams(struct v4l2_subdev *sd,
> +				   struct v4l2_subdev_state *state,
> +				   u32 source_pad, u64 streams_mask)
> +{
> +	struct max96714_priv *priv = sd_to_max96714(sd);
> +	u64 sink_streams;
> +	int ret;
> +
> +	if (!priv->enabled_source_streams)
> +		max96714_enable_tx_port(priv);
> +
> +	ret = max96714_apply_patgen(priv, state);
> +	if (ret)
> +		goto err;
> +
> +	if (!priv->pattern) {
> +		if (!priv->rxport.source.sd) {
> +			ret = -ENODEV;
> +			goto err;
> +		}
> +
> +		sink_streams =
> +			v4l2_subdev_state_xlate_streams(state,
> +							MAX96714_PAD_SOURCE,
> +							MAX96714_PAD_SINK,
> +							&streams_mask);
> +
> +		ret = v4l2_subdev_enable_streams(priv->rxport.source.sd,
> +						 priv->rxport.source.pad,
> +						 sink_streams);
> +		if (ret)
> +			goto err;
> +	}
> +
> +	priv->enabled_source_streams |= streams_mask;
> +
> +	return 0;
> +
> +err:
> +	if (!priv->enabled_source_streams)
> +		max96714_disable_tx_port(priv);
> +
> +	return ret;
> +}
> +
> +static int max96714_disable_streams(struct v4l2_subdev *sd,
> +				    struct v4l2_subdev_state *state,
> +				    u32 source_pad, u64 streams_mask)
> +{
> +	struct max96714_priv *priv = sd_to_max96714(sd);
> +	u64 sink_streams;
> +	int ret;
> +
> +	if (!priv->pattern && priv->rxport.source.sd) {
> +		sink_streams =
> +			v4l2_subdev_state_xlate_streams(state,
> +							MAX96714_PAD_SOURCE,
> +							MAX96714_PAD_SINK,
> +							&streams_mask);
> +
> +		ret = v4l2_subdev_disable_streams(priv->rxport.source.sd,
> +						  priv->rxport.source.pad,
> +						  sink_streams);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	priv->enabled_source_streams &= ~streams_mask;
> +
> +	if (!priv->enabled_source_streams)
> +		max96714_disable_tx_port(priv);
> +
> +	return 0;
> +}
> +
> +static int max96714_s_stream(struct v4l2_subdev *subdev, int enable)
> +{
> +	int ret;
> +	u64 streams_mask = BIT(0);
> +
> +	if (enable)
> +		ret = v4l2_subdev_enable_streams(subdev, MAX96714_PAD_SOURCE,
> +						 streams_mask);
> +	else
> +		ret = v4l2_subdev_disable_streams(subdev, MAX96714_PAD_SOURCE,
> +						  streams_mask);
> +	return ret;
> +}
> +
> +static int max96714_set_fmt(struct v4l2_subdev *sd,
> +			    struct v4l2_subdev_state *state,
> +			    struct v4l2_subdev_format *format)
> +{
> +	struct max96714_priv *priv = sd_to_max96714(sd);
> +	struct v4l2_mbus_framefmt *fmt;
> +
> +	if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE &&
> +	    priv->enabled_source_streams)
> +		return -EBUSY;
> +
> +	/* No transcoding, source and sink formats must match. */
> +	if (format->pad == MAX96714_PAD_SOURCE)
> +		return v4l2_subdev_get_fmt(sd, state, format);
> +
> +	fmt = v4l2_subdev_state_get_format(state, format->pad, format->stream);
> +	if (!fmt)
> +		return -EINVAL;
> +
> +	*fmt = format->format;
> +
> +	fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad,
> +							   format->stream);
> +	if (!fmt)
> +		return -EINVAL;
> +
> +	*fmt = format->format;
> +
> +	return 0;
> +}
> +
> +static int _max96714_set_routing(struct v4l2_subdev *sd,
> +				 struct v4l2_subdev_state *state,
> +				 enum v4l2_subdev_format_whence which,
> +				 struct v4l2_subdev_krouting *routing)
> +{
> +	int ret;

Please declare ret as last.

> +

Extra newline.

> +	static const struct v4l2_mbus_framefmt format = {
> +		.width = 1280,
> +		.height = 1080,
> +		.code = MEDIA_BUS_FMT_Y8_1X8,
> +		.field = V4L2_FIELD_NONE,
> +	};
> +
> +	/*
> +	 * Note: we can only support up to V4L2_FRAME_DESC_ENTRY_MAX, until
> +	 * frame desc is made dynamically allocated.
> +	 */
> +	if (routing->num_routes > V4L2_FRAME_DESC_ENTRY_MAX)
> +		return -EINVAL;
> +
> +	ret = v4l2_subdev_routing_validate(sd, routing,
> +					   V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
> +	if (ret)
> +		return ret;
> +
> +	ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);

	return ...;

> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int max96714_set_routing(struct v4l2_subdev *sd,
> +				struct v4l2_subdev_state *state,
> +				enum v4l2_subdev_format_whence which,
> +				struct v4l2_subdev_krouting *routing)
> +{
> +	struct max96714_priv *priv = sd_to_max96714(sd);
> +
> +	if (which == V4L2_SUBDEV_FORMAT_ACTIVE && priv->enabled_source_streams)
> +		return -EBUSY;
> +
> +	return _max96714_set_routing(sd, state, which, routing);
> +}
> +
> +static int max96714_init_state(struct v4l2_subdev *sd,
> +			       struct v4l2_subdev_state *state)
> +{
> +	struct v4l2_subdev_route routes[] = {
> +		{
> +			.sink_pad = MAX96714_PAD_SINK,
> +			.sink_stream = 0,
> +			.source_pad = MAX96714_PAD_SOURCE,
> +			.source_stream = 0,
> +			.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
> +		}
> +	};
> +

Extra newline.

> +	struct v4l2_subdev_krouting routing = {
> +		.num_routes = ARRAY_SIZE(routes),
> +		.routes = routes,
> +	};
> +
> +	return _max96714_set_routing(sd, state, V4L2_SUBDEV_FORMAT_ACTIVE,
> +				     &routing);
> +}
> +
> +static const struct v4l2_subdev_pad_ops max96714_pad_ops = {
> +	.enable_streams = max96714_enable_streams,
> +	.disable_streams = max96714_disable_streams,
> +
> +	.set_routing = max96714_set_routing,
> +	.get_fmt = v4l2_subdev_get_fmt,
> +	.set_fmt = max96714_set_fmt,
> +};
> +
> +static bool max96714_link_locked(struct max96714_priv *priv)
> +{
> +	u64 val = 0;
> +
> +	cci_read(priv->regmap, MAX96714_LINK_LOCK, &val, NULL);
> +
> +	return val & MAX96714_LINK_LOCK_BIT;
> +}
> +
> +static void max96714_link_status(struct max96714_priv *priv)
> +{
> +	struct device *dev = &priv->client->dev;
> +
> +	dev_info(dev, "Link locked:%d\n", max96714_link_locked(priv));
> +}
> +
> +static bool max96714_pipe_locked(struct max96714_priv *priv)
> +{
> +	u64 val;
> +
> +	cci_read(priv->regmap, MAX96714_VIDEO_RX8, &val, NULL);
> +
> +	return val & MAX96714_VID_LOCK;
> +}
> +
> +static void max96714_pipe_status(struct max96714_priv *priv)
> +{
> +	struct device *dev = &priv->client->dev;
> +
> +	dev_info(dev, "Pipe vidlock:%d\n", max96714_pipe_locked(priv));
> +}
> +
> +static void max96714_csi_status(struct max96714_priv *priv)
> +{
> +	struct device *dev = &priv->client->dev;
> +	u64 freq = 0;
> +
> +	cci_read(priv->regmap, MAX96714_BACKTOP25, &freq, NULL);
> +	freq = FIELD_GET(CSI_DPLL_FREQ_MASK, freq);
> +
> +	dev_info(dev, "CSI controller DPLL freq:%u00MHz CSIPHY enabled:%d\n",
> +		 (u8)freq, max96714_tx_port_enabled(priv));
> +}
> +
> +static int max96714_log_status(struct v4l2_subdev *sd)
> +{
> +	struct max96714_priv *priv = sd_to_max96714(sd);
> +	struct device *dev = &priv->client->dev;
> +
> +	dev_info(dev, "Deserializer: max96714\n");
> +
> +	max96714_link_status(priv);
> +	max96714_pipe_status(priv);
> +	max96714_csi_status(priv);
> +
> +	return 0;
> +}
> +
> +static const struct v4l2_subdev_core_ops max96714_subdev_core_ops = {
> +	.log_status = max96714_log_status,
> +};
> +
> +static const struct v4l2_subdev_video_ops max96714_video_ops = {
> +	.s_stream	= max96714_s_stream,
> +};
> +
> +static const struct v4l2_subdev_internal_ops max96714_internal_ops = {
> +	.init_state = max96714_init_state,
> +};
> +
> +static const struct v4l2_subdev_ops max96714_subdev_ops = {
> +	.video = &max96714_video_ops,
> +	.core = &max96714_subdev_core_ops,
> +	.pad = &max96714_pad_ops,
> +};
> +
> +static const struct media_entity_operations max96714_entity_ops = {
> +	.link_validate = v4l2_subdev_link_validate,
> +};
> +
> +static int max96714_notify_bound(struct v4l2_async_notifier *notifier,
> +				 struct v4l2_subdev *subdev,
> +				 struct v4l2_async_connection *asd)
> +{
> +	struct max96714_priv *priv = sd_to_max96714(notifier->sd);
> +	struct device *dev = &priv->client->dev;
> +	int ret;
> +
> +	ret = media_entity_get_fwnode_pad(&subdev->entity,
> +					  priv->rxport.source.ep_fwnode,
> +					  MEDIA_PAD_FL_SOURCE);
> +	if (ret < 0) {
> +		dev_err(dev, "Failed to find pad for %s\n", subdev->name);
> +		return ret;
> +	}
> +
> +	priv->rxport.source.sd = subdev;
> +	priv->rxport.source.pad = ret;
> +
> +	ret = media_create_pad_link(&priv->rxport.source.sd->entity,
> +				    priv->rxport.source.pad, &priv->sd.entity,
> +				    MAX96714_PAD_SINK,
> +				    MEDIA_LNK_FL_ENABLED |
> +				    MEDIA_LNK_FL_IMMUTABLE);
> +	if (ret) {
> +		dev_err(dev, "Unable to link %s:%u -> %s:%u\n",
> +			priv->rxport.source.sd->name, priv->rxport.source.pad,
> +			priv->sd.name, MAX96714_PAD_SINK);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct v4l2_async_notifier_operations max96714_notify_ops = {
> +	.bound = max96714_notify_bound,
> +};
> +
> +static int max96714_v4l2_notifier_register(struct max96714_priv *priv)
> +{
> +	struct device *dev = &priv->client->dev;
> +	struct max96714_rxport *rxport = &priv->rxport;
> +	struct v4l2_async_connection *asd;
> +	int ret;
> +
> +	if (!rxport->source.ep_fwnode)
> +		return 0;
> +
> +	v4l2_async_subdev_nf_init(&priv->notifier, &priv->sd);
> +
> +	asd = v4l2_async_nf_add_fwnode(&priv->notifier,
> +				       rxport->source.ep_fwnode,
> +				       struct v4l2_async_connection);
> +	if (IS_ERR(asd)) {
> +		dev_err(dev, "Failed to add subdev: %pe", asd);
> +		v4l2_async_nf_cleanup(&priv->notifier);
> +		return PTR_ERR(asd);
> +	}
> +
> +	priv->notifier.ops = &max96714_notify_ops;
> +
> +	ret = v4l2_async_nf_register(&priv->notifier);
> +	if (ret) {
> +		dev_err(dev, "Failed to register subdev_notifier");
> +		v4l2_async_nf_cleanup(&priv->notifier);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static void max96714_v4l2_notifier_unregister(struct max96714_priv *priv)
> +{
> +	v4l2_async_nf_unregister(&priv->notifier);
> +	v4l2_async_nf_cleanup(&priv->notifier);
> +}
> +
> +static int max96714_create_subdev(struct max96714_priv *priv)
> +{
> +	struct device *dev = &priv->client->dev;
> +	int ret;
> +
> +	v4l2_i2c_subdev_init(&priv->sd, priv->client, &max96714_subdev_ops);
> +	priv->sd.internal_ops = &max96714_internal_ops;
> +
> +	v4l2_ctrl_handler_init(&priv->ctrl_handler, 1);
> +	priv->sd.ctrl_handler = &priv->ctrl_handler;
> +
> +	priv->tx_link_freq = priv->vep.link_frequencies[0];
> +	if (priv->tx_link_freq < 0)
> +		return priv->tx_link_freq;
> +
> +	v4l2_ctrl_new_int_menu(&priv->ctrl_handler, NULL, V4L2_CID_LINK_FREQ,
> +			       0, 0, &priv->tx_link_freq);
> +	if (priv->ctrl_handler.error) {
> +		ret = priv->ctrl_handler.error;
> +		goto err_free_ctrl;
> +	}
> +
> +	v4l2_ctrl_new_std_menu_items(&priv->ctrl_handler,
> +				     &max96714_ctrl_ops,
> +				     V4L2_CID_TEST_PATTERN,
> +				     ARRAY_SIZE(max96714_test_pattern) - 1,
> +				     0, 0, max96714_test_pattern);
> +	if (priv->ctrl_handler.error) {
> +		ret = priv->ctrl_handler.error;
> +		goto err_free_ctrl;
> +	}
> +
> +	priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS;
> +	priv->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
> +	priv->sd.entity.ops = &max96714_entity_ops;
> +
> +	priv->pads[MAX96714_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
> +	priv->pads[MAX96714_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
> +
> +	ret = media_entity_pads_init(&priv->sd.entity,
> +				     MAX96714_NPORTS,
> +				     priv->pads);
> +	if (ret)
> +		goto err_free_ctrl;
> +
> +	priv->sd.state_lock = priv->sd.ctrl_handler->lock;
> +
> +	ret = v4l2_subdev_init_finalize(&priv->sd);
> +	if (ret)
> +		goto err_entity_cleanup;
> +
> +	ret = max96714_v4l2_notifier_register(priv);
> +	if (ret) {
> +		dev_err(dev, "v4l2 subdev notifier register failed: %d\n", ret);
> +		goto err_subdev_cleanup;
> +	}
> +
> +	ret = v4l2_async_register_subdev(&priv->sd);
> +	if (ret) {
> +		dev_err(dev, "v4l2_async_register_subdev error: %d\n", ret);
> +		goto err_unreg_notif;
> +	}
> +
> +	return 0;
> +
> +err_unreg_notif:
> +	max96714_v4l2_notifier_unregister(priv);
> +err_subdev_cleanup:
> +	v4l2_subdev_cleanup(&priv->sd);
> +err_entity_cleanup:
> +	media_entity_cleanup(&priv->sd.entity);
> +err_free_ctrl:
> +	v4l2_ctrl_handler_free(&priv->ctrl_handler);
> +
> +	return ret;
> +};
> +
> +static void max96714_destroy_subdev(struct max96714_priv *priv)
> +{
> +	max96714_v4l2_notifier_unregister(priv);
> +	v4l2_async_unregister_subdev(&priv->sd);
> +
> +	v4l2_subdev_cleanup(&priv->sd);
> +
> +	media_entity_cleanup(&priv->sd.entity);
> +	v4l2_ctrl_handler_free(&priv->ctrl_handler);
> +}
> +
> +static int max96714_i2c_mux_select(struct i2c_mux_core *mux, u32 chan)
> +{
> +	return 0;
> +}
> +
> +static int max96714_i2c_mux_init(struct max96714_priv *priv)
> +{
> +	priv->mux = i2c_mux_alloc(priv->client->adapter, &priv->client->dev,
> +				  1, 0, I2C_MUX_LOCKED | I2C_MUX_GATE,
> +				  max96714_i2c_mux_select, NULL);
> +	if (!priv->mux)
> +		return -ENOMEM;
> +
> +	return i2c_mux_add_adapter(priv->mux, 0, 0, 0);
> +}
> +
> +static int max96714_init_tx_port(struct max96714_priv *priv)
> +{
> +	struct v4l2_mbus_config_mipi_csi2 *mipi;
> +	unsigned long lanes_used = 0;
> +	u8 val, lane;
> +	int ret;
> +
> +	ret = max96714_disable_tx_port(priv);
> +
> +	mipi = &priv->vep.bus.mipi_csi2;
> +	val = *priv->vep.link_frequencies * 2 / MHZ(100);
> +
> +	cci_update_bits(priv->regmap, MAX96714_BACKTOP25,
> +			CSI_DPLL_FREQ_MASK, val, &ret);
> +
> +	val = FIELD_PREP(MAX96714_CSI2_LANE_CNT_MASK, mipi->num_data_lanes - 1);
> +	cci_update_bits(priv->regmap, MAX96714_MIPI_LANE_CNT,
> +			MAX96714_CSI2_LANE_CNT_MASK, val, &ret);
> +
> +	/* lanes polarity */
> +	val = 0;
> +	for (lane = 0; lane < mipi->num_data_lanes + 1; lane++) {
> +		if (!mipi->lane_polarities[lane])
> +			continue;
> +		if (lane == 0)
> +			/* clock lane */
> +			val |= BIT(5);
> +		else if (lane < 3)
> +			/* Lane D0 and D1 */
> +			val |= BIT(lane - 1);
> +		else
> +			/* D2 and D3 */
> +			val |= BIT(lane);
> +	}
> +
> +	cci_update_bits(priv->regmap, MAX96714_MIPI_POLARITY,
> +			MAX96714_MIPI_POLARITY_MASK, val, &ret);
> +
> +	/* lanes mapping */
> +	val = 0;
> +	for (lane = 0; lane < mipi->num_data_lanes; lane++) {
> +		val |= (mipi->data_lanes[lane] - 1) << (lane * 2);
> +		lanes_used |= BIT(mipi->data_lanes[lane] - 1);
> +	}
> +
> +	/* Unused lanes need to be mapped as well to not have
> +	 * the same lanes mapped twice.
> +	 */
> +	for (; lane < 4; lane++) {
> +		unsigned int idx = find_first_zero_bit(&lanes_used, 4);
> +
> +		val |= idx << (lane * 2);
> +		lanes_used |= BIT(idx);
> +	}
> +
> +	return cci_write(priv->regmap, MAX96714_MIPI_LANE_MAP, val, &ret);
> +}
> +
> +static int max96714_rxport_enable_poc(struct max96714_priv *priv)
> +{
> +	struct max96714_rxport *rxport = &priv->rxport;
> +
> +	if (!rxport->poc)
> +		return 0;
> +
> +	return regulator_enable(rxport->poc);
> +}
> +
> +static int max96714_rxport_disable_poc(struct max96714_priv *priv)
> +{
> +	struct max96714_rxport *rxport = &priv->rxport;
> +
> +	if (!rxport->poc)
> +		return 0;
> +
> +	return regulator_disable(rxport->poc);
> +}
> +
> +static int max96714_parse_dt_txport(struct max96714_priv *priv)
> +{
> +	struct device *dev = &priv->client->dev;
> +	struct fwnode_handle *ep_fwnode;
> +	u32 num_data_lanes;
> +	s64 dpll_freq;
> +	int ret;
> +
> +	ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
> +						    MAX96714_PAD_SOURCE, 0, 0);
> +	if (!ep_fwnode)
> +		return -EINVAL;
> +
> +	priv->vep.bus_type = V4L2_MBUS_UNKNOWN;
> +
> +	ret = v4l2_fwnode_endpoint_alloc_parse(ep_fwnode, &priv->vep);
> +	fwnode_handle_put(ep_fwnode);
> +	if (ret) {
> +		dev_err(dev, "tx: failed to parse endpoint data\n");
> +		return -EINVAL;
> +	}
> +
> +	if (priv->vep.bus_type != V4L2_MBUS_CSI2_DPHY) {
> +		dev_err(&priv->client->dev, "Unsupported bus-type %u\n",
> +			priv->vep.bus_type);
> +		return -EINVAL;
> +	}
> +
> +	if (priv->vep.nr_of_link_frequencies != 1) {
> +		ret = -EINVAL;
> +		goto err_free_vep;
> +	}
> +
> +	/* DPLL freq is twice the link frequency */
> +	dpll_freq = priv->vep.link_frequencies[0] * 2;
> +
> +	/* 100Mbps step, Min 100Mbps, Max 2500Mbps */
> +	if (dpll_freq % MHZ(100) || dpll_freq < MHZ(100) ||
> +	    dpll_freq > MHZ(2500)) {
> +		dev_err(dev, "tx: invalid link frequency\n");
> +		ret = -EINVAL;
> +		goto err_free_vep;
> +	}
> +
> +	num_data_lanes = priv->vep.bus.mipi_csi2.num_data_lanes;
> +	if (num_data_lanes < 1 || num_data_lanes > 4) {
> +		dev_err(dev,
> +			"tx: invalid number of data lanes should be 1 to 4\n");
> +		ret = -EINVAL;
> +		goto err_free_vep;
> +	}
> +
> +	return 0;
> +
> +err_free_vep:
> +	v4l2_fwnode_endpoint_free(&priv->vep);
> +
> +	return ret;
> +};
> +
> +static int max96714_parse_dt_rxport(struct max96714_priv *priv)
> +{
> +	static const char *poc_name = "port0-poc";
> +	struct max96714_rxport *rxport = &priv->rxport;
> +	struct device *dev = &priv->client->dev;
> +	struct fwnode_handle *ep_fwnode;
> +	int ret;
> +
> +	ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
> +						    MAX96714_PAD_SINK,
> +						    0, 0);

Fits on the previous line.

> +	if (!ep_fwnode)
> +		return -ENOENT;
> +
> +	rxport->source.ep_fwnode = fwnode_graph_get_remote_endpoint(ep_fwnode);
> +	fwnode_handle_put(ep_fwnode);
> +
> +	if (!rxport->source.ep_fwnode) {
> +		dev_err(dev, "rx: no remote endpoint\n");
> +		return -EINVAL;
> +	}
> +
> +	rxport->poc = devm_regulator_get_optional(dev, poc_name);
> +	if (IS_ERR(rxport->poc)) {
> +		ret = PTR_ERR(rxport->poc);
> +		if (ret == -ENODEV) {
> +			rxport->poc = NULL;
> +		} else {
> +			dev_err(dev, "rx: failed to get POC supply: %d\n", ret);
> +			goto err_put_source_ep_fwnode;
> +		}
> +	}
> +
> +	return 0;
> +
> +err_put_source_ep_fwnode:
> +	fwnode_handle_put(rxport->source.ep_fwnode);
> +	return ret;
> +}
> +
> +static int max96714_parse_dt(struct max96714_priv *priv)
> +{
> +	int ret;
> +
> +	ret = max96714_parse_dt_rxport(priv);
> +	/* The deserializer can create a test pattern even if the
> +	 * rx port is not connected to a serializer.
> +	 */
> +	if (ret && ret != -ENOENT)
> +		return ret;
> +
> +	ret = max96714_parse_dt_txport(priv);
> +	if (ret)
> +		goto err_put_fwnode;
> +
> +	return 0;
> +
> +err_put_fwnode:
> +	fwnode_handle_put(priv->rxport.source.ep_fwnode);

This is already done in max96714_parse_dt_txport().

> +
> +	return ret;
> +}
> +
> +static int max96714_enable_core_hw(struct max96714_priv *priv)
> +{
> +	struct device *dev = &priv->client->dev;
> +	u64 val;
> +	int ret;
> +
> +	if (priv->pd_gpio) {
> +		/* wait min 2 ms for reset to complete */
> +		gpiod_set_value_cansleep(priv->pd_gpio, 1);
> +		fsleep(2000);
> +		gpiod_set_value_cansleep(priv->pd_gpio, 0);
> +		/* wait min 2 ms for power up to finish */
> +		fsleep(2000);
> +	}
> +
> +	ret = cci_read(priv->regmap, MAX96714_REG13, &val, NULL);
> +	if (ret) {
> +		dev_err_probe(dev, ret, "Cannot read first register, abort\n");
> +		goto err_pd_gpio;
> +	}
> +
> +	if (val != MAX96714F_DEVICE_ID) {
> +		dev_err(dev, "Unsupported device id expected %x got %x\n",
> +			MAX96714F_DEVICE_ID, (u8)val);
> +		ret = -EOPNOTSUPP;
> +		goto err_pd_gpio;
> +	}
> +
> +	ret = cci_read(priv->regmap, MAX96714_DEV_REV, &val, NULL);
> +	if (ret)
> +		goto err_pd_gpio;
> +
> +	dev_dbg(dev, "Found %x (rev %lx)\n", MAX96714F_DEVICE_ID,
> +		(u8)val & MAX96714_DEV_REV_MASK);
> +
> +	ret = cci_read(priv->regmap, MAX96714_MIPI_TX52, &val, NULL);
> +	if (ret)
> +		goto err_pd_gpio;
> +
> +	if (!(val & MAX96714_TUN_EN)) {
> +		dev_err(dev, "Only supporting tunnel mode");
> +		ret = -EOPNOTSUPP;
> +		goto err_pd_gpio;
> +	}
> +
> +	return 0;
> +
> +err_pd_gpio:
> +	gpiod_set_value_cansleep(priv->pd_gpio, 1);
> +	return ret;
> +}
> +
> +static void max96714_disable_core_hw(struct max96714_priv *priv)
> +{
> +	gpiod_set_value_cansleep(priv->pd_gpio, 1);
> +}
> +
> +static int max96714_get_hw_resources(struct max96714_priv *priv)
> +{
> +	struct device *dev = &priv->client->dev;
> +
> +	priv->regmap = devm_cci_regmap_init_i2c(priv->client, 16);
> +	if (IS_ERR(priv->regmap))
> +		return PTR_ERR(priv->regmap);
> +
> +	priv->pd_gpio =
> +		devm_gpiod_get_optional(dev, "powerdown", GPIOD_OUT_HIGH);
> +	if (IS_ERR(priv->pd_gpio))
> +		return dev_err_probe(dev, PTR_ERR(priv->pd_gpio),
> +				     "Cannot get powerdown GPIO\n");
> +	return 0;
> +}
> +
> +static int max96714_probe(struct i2c_client *client)
> +{
> +	struct device *dev = &client->dev;
> +	struct max96714_priv *priv;
> +	int ret;
> +
> +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	priv->client = client;
> +	priv->regmap = devm_regmap_init_i2c(priv->client,
> +					    &max96714_regmap_config);
> +
> +	ret = max96714_get_hw_resources(priv);
> +	if (ret)
> +		return ret;
> +
> +	ret = max96714_enable_core_hw(priv);
> +	if (ret)
> +		return ret;
> +
> +	ret = max96714_parse_dt(priv);
> +	if (ret)
> +		goto err_disable_core_hw;
> +
> +	max96714_init_tx_port(priv);
> +
> +	ret = max96714_rxport_enable_poc(priv);
> +	if (ret)
> +		goto err_free_ports;
> +
> +	ret = max96714_i2c_mux_init(priv);
> +	if (ret)
> +		goto err_disable_poc;
> +
> +	ret = max96714_create_subdev(priv);
> +	if (ret)
> +		goto err_del_mux;
> +
> +	return 0;
> +
> +err_del_mux:
> +	i2c_mux_del_adapters(priv->mux);
> +err_disable_poc:
> +	max96714_rxport_disable_poc(priv);
> +err_free_ports:
> +	fwnode_handle_put(priv->rxport.source.ep_fwnode);
> +	v4l2_fwnode_endpoint_free(&priv->vep);
> +err_disable_core_hw:
> +	max96714_disable_core_hw(priv);
> +
> +	return ret;
> +}
> +
> +static void max96714_remove(struct i2c_client *client)
> +{
> +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
> +	struct max96714_priv *priv = sd_to_max96714(sd);
> +
> +	max96714_destroy_subdev(priv);
> +	i2c_mux_del_adapters(priv->mux);
> +	max96714_rxport_disable_poc(priv);
> +	fwnode_handle_put(priv->rxport.source.ep_fwnode);
> +	v4l2_fwnode_endpoint_free(&priv->vep);
> +	max96714_disable_core_hw(priv);
> +	gpiod_set_value_cansleep(priv->pd_gpio, 1);
> +}
> +
> +static const struct of_device_id max96714_of_ids[] = {
> +	{ .compatible = "maxim,max96714f" },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, max96714_of_ids);
> +
> +static struct i2c_driver max96714_i2c_driver = {
> +	.driver	= {
> +		.name		= "max96714",
> +		.of_match_table	= max96714_of_ids,
> +	},
> +	.probe		= max96714_probe,
> +	.remove		= max96714_remove,
> +};
> +
> +module_i2c_driver(max96714_i2c_driver);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("Maxim Integrated GMSL2 Deserializers Driver");
> +MODULE_AUTHOR("Julien Massot <julien.massot@collabora.com>");

-- 
Kind regards,

Sakari Ailus

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

* Re: [PATCH v3 2/4] dt-bindings: media: add Maxim MAX96714F GMSL2 Deserializer
  2024-02-09  9:48   ` Sakari Ailus
@ 2024-02-21 10:10     ` Julien Massot
  0 siblings, 0 replies; 18+ messages in thread
From: Julien Massot @ 2024-02-21 10:10 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: linux-media, kernel, devicetree, linux-kernel, mchehab, robh+dt,
	krzysztof.kozlowski+dt, conor+dt

Hi Sakari,

Thanks for the review!

On 2/9/24 10:48, Sakari Ailus wrote:
> Hi Julien,
> 
> On Thu, Jan 11, 2024 at 02:03:47PM +0100, Julien Massot wrote:
>> Add DT bindings for Maxim MAX96714F GMSL2 Deserializer.
>>
>> Signed-off-by: Julien Massot <julien.massot@collabora.com>
>>
>> ---
>> Change since v2:
>>   - remove reg description
>>   - rename enable gpio to powerdown
>>   - use generic node name: i2c, serializer, deserializer
>> ---
>>   .../bindings/media/i2c/maxim,max96714f.yaml   | 171 ++++++++++++++++++
>>   1 file changed, 171 insertions(+)
>>   create mode 100644 Documentation/devicetree/bindings/media/i2c/maxim,max96714f.yaml
>>
>> diff --git a/Documentation/devicetree/bindings/media/i2c/maxim,max96714f.yaml b/Documentation/devicetree/bindings/media/i2c/maxim,max96714f.yaml
>> new file mode 100644
>> index 000000000000..2423d166c954
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/media/i2c/maxim,max96714f.yaml
>> @@ -0,0 +1,171 @@
>> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>> +# Copyright (C) 2024 Collabora Ltd.
>> +%YAML 1.2
>> +---
>> +$id: http://devicetree.org/schemas/media/i2c/maxim,max96714f.yaml#
>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>> +
>> +title: Maxim MAX96714 GMSL2 to CSI-2 Deserializer
>> +
>> +maintainers:
>> +  - Julien Massot <julien.massot@collabora.com>
>> +
>> +description: |
>> +  The MAX96714F deserializer converts GMSL2 serial inputs into MIPI
>> +  CSI-2 D-PHY or C-PHY formatted output. The device allows the GMSL2 link to

Description is wrong if the MAX96717F is supporting C-PHY MAX96714F doesn't.
I will remove the mention to C-PHY sorry for the confusion.

>> +  simultaneously transmit bidirectional control-channel data while forward
>> +  video transmissions are in progress. The MAX96714F can connect to one
>> +  remotely located serializer using industry-standard coax or STP
>> +  interconnects. The device cans operate in pixel or tunnel mode. In pixel mode
>> +  the MAX96714F can select individual video stream, while the tunnel mode forward all
>> +  the MIPI data received by the serializer.
>> +
>> +  The GMSL2 serial link operates at a fixed rate of 3Gbps in the
>> +  forward direction and 187.5Mbps in the reverse direction.
>> +
>> +properties:
>> +  compatible:
>> +    const: maxim,max96714f
>> +
>> +  reg:
>> +    maxItems: 1
>> +
>> +  powerdown-gpios:
>> +    maxItems: 1
>> +    description:
>> +      Specifier for the GPIO connected to the PWDNB pin.
>> +
>> +  ports:
>> +    $ref: /schemas/graph.yaml#/properties/ports
>> +
>> +    properties:
>> +      port@0:
>> +        $ref: /schemas/graph.yaml#/properties/port
>> +        unevaluatedProperties: false
>> +        description: GMSL Input
>> +        properties:
>> +          endpoint:
>> +            $ref: /schemas/media/video-interfaces.yaml#
>> +            unevaluatedProperties: false
>> +            description:
>> +              Endpoint for GMSL2-Link port.
>> +
>> +      port@1:
>> +        $ref: /schemas/graph.yaml#/$defs/port-base
>> +        unevaluatedProperties: false
>> +        description: CSI-2 Output port
>> +
>> +        properties:
>> +          endpoint:
>> +            $ref: /schemas/media/video-interfaces.yaml#
>> +            unevaluatedProperties: false
>> +
>> +            properties:
>> +              data-lanes:
>> +                minItems: 1
>> +                maxItems: 4
>> +
>> +              link-frequencies:
>> +                maxItems: 1
>> +
>> +              bus-type:
>> +                enum:
>> +                  - 4 # MEDIA_BUS_TYPE_CSI2_DPHY
> 
> MEDIA_BUS_TYPE_CSI2_CPHY (1) is missing. Even if the driver doesn't support
> it, it would be best to list it here.

After verification the hardware only support D-PHY and not C-PHY, will
drop the required
property for 'bus-type'.

> 
>> +
>> +            required:
>> +              - data-lanes
>> +              - bus-type
>> +
>> +    required:
>> +      - port@1
> 
> Why port@1 but not port@0?

The device can be used as a pattern generator, so this is fine not to
have a serializer connected to the port@0, but it makes no sense to 
leave the deserializer CSI port not connected.

> 
>> +
>> +  i2c-gate:
>> +    $ref: /schemas/i2c/i2c-controller.yaml
>> +    unevaluatedProperties: false
>> +    description: |
>> +      The MAX96714 will pass through and forward the I2C requests from the
>> +      incoming I2C bus over the GMSL2 link. Therefore it supports an i2c-gate
>> +      subnode to configure a serializer.
>> +
>> +  port0-poc-supply:
>> +    description: Regulator providing Power over Coax for the GMSL port
>> +
>> +required:
>> +  - compatible
>> +  - reg
>> +  - ports
>> +
>> +additionalProperties: false
>> +
>> +examples:
>> +  - |
>> +    #include <dt-bindings/gpio/gpio.h>
>> +    #include <dt-bindings/media/video-interfaces.h>
>> +
>> +    i2c {
>> +        #address-cells = <1>;
>> +        #size-cells = <0>;
>> +
>> +        deserializer@28 {
>> +            compatible = "maxim,max96714f";
>> +            reg = <0x28>;
>> +            powerdown-gpios = <&main_gpio0 37 GPIO_ACTIVE_LOW>;
>> +
>> +            ports {
>> +                #address-cells = <1>;
>> +                #size-cells = <0>;
>> +                port@0 {
>> +                    reg = <0>;
>> +                    max96714_gmsl_in: endpoint {
>> +                        remote-endpoint = <&max96917f_gmsl_out>;
>> +                    };
>> +                };
>> +
>> +                port@1 {
>> +                    reg = <1>;
>> +                    max96714_csi_out: endpoint {
>> +                        bus-type = <MEDIA_BUS_TYPE_CSI2_DPHY>;
>> +                        clock-lanes = <0>;
> 
> clock-lanes isn't listed in bindings. You can drop it from here.
Ok

> 
>> +                        data-lanes = <1 2 3 4>;
>> +                        link-frequencies = /bits/ 64 <400000000>;
>> +                        remote-endpoint = <&csi_in>;
>> +                    };
>> +                };
>> +            };
>> +
>> +            i2c-gate {
>> +                #address-cells = <1>;
>> +                #size-cells = <0>;
>> +
>> +                serializer@40 {
>> +                    compatible = "maxim,max96717f";
>> +                    reg = <0x40>;
>> +                    gpio-controller;
>> +                    #gpio-cells = <2>;
>> +                    #clock-cells = <0>;
>> +
>> +                    ports {
>> +                        #address-cells = <1>;
>> +                        #size-cells = <0>;
>> +
>> +                        port@0 {
>> +                            reg = <0>;
>> +                            max96717f_csi_in: endpoint {
>> +                                data-lanes = <1 2>;
>> +                                lane-polarities = <1 0 1>;
> 
> Shouldn't lane-polarities be mentioned in bindings, too?

You are right I will add lane-polarities to my next patchset.
> 
>> +                                remote-endpoint = <&sensor_out>;
>> +                            };
>> +                        };
>> +
>> +                        port@1 {
>> +                            reg = <1>;
>> +                            max96917f_gmsl_out: endpoint {
>> +                                remote-endpoint = <&max96714_gmsl_in>;
>> +                            };
>> +                        };
>> +                    };
>> +                };
>> +            };
>> +        };
>> +    };
>> +...
> 

Best regards,

-- 
Julien Massot
Senior Software Engineer
Collabora Ltd.
Platinum Building, St John's Innovation Park, Cambridge CB4 0DS, UK
Registered in England & Wales, no. 5513718

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

* Re: [PATCH v3 1/4] dt-bindings: media: add Maxim MAX96717F GMSL2 Serializer
  2024-02-09  9:28   ` Sakari Ailus
@ 2024-02-21 10:12     ` Julien Massot
  0 siblings, 0 replies; 18+ messages in thread
From: Julien Massot @ 2024-02-21 10:12 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: linux-media, kernel, devicetree, linux-kernel, mchehab, robh+dt,
	krzysztof.kozlowski+dt, conor+dt

Hi Sakari,

On 2/9/24 10:28, Sakari Ailus wrote:
> Hi Julien,
> 
> On Thu, Jan 11, 2024 at 02:03:46PM +0100, Julien Massot wrote:
>> Add DT bindings for Maxim MAX96717F GMSL2 Serializer.
>>
>> Signed-off-by: Julien Massot <julien.massot@collabora.com>
>> ---
>> Change since v2:
>>   - remove reg description
>>   - add data lanes min/maxItems
>>   - Use generic node name
>>
>> ---
>>   .../bindings/media/i2c/maxim,max96717f.yaml   | 147 ++++++++++++++++++
>>   1 file changed, 147 insertions(+)
>>   create mode 100644 Documentation/devicetree/bindings/media/i2c/maxim,max96717f.yaml
>>
>> diff --git a/Documentation/devicetree/bindings/media/i2c/maxim,max96717f.yaml b/Documentation/devicetree/bindings/media/i2c/maxim,max96717f.yaml
>> new file mode 100644
>> index 000000000000..f31517b1dbc8
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/media/i2c/maxim,max96717f.yaml
>> @@ -0,0 +1,147 @@
>> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>> +# Copyright (C) 2024 Collabora Ltd.
>> +%YAML 1.2
>> +---
>> +$id: http://devicetree.org/schemas/media/i2c/maxim,max96717f.yaml#
>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>> +
>> +title: MAX96717 CSI-2 to GMSL2 Serializer
>> +
>> +maintainers:
>> +  - Julien Massot <julien.massot@collabora.com>
>> +
>> +description: |
>> +  The MAX96717F serializer converts MIPI CSI-2 D-PHY or C-PHY formatted input
> 
> Presumably this will need to be configured on the device? You should thus
> require the bus-type property in the endpoint.

Yes I will add the bus-type propery

> 
>> +  into GMSL2 serial outputs. The device allows the GMSL2 link to
>> +  simultaneously transmit bidirectional control-channel data while forward
>> +  video transmissions are in progress. The MAX96717F can connect to one
>> +  remotely located deserializer using industry-standard coax or STP
>> +  interconnects. The device cans operate in pixel or tunnel mode. In pixel mode
>> +  the MAX96717F can select the MIPI datatype, while the tunnel mode forward all the MIPI
>> +  data received by the serializer.
>> +  The MAX96717F supports Reference Over Reverse (channel),
>> +  to generate a clock output for the sensor from the GMSL reverse channel.
>> +
>> +  The GMSL2 serial link operates at a fixed rate of 3Gbps in the
>> +  forward direction and 187.5Mbps in the reverse direction.
>> +
>> +properties:
>> +  compatible:
>> +    const: maxim,max96717f
>> +
>> +  '#gpio-cells':
>> +    const: 2
>> +    description:
>> +      First cell is the GPIO pin number, second cell is the flags. The GPIO pin
>> +      number must be in range of [0, 10].
>> +
>> +  gpio-controller: true
>> +
>> +  '#clock-cells':
>> +    const: 0
>> +
>> +  reg:
>> +    maxItems: 1
>> +
>> +  ports:
>> +    $ref: /schemas/graph.yaml#/properties/ports
>> +
>> +    properties:
>> +      port@0:
>> +        $ref: /schemas/graph.yaml#/$defs/port-base
>> +        unevaluatedProperties: false
>> +        description: CSI-2 Input port
>> +
>> +        properties:
>> +          endpoint:
>> +            $ref: /schemas/media/video-interfaces.yaml#
>> +            unevaluatedProperties: false
>> +
>> +            properties:
>> +              data-lanes:
>> +                minItems: 1
>> +                maxItems: 4
>> +
>> +            required:
>> +              - data-lanes
>> +
>> +      port@1:
>> +        $ref: /schemas/graph.yaml#/properties/port
>> +        unevaluatedProperties: false
>> +        description: GMSL Output port
>> +
>> +    required:
>> +      - port@1
>> +
>> +  i2c-gate:
>> +    $ref: /schemas/i2c/i2c-controller.yaml
>> +    unevaluatedProperties: false
>> +    description: |
> 
> I think you can remove ' |'.
Ok

> 
>> +      The MAX96717F will forward the I2C requests from the
>> +      incoming GMSL2 link. Therefore, it supports an i2c-gate
>> +      subnode to configure a sensor.
>> +
>> +required:
>> +  - compatible
>> +  - reg
>> +  - ports
>> +
>> +additionalProperties: false
>> +
>> +examples:
>> +  - |
>> +    #include <dt-bindings/gpio/gpio.h>
>> +    #include <dt-bindings/media/video-interfaces.h>
>> +
>> +    i2c {
>> +        #address-cells = <1>;
>> +        #size-cells = <0>;
>> +        serializer: serializer@40 {
>> +            compatible = "maxim,max96717f";
>> +            reg = <0x40>;
>> +            gpio-controller;
>> +            #gpio-cells = <2>;
>> +            #clock-cells = <0>;
>> +
>> +            ports {
>> +                #address-cells = <1>;
>> +                #size-cells = <0>;
>> +
>> +                port@0 {
>> +                    reg = <0>;
>> +                    max96717f_csi_in: endpoint {
>> +                        data-lanes = <1 2 3 4>;
>> +                        remote-endpoint = <&sensor_out>;
>> +                    };
>> +                };
>> +
>> +                port@1 {
>> +                    reg = <1>;
>> +                    max96917f_gmsl_out: endpoint {
>> +                        remote-endpoint = <&deser_gmsl_in>;
>> +                    };
>> +                };
>> +            };
>> +
>> +            i2c-gate {
>> +                #address-cells = <1>;
>> +                #size-cells = <0>;
>> +                sensor@10 {
>> +                    compatible = "st,st-vgxy61";
>> +                    reg = <0x10>;
>> +                    reset-gpios = <&serializer 0 GPIO_ACTIVE_LOW>;
>> +                    clocks = <&serializer>;
>> +                    VCORE-supply = <&v1v2>;
>> +                    VDDIO-supply = <&v1v8>;
>> +                    VANA-supply = <&v2v8>;
>> +                    port {
>> +                        sensor_out: endpoint {
>> +                            data-lanes = <1 2 3 4>;
>> +                            remote-endpoint = <&max96717f_csi_in>;
>> +                        };
>> +                    };
>> +                };
>> +            };
>> +        };
>> +    };
>> +...
> 

-- 
Julien Massot
Senior Software Engineer
Collabora Ltd.
Platinum Building, St John's Innovation Park, Cambridge CB4 0DS, UK
Registered in England & Wales, no. 5513718

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

* Re: [PATCH v3 3/4] media: i2c: add MAX96717 driver
  2024-02-09 15:26   ` Sakari Ailus
@ 2024-02-21 10:34     ` Julien Massot
  2024-02-21 11:04       ` Sakari Ailus
  0 siblings, 1 reply; 18+ messages in thread
From: Julien Massot @ 2024-02-21 10:34 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: linux-media, kernel, devicetree, linux-kernel, mchehab, robh+dt,
	krzysztof.kozlowski+dt, conor+dt

Hi Sakari,

On 2/9/24 16:26, Sakari Ailus wrote:
> Hi Julien,
> 
> On Thu, Jan 11, 2024 at 02:03:48PM +0100, Julien Massot wrote:
>> This driver handle the MAX96717 serializer in tunnel mode.
>> All incoming CSI traffic will be tunneled through the GMSL2
>> link.
>>
>> Signed-off-by: Julien Massot <julien.massot@collabora.com>
>> ---
>> Change since v2:
>>   - Use CCI helpers instead of recoding register access
>>   - add missing bitfield header
>> ---
>>   MAINTAINERS                  |   7 +
>>   drivers/media/i2c/Kconfig    |  13 +
>>   drivers/media/i2c/Makefile   |   1 +
>>   drivers/media/i2c/max96717.c | 956 +++++++++++++++++++++++++++++++++++
>>   4 files changed, 977 insertions(+)
>>   create mode 100644 drivers/media/i2c/max96717.c
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index 675e5d63a25b..a64a7932fe76 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -13039,6 +13039,13 @@ S:	Maintained
>>   F:	Documentation/devicetree/bindings/media/i2c/maxim,max96712.yaml
>>   F:	drivers/staging/media/max96712/max96712.c
>>   
>> +MAX96717 GMSL2 SERIALIZER DRIVER
>> +M:	Julien Massot <julien.massot@collabora.com>
>> +L:	linux-media@vger.kernel.org
>> +S:	Maintained
>> +F:	Documentation/devicetree/bindings/media/i2c/maxim,max96717f.yaml
> 
> What's that "f" for? It's in bindings but not in the name of the driver.
> Not a typo I suppose? :-)

Indeed that's not a typo, the Maxim's GMSL2 chips are available under 
multiple
variants:
- MAX96717 which supports GMSL link speed 6 and 3Gbps and CSI lanes up 
to 2.5Gbps
- MAX96717K which supports GMSL link speed 6 and 3Gbps and CSI lanes up 
to 1.5Gbps
- MAX96717F which only supports GMSL link speed 3Gbps and CSI lanes up 
to 2.5Gbps

They have the same register mapping, so we should be able to add support for
the other variants in the future.

> 
>> +F:	drivers/media/i2c/max96717.c
>> +
>>   MAX9860 MONO AUDIO VOICE CODEC DRIVER
>>   M:	Peter Rosin <peda@axentia.se>
>>   L:	alsa-devel@alsa-project.org (moderated for non-subscribers)
>> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
>> index 78a87331686e..64f6a35f4481 100644
>> --- a/drivers/media/i2c/Kconfig
>> +++ b/drivers/media/i2c/Kconfig
>> @@ -1570,6 +1570,19 @@ config VIDEO_DS90UB960
>>   	  Device driver for the Texas Instruments DS90UB960
>>   	  FPD-Link III Deserializer and DS90UB9702 FPD-Link IV Deserializer.
>>   
>> +config VIDEO_MAX96717
>> +	tristate "Maxim MAX96717 GMSL2 Serializer support"
>> +	depends on OF && I2C && VIDEO_DEV && COMMON_CLK
>> +	select I2C_MUX
>> +	select GPIOLIB
>> +	select V4L2_CCI_I2C
>> +	help
>> +	  Device driver for the Maxim MAX96717
>> +	  GMSL2 Serializer.
> 
> Fits on the previous line.
ok
> 
>> +
>> +	  To compile this driver as a module, choose M here: the
>> +	  module will be called max96717.
>> +
>>   endmenu
>>   
>>   endif # VIDEO_DEV
>> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
>> index dfbe6448b549..9e007116f929 100644
>> --- a/drivers/media/i2c/Makefile
>> +++ b/drivers/media/i2c/Makefile
>> @@ -64,6 +64,7 @@ obj-$(CONFIG_VIDEO_LM3646) += lm3646.o
>>   obj-$(CONFIG_VIDEO_M52790) += m52790.o
>>   obj-$(CONFIG_VIDEO_MAX9271_LIB) += max9271.o
>>   obj-$(CONFIG_VIDEO_MAX9286) += max9286.o
>> +obj-$(CONFIG_VIDEO_MAX96717) += max96717.o
>>   obj-$(CONFIG_VIDEO_ML86V7667) += ml86v7667.o
>>   obj-$(CONFIG_VIDEO_MSP3400) += msp3400.o
>>   obj-$(CONFIG_VIDEO_MT9M001) += mt9m001.o
>> diff --git a/drivers/media/i2c/max96717.c b/drivers/media/i2c/max96717.c
>> new file mode 100644
>> index 000000000000..700e50894250
>> --- /dev/null
>> +++ b/drivers/media/i2c/max96717.c
>> @@ -0,0 +1,956 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Maxim GMSL2 Serializer Driver
>> + *
>> + * Copyright (C) 2024 Collabora Ltd.
>> + */
>> +
>> +#include <linux/bitfield.h>
>> +#include <linux/clk.h>
>> +#include <linux/clk-provider.h>
>> +#include <linux/delay.h>
>> +#include <linux/fwnode.h>
>> +#include <linux/gpio/driver.h>
>> +#include <linux/i2c-mux.h>
>> +#include <linux/i2c.h>
>> +#include <linux/regmap.h>
>> +
>> +#include <media/v4l2-cci.h>
>> +#include <media/v4l2-fwnode.h>
>> +#include <media/v4l2-subdev.h>
>> +
>> +#define MAX96717F_DEVICE_ID 0xc8
>> +#define MAX96717_PORTS      2
>> +#define MAX96717_PAD_SINK   0
>> +#define MAX96717_PAD_SOURCE 1
>> +
>> +#define MAX96717_DEFAULT_CLKOUT_RATE	24000000UL
>> +
>> +/* DEV */
>> +#define REG3             CCI_REG8(0x3)
>> +#define MAX96717_RCLKSEL GENMASK(1, 0)
>> +#define RCLKSEL_REF_PLL  CCI_REG8(0x3)
>> +#define MAX96717_REG6    CCI_REG8(0x6)
>> +#define RCLKEN           BIT(5)
>> +#define MAX96717_DEV_ID  CCI_REG8(0xd)
>> +#define MAX96717_DEV_REV CCI_REG8(0xe)
>> +#define MAX96717_DEV_REV_MASK GENMASK(3, 0)
>> +
>> +/* VID_TX Z */
>> +#define MAX96717_VIDEO_TX2 CCI_REG8(0x112)
>> +#define MAX96717_VIDEO_PCLKDET BIT(7)
>> +
>> +/* GPIO */
>> +#define MAX96717_NUM_GPIO         11
>> +#define MAX96717_GPIO_REG_A(gpio) CCI_REG8(0x2be + (gpio) * 3)
>> +#define MAX96717_GPIO_OUT         BIT(4)
>> +#define MAX96717_GPIO_IN          BIT(3)
>> +#define MAX96717_GPIO_RX_EN       BIT(2)
>> +#define MAX96717_GPIO_TX_EN       BIT(1)
>> +#define MAX96717_GPIO_OUT_DIS     BIT(0)
>> +
>> +/* FRONTTOP */
>> +/* MAX96717 only have CSI port 'B' */
>> +#define MAX96717_FRONTOP0     CCI_REG8(0x308)
>> +#define MAX96717_START_PORT_B BIT(5)
>> +
>> +/* MIPI_RX */
>> +#define MAX96717_MIPI_RX1       CCI_REG8(0x331)
>> +#define MAX96717_MIPI_LANES_CNT GENMASK(5, 4)
>> +#define MAX96717_MIPI_RX2       CCI_REG8(0x332) /* phy1 Lanes map */
>> +#define MAX96717_PHY2_LANES_MAP GENMASK(7, 4)
>> +#define MAX96717_MIPI_RX3       CCI_REG8(0x333) /* phy2 Lanes map */
>> +#define MAX96717_PHY1_LANES_MAP GENMASK(3, 0)
>> +#define MAX96717_MIPI_RX4       CCI_REG8(0x334) /* phy1 lane polarities */
>> +#define MAX96717_PHY1_LANES_POL GENMASK(6, 4)
>> +#define MAX96717_MIPI_RX5       CCI_REG8(0x335) /* phy2 lane polarities */
>> +#define MAX96717_PHY2_LANES_POL GENMASK(2, 0)
>> +
>> +/* MIPI_RX_EXT */
>> +#define MAX96717_MIPI_RX_EXT11 CCI_REG8(0x383)
>> +#define MAX96717_TUN_MODE      BIT(7)
>> +
>> +/* REF_VTG */
>> +#define REF_VTG0                CCI_REG8(0x3f0)
>> +#define REFGEN_PREDEF_EN        BIT(6)
>> +#define REFGEN_PREDEF_FREQ_MASK GENMASK(5, 4)
>> +#define REFGEN_PREDEF_FREQ_ALT  BIT(3)
>> +#define REFGEN_RST              BIT(1)
>> +#define REFGEN_EN               BIT(0)
>> +
>> +/* MISC */
>> +#define PIO_SLEW_1 CCI_REG8(0x570)
>> +
>> +struct max96717_hw_data {
>> +	const char *model;
>> +	u8 device_id;
>> +};
>> +
>> +static const struct max96717_hw_data max96717f_data = {
>> +	.model = "max96717f",
>> +	.device_id = MAX96717F_DEVICE_ID,
>> +};
>> +
>> +struct max96717_priv {
>> +	const struct max96717_hw_data *data;
>> +	struct i2c_client             *client;
>> +	struct regmap                 *regmap;
>> +	struct i2c_mux_core           *mux;
>> +	struct v4l2_fwnode_endpoint   vep;
>> +	struct v4l2_subdev            sd;
>> +	struct media_pad              pads[MAX96717_PORTS];
>> +	struct v4l2_async_notifier    notifier;
>> +	struct v4l2_subdev            *source_sd;
>> +	u16                           source_sd_pad;
>> +	u64			      enabled_source_streams;
>> +	u8                            pll_predef_index;
>> +	struct clk_hw                 clk_hw;
>> +	struct gpio_chip              gpio_chip;
>> +};
>> +
>> +static inline struct max96717_priv *sd_to_max96717(struct v4l2_subdev *sd)
>> +{
>> +	return container_of(sd, struct max96717_priv, sd);
>> +}
>> +
>> +static inline struct max96717_priv *clk_hw_to_max96717(struct clk_hw *hw)
>> +{
>> +	return container_of(hw, struct max96717_priv, clk_hw);
>> +}
>> +
>> +static int max96717_i2c_mux_select(struct i2c_mux_core *mux, u32 chan)
>> +{
>> +	return 0;
>> +}
>> +
>> +static int max96717_i2c_mux_init(struct max96717_priv *priv)
>> +{
>> +	priv->mux = i2c_mux_alloc(priv->client->adapter, &priv->client->dev,
>> +				  1, 0, I2C_MUX_LOCKED | I2C_MUX_GATE,
>> +				  max96717_i2c_mux_select, NULL);
>> +	if (!priv->mux)
>> +		return -ENOMEM;
>> +
>> +	return i2c_mux_add_adapter(priv->mux, 0, 0, 0);
>> +}
>> +
>> +static inline int max96717_start_csi(struct max96717_priv *priv, bool start)
>> +{
>> +	return cci_update_bits(priv->regmap, MAX96717_FRONTOP0,
>> +			       MAX96717_START_PORT_B,
>> +			       start ? MAX96717_START_PORT_B : 0, NULL);
>> +}
>> +
>> +static int max96717_gpiochip_get(struct gpio_chip *gpiochip,
>> +				 unsigned int offset)
>> +{
>> +	struct max96717_priv *priv = gpiochip_get_data(gpiochip);
>> +	u64 val;
>> +	int ret;
>> +
>> +	ret = cci_read(priv->regmap, MAX96717_GPIO_REG_A(offset),
>> +		       &val, NULL);
>> +	if (ret)
>> +		return ret;
>> +
>> +	if (val & MAX96717_GPIO_OUT_DIS)
>> +		return !!(val & MAX96717_GPIO_IN);
>> +	else
>> +		return !!(val & MAX96717_GPIO_OUT);
>> +}
>> +
>> +static void max96717_gpiochip_set(struct gpio_chip *gpiochip,
>> +				  unsigned int offset, int value)
>> +{
>> +	struct max96717_priv *priv = gpiochip_get_data(gpiochip);
>> +
>> +	cci_update_bits(priv->regmap, MAX96717_GPIO_REG_A(offset),
>> +			MAX96717_GPIO_OUT, MAX96717_GPIO_OUT, NULL);
>> +}
>> +
>> +static int max96717_gpio_get_direction(struct gpio_chip *gpiochip,
>> +				       unsigned int offset)
>> +{
>> +	struct max96717_priv *priv = gpiochip_get_data(gpiochip);
>> +	u64 val;
>> +	int ret;
>> +
>> +	ret = cci_read(priv->regmap, MAX96717_GPIO_REG_A(offset), &val, NULL);
>> +	if (ret < 0)
>> +		return ret;
>> +
>> +	return !!(val & MAX96717_GPIO_OUT_DIS);
>> +}
>> +
>> +static int max96717_gpio_direction_out(struct gpio_chip *gpiochip,
>> +				       unsigned int offset, int value)
>> +{
>> +	struct max96717_priv *priv = gpiochip_get_data(gpiochip);
>> +
>> +	return cci_update_bits(priv->regmap, MAX96717_GPIO_REG_A(offset),
>> +			       MAX96717_GPIO_OUT_DIS | MAX96717_GPIO_OUT,
>> +			       value ? MAX96717_GPIO_OUT : 0, NULL);
>> +}
>> +
>> +static int max96717_gpio_direction_in(struct gpio_chip *gpiochip,
>> +				      unsigned int offset)
>> +{
>> +	struct max96717_priv *priv = gpiochip_get_data(gpiochip);
>> +
>> +	return cci_update_bits(priv->regmap, MAX96717_GPIO_REG_A(offset),
>> +			       MAX96717_GPIO_OUT_DIS, MAX96717_GPIO_OUT_DIS,
>> +			       NULL);
>> +}
>> +
>> +static int max96717_gpiochip_probe(struct max96717_priv *priv)
>> +{
>> +	struct device *dev = &priv->client->dev;
>> +	struct gpio_chip *gc = &priv->gpio_chip;
>> +	int ret, i;
>> +
>> +	gc->label = dev_name(dev);
>> +	gc->parent = dev;
>> +	gc->owner = THIS_MODULE;
>> +	gc->ngpio = MAX96717_NUM_GPIO;
>> +	gc->base = -1;
>> +	gc->can_sleep = true;
>> +	gc->get_direction = max96717_gpio_get_direction;
>> +	gc->direction_input = max96717_gpio_direction_in;
>> +	gc->direction_output = max96717_gpio_direction_out;
>> +	gc->set = max96717_gpiochip_set;
>> +	gc->get = max96717_gpiochip_get;
>> +	gc->of_gpio_n_cells = 2;
>> +
>> +	/* Disable GPIO forwarding */
>> +	for (i = 0; i < gc->ngpio; i++)
>> +		cci_update_bits(priv->regmap, MAX96717_GPIO_REG_A(i),
>> +				MAX96717_GPIO_RX_EN | MAX96717_GPIO_TX_EN,
>> +				0, &ret);
>> +
>> +	if (ret)
>> +		return ret;
>> +
>> +	ret = devm_gpiochip_add_data(dev, gc, priv);
>> +	if (ret) {
>> +		dev_err(dev, "Unable to create gpio_chip\n");
>> +		return ret;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static int _max96717_set_routing(struct v4l2_subdev *sd,
>> +				 struct v4l2_subdev_state *state,
>> +				 struct v4l2_subdev_krouting *routing)
>> +{
>> +	static const struct v4l2_mbus_framefmt format = {
>> +		.width = 1280,
>> +		.height = 1080,
>> +		.code = MEDIA_BUS_FMT_Y8_1X8,
>> +		.field = V4L2_FIELD_NONE,
>> +	};
>> +	int ret;
>> +
>> +	ret = v4l2_subdev_routing_validate(sd, routing,
>> +					   V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
>> +	if (ret)
>> +		return ret;
>> +
>> +	ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
>> +	if (ret)
>> +		return ret;
>> +
>> +	return 0;
>> +}
>> +
>> +static int max96717_set_routing(struct v4l2_subdev *sd,
>> +				struct v4l2_subdev_state *state,
>> +				enum v4l2_subdev_format_whence which,
>> +				struct v4l2_subdev_krouting *routing)
>> +{
>> +	struct max96717_priv *priv = sd_to_max96717(sd);
>> +
>> +	if (which == V4L2_SUBDEV_FORMAT_ACTIVE && priv->enabled_source_streams)
>> +		return -EBUSY;
>> +
>> +	return _max96717_set_routing(sd, state, routing);
>> +}
>> +
>> +static int max96717_set_fmt(struct v4l2_subdev *sd,
>> +			    struct v4l2_subdev_state *state,
>> +			    struct v4l2_subdev_format *format)
>> +{
>> +	struct max96717_priv *priv = sd_to_max96717(sd);
>> +	struct v4l2_mbus_framefmt *fmt;
>> +	u64 stream_source_mask;
>> +
>> +	if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE &&
>> +	    priv->enabled_source_streams)
>> +		return -EBUSY;
>> +
>> +	/* No transcoding, source and sink formats must match. */
>> +	if (format->pad == MAX96717_PAD_SOURCE)
>> +		return v4l2_subdev_get_fmt(sd, state, format);
>> +
>> +	/* Set sink format */
>> +	fmt = v4l2_subdev_state_get_format(state, format->pad, format->stream);
>> +	if (!fmt)
>> +		return -EINVAL;
>> +
>> +	*fmt = format->format;
>> +
>> +	/* Propagate to source format */
>> +	fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad,
>> +							   format->stream);
>> +	if (!fmt)
>> +		return -EINVAL;
>> +	*fmt = format->format;
>> +
>> +	stream_source_mask = BIT(format->stream);
>> +
>> +	return v4l2_subdev_state_xlate_streams(state, MAX96717_PAD_SOURCE,
>> +					       MAX96717_PAD_SINK,
>> +					       &stream_source_mask);
>> +}
>> +
>> +static int max96717_init_state(struct v4l2_subdev *sd,
>> +			       struct v4l2_subdev_state *state)
>> +{
>> +	struct v4l2_subdev_route routes[] = {
>> +		{
>> +			.sink_pad = MAX96717_PAD_SINK,
>> +			.sink_stream = 0,
>> +			.source_pad = MAX96717_PAD_SOURCE,
>> +			.source_stream = 0,
>> +			.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
>> +		},
>> +	};
>> +
> 
> Extra newline.
ok
> 
>> +	struct v4l2_subdev_krouting routing = {
>> +		.num_routes = ARRAY_SIZE(routes),
>> +		.routes = routes,
>> +	};
>> +
>> +	return _max96717_set_routing(sd, state, &routing);
>> +}
>> +
>> +static bool max96717_pipe_pclkdet(struct max96717_priv *priv)
>> +{
>> +	u64 val = 0;
>> +
>> +	cci_read(priv->regmap, MAX96717_VIDEO_TX2, &val, NULL);
>> +
>> +	return val & MAX96717_VIDEO_PCLKDET;
>> +}
>> +
>> +static int max96717_log_status(struct v4l2_subdev *sd)
>> +{
>> +	struct max96717_priv *priv = sd_to_max96717(sd);
>> +	struct device *dev = &priv->client->dev;
>> +
>> +	dev_info(dev, "Serializer: %s\n", priv->data->model);
>> +	dev_info(dev, "Pipe: pclkdet:%d\n", max96717_pipe_pclkdet(priv));
>> +
>> +	return 0;
>> +}
>> +
>> +static int max96717_enable_streams(struct v4l2_subdev *sd,
>> +				   struct v4l2_subdev_state *state, u32 pad,
>> +				   u64 streams_mask)
>> +{
>> +	struct max96717_priv *priv = sd_to_max96717(sd);
>> +	struct device *dev = &priv->client->dev;
>> +	u64 sink_streams;
>> +	int ret;
>> +
>> +	sink_streams = v4l2_subdev_state_xlate_streams(state,
>> +						       MAX96717_PAD_SOURCE,
>> +						       MAX96717_PAD_SINK,
>> +						       &streams_mask);
>> +
>> +	if (!priv->enabled_source_streams)
>> +		max96717_start_csi(priv, true);
>> +
>> +	ret = v4l2_subdev_enable_streams(priv->source_sd, priv->source_sd_pad,
>> +					 sink_streams);
>> +	if (ret) {
>> +		dev_err(dev, "Fail to start streams:%llu on remote subdev\n",
>> +			sink_streams);
>> +		goto stop_csi;
>> +	}
>> +
>> +	priv->enabled_source_streams |= streams_mask;
>> +
>> +	return 0;
>> +
>> +stop_csi:
>> +	if (!priv->enabled_source_streams)
>> +		max96717_start_csi(priv, false);
>> +	return ret;
>> +}
>> +
>> +static int max96717_disable_streams(struct v4l2_subdev *sd,
>> +				    struct v4l2_subdev_state *state, u32 pad,
>> +				    u64 streams_mask)
>> +{
>> +	struct max96717_priv *priv = sd_to_max96717(sd);
>> +	u64 sink_streams;
>> +	int ret;
>> +
>> +	sink_streams = v4l2_subdev_state_xlate_streams(state,
>> +						       MAX96717_PAD_SOURCE,
>> +						       MAX96717_PAD_SINK,
>> +						       &streams_mask);
>> +
>> +	ret = v4l2_subdev_disable_streams(priv->source_sd, priv->source_sd_pad,
>> +					  sink_streams);
>> +	if (ret)
>> +		return ret;
>> +
>> +	priv->enabled_source_streams &= ~streams_mask;
>> +
>> +	if (!priv->enabled_source_streams)
>> +		max96717_start_csi(priv, false);
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct v4l2_subdev_pad_ops max96717_pad_ops = {
>> +	.enable_streams = max96717_enable_streams,
>> +	.disable_streams = max96717_disable_streams,
>> +	.set_routing = max96717_set_routing,
>> +	.get_fmt = v4l2_subdev_get_fmt,
>> +	.set_fmt = max96717_set_fmt,
>> +};
>> +
>> +static const struct v4l2_subdev_core_ops max96717_subdev_core_ops = {
>> +	.log_status = max96717_log_status,
>> +};
>> +
>> +static const struct v4l2_subdev_internal_ops max96717_internal_ops = {
>> +	.init_state = max96717_init_state,
>> +};
>> +
>> +static const struct v4l2_subdev_ops max96717_subdev_ops = {
>> +	.core = &max96717_subdev_core_ops,
>> +	.pad = &max96717_pad_ops,
>> +};
>> +
>> +static const struct media_entity_operations max96717_entity_ops = {
>> +	.link_validate = v4l2_subdev_link_validate,
>> +};
>> +
>> +static int max96717_notify_bound(struct v4l2_async_notifier *notifier,
>> +				 struct v4l2_subdev *source_subdev,
>> +				 struct v4l2_async_connection *asd)
>> +{
>> +	struct max96717_priv *priv = sd_to_max96717(notifier->sd);
>> +	struct device *dev = &priv->client->dev;
>> +	int ret;
>> +
>> +	ret = media_entity_get_fwnode_pad(&source_subdev->entity,
>> +					  source_subdev->fwnode,
>> +					  MEDIA_PAD_FL_SOURCE);
>> +	if (ret < 0) {
>> +		dev_err(dev, "Failed to find pad for %s\n",
>> +			source_subdev->name);
>> +		return ret;
>> +	}
>> +
>> +	priv->source_sd = source_subdev;
>> +	priv->source_sd_pad = ret;
>> +
>> +	ret = media_create_pad_link(&source_subdev->entity, priv->source_sd_pad,
>> +				    &priv->sd.entity, 0,
>> +				    MEDIA_LNK_FL_ENABLED |
>> +				    MEDIA_LNK_FL_IMMUTABLE);
>> +	if (ret) {
>> +		dev_err(dev, "Unable to link %s:%u -> %s:0\n",
>> +			source_subdev->name, priv->source_sd_pad,
>> +			priv->sd.name);
>> +		return ret;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct v4l2_async_notifier_operations max96717_notify_ops = {
>> +	.bound = max96717_notify_bound,
>> +};
>> +
>> +static int max96717_v4l2_notifier_register(struct max96717_priv *priv)
>> +{
>> +	struct device *dev = &priv->client->dev;
>> +	struct v4l2_async_connection *asd;
>> +	struct fwnode_handle *ep_fwnode;
>> +	int ret;
>> +
>> +	ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
>> +						    MAX96717_PAD_SINK, 0, 0);
>> +	if (!ep_fwnode) {
>> +		dev_err(dev, "No graph endpoint\n");
>> +		return -ENODEV;
>> +	}
>> +
>> +	v4l2_async_subdev_nf_init(&priv->notifier, &priv->sd);
>> +
>> +	asd = v4l2_async_nf_add_fwnode_remote(&priv->notifier, ep_fwnode,
>> +					      struct v4l2_async_connection);
>> +
>> +	fwnode_handle_put(ep_fwnode);
>> +
>> +	if (IS_ERR(asd)) {
>> +		dev_err(dev, "Failed to add subdev: %ld", PTR_ERR(asd));
>> +		v4l2_async_nf_cleanup(&priv->notifier);
>> +		return PTR_ERR(asd);
>> +	}
>> +
>> +	priv->notifier.ops = &max96717_notify_ops;
>> +
>> +	ret = v4l2_async_nf_register(&priv->notifier);
>> +	if (ret) {
>> +		dev_err(dev, "Failed to register subdev_notifier");
>> +		v4l2_async_nf_cleanup(&priv->notifier);
>> +		return ret;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static void max96717_v4l2_notifier_unregister(struct max96717_priv *priv)
>> +{
>> +	v4l2_async_nf_unregister(&priv->notifier);
>> +	v4l2_async_nf_cleanup(&priv->notifier);
>> +}
>> +
>> +static int max96717_subdev_init(struct max96717_priv *priv)
>> +{
>> +	struct device *dev = &priv->client->dev;
>> +	int ret;
>> +
>> +	v4l2_i2c_subdev_init(&priv->sd, priv->client, &max96717_subdev_ops);
>> +	priv->sd.internal_ops = &max96717_internal_ops;
>> +
>> +	priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS;
>> +	priv->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
>> +	priv->sd.entity.ops = &max96717_entity_ops;
>> +
>> +	priv->pads[0].flags = MEDIA_PAD_FL_SINK;
>> +	priv->pads[1].flags = MEDIA_PAD_FL_SOURCE;
> 
> You have MAX96717_PAD_SINK and MAX96717_PAD_SOURCE as well.
Ok will use the MAX96717_PAD_ declarations

> 
>> +
>> +	ret = media_entity_pads_init(&priv->sd.entity, 2, priv->pads);
>> +	if (ret)
>> +		return dev_err_probe(dev, ret, "Failed to init pads\n");
>> +
>> +	ret = v4l2_subdev_init_finalize(&priv->sd);
>> +	if (ret) {
>> +		dev_err_probe(dev, ret,
>> +			      "v4l2 subdev init finalized failed\n");
>> +		goto err_fwnode_put;
>> +	}
>> +	ret = max96717_v4l2_notifier_register(priv);
>> +	if (ret) {
>> +		dev_err_probe(dev, ret,
>> +			      "v4l2 subdev notifier register failed\n");
>> +		goto err_free_state;
>> +	}
>> +
>> +	ret = v4l2_async_register_subdev(&priv->sd);
>> +	if (ret) {
>> +		dev_err_probe(dev, ret, "v4l2_async_register_subdev error\n");
>> +		goto err_unreg_notif;
>> +	}
>> +
>> +	return 0;
>> +
>> +err_unreg_notif:
>> +	max96717_v4l2_notifier_unregister(priv);
>> +err_free_state:
>> +	v4l2_subdev_cleanup(&priv->sd);
>> +err_fwnode_put:
>> +	fwnode_handle_put(priv->sd.fwnode);
> 
> Hmm. Does this belong here? Or... should it be done at all?
eh, we should not call fwnode_handle_put for the sd.fwnode Thanks!

> 
>> +	media_entity_cleanup(&priv->sd.entity);
>> +
>> +	return ret;
>> +}
>> +
>> +static void max96717_subdev_uninit(struct max96717_priv *priv)
>> +{
>> +	v4l2_async_unregister_subdev(&priv->sd);
>> +	max96717_v4l2_notifier_unregister(priv);
>> +	v4l2_subdev_cleanup(&priv->sd);
>> +	fwnode_handle_put(priv->sd.fwnode);
> 
> Same here.
Ok
> 
>> +	media_entity_cleanup(&priv->sd.entity);
>> +}
>> +
>> +struct max96717_pll_predef_freq {
>> +	unsigned long freq;
>> +	bool is_alt;
>> +	u8 val;
>> +};
>> +
>> +static const struct max96717_pll_predef_freq max96717_predef_freqs[] = {
>> +	{ 13500000, true,  0 }, { 19200000, false, 0 },
>> +	{ 24000000, true,  1 }, { 27000000, false, 1 },
>> +	{ 37125000, false, 2 }, { 74250000, false, 3 },
>> +};
>> +
>> +static unsigned long
>> +max96717_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
>> +{
>> +	struct max96717_priv *priv = clk_hw_to_max96717(hw);
>> +
>> +	return max96717_predef_freqs[priv->pll_predef_index].freq;
>> +}
>> +
>> +static unsigned int max96717_clk_find_best_index(struct max96717_priv *priv,
>> +						 unsigned long rate)
>> +{
>> +	u8 i, idx;
> 
> unsigned int please, and declare these after the diff* below.
ok
> 
>> +	unsigned long diff_new, diff_old;
>> +
>> +	diff_old = U32_MAX;
>> +	idx = 0;
>> +
>> +	for (i = 0; i < ARRAY_SIZE(max96717_predef_freqs); i++) {
>> +		diff_new = abs(rate - max96717_predef_freqs[i].freq);
>> +		if (diff_new < diff_old) {
>> +			diff_old = diff_new;
>> +			idx = i;
>> +		}
>> +	}
>> +
>> +	return idx;
>> +}
>> +
>> +static long max96717_clk_round_rate(struct clk_hw *hw, unsigned long rate,
>> +				    unsigned long *parent_rate)
>> +{
>> +	struct max96717_priv *priv = clk_hw_to_max96717(hw);
>> +	struct device *dev = &priv->client->dev;
>> +	u8 idx;
> 
> unsigned int.
ok
> 
>> +
>> +	idx = max96717_clk_find_best_index(priv, rate);
>> +
>> +	if (rate != max96717_predef_freqs[idx].freq) {
>> +		dev_warn(dev, "Request CLK freq:%lu, found CLK freq:%lu\n",
>> +			 rate, max96717_predef_freqs[idx].freq);
>> +	}
>> +
>> +	return max96717_predef_freqs[idx].freq;
>> +}
>> +
>> +static int max96717_clk_set_rate(struct clk_hw *hw, unsigned long rate,
>> +				 unsigned long parent_rate)
>> +{
>> +	struct max96717_priv *priv = clk_hw_to_max96717(hw);
>> +	int ret = 0;
>> +	u8 val, idx;
> 
> Ditto.
ok
> 
>> +
>> +	idx = max96717_clk_find_best_index(priv, rate);
>> +
>> +	val = FIELD_PREP(REFGEN_PREDEF_FREQ_MASK,
>> +			 max96717_predef_freqs[idx].val);
>> +
>> +	if (max96717_predef_freqs[idx].is_alt)
>> +		val |= REFGEN_PREDEF_FREQ_ALT;
>> +
>> +	val |= REFGEN_RST | REFGEN_PREDEF_EN;
>> +
>> +	cci_write(priv->regmap, REF_VTG0, val, &ret);
>> +	cci_update_bits(priv->regmap, REF_VTG0, REFGEN_RST | REFGEN_EN,
>> +			REFGEN_EN, &ret);
>> +	if (ret)
>> +		return ret;
>> +
>> +	priv->pll_predef_index = idx;
>> +
>> +	return 0;
>> +}
>> +
>> +static int max96717_clk_prepare(struct clk_hw *hw)
>> +{
>> +	struct max96717_priv *priv = clk_hw_to_max96717(hw);
>> +
>> +	return cci_update_bits(priv->regmap, MAX96717_REG6, RCLKEN,
>> +			       RCLKEN, NULL);
>> +}
>> +
>> +static void max96717_clk_unprepare(struct clk_hw *hw)
>> +{
>> +	struct max96717_priv *priv = clk_hw_to_max96717(hw);
>> +
>> +	cci_update_bits(priv->regmap, MAX96717_REG6, RCLKEN, 0, NULL);
>> +}
>> +
>> +static const struct clk_ops max96717_clk_ops = {
>> +	.prepare     = max96717_clk_prepare,
>> +	.unprepare   = max96717_clk_unprepare,
>> +	.set_rate    = max96717_clk_set_rate,
>> +	.recalc_rate = max96717_clk_recalc_rate,
>> +	.round_rate  = max96717_clk_round_rate,
>> +};
>> +
>> +static int max96717_register_clkout(struct max96717_priv *priv)
>> +{
>> +	struct device *dev = &priv->client->dev;
>> +	int ret;
>> +
> 
> Extra newline.
ok
> 
>> +	const struct clk_init_data init = {
>> +		.name = kasprintf(GFP_KERNEL, "%s.%s.clk_out",
>> +				  priv->data->model,
>> +				  dev_name(dev)),
> 
> Please move kasprintf() outside declaration. It's not nice to do things
> there that can fail.
ok
> 
>> +		.ops = &max96717_clk_ops,
>> +	};
> 
> This would be nicer declared before rett.
ok
> 
>> +
>> +	if (!init.name)
>> +		return -ENOMEM;
>> +
>> +	/* RCLKSEL Reference PLL output */
>> +	ret = cci_update_bits(priv->regmap, REG3, MAX96717_RCLKSEL,
>> +			      RCLKSEL_REF_PLL, NULL);
>> +	/* MFP4 fastest slew rate */
>> +	cci_update_bits(priv->regmap, PIO_SLEW_1, BIT(5) | BIT(4), 0, &ret);
>> +	if (ret)
>> +		goto free_init_name;
>> +
>> +	priv->clk_hw.init = &init;
>> +
>> +	/* Initialize to 24 MHz */
>> +	ret = max96717_clk_set_rate(&priv->clk_hw,
>> +				    MAX96717_DEFAULT_CLKOUT_RATE, 0);
>> +	if (ret < 0)
>> +		goto free_init_name;
>> +
>> +	ret = devm_clk_hw_register(dev, &priv->clk_hw);
>> +	kfree(init.name);
>> +	if (ret)
>> +		return dev_err_probe(dev, ret, "Cannot register clock HW\n");
>> +
>> +	ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get,
>> +					  &priv->clk_hw);
>> +	if (ret)
>> +		return dev_err_probe(dev, ret,
>> +				     "Cannot add OF clock provider\n");
>> +
>> +	return 0;
>> +
>> +free_init_name:
>> +	kfree(init.name);
>> +	return ret;
>> +}
>> +
>> +static int max96717_init_csi_lanes(struct max96717_priv *priv)
>> +{
>> +	struct v4l2_mbus_config_mipi_csi2 *mipi = &priv->vep.bus.mipi_csi2;
>> +	unsigned long lanes_used = 0;
>> +	u8 nlanes, lane, val = 0;
> 
> unsigned int.
ok
> 
>> +	int ret;
>> +
>> +	nlanes = mipi->num_data_lanes;
>> +
>> +	ret = cci_update_bits(priv->regmap, MAX96717_MIPI_RX1,
>> +			      MAX96717_MIPI_LANES_CNT,
>> +			      FIELD_PREP(MAX96717_MIPI_LANES_CNT,
>> +					 nlanes - 1), NULL);
>> +
>> +	/* lanes polarity */
>> +	for (lane = 0; lane < nlanes + 1; lane++) {
>> +		if (!mipi->lane_polarities[lane])
>> +			continue;
>> +		/* Clock lane */
>> +		if (lane == 0)
>> +			val |= BIT(2);
>> +		else if (lane < 3)
>> +			val |= BIT(lane - 1);
>> +		else
>> +			val |= BIT(lane);
>> +	}
>> +
>> +	cci_update_bits(priv->regmap, MAX96717_MIPI_RX5,
>> +			MAX96717_PHY2_LANES_POL,
>> +			FIELD_PREP(MAX96717_PHY2_LANES_POL, val), &ret);
>> +
>> +	cci_update_bits(priv->regmap, MAX96717_MIPI_RX4,
>> +			MAX96717_PHY1_LANES_POL,
>> +			FIELD_PREP(MAX96717_PHY1_LANES_POL,
>> +				   val >> 3), &ret);
>> +	/* lanes mapping */
>> +	val = 0;
>> +	for (lane = 0; lane < nlanes; lane++) {
> 
> You can initialise val in loop initialisation (i.e. lane = 0, val = 0).
ok
> 
>> +		val |= (mipi->data_lanes[lane] - 1) << (lane * 2);
>> +		lanes_used |= BIT(mipi->data_lanes[lane] - 1);
>> +	}
>> +
>> +	/* Unused lanes need to be mapped as well to not have
> 
> /*
>   * Multi-line
>   * comment.
>   */
> 
ok
>> +	 * the same lanes mapped twice.
>> +	 */
>> +	for (; lane < 4; lane++) {
>> +		unsigned int idx = find_first_zero_bit(&lanes_used, 4);
>> +
>> +		val |= idx << (lane * 2);
>> +		lanes_used |= BIT(idx);
>> +	}
>> +
>> +	cci_update_bits(priv->regmap, MAX96717_MIPI_RX3,
>> +			MAX96717_PHY1_LANES_MAP,
>> +			FIELD_PREP(MAX96717_PHY1_LANES_MAP, val), &ret);
>> +
>> +	return cci_update_bits(priv->regmap, MAX96717_MIPI_RX2,
>> +			       MAX96717_PHY2_LANES_MAP,
>> +			       FIELD_PREP(MAX96717_PHY2_LANES_MAP, val >> 4),
>> +			       &ret);
>> +}
>> +
>> +static int max96717_hw_init(struct max96717_priv *priv)
>> +{
>> +	struct device *dev = &priv->client->dev;
>> +	u64 val;
>> +	int ret;
>> +
>> +	ret = cci_read(priv->regmap, MAX96717_DEV_ID, &val, NULL);
>> +	if (ret)
>> +		return dev_err_probe(dev, ret,
>> +				     "Fail to read the device id\n");
>> +
>> +	if (val != priv->data->device_id)
>> +		return dev_err_probe(dev, -EOPNOTSUPP,
>> +				     "Unsupported device id expected %x got %x\n",
>> +				     priv->data->device_id, (u8)val);
>> +
>> +	ret = cci_read(priv->regmap, MAX96717_DEV_REV, &val, NULL);
>> +	if (ret)
>> +		return dev_err_probe(dev, ret,
>> +				     "Fail to read device revision");
>> +
>> +	dev_dbg(dev, "Found %x (rev %lx)\n", priv->data->device_id,
>> +		(u8)val & MAX96717_DEV_REV_MASK);
>> +
>> +	ret = cci_read(priv->regmap, MAX96717_MIPI_RX_EXT11, &val, NULL);
>> +	if (ret)
>> +		return dev_err_probe(dev, ret,
>> +				     "Fail to read mipi rx extension");
>> +
>> +	if (!(val & MAX96717_TUN_MODE))
>> +		return dev_err_probe(dev, -EOPNOTSUPP,
>> +				     "Only supporting tunnel mode");
>> +
>> +	return max96717_init_csi_lanes(priv);
>> +}
>> +
>> +static int max96717_parse_dt(struct max96717_priv *priv)
>> +{
>> +	struct device *dev = &priv->client->dev;
>> +	struct fwnode_handle *ep_fwnode;
>> +	unsigned char num_data_lanes;
>> +	int ret;
>> +
>> +	priv->vep.bus_type = V4L2_MBUS_CSI2_DPHY;
>> +
>> +	ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
>> +						    MAX96717_PAD_SINK, 0, 0);
>> +	if (!ep_fwnode)
>> +		return dev_err_probe(dev, -ENOENT, "no endpoint found\n");
>> +
>> +	ret = v4l2_fwnode_endpoint_parse(ep_fwnode, &priv->vep);
>> +
>> +	fwnode_handle_put(ep_fwnode);
>> +
>> +	if (ret < 0)
>> +		return dev_err_probe(dev, ret, "Failed to parse sink endpoint");
>> +
>> +	num_data_lanes = priv->vep.bus.mipi_csi2.num_data_lanes;
> 
> If all you need from the endpoint is the number of lanes and their
> polarities, then I'd store just that, not the entire endpoint.
ok
> 
>> +	if (num_data_lanes < 1 || num_data_lanes > 4)
>> +		return dev_err_probe(dev, -EINVAL,
>> +				     "Invalid data lanes should be 1 to 4\n");
> 
> s/should/must/
> s/be \K/from /
ok
> 
>> +
>> +	return 0;
>> +}
>> +
>> +static int max96717_probe(struct i2c_client *client)
>> +{
>> +	struct device *dev = &client->dev;
>> +	struct max96717_priv *priv;
>> +	int ret;
>> +
>> +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
>> +	if (!priv)
>> +		return -ENOMEM;
>> +
>> +	priv->client = client;
>> +
> 
> Extra newline.
ok
> 
>> +	priv->data = of_device_get_match_data(&client->dev);
> 
> We have device_get_match_data(), too. Up to you.
ok
> 
>> +
>> +	i2c_set_clientdata(client, priv);
>> +
>> +	priv->regmap = devm_cci_regmap_init_i2c(client, 16);
>> +	if (IS_ERR(priv->regmap)) {
>> +		ret = PTR_ERR(priv->regmap);
>> +		return dev_err_probe(dev, ret, "Failed to init regmap\n");
>> +	}
>> +
>> +	ret = max96717_parse_dt(priv);
>> +	if (ret)
>> +		return dev_err_probe(dev, ret, "Failed to parse the dt\n");
>> +
>> +	ret = max96717_hw_init(priv);
>> +	if (ret)
>> +		return dev_err_probe(dev, ret,
>> +				     "Failed to initialize the hardware\n");
>> +
>> +	ret = max96717_gpiochip_probe(priv);
>> +	if (ret) {
>> +		dev_err(&client->dev, "Failed to init gpiochip\n");
>> +		return ret;
>> +	}
>> +
>> +	ret = max96717_register_clkout(priv);
>> +	if (ret)
>> +		return dev_err_probe(dev, ret, "Failed to register clkout\n");
>> +
>> +	ret = max96717_subdev_init(priv);
>> +	if (ret)
>> +		return dev_err_probe(dev, ret,
>> +				     "Failed to initialize v4l2 subdev\n");
>> +
>> +	ret = max96717_i2c_mux_init(priv);
>> +	if (ret) {
>> +		dev_err_probe(dev, ret, "failed to add remote i2c adapter\n");
>> +		goto err_subdev_uninit;
> 
> No need for goto as you use the lable in a single location only. Move
> max96717_subdev_uninit() here.
ok
> 
>> +	}
>> +
>> +	return 0;
>> +
>> +err_subdev_uninit:
>> +	max96717_subdev_uninit(priv);
>> +	return ret;
>> +}
>> +
>> +static void max96717_remove(struct i2c_client *client)
>> +{
>> +	struct max96717_priv *priv = i2c_get_clientdata(client);
>> +
>> +	max96717_subdev_uninit(priv);
>> +	i2c_mux_del_adapters(priv->mux);
>> +}
>> +
>> +static const struct of_device_id max96717_of_ids[] = {
>> +	{ .compatible = "maxim,max96717f", .data = &max96717f_data },
>> +	{ }
>> +};
>> +MODULE_DEVICE_TABLE(of, max96717_of_ids);
>> +
>> +static struct i2c_driver max96717_i2c_driver = {
>> +	.driver	= {
>> +		.name		= "max96717",
>> +		.of_match_table	= max96717_of_ids,
>> +	},
>> +	.probe		= max96717_probe,
>> +	.remove		= max96717_remove,
>> +};
>> +
>> +module_i2c_driver(max96717_i2c_driver);
>> +
>> +MODULE_DESCRIPTION("Maxim GMSL2 MAX96717 Serializer Driver");
>> +MODULE_AUTHOR("Julien Massot <julien.massot@collabora.com>");
>> +MODULE_LICENSE("GPL");
> 

-- 
Julien Massot
Senior Software Engineer
Collabora Ltd.
Platinum Building, St John's Innovation Park, Cambridge CB4 0DS, UK
Registered in England & Wales, no. 5513718

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

* Re: [PATCH v3 4/4] media: i2c: add MAX96714 driver
  2024-02-09 15:49   ` Sakari Ailus
@ 2024-02-21 10:45     ` Julien Massot
  0 siblings, 0 replies; 18+ messages in thread
From: Julien Massot @ 2024-02-21 10:45 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: linux-media, kernel, devicetree, linux-kernel, mchehab, robh+dt,
	krzysztof.kozlowski+dt, conor+dt

Hi Sakari,

On 2/9/24 16:49, Sakari Ailus wrote:
> Hi Julien,
> 
> On Thu, Jan 11, 2024 at 02:03:49PM +0100, Julien Massot wrote:
>> This driver handle the MAX96714 deserializer in tunnel mode.
>> The CSI output will replicate all the CSI traffic forwarded by
>> the remote serializer.
>>
>> Signed-off-by: Julien Massot <julien.massot@collabora.com>
>> ---
>> Change since v2:
>>   - Use CCI helpers instead of recoding register access
>>   - add missing bitfield header
>>   - Add pattern generator so the deserializer can be tested without a serializer/sensor
>> ---
>>   MAINTAINERS                  |    7 +
>>   drivers/media/i2c/Kconfig    |   13 +
>>   drivers/media/i2c/Makefile   |    1 +
>>   drivers/media/i2c/max96714.c | 1077 ++++++++++++++++++++++++++++++++++
>>   4 files changed, 1098 insertions(+)
>>   create mode 100644 drivers/media/i2c/max96714.c
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index a64a7932fe76..a7691d38513a 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -13039,6 +13039,13 @@ S:	Maintained
>>   F:	Documentation/devicetree/bindings/media/i2c/maxim,max96712.yaml
>>   F:	drivers/staging/media/max96712/max96712.c
>>   
>> +MAX96714 GMSL2 DESERIALIZER DRIVER
>> +M:	Julien Massot <julien.massot@collabora.com>
>> +L:	linux-media@vger.kernel.org
>> +S:	Maintained
>> +F:	Documentation/devicetree/bindings/media/i2c/maxim,max96714f.yaml
>> +F:	drivers/media/i2c/max96714.c
>> +
>>   MAX96717 GMSL2 SERIALIZER DRIVER
>>   M:	Julien Massot <julien.massot@collabora.com>
>>   L:	linux-media@vger.kernel.org
>> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
>> index 64f6a35f4481..d0dfab513154 100644
>> --- a/drivers/media/i2c/Kconfig
>> +++ b/drivers/media/i2c/Kconfig
>> @@ -1570,6 +1570,19 @@ config VIDEO_DS90UB960
>>   	  Device driver for the Texas Instruments DS90UB960
>>   	  FPD-Link III Deserializer and DS90UB9702 FPD-Link IV Deserializer.
>>   
>> +config VIDEO_MAX96714
>> +	tristate "Maxim MAX96714 GMSL2 deserializer"
>> +	depends on OF && I2C && VIDEO_DEV
>> +	select I2C_MUX
>> +	select GPIOLIB
>> +	select V4L2_CCI_I2C
>> +	help
>> +	  Device driver for the Maxim MAX96714
>> +	  GMSL2 Deserializer.
>> +
>> +	  To compile this driver as a module, choose M here: the
>> +	  module will be called max96714.
>> +
>>   config VIDEO_MAX96717
>>   	tristate "Maxim MAX96717 GMSL2 Serializer support"
>>   	depends on OF && I2C && VIDEO_DEV && COMMON_CLK
>> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
>> index 9e007116f929..7c794441eaff 100644
>> --- a/drivers/media/i2c/Makefile
>> +++ b/drivers/media/i2c/Makefile
>> @@ -64,6 +64,7 @@ obj-$(CONFIG_VIDEO_LM3646) += lm3646.o
>>   obj-$(CONFIG_VIDEO_M52790) += m52790.o
>>   obj-$(CONFIG_VIDEO_MAX9271_LIB) += max9271.o
>>   obj-$(CONFIG_VIDEO_MAX9286) += max9286.o
>> +obj-$(CONFIG_VIDEO_MAX96714) += max96714.o
>>   obj-$(CONFIG_VIDEO_MAX96717) += max96717.o
>>   obj-$(CONFIG_VIDEO_ML86V7667) += ml86v7667.o
>>   obj-$(CONFIG_VIDEO_MSP3400) += msp3400.o
>> diff --git a/drivers/media/i2c/max96714.c b/drivers/media/i2c/max96714.c
>> new file mode 100644
>> index 000000000000..8bf1f5babf5d
>> --- /dev/null
>> +++ b/drivers/media/i2c/max96714.c
>> @@ -0,0 +1,1077 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Maxim GMSL2 Deserializer Driver
>> + *
>> + * Copyright (C) 2024 Collabora Ltd.
>> + */
>> +
>> +#include <linux/bitfield.h>
>> +#include <linux/bitops.h>
>> +#include <linux/fwnode.h>
>> +#include <linux/gpio/consumer.h>
>> +#include <linux/i2c.h>
>> +#include <linux/i2c-mux.h>
>> +#include <linux/module.h>
>> +#include <linux/regmap.h>
>> +#include <linux/regulator/consumer.h>
>> +
>> +#include <media/v4l2-cci.h>
>> +#include <media/v4l2-ctrls.h>
>> +#include <media/v4l2-fwnode.h>
>> +#include <media/v4l2-subdev.h>
>> +
>> +#define MAX96714F_DEVICE_ID 0xca
>> +#define MAX96714_NPORTS     2
>> +#define MAX96714_PAD_SINK   0
>> +#define MAX96714_PAD_SOURCE 1
>> +
>> +/* DEV */
>> +#define MAX96714_REG13            CCI_REG8(0x0d)
>> +#define MAX96714_DEV_REV          CCI_REG8(0x0e)
>> +#define MAX96714_DEV_REV_MASK     GENMASK(3, 0)
>> +#define MAX96714_LINK_LOCK        CCI_REG8(0x13)
>> +#define MAX96714_LINK_LOCK_BIT    BIT(3)
>> +#define MAX96714_IO_CHK0          CCI_REG8(0x38)
>> +#define MAX96714_PATTERN_CLK_FREQ GENMASK(1, 0)
>> +/* VID_RX */
>> +#define MAX96714_VIDEO_RX8        CCI_REG8(0x11a)
>> +#define MAX96714_VID_LOCK         BIT(6)
>> +
>> +/* VRX_PATGEN_0 */
>> +#define MAX96714_PATGEN_0              CCI_REG8(0x240)
>> +#define MAX96714_PATGEN_1              CCI_REG8(0x241)
>> +#define MAX96714_PATGEN_MODE           GENMASK(5, 4)
>> +#define MAX96714_PATGEN_VS_DLY         CCI_REG24(0x242)
>> +#define MAX96714_PATGEN_VS_HIGH        CCI_REG24(0x245)
>> +#define MAX96714_PATGEN_VS_LOW         CCI_REG24(0x248)
>> +#define MAX96714_PATGEN_V2H            CCI_REG24(0x24b)
>> +#define MAX96714_PATGEN_HS_HIGH        CCI_REG16(0x24e)
>> +#define MAX96714_PATGEN_HS_LOW	       CCI_REG16(0x250)
>> +#define MAX96714_PATGEN_HS_CNT	       CCI_REG16(0x252)
>> +#define MAX96714_PATGEN_V2D            CCI_REG24(0x254)
>> +#define MAX96714_PATGEN_DE_HIGH        CCI_REG16(0x257)
>> +#define MAX96714_PATGEN_DE_LOW         CCI_REG16(0x259)
>> +#define MAX96714_PATGEN_DE_CNT         CCI_REG16(0x25B)
>> +#define MAX96714_PATGEN_GRAD_INC       CCI_REG8(0x25d)
>> +#define MAX96714_PATGEN_CHKB_COLOR_A   CCI_REG24(0x25E)
>> +#define MAX96714_PATGEN_CHKB_COLOR_B   CCI_REG24(0x261)
>> +#define MAX96714_PATGEN_CHKB_RPT_CNT_A CCI_REG8(0x264)
>> +#define MAX96714_PATGEN_CHKB_RPT_CNT_B CCI_REG8(0x265)
>> +#define MAX96714_PATGEN_CHKB_ALT       CCI_REG8(0x266)
>> +/* BACKTOP */
>> +#define MAX96714_BACKTOP25 CCI_REG8(0x320)
>> +#define CSI_DPLL_FREQ_MASK GENMASK(4, 0)
>> +
>> +/* MIPI_PHY */
>> +#define MAX96714_MIPI_PHY0       CCI_REG8(0x330)
>> +#define MAX96714_FORCE_CSI_OUT   BIT(7)
>> +#define MAX96714_MIPI_STDBY_N    CCI_REG8(0x332)
>> +#define MAX96714_MIPI_STDBY_MASK GENMASK(5, 4)
>> +#define MAX96714_MIPI_LANE_MAP   CCI_REG8(0x333)
>> +#define MAX96714_MIPI_POLARITY   CCI_REG8(0x335)
>> +#define MAX96714_MIPI_POLARITY_MASK GENMASK(5, 0)
>> +
>> +/* MIPI_TX */
>> +#define MAX96714_MIPI_LANE_CNT CCI_REG8(0x44a)
>> +#define MAX96714_CSI2_LANE_CNT_MASK GENMASK(7, 6)
>> +#define MAX96714_MIPI_TX52 CCI_REG8(0x474)
>> +#define MAX96714_TUN_EN BIT(0)
> 
> It'd be nice to align the macro expansions above.
Ok will do
> 
>> +
>> +#define MHZ(v) ((u32)((v)  * 1000000U))
>> +
>> +enum max96714_vpg_mode {
>> +	MAX96714_VPG_DISABLED = 0,
>> +	MAX96714_VPG_CHECKERBOARD = 1,
>> +	MAX96714_VPG_GRADIENT = 2,
>> +};
>> +
>> +struct max96714_rxport {
>> +	struct {
>> +		struct v4l2_subdev   *sd;
>> +		u16 pad;
>> +		struct fwnode_handle *ep_fwnode;
>> +	} source;
>> +	struct regulator	     *poc;
>> +};
>> +
>> +struct max96714_txport {
>> +	struct v4l2_fwnode_endpoint vep;
>> +};
>> +
>> +struct max96714_priv {
>> +	struct i2c_client *client;
>> +	struct regmap     *regmap;
>> +	struct gpio_desc  *pd_gpio;
>> +	struct max96714_rxport rxport;
>> +	struct i2c_mux_core *mux;
>> +	u64 enabled_source_streams;
>> +	struct v4l2_subdev	sd;
>> +	struct media_pad	pads[MAX96714_NPORTS];
>> +	struct v4l2_fwnode_endpoint vep;
>> +	struct v4l2_ctrl_handler   ctrl_handler;
>> +	struct v4l2_async_notifier notifier;
>> +	s64 tx_link_freq;
>> +	enum max96714_vpg_mode pattern;
>> +};
>> +
>> +static struct regmap_config max96714_regmap_config = {
>> +	.name = "max96714",
>> +	.reg_bits = 16,
>> +	.val_bits = 8,
>> +	.max_register = 0xFFFF,
>> +};
>> +
>> +static inline struct max96714_priv *sd_to_max96714(struct v4l2_subdev *sd)
>> +{
>> +	return container_of(sd, struct max96714_priv, sd);
>> +}
>> +
>> +static int max96714_enable_tx_port(struct max96714_priv *priv)
>> +{
>> +	return cci_update_bits(priv->regmap, MAX96714_MIPI_STDBY_N,
>> +			       MAX96714_MIPI_STDBY_MASK,
>> +			       MAX96714_MIPI_STDBY_MASK, NULL);
>> +}
>> +
>> +static int max96714_disable_tx_port(struct max96714_priv *priv)
>> +{
>> +	return cci_update_bits(priv->regmap, MAX96714_MIPI_STDBY_N,
>> +			       MAX96714_MIPI_STDBY_MASK, 0, NULL);
>> +}
>> +
>> +static bool max96714_tx_port_enabled(struct max96714_priv *priv)
>> +{
>> +	u64 val;
>> +
>> +	cci_read(priv->regmap, MAX96714_MIPI_STDBY_N, &val, NULL);
>> +
>> +	return val & MAX96714_MIPI_STDBY_MASK;
>> +}
>> +
>> +static int max96714_apply_patgen_timing(struct max96714_priv *priv,
>> +					struct v4l2_subdev_state *state)
>> +{
>> +	struct v4l2_mbus_framefmt *fmt =
>> +		v4l2_subdev_state_get_format(state, MAX96714_PAD_SOURCE);
>> +	const u32 h_active = fmt->width;
>> +	const u32 h_fp = 88;
>> +	const u32 h_sw = 44;
>> +	const u32 h_bp = 148;
>> +	u32 h_tot;
>> +
>> +	const u32 v_active = fmt->height;
>> +	const u32 v_fp = 4;
>> +	const u32 v_sw = 5;
>> +	const u32 v_bp = 36;
>> +	u32 v_tot;
>> +	int ret = 0;
>> +
>> +	h_tot = h_active + h_fp + h_sw + h_bp;
>> +	v_tot = v_active + v_fp + v_sw + v_bp;
>> +
>> +	/* 75 Mhz pixel clock */
>> +	cci_update_bits(priv->regmap, MAX96714_IO_CHK0,
>> +			MAX96714_PATTERN_CLK_FREQ, 1, &ret);
>> +
>> +	dev_info(&priv->client->dev, "height: %d width: %d\n", fmt->height,
>> +		 fmt->width);
>> +
>> +	cci_write(priv->regmap, MAX96714_PATGEN_VS_DLY, 0, &ret);
>> +	cci_write(priv->regmap, MAX96714_PATGEN_VS_HIGH, v_sw * h_tot, &ret);
>> +	cci_write(priv->regmap, MAX96714_PATGEN_VS_LOW,
>> +		  (v_active + v_fp + v_bp) * h_tot, &ret);
>> +	cci_write(priv->regmap, MAX96714_PATGEN_HS_HIGH, h_sw, &ret);
>> +	cci_write(priv->regmap, MAX96714_PATGEN_HS_LOW, h_active + h_fp + h_bp,
>> +		  &ret);
>> +	cci_write(priv->regmap, MAX96714_PATGEN_V2D,
>> +		  h_tot * (v_sw + v_bp) + (h_sw + h_bp), &ret);
>> +	cci_write(priv->regmap, MAX96714_PATGEN_HS_CNT, v_tot, &ret);
>> +	cci_write(priv->regmap, MAX96714_PATGEN_DE_HIGH, h_active, &ret);
>> +	cci_write(priv->regmap, MAX96714_PATGEN_DE_LOW, h_fp + h_sw + h_bp,
>> +		  &ret);
>> +	cci_write(priv->regmap, MAX96714_PATGEN_DE_CNT, v_active, &ret);
>> +	/* B G R */
>> +	cci_write(priv->regmap, MAX96714_PATGEN_CHKB_COLOR_A, 0xfecc00, &ret);
>> +	/* B G R */
>> +	cci_write(priv->regmap, MAX96714_PATGEN_CHKB_COLOR_B, 0x006aa7, &ret);
>> +	cci_write(priv->regmap, MAX96714_PATGEN_CHKB_RPT_CNT_A, 0x3c, &ret);
>> +	cci_write(priv->regmap, MAX96714_PATGEN_CHKB_RPT_CNT_B, 0x3c, &ret);
>> +	cci_write(priv->regmap, MAX96714_PATGEN_CHKB_ALT, 0x3c, &ret);
>> +	cci_write(priv->regmap, MAX96714_PATGEN_GRAD_INC, 0x10, &ret);
>> +
>> +	return ret;
>> +}
>> +
>> +static int max96714_apply_patgen(struct max96714_priv *priv,
>> +				 struct v4l2_subdev_state *state)
>> +{
>> +	int ret = 0;
>> +	u8 val;
>> +
>> +	if (priv->pattern)
>> +		ret = max96714_apply_patgen_timing(priv, state);
>> +
>> +	cci_write(priv->regmap, MAX96714_PATGEN_0, priv->pattern ? 0xfb : 0,
>> +		  &ret);
>> +
>> +	val = FIELD_PREP(MAX96714_PATGEN_MODE, priv->pattern);
>> +	cci_update_bits(priv->regmap, MAX96714_PATGEN_1, MAX96714_PATGEN_MODE,
>> +			val, &ret);
>> +	return ret;
>> +}
>> +
>> +static int max96714_s_ctrl(struct v4l2_ctrl *ctrl)
>> +{
>> +	struct max96714_priv *priv =
>> +		container_of(ctrl->handler, struct max96714_priv, ctrl_handler);
>> +	int ret;
>> +
>> +	switch (ctrl->id) {
>> +	case V4L2_CID_TEST_PATTERN:
>> +		priv->pattern = ctrl->val;
>> +		break;
>> +	default:
>> +		return -EINVAL;
>> +	}
>> +
>> +	ret = cci_update_bits(priv->regmap, MAX96714_MIPI_PHY0,
>> +			      MAX96714_FORCE_CSI_OUT,
>> +			      priv->pattern ? MAX96714_FORCE_CSI_OUT : 0, NULL);
>> +
>> +	/* Pattern generator doesn't work with tunnel mode */
>> +	return cci_update_bits(priv->regmap, MAX96714_MIPI_TX52,
>> +			       MAX96714_TUN_EN,
>> +			       priv->pattern ? 0 : MAX96714_TUN_EN, &ret);
>> +}
>> +
>> +static const char * const max96714_test_pattern[] = {
>> +	"Disabled",
>> +	"Checkerboard",
>> +	"Gradient"
>> +};
>> +
>> +static const struct v4l2_ctrl_ops max96714_ctrl_ops = {
>> +	.s_ctrl = max96714_s_ctrl,
>> +};
>> +
>> +static int max96714_enable_streams(struct v4l2_subdev *sd,
>> +				   struct v4l2_subdev_state *state,
>> +				   u32 source_pad, u64 streams_mask)
>> +{
>> +	struct max96714_priv *priv = sd_to_max96714(sd);
>> +	u64 sink_streams;
>> +	int ret;
>> +
>> +	if (!priv->enabled_source_streams)
>> +		max96714_enable_tx_port(priv);
>> +
>> +	ret = max96714_apply_patgen(priv, state);
>> +	if (ret)
>> +		goto err;
>> +
>> +	if (!priv->pattern) {
>> +		if (!priv->rxport.source.sd) {
>> +			ret = -ENODEV;
>> +			goto err;
>> +		}
>> +
>> +		sink_streams =
>> +			v4l2_subdev_state_xlate_streams(state,
>> +							MAX96714_PAD_SOURCE,
>> +							MAX96714_PAD_SINK,
>> +							&streams_mask);
>> +
>> +		ret = v4l2_subdev_enable_streams(priv->rxport.source.sd,
>> +						 priv->rxport.source.pad,
>> +						 sink_streams);
>> +		if (ret)
>> +			goto err;
>> +	}
>> +
>> +	priv->enabled_source_streams |= streams_mask;
>> +
>> +	return 0;
>> +
>> +err:
>> +	if (!priv->enabled_source_streams)
>> +		max96714_disable_tx_port(priv);
>> +
>> +	return ret;
>> +}
>> +
>> +static int max96714_disable_streams(struct v4l2_subdev *sd,
>> +				    struct v4l2_subdev_state *state,
>> +				    u32 source_pad, u64 streams_mask)
>> +{
>> +	struct max96714_priv *priv = sd_to_max96714(sd);
>> +	u64 sink_streams;
>> +	int ret;
>> +
>> +	if (!priv->pattern && priv->rxport.source.sd) {
>> +		sink_streams =
>> +			v4l2_subdev_state_xlate_streams(state,
>> +							MAX96714_PAD_SOURCE,
>> +							MAX96714_PAD_SINK,
>> +							&streams_mask);
>> +
>> +		ret = v4l2_subdev_disable_streams(priv->rxport.source.sd,
>> +						  priv->rxport.source.pad,
>> +						  sink_streams);
>> +		if (ret)
>> +			return ret;
>> +	}
>> +
>> +	priv->enabled_source_streams &= ~streams_mask;
>> +
>> +	if (!priv->enabled_source_streams)
>> +		max96714_disable_tx_port(priv);
>> +
>> +	return 0;
>> +}
>> +
>> +static int max96714_s_stream(struct v4l2_subdev *subdev, int enable)
>> +{
>> +	int ret;
>> +	u64 streams_mask = BIT(0);
>> +
>> +	if (enable)
>> +		ret = v4l2_subdev_enable_streams(subdev, MAX96714_PAD_SOURCE,
>> +						 streams_mask);
>> +	else
>> +		ret = v4l2_subdev_disable_streams(subdev, MAX96714_PAD_SOURCE,
>> +						  streams_mask);
>> +	return ret;
>> +}
>> +
>> +static int max96714_set_fmt(struct v4l2_subdev *sd,
>> +			    struct v4l2_subdev_state *state,
>> +			    struct v4l2_subdev_format *format)
>> +{
>> +	struct max96714_priv *priv = sd_to_max96714(sd);
>> +	struct v4l2_mbus_framefmt *fmt;
>> +
>> +	if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE &&
>> +	    priv->enabled_source_streams)
>> +		return -EBUSY;
>> +
>> +	/* No transcoding, source and sink formats must match. */
>> +	if (format->pad == MAX96714_PAD_SOURCE)
>> +		return v4l2_subdev_get_fmt(sd, state, format);
>> +
>> +	fmt = v4l2_subdev_state_get_format(state, format->pad, format->stream);
>> +	if (!fmt)
>> +		return -EINVAL;
>> +
>> +	*fmt = format->format;
>> +
>> +	fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad,
>> +							   format->stream);
>> +	if (!fmt)
>> +		return -EINVAL;
>> +
>> +	*fmt = format->format;
>> +
>> +	return 0;
>> +}
>> +
>> +static int _max96714_set_routing(struct v4l2_subdev *sd,
>> +				 struct v4l2_subdev_state *state,
>> +				 enum v4l2_subdev_format_whence which,
>> +				 struct v4l2_subdev_krouting *routing)
>> +{
>> +	int ret;
> 
> Please declare ret as last.
Ok
> 
>> +
> 
> Extra newline.
Ok
> 
>> +	static const struct v4l2_mbus_framefmt format = {
>> +		.width = 1280,
>> +		.height = 1080,
>> +		.code = MEDIA_BUS_FMT_Y8_1X8,
>> +		.field = V4L2_FIELD_NONE,
>> +	};
>> +
>> +	/*
>> +	 * Note: we can only support up to V4L2_FRAME_DESC_ENTRY_MAX, until
>> +	 * frame desc is made dynamically allocated.
>> +	 */
>> +	if (routing->num_routes > V4L2_FRAME_DESC_ENTRY_MAX)
>> +		return -EINVAL;
>> +
>> +	ret = v4l2_subdev_routing_validate(sd, routing,
>> +					   V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
>> +	if (ret)
>> +		return ret;
>> +
>> +	ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
> 
> 	return ...;
Ok
> 
>> +	if (ret)
>> +		return ret;
>> +
>> +	return 0;
>> +}
>> +
>> +static int max96714_set_routing(struct v4l2_subdev *sd,
>> +				struct v4l2_subdev_state *state,
>> +				enum v4l2_subdev_format_whence which,
>> +				struct v4l2_subdev_krouting *routing)
>> +{
>> +	struct max96714_priv *priv = sd_to_max96714(sd);
>> +
>> +	if (which == V4L2_SUBDEV_FORMAT_ACTIVE && priv->enabled_source_streams)
>> +		return -EBUSY;
>> +
>> +	return _max96714_set_routing(sd, state, which, routing);
>> +}
>> +
>> +static int max96714_init_state(struct v4l2_subdev *sd,
>> +			       struct v4l2_subdev_state *state)
>> +{
>> +	struct v4l2_subdev_route routes[] = {
>> +		{
>> +			.sink_pad = MAX96714_PAD_SINK,
>> +			.sink_stream = 0,
>> +			.source_pad = MAX96714_PAD_SOURCE,
>> +			.source_stream = 0,
>> +			.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
>> +		}
>> +	};
>> +
> 
> Extra newline.
Ok
> 
>> +	struct v4l2_subdev_krouting routing = {
>> +		.num_routes = ARRAY_SIZE(routes),
>> +		.routes = routes,
>> +	};
>> +
>> +	return _max96714_set_routing(sd, state, V4L2_SUBDEV_FORMAT_ACTIVE,
>> +				     &routing);
>> +}
>> +
>> +static const struct v4l2_subdev_pad_ops max96714_pad_ops = {
>> +	.enable_streams = max96714_enable_streams,
>> +	.disable_streams = max96714_disable_streams,
>> +
>> +	.set_routing = max96714_set_routing,
>> +	.get_fmt = v4l2_subdev_get_fmt,
>> +	.set_fmt = max96714_set_fmt,
>> +};
>> +
>> +static bool max96714_link_locked(struct max96714_priv *priv)
>> +{
>> +	u64 val = 0;
>> +
>> +	cci_read(priv->regmap, MAX96714_LINK_LOCK, &val, NULL);
>> +
>> +	return val & MAX96714_LINK_LOCK_BIT;
>> +}
>> +
>> +static void max96714_link_status(struct max96714_priv *priv)
>> +{
>> +	struct device *dev = &priv->client->dev;
>> +
>> +	dev_info(dev, "Link locked:%d\n", max96714_link_locked(priv));
>> +}
>> +
>> +static bool max96714_pipe_locked(struct max96714_priv *priv)
>> +{
>> +	u64 val;
>> +
>> +	cci_read(priv->regmap, MAX96714_VIDEO_RX8, &val, NULL);
>> +
>> +	return val & MAX96714_VID_LOCK;
>> +}
>> +
>> +static void max96714_pipe_status(struct max96714_priv *priv)
>> +{
>> +	struct device *dev = &priv->client->dev;
>> +
>> +	dev_info(dev, "Pipe vidlock:%d\n", max96714_pipe_locked(priv));
>> +}
>> +
>> +static void max96714_csi_status(struct max96714_priv *priv)
>> +{
>> +	struct device *dev = &priv->client->dev;
>> +	u64 freq = 0;
>> +
>> +	cci_read(priv->regmap, MAX96714_BACKTOP25, &freq, NULL);
>> +	freq = FIELD_GET(CSI_DPLL_FREQ_MASK, freq);
>> +
>> +	dev_info(dev, "CSI controller DPLL freq:%u00MHz CSIPHY enabled:%d\n",
>> +		 (u8)freq, max96714_tx_port_enabled(priv));
>> +}
>> +
>> +static int max96714_log_status(struct v4l2_subdev *sd)
>> +{
>> +	struct max96714_priv *priv = sd_to_max96714(sd);
>> +	struct device *dev = &priv->client->dev;
>> +
>> +	dev_info(dev, "Deserializer: max96714\n");
>> +
>> +	max96714_link_status(priv);
>> +	max96714_pipe_status(priv);
>> +	max96714_csi_status(priv);
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct v4l2_subdev_core_ops max96714_subdev_core_ops = {
>> +	.log_status = max96714_log_status,
>> +};
>> +
>> +static const struct v4l2_subdev_video_ops max96714_video_ops = {
>> +	.s_stream	= max96714_s_stream,

Patch v4 will use v4l2_subdev_s_stream_helper instead of reimplementing
the function here.

>> +};
>> +
>> +static const struct v4l2_subdev_internal_ops max96714_internal_ops = {
>> +	.init_state = max96714_init_state,
>> +};
>> +
>> +static const struct v4l2_subdev_ops max96714_subdev_ops = {
>> +	.video = &max96714_video_ops,
>> +	.core = &max96714_subdev_core_ops,
>> +	.pad = &max96714_pad_ops,
>> +};
>> +
>> +static const struct media_entity_operations max96714_entity_ops = {
>> +	.link_validate = v4l2_subdev_link_validate,
>> +};
>> +
>> +static int max96714_notify_bound(struct v4l2_async_notifier *notifier,
>> +				 struct v4l2_subdev *subdev,
>> +				 struct v4l2_async_connection *asd)
>> +{
>> +	struct max96714_priv *priv = sd_to_max96714(notifier->sd);
>> +	struct device *dev = &priv->client->dev;
>> +	int ret;
>> +
>> +	ret = media_entity_get_fwnode_pad(&subdev->entity,
>> +					  priv->rxport.source.ep_fwnode,
>> +					  MEDIA_PAD_FL_SOURCE);
>> +	if (ret < 0) {
>> +		dev_err(dev, "Failed to find pad for %s\n", subdev->name);
>> +		return ret;
>> +	}
>> +
>> +	priv->rxport.source.sd = subdev;
>> +	priv->rxport.source.pad = ret;
>> +
>> +	ret = media_create_pad_link(&priv->rxport.source.sd->entity,
>> +				    priv->rxport.source.pad, &priv->sd.entity,
>> +				    MAX96714_PAD_SINK,
>> +				    MEDIA_LNK_FL_ENABLED |
>> +				    MEDIA_LNK_FL_IMMUTABLE);
>> +	if (ret) {
>> +		dev_err(dev, "Unable to link %s:%u -> %s:%u\n",
>> +			priv->rxport.source.sd->name, priv->rxport.source.pad,
>> +			priv->sd.name, MAX96714_PAD_SINK);
>> +		return ret;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct v4l2_async_notifier_operations max96714_notify_ops = {
>> +	.bound = max96714_notify_bound,
>> +};
>> +
>> +static int max96714_v4l2_notifier_register(struct max96714_priv *priv)
>> +{
>> +	struct device *dev = &priv->client->dev;
>> +	struct max96714_rxport *rxport = &priv->rxport;
>> +	struct v4l2_async_connection *asd;
>> +	int ret;
>> +
>> +	if (!rxport->source.ep_fwnode)
>> +		return 0;
>> +
>> +	v4l2_async_subdev_nf_init(&priv->notifier, &priv->sd);
>> +
>> +	asd = v4l2_async_nf_add_fwnode(&priv->notifier,
>> +				       rxport->source.ep_fwnode,
>> +				       struct v4l2_async_connection);
>> +	if (IS_ERR(asd)) {
>> +		dev_err(dev, "Failed to add subdev: %pe", asd);
>> +		v4l2_async_nf_cleanup(&priv->notifier);
>> +		return PTR_ERR(asd);
>> +	}
>> +
>> +	priv->notifier.ops = &max96714_notify_ops;
>> +
>> +	ret = v4l2_async_nf_register(&priv->notifier);
>> +	if (ret) {
>> +		dev_err(dev, "Failed to register subdev_notifier");
>> +		v4l2_async_nf_cleanup(&priv->notifier);
>> +		return ret;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static void max96714_v4l2_notifier_unregister(struct max96714_priv *priv)
>> +{
>> +	v4l2_async_nf_unregister(&priv->notifier);
>> +	v4l2_async_nf_cleanup(&priv->notifier);
>> +}
>> +
>> +static int max96714_create_subdev(struct max96714_priv *priv)
>> +{
>> +	struct device *dev = &priv->client->dev;
>> +	int ret;
>> +
>> +	v4l2_i2c_subdev_init(&priv->sd, priv->client, &max96714_subdev_ops);
>> +	priv->sd.internal_ops = &max96714_internal_ops;
>> +
>> +	v4l2_ctrl_handler_init(&priv->ctrl_handler, 1);
>> +	priv->sd.ctrl_handler = &priv->ctrl_handler;
>> +
>> +	priv->tx_link_freq = priv->vep.link_frequencies[0];
>> +	if (priv->tx_link_freq < 0)
>> +		return priv->tx_link_freq;
>> +
>> +	v4l2_ctrl_new_int_menu(&priv->ctrl_handler, NULL, V4L2_CID_LINK_FREQ,
>> +			       0, 0, &priv->tx_link_freq);
>> +	if (priv->ctrl_handler.error) {
>> +		ret = priv->ctrl_handler.error;
>> +		goto err_free_ctrl;
>> +	}
>> +
>> +	v4l2_ctrl_new_std_menu_items(&priv->ctrl_handler,
>> +				     &max96714_ctrl_ops,
>> +				     V4L2_CID_TEST_PATTERN,
>> +				     ARRAY_SIZE(max96714_test_pattern) - 1,
>> +				     0, 0, max96714_test_pattern);
>> +	if (priv->ctrl_handler.error) {
>> +		ret = priv->ctrl_handler.error;
>> +		goto err_free_ctrl;
>> +	}
>> +
>> +	priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS;
>> +	priv->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
>> +	priv->sd.entity.ops = &max96714_entity_ops;
>> +
>> +	priv->pads[MAX96714_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
>> +	priv->pads[MAX96714_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
>> +
>> +	ret = media_entity_pads_init(&priv->sd.entity,
>> +				     MAX96714_NPORTS,
>> +				     priv->pads);
>> +	if (ret)
>> +		goto err_free_ctrl;
>> +
>> +	priv->sd.state_lock = priv->sd.ctrl_handler->lock;
>> +
>> +	ret = v4l2_subdev_init_finalize(&priv->sd);
>> +	if (ret)
>> +		goto err_entity_cleanup;
>> +
>> +	ret = max96714_v4l2_notifier_register(priv);
>> +	if (ret) {
>> +		dev_err(dev, "v4l2 subdev notifier register failed: %d\n", ret);
>> +		goto err_subdev_cleanup;
>> +	}
>> +
>> +	ret = v4l2_async_register_subdev(&priv->sd);
>> +	if (ret) {
>> +		dev_err(dev, "v4l2_async_register_subdev error: %d\n", ret);
>> +		goto err_unreg_notif;
>> +	}
>> +
>> +	return 0;
>> +
>> +err_unreg_notif:
>> +	max96714_v4l2_notifier_unregister(priv);
>> +err_subdev_cleanup:
>> +	v4l2_subdev_cleanup(&priv->sd);
>> +err_entity_cleanup:
>> +	media_entity_cleanup(&priv->sd.entity);
>> +err_free_ctrl:
>> +	v4l2_ctrl_handler_free(&priv->ctrl_handler);
>> +
>> +	return ret;
>> +};
>> +
>> +static void max96714_destroy_subdev(struct max96714_priv *priv)
>> +{
>> +	max96714_v4l2_notifier_unregister(priv);
>> +	v4l2_async_unregister_subdev(&priv->sd);
>> +
>> +	v4l2_subdev_cleanup(&priv->sd);
>> +
>> +	media_entity_cleanup(&priv->sd.entity);
>> +	v4l2_ctrl_handler_free(&priv->ctrl_handler);
>> +}
>> +
>> +static int max96714_i2c_mux_select(struct i2c_mux_core *mux, u32 chan)
>> +{
>> +	return 0;
>> +}
>> +
>> +static int max96714_i2c_mux_init(struct max96714_priv *priv)
>> +{
>> +	priv->mux = i2c_mux_alloc(priv->client->adapter, &priv->client->dev,
>> +				  1, 0, I2C_MUX_LOCKED | I2C_MUX_GATE,
>> +				  max96714_i2c_mux_select, NULL);
>> +	if (!priv->mux)
>> +		return -ENOMEM;
>> +
>> +	return i2c_mux_add_adapter(priv->mux, 0, 0, 0);
>> +}
>> +
>> +static int max96714_init_tx_port(struct max96714_priv *priv)
>> +{
>> +	struct v4l2_mbus_config_mipi_csi2 *mipi;
>> +	unsigned long lanes_used = 0;
>> +	u8 val, lane;
>> +	int ret;
>> +
>> +	ret = max96714_disable_tx_port(priv);
>> +
>> +	mipi = &priv->vep.bus.mipi_csi2;
>> +	val = *priv->vep.link_frequencies * 2 / MHZ(100);
>> +
>> +	cci_update_bits(priv->regmap, MAX96714_BACKTOP25,
>> +			CSI_DPLL_FREQ_MASK, val, &ret);
>> +
>> +	val = FIELD_PREP(MAX96714_CSI2_LANE_CNT_MASK, mipi->num_data_lanes - 1);
>> +	cci_update_bits(priv->regmap, MAX96714_MIPI_LANE_CNT,
>> +			MAX96714_CSI2_LANE_CNT_MASK, val, &ret);
>> +
>> +	/* lanes polarity */
>> +	val = 0;
>> +	for (lane = 0; lane < mipi->num_data_lanes + 1; lane++) {
>> +		if (!mipi->lane_polarities[lane])
>> +			continue;
>> +		if (lane == 0)
>> +			/* clock lane */
>> +			val |= BIT(5);
>> +		else if (lane < 3)
>> +			/* Lane D0 and D1 */
>> +			val |= BIT(lane - 1);
>> +		else
>> +			/* D2 and D3 */
>> +			val |= BIT(lane);
>> +	}
>> +
>> +	cci_update_bits(priv->regmap, MAX96714_MIPI_POLARITY,
>> +			MAX96714_MIPI_POLARITY_MASK, val, &ret);
>> +
>> +	/* lanes mapping */
>> +	val = 0;
>> +	for (lane = 0; lane < mipi->num_data_lanes; lane++) {
>> +		val |= (mipi->data_lanes[lane] - 1) << (lane * 2);
>> +		lanes_used |= BIT(mipi->data_lanes[lane] - 1);
>> +	}
>> +
>> +	/* Unused lanes need to be mapped as well to not have
>> +	 * the same lanes mapped twice.
>> +	 */
>> +	for (; lane < 4; lane++) {
>> +		unsigned int idx = find_first_zero_bit(&lanes_used, 4);
>> +
>> +		val |= idx << (lane * 2);
>> +		lanes_used |= BIT(idx);
>> +	}
>> +
>> +	return cci_write(priv->regmap, MAX96714_MIPI_LANE_MAP, val, &ret);
>> +}
>> +
>> +static int max96714_rxport_enable_poc(struct max96714_priv *priv)
>> +{
>> +	struct max96714_rxport *rxport = &priv->rxport;
>> +
>> +	if (!rxport->poc)
>> +		return 0;
>> +
>> +	return regulator_enable(rxport->poc);
>> +}
>> +
>> +static int max96714_rxport_disable_poc(struct max96714_priv *priv)
>> +{
>> +	struct max96714_rxport *rxport = &priv->rxport;
>> +
>> +	if (!rxport->poc)
>> +		return 0;
>> +
>> +	return regulator_disable(rxport->poc);
>> +}
>> +
>> +static int max96714_parse_dt_txport(struct max96714_priv *priv)
>> +{
>> +	struct device *dev = &priv->client->dev;
>> +	struct fwnode_handle *ep_fwnode;
>> +	u32 num_data_lanes;
>> +	s64 dpll_freq;
>> +	int ret;
>> +
>> +	ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
>> +						    MAX96714_PAD_SOURCE, 0, 0);
>> +	if (!ep_fwnode)
>> +		return -EINVAL;
>> +
>> +	priv->vep.bus_type = V4L2_MBUS_UNKNOWN;
>> +
>> +	ret = v4l2_fwnode_endpoint_alloc_parse(ep_fwnode, &priv->vep);
>> +	fwnode_handle_put(ep_fwnode);
>> +	if (ret) {
>> +		dev_err(dev, "tx: failed to parse endpoint data\n");
>> +		return -EINVAL;
>> +	}
>> +
>> +	if (priv->vep.bus_type != V4L2_MBUS_CSI2_DPHY) {
>> +		dev_err(&priv->client->dev, "Unsupported bus-type %u\n",
>> +			priv->vep.bus_type);
>> +		return -EINVAL;
>> +	}
>> +
>> +	if (priv->vep.nr_of_link_frequencies != 1) {
>> +		ret = -EINVAL;
>> +		goto err_free_vep;
>> +	}
>> +
>> +	/* DPLL freq is twice the link frequency */
>> +	dpll_freq = priv->vep.link_frequencies[0] * 2;
>> +
>> +	/* 100Mbps step, Min 100Mbps, Max 2500Mbps */
>> +	if (dpll_freq % MHZ(100) || dpll_freq < MHZ(100) ||
>> +	    dpll_freq > MHZ(2500)) {
>> +		dev_err(dev, "tx: invalid link frequency\n");
>> +		ret = -EINVAL;
>> +		goto err_free_vep;
>> +	}
>> +
>> +	num_data_lanes = priv->vep.bus.mipi_csi2.num_data_lanes;
>> +	if (num_data_lanes < 1 || num_data_lanes > 4) {
>> +		dev_err(dev,
>> +			"tx: invalid number of data lanes should be 1 to 4\n");
>> +		ret = -EINVAL;
>> +		goto err_free_vep;
>> +	}
>> +
>> +	return 0;
>> +
>> +err_free_vep:
>> +	v4l2_fwnode_endpoint_free(&priv->vep);
>> +
>> +	return ret;
>> +};
>> +
>> +static int max96714_parse_dt_rxport(struct max96714_priv *priv)
>> +{
>> +	static const char *poc_name = "port0-poc";
>> +	struct max96714_rxport *rxport = &priv->rxport;
>> +	struct device *dev = &priv->client->dev;
>> +	struct fwnode_handle *ep_fwnode;
>> +	int ret;
>> +
>> +	ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
>> +						    MAX96714_PAD_SINK,
>> +						    0, 0);
> 
> Fits on the previous line.
ok
> 
>> +	if (!ep_fwnode)
>> +		return -ENOENT;
>> +
>> +	rxport->source.ep_fwnode = fwnode_graph_get_remote_endpoint(ep_fwnode);
>> +	fwnode_handle_put(ep_fwnode);
>> +
>> +	if (!rxport->source.ep_fwnode) {
>> +		dev_err(dev, "rx: no remote endpoint\n");
>> +		return -EINVAL;
>> +	}
>> +
>> +	rxport->poc = devm_regulator_get_optional(dev, poc_name);
>> +	if (IS_ERR(rxport->poc)) {
>> +		ret = PTR_ERR(rxport->poc);
>> +		if (ret == -ENODEV) {
>> +			rxport->poc = NULL;
>> +		} else {
>> +			dev_err(dev, "rx: failed to get POC supply: %d\n", ret);
>> +			goto err_put_source_ep_fwnode;
>> +		}
>> +	}
>> +
>> +	return 0;
>> +
>> +err_put_source_ep_fwnode:
>> +	fwnode_handle_put(rxport->source.ep_fwnode);
>> +	return ret;
>> +}
>> +
>> +static int max96714_parse_dt(struct max96714_priv *priv)
>> +{
>> +	int ret;
>> +
>> +	ret = max96714_parse_dt_rxport(priv);
>> +	/* The deserializer can create a test pattern even if the
>> +	 * rx port is not connected to a serializer.
>> +	 */
>> +	if (ret && ret != -ENOENT)
>> +		return ret;
>> +
>> +	ret = max96714_parse_dt_txport(priv);
>> +	if (ret)
>> +		goto err_put_fwnode;
>> +
>> +	return 0;
>> +
>> +err_put_fwnode:
>> +	fwnode_handle_put(priv->rxport.source.ep_fwnode);
> 
> This is already done in max96714_parse_dt_txport().
ok
> 
>> +
>> +	return ret;
>> +}
>> +
>> +static int max96714_enable_core_hw(struct max96714_priv *priv)
>> +{
>> +	struct device *dev = &priv->client->dev;
>> +	u64 val;
>> +	int ret;
>> +
>> +	if (priv->pd_gpio) {
>> +		/* wait min 2 ms for reset to complete */
>> +		gpiod_set_value_cansleep(priv->pd_gpio, 1);
>> +		fsleep(2000);
>> +		gpiod_set_value_cansleep(priv->pd_gpio, 0);
>> +		/* wait min 2 ms for power up to finish */
>> +		fsleep(2000);
>> +	}
>> +
>> +	ret = cci_read(priv->regmap, MAX96714_REG13, &val, NULL);
>> +	if (ret) {
>> +		dev_err_probe(dev, ret, "Cannot read first register, abort\n");
>> +		goto err_pd_gpio;
>> +	}
>> +
>> +	if (val != MAX96714F_DEVICE_ID) {
>> +		dev_err(dev, "Unsupported device id expected %x got %x\n",
>> +			MAX96714F_DEVICE_ID, (u8)val);
>> +		ret = -EOPNOTSUPP;
>> +		goto err_pd_gpio;
>> +	}
>> +
>> +	ret = cci_read(priv->regmap, MAX96714_DEV_REV, &val, NULL);
>> +	if (ret)
>> +		goto err_pd_gpio;
>> +
>> +	dev_dbg(dev, "Found %x (rev %lx)\n", MAX96714F_DEVICE_ID,
>> +		(u8)val & MAX96714_DEV_REV_MASK);
>> +
>> +	ret = cci_read(priv->regmap, MAX96714_MIPI_TX52, &val, NULL);
>> +	if (ret)
>> +		goto err_pd_gpio;
>> +
>> +	if (!(val & MAX96714_TUN_EN)) {
>> +		dev_err(dev, "Only supporting tunnel mode");
>> +		ret = -EOPNOTSUPP;
>> +		goto err_pd_gpio;
>> +	}
>> +
>> +	return 0;
>> +
>> +err_pd_gpio:
>> +	gpiod_set_value_cansleep(priv->pd_gpio, 1);
>> +	return ret;
>> +}
>> +
>> +static void max96714_disable_core_hw(struct max96714_priv *priv)
>> +{
>> +	gpiod_set_value_cansleep(priv->pd_gpio, 1);
>> +}
>> +
>> +static int max96714_get_hw_resources(struct max96714_priv *priv)
>> +{
>> +	struct device *dev = &priv->client->dev;
>> +
>> +	priv->regmap = devm_cci_regmap_init_i2c(priv->client, 16);
>> +	if (IS_ERR(priv->regmap))
>> +		return PTR_ERR(priv->regmap);
>> +
>> +	priv->pd_gpio =
>> +		devm_gpiod_get_optional(dev, "powerdown", GPIOD_OUT_HIGH);
>> +	if (IS_ERR(priv->pd_gpio))
>> +		return dev_err_probe(dev, PTR_ERR(priv->pd_gpio),
>> +				     "Cannot get powerdown GPIO\n");
>> +	return 0;
>> +}
>> +
>> +static int max96714_probe(struct i2c_client *client)
>> +{
>> +	struct device *dev = &client->dev;
>> +	struct max96714_priv *priv;
>> +	int ret;
>> +
>> +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
>> +	if (!priv)
>> +		return -ENOMEM;
>> +
>> +	priv->client = client;
>> +	priv->regmap = devm_regmap_init_i2c(priv->client,
>> +					    &max96714_regmap_config);

regmap is intialized twice, here and in max96714_get_hw_resources,
I will keep the one in get_hw_resources only for the v4.

>> +
>> +	ret = max96714_get_hw_resources(priv);
>> +	if (ret)
>> +		return ret;
>> +
>> +	ret = max96714_enable_core_hw(priv);
>> +	if (ret)
>> +		return ret;
>> +
>> +	ret = max96714_parse_dt(priv);
>> +	if (ret)
>> +		goto err_disable_core_hw;
>> +
>> +	max96714_init_tx_port(priv);
>> +
>> +	ret = max96714_rxport_enable_poc(priv);
>> +	if (ret)
>> +		goto err_free_ports;
>> +
>> +	ret = max96714_i2c_mux_init(priv);
>> +	if (ret)
>> +		goto err_disable_poc;
>> +
>> +	ret = max96714_create_subdev(priv);
>> +	if (ret)
>> +		goto err_del_mux;
>> +
>> +	return 0;
>> +
>> +err_del_mux:
>> +	i2c_mux_del_adapters(priv->mux);
>> +err_disable_poc:
>> +	max96714_rxport_disable_poc(priv);
>> +err_free_ports:
>> +	fwnode_handle_put(priv->rxport.source.ep_fwnode);
>> +	v4l2_fwnode_endpoint_free(&priv->vep);
>> +err_disable_core_hw:
>> +	max96714_disable_core_hw(priv);
>> +
>> +	return ret;
>> +}
>> +
>> +static void max96714_remove(struct i2c_client *client)
>> +{
>> +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
>> +	struct max96714_priv *priv = sd_to_max96714(sd);
>> +
>> +	max96714_destroy_subdev(priv);
>> +	i2c_mux_del_adapters(priv->mux);
>> +	max96714_rxport_disable_poc(priv);
>> +	fwnode_handle_put(priv->rxport.source.ep_fwnode);
>> +	v4l2_fwnode_endpoint_free(&priv->vep);
>> +	max96714_disable_core_hw(priv);
>> +	gpiod_set_value_cansleep(priv->pd_gpio, 1);
>> +}
>> +
>> +static const struct of_device_id max96714_of_ids[] = {
>> +	{ .compatible = "maxim,max96714f" },
>> +	{ }
>> +};
>> +MODULE_DEVICE_TABLE(of, max96714_of_ids);
>> +
>> +static struct i2c_driver max96714_i2c_driver = {
>> +	.driver	= {
>> +		.name		= "max96714",
>> +		.of_match_table	= max96714_of_ids,
>> +	},
>> +	.probe		= max96714_probe,
>> +	.remove		= max96714_remove,
>> +};
>> +
>> +module_i2c_driver(max96714_i2c_driver);
>> +
>> +MODULE_LICENSE("GPL");
>> +MODULE_DESCRIPTION("Maxim Integrated GMSL2 Deserializers Driver");
>> +MODULE_AUTHOR("Julien Massot <julien.massot@collabora.com>");
> 

Regards,
-- 
Julien Massot
Senior Software Engineer
Collabora Ltd.
Platinum Building, St John's Innovation Park, Cambridge CB4 0DS, UK
Registered in England & Wales, no. 5513718

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

* Re: [PATCH v3 3/4] media: i2c: add MAX96717 driver
  2024-02-21 10:34     ` Julien Massot
@ 2024-02-21 11:04       ` Sakari Ailus
  0 siblings, 0 replies; 18+ messages in thread
From: Sakari Ailus @ 2024-02-21 11:04 UTC (permalink / raw)
  To: Julien Massot
  Cc: linux-media, kernel, devicetree, linux-kernel, mchehab, robh+dt,
	krzysztof.kozlowski+dt, conor+dt

Hi Julien,

On Wed, Feb 21, 2024 at 11:34:38AM +0100, Julien Massot wrote:
> Hi Sakari,
> 
> On 2/9/24 16:26, Sakari Ailus wrote:
> > Hi Julien,
> > 
> > On Thu, Jan 11, 2024 at 02:03:48PM +0100, Julien Massot wrote:
> > > This driver handle the MAX96717 serializer in tunnel mode.
> > > All incoming CSI traffic will be tunneled through the GMSL2
> > > link.
> > > 
> > > Signed-off-by: Julien Massot <julien.massot@collabora.com>
> > > ---
> > > Change since v2:
> > >   - Use CCI helpers instead of recoding register access
> > >   - add missing bitfield header
> > > ---
> > >   MAINTAINERS                  |   7 +
> > >   drivers/media/i2c/Kconfig    |  13 +
> > >   drivers/media/i2c/Makefile   |   1 +
> > >   drivers/media/i2c/max96717.c | 956 +++++++++++++++++++++++++++++++++++
> > >   4 files changed, 977 insertions(+)
> > >   create mode 100644 drivers/media/i2c/max96717.c
> > > 
> > > diff --git a/MAINTAINERS b/MAINTAINERS
> > > index 675e5d63a25b..a64a7932fe76 100644
> > > --- a/MAINTAINERS
> > > +++ b/MAINTAINERS
> > > @@ -13039,6 +13039,13 @@ S:	Maintained
> > >   F:	Documentation/devicetree/bindings/media/i2c/maxim,max96712.yaml
> > >   F:	drivers/staging/media/max96712/max96712.c
> > > +MAX96717 GMSL2 SERIALIZER DRIVER
> > > +M:	Julien Massot <julien.massot@collabora.com>
> > > +L:	linux-media@vger.kernel.org
> > > +S:	Maintained
> > > +F:	Documentation/devicetree/bindings/media/i2c/maxim,max96717f.yaml
> > 
> > What's that "f" for? It's in bindings but not in the name of the driver.
> > Not a typo I suppose? :-)
> 
> Indeed that's not a typo, the Maxim's GMSL2 chips are available under
> multiple
> variants:
> - MAX96717 which supports GMSL link speed 6 and 3Gbps and CSI lanes up to
> 2.5Gbps
> - MAX96717K which supports GMSL link speed 6 and 3Gbps and CSI lanes up to
> 1.5Gbps
> - MAX96717F which only supports GMSL link speed 3Gbps and CSI lanes up to
> 2.5Gbps
> 
> They have the same register mapping, so we should be able to add support for
> the other variants in the future.

I think you should probably do this for the bindings, too. I'd thus drop
"f" from the file name.

-- 
Regards,

Sakari Ailus

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

end of thread, other threads:[~2024-02-21 11:04 UTC | newest]

Thread overview: 18+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-01-11 13:03 [PATCH v3 0/4] Add support for MAX96714F and MAX96717F GMSL2 ser/des Julien Massot
2024-01-11 13:03 ` [PATCH v3 1/4] dt-bindings: media: add Maxim MAX96717F GMSL2 Serializer Julien Massot
2024-01-12  8:13   ` Krzysztof Kozlowski
2024-02-09  9:28   ` Sakari Ailus
2024-02-21 10:12     ` Julien Massot
2024-01-11 13:03 ` [PATCH v3 2/4] dt-bindings: media: add Maxim MAX96714F GMSL2 Deserializer Julien Massot
2024-01-12  8:15   ` Krzysztof Kozlowski
2024-02-09  9:48   ` Sakari Ailus
2024-02-21 10:10     ` Julien Massot
2024-01-11 13:03 ` [PATCH v3 3/4] media: i2c: add MAX96717 driver Julien Massot
2024-02-09 15:26   ` Sakari Ailus
2024-02-21 10:34     ` Julien Massot
2024-02-21 11:04       ` Sakari Ailus
2024-01-11 13:03 ` [PATCH v3 4/4] media: i2c: add MAX96714 driver Julien Massot
2024-01-13 11:31   ` kernel test robot
2024-02-09 15:49   ` Sakari Ailus
2024-02-21 10:45     ` Julien Massot
2024-01-12  8:10 ` [PATCH v3 0/4] Add support for MAX96714F and MAX96717F GMSL2 ser/des Krzysztof Kozlowski

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