From b8a65a3b2a487d95717212f23119dfab2786537b Mon Sep 17 00:00:00 2001
From: Julien Massot <julien.massot@collabora.com>
Date: Wed, 3 May 2023 09:54:42 +0200
Subject: [PATCH] update gmsl patches

---
 debian/config/arm64/config                    |    3 +
 ...edia-platform-cadence-use-asd-fwnode.patch |   28 +
 ...-add-maxim-GMSL2-deserializer-driver.patch | 3802 +++++++++++++----
 ...ax-gmsl2-deser-report-link-frequency.patch |  113 -
 ...do-not-try-to-link-non-existing-pipe.patch |   23 -
 ...ax-gmsl2-deser-fix-pattern-generator.patch |  169 -
 ...change-max96714-compatible-to-max967.patch |   64 -
 .../0168-Add-raw8-video-formats.patch         |   85 +
 debian/patches/series                         |    7 +-
 9 files changed, 3001 insertions(+), 1293 deletions(-)
 create mode 100644 debian/patches/apertis/am62x-csi/0160-media-platform-cadence-use-asd-fwnode.patch
 delete mode 100644 debian/patches/apertis/maxim-gmsl2/0151-max-gmsl2-deser-report-link-frequency.patch
 delete mode 100644 debian/patches/apertis/maxim-gmsl2/0152-max-gmsl2-deser-do-not-try-to-link-non-existing-pipe.patch
 delete mode 100644 debian/patches/apertis/maxim-gmsl2/0153-drivers-media-max-gmsl2-deser-fix-pattern-generator.patch
 delete mode 100644 debian/patches/apertis/maxim-gmsl2/0154-max-gmsl2-deser-change-max96714-compatible-to-max967.patch
 create mode 100644 debian/patches/apertis/st-vgxy61/0168-Add-raw8-video-formats.patch

diff --git a/debian/config/arm64/config b/debian/config/arm64/config
index 47b4d8decad..f1d31c25317 100644
--- a/debian/config/arm64/config
+++ b/debian/config/arm64/config
@@ -791,6 +791,9 @@ CONFIG_CEC_MESON_G12A_AO=m
 ## file: drivers/media/i2c/Kconfig
 ##
 CONFIG_VIDEO_MAXIM_GMSL2=m
+CONFIG_VIDEO_MAXIM_GMSL2_DES=m
+CONFIG_VIDEO_MAXIM_GMSL2_SER=m
+CONFIG_VIDEO_ST_VGXY61=m
 
 ##
 ## file: drivers/media/platform/amlogic/meson-ge2d/Kconfig
diff --git a/debian/patches/apertis/am62x-csi/0160-media-platform-cadence-use-asd-fwnode.patch b/debian/patches/apertis/am62x-csi/0160-media-platform-cadence-use-asd-fwnode.patch
new file mode 100644
index 00000000000..1d63d525127
--- /dev/null
+++ b/debian/patches/apertis/am62x-csi/0160-media-platform-cadence-use-asd-fwnode.patch
@@ -0,0 +1,28 @@
+From f439f422a26dad42328d8cbfca5c7e37ac1968a3 Mon Sep 17 00:00:00 2001
+From: Julien Massot <julien.massot@collabora.com>
+Date: Wed, 3 May 2023 09:28:14 +0200
+Subject: [PATCH 4/6] media: platform: cadence: use asd fwnode
+
+On some subdev the fwnode is the device node and not the endpoint node.
+Using the subdev fwnode doesn't allow to fetch the subdev
+pad we are connected to.
+---
+ drivers/media/platform/cadence/cdns-csi2rx.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/drivers/media/platform/cadence/cdns-csi2rx.c b/drivers/media/platform/cadence/cdns-csi2rx.c
+index 1c7541a2b6..51aa68c7cf 100644
+--- a/drivers/media/platform/cadence/cdns-csi2rx.c
++++ b/drivers/media/platform/cadence/cdns-csi2rx.c
+@@ -494,7 +494,7 @@ static int csi2rx_async_bound(struct v4l2_async_notifier *notifier,
+ 	struct csi2rx_priv *csi2rx = v4l2_subdev_to_csi2rx(subdev);
+ 
+ 	csi2rx->source_pad = media_entity_get_fwnode_pad(&s_subdev->entity,
+-							 s_subdev->fwnode,
++							 asd->match.fwnode,
+ 							 MEDIA_PAD_FL_SOURCE);
+ 	if (csi2rx->source_pad < 0) {
+ 		dev_err(csi2rx->dev, "Couldn't find output pad for subdev %s\n",
+-- 
+2.40.1
+
diff --git a/debian/patches/apertis/maxim-gmsl2/0001-drivers-media-add-maxim-GMSL2-deserializer-driver.patch b/debian/patches/apertis/maxim-gmsl2/0001-drivers-media-add-maxim-GMSL2-deserializer-driver.patch
index 43ee7b473f3..d2c9509aaac 100644
--- a/debian/patches/apertis/maxim-gmsl2/0001-drivers-media-add-maxim-GMSL2-deserializer-driver.patch
+++ b/debian/patches/apertis/maxim-gmsl2/0001-drivers-media-add-maxim-GMSL2-deserializer-driver.patch
@@ -1,122 +1,473 @@
 From: Julien Massot <julien.massot@collabora.com>
-Date: Fri, 20 Jan 2023 10:51:34 +0100
-Subject: [PATCH] drivers: media: add maxim GMSL2 deserializer driver
+Date: Thu, 30 Mar 2023 09:52:16 +0200
+Subject: drivers: media: add GMSL2 deserializer
 
 This driver support MAX96914, MAX96934 and MAX96714
 deserializers that are quad, dual, and one GMSL2 link
 deserializer.
 
-This driver has support for:
+The deserializer driver has support for:
 Up to 2 4-lane CSI outputs, up to 8 video pipes, and up 4
 GMSL2 Link.
+Power over Coax, pattern generator, gpio controller as well as
+GMSL1 backward compatibility.
 It also supports pattern generator as well as GMSL1 backward
 compatibility.
-
-Signed-off-by: Julien Massot <julien.massot@collabora.com>
-[Martyn Welch: Ported to v6.1.4]
-Signed-off-by: Martyn Welch <martyn.welch@collabora.com>
 ---
- drivers/media/i2c/Kconfig            |   12 +
- drivers/media/i2c/Makefile           |    2 +
- drivers/media/i2c/max-gmsl2-deser.c  | 1592 ++++++++++++++++++++++++++++++++++
- drivers/media/i2c/max-gmsl2-patgen.c |  192 ++++
- drivers/media/i2c/max-gmsl2-patgen.h |   23 +
- 5 files changed, 1821 insertions(+)
+ .../bindings/media/i2c/maxim,max96914.yaml         |  250 ++++
+ drivers/media/i2c/Kconfig                          |   42 +
+ drivers/media/i2c/Makefile                         |    5 +
+ drivers/media/i2c/gmsl2-def.h                      |  153 ++
+ drivers/media/i2c/max-gmsl2-deser.c                | 1463 ++++++++++++++++++++
+ drivers/media/i2c/max-gmsl2-gpio.c                 |  207 +++
+ drivers/media/i2c/max-gmsl2-patgen.c               |  175 +++
+ drivers/media/i2c/max-gmsl2-ser.c                  |  992 +++++++++++++
+ drivers/media/i2c/max-gmsl2.c                      |  394 ++++++
+ include/linux/gpio/max-gmsl2-gpio.h                |   16 +
+ include/media/gmsl2/max-gmsl2-patgen.h             |   45 +
+ 11 files changed, 3742 insertions(+)
+ create mode 100644 Documentation/devicetree/bindings/media/i2c/maxim,max96914.yaml
+ create mode 100644 drivers/media/i2c/gmsl2-def.h
  create mode 100644 drivers/media/i2c/max-gmsl2-deser.c
+ create mode 100644 drivers/media/i2c/max-gmsl2-gpio.c
  create mode 100644 drivers/media/i2c/max-gmsl2-patgen.c
- create mode 100644 drivers/media/i2c/max-gmsl2-patgen.h
+ create mode 100644 drivers/media/i2c/max-gmsl2-ser.c
+ create mode 100644 drivers/media/i2c/max-gmsl2.c
+ create mode 100644 include/linux/gpio/max-gmsl2-gpio.h
+ create mode 100644 include/media/gmsl2/max-gmsl2-patgen.h
 
+diff --git a/Documentation/devicetree/bindings/media/i2c/maxim,max96914.yaml b/Documentation/devicetree/bindings/media/i2c/maxim,max96914.yaml
+new file mode 100644
+index 00000000..b9061d5
+--- /dev/null
++++ b/Documentation/devicetree/bindings/media/i2c/maxim,max96914.yaml
+@@ -0,0 +1,250 @@
++# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
++%YAML 1.2
++---
++$id: http://devicetree.org/schemas/media/i2c/max,max96914.yaml#
++$schema: http://devicetree.org/meta-schemas/core.yaml#
++
++title: Maxim Integrated MAX96xxx Family GMSL2 Deserializer Hubs
++
++maintainers:
++  - Julien Massot <julien.massot@collabora.com>
++
++description:
++  The Maxim MAX96xxx devices are GMSL2 video deserializer with I2C and GPIO
++  forwarding.
++
++properties:
++  compatible:
++    enum:
++      - maxim,max96934
++  reg:
++    maxItems: 1
++
++  enable-gpios:
++    maxItems: 1
++    description:
++      Specifier for the GPIO connected to the PWDNB pin.
++
++  gpio-controller: true
++
++  '#gpio-cells':
++    const: 2
++
++  maxim,pipe-stream-id:
++    $ref: /schemas/types.yaml#/definitions/uint8-array
++    minItems: 1
++    maxItems: 8
++    items:
++      # Possible stream id [0,1,2,3]
++      maximum: 3
++    description:
++      An array of stream identifier. Position of an entry determines
++      the pipe number, while the value of an entry indicates the given
++      stream identifier, e.g. for 8-pipes device we could have
++      "maxim,pipe-stream-id = <3 2 1 0 3 2 1 0>;" overriding
++      the default <0 1 2 3 0 1 2 3>.
++      Stream id will be used to select a particular stream for a video pipe.
++
++  ports:
++    $ref: /schemas/graph.yaml#/properties/ports
++
++    properties:
++      port@0:
++        $ref: /schemas/graph.yaml#/$defs/port-base
++        unevaluatedProperties: false
++        description: GMSL-Link input 0
++
++        properties:
++          endpoint:
++            $ref: /schemas/media/video-interfaces.yaml#
++            unevaluatedProperties: false
++            description:
++              Endpoint for GMSL-Link port.
++
++      port@1:
++        $ref: /schemas/graph.yaml#/$defs/port-base
++        unevaluatedProperties: false
++        description: GMSL-Link input 1
++
++        properties:
++          endpoint:
++            $ref: /schemas/media/video-interfaces.yaml#
++            unevaluatedProperties: false
++            description:
++               Endpoint for GMSL-Link port.
++
++      port@2:
++        $ref: /schemas/graph.yaml#/$defs/port-base
++        unevaluatedProperties: false
++        description: GMSL-Link input 2
++
++        properties:
++          endpoint:
++            $ref: /schemas/media/video-interfaces.yaml#
++            unevaluatedProperties: false
++            description:
++               Endpoint for GMSL-Link port.
++
++      port@3:
++        $ref: /schemas/graph.yaml#/$defs/port-base
++        unevaluatedProperties: false
++        description: GMSL-Link input 3
++
++        properties:
++          endpoint:
++            $ref: /schemas/media/video-interfaces.yaml#
++            unevaluatedProperties: false
++            description:
++               Endpoint for GMSL-Link port.
++
++      port@4:
++        $ref: /schemas/graph.yaml#/$defs/port-base
++        unevaluatedProperties: false
++        description: CSI-2 Output 0
++
++        properties:
++          endpoint:
++            $ref: /schemas/media/video-interfaces.yaml#
++            unevaluatedProperties: false
++
++            properties:
++              data-lanes:
++                minItems: 1
++                maxItems: 4
++              link-frequencies:
++                maxItems: 1
++
++            required:
++              - data-lanes
++              - link-frequencies
++
++      port@5:
++        $ref: /schemas/graph.yaml#/$defs/port-base
++        unevaluatedProperties: false
++        description: CSI-2 Output 1
++
++        properties:
++          endpoint:
++            $ref: /schemas/media/video-interfaces.yaml#
++            unevaluatedProperties: false
++
++            properties:
++              data-lanes:
++                minItems: 1
++                maxItems: 4
++              link-frequencies:
++                maxItems: 1
++
++            required:
++              - data-lanes
++              - link-frequencies
++
++    required:
++      - port@0
++      - port@1
++      - port@2
++      - port@3
++      - port@4
++      - port@5
++
++patternProperties:
++  "^port[0-3]-poc-supply$":
++    description: Regulator providing Power over Coax for a particular port
++
++required:
++  - compatible
++  - reg
++  - ports
++
++additionalProperties: false
++
++examples:
++  - |
++    #include <dt-bindings/gpio/gpio.h>
++
++    i2c {
++      clock-frequency = <400000>;
++      #address-cells = <1>;
++      #size-cells = <0>;
++
++      deser@68 {
++        compatible = "maxim,max96914";
++        reg = <0x68>;
++
++        enable-gpios = <&tlmm 7 GPIO_ACTIVE_HIGH>;
++        port0-poc-supply = <&camera_poc_12v>;
++
++        ports {
++          #address-cells = <1>;
++          #size-cells = <0>;
++
++          /* Port A, Camera 0 */
++          port@0 {
++            reg = <0>;
++
++            max96914_gmsl_a_in: endpoint {
++              remote-endpoint = <&max96971_gmsl_out>;
++            };
++          };
++
++          /* Port B, unconnected */
++          port@1 {
++            reg = <1>;
++          };
++
++          /* Port C, unconnected */
++          port@2 {
++            reg = <2>;
++          };
++
++          /* Port D, unconnected */
++          port@3 {
++            reg = <3>;
++          };
++
++          /* Port 4, CSI-2 TX */
++          port@4 {
++            reg = <4>;
++            max96914_0_csi_out: endpoint {
++              data-lanes = <1 2 3 4>;
++              link-frequencies = /bits/ 64 <800000000>;
++              remote-endpoint = <&csi2_phy0>;
++            };
++          };
++
++          /* Port 5, unconnected */
++          port@5 {
++            reg = <5>;
++          };
++        };
++
++        i2c-gate {
++         #address-cells = <1>;
++         #size-cells = <0>;
++
++         serializer1: serializer@40 {
++           compatible = "maxim,max96971f";
++           reg = <0x40>;
++           gpio-controller;
++           #gpio-cells = <2>;
++           ports {
++             #address-cells = <1>;
++             #size-cells = <0>;
++             port@0 {
++               reg = <0>;
++               max96971_csi_in: endpoint {
++               };
++             };
++
++             port@1 {
++               reg = <1>;
++               max96971_gmsl_out: endpoint {
++                 remote-endpoint = <&max96914_gmsl_a_in>;
++               };
++             };
++           }; /* End of serializer ports*/
++         }; /* End of serializer */
++        }; /* End of I2C gate*/
++      }; /* End of deserializer */
++    }; /* End of I2C */
++...
 diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
-index 7dce81b..a35b2e4 100644
+index 7dce81b..8223b3e 100644
 --- a/drivers/media/i2c/Kconfig
 +++ b/drivers/media/i2c/Kconfig
-@@ -1225,6 +1225,18 @@ config VIDEO_MAX9286
+@@ -1225,6 +1225,48 @@ config VIDEO_MAX9286
  	  To compile this driver as a module, choose M here: the
  	  module will be called max9286.
  
 +config VIDEO_MAXIM_GMSL2
-+	tristate "Maxim GMSL2 Serializer "
-+	depends on I2C
++	tristate "Maxim GMSL2 Deserializer"
++	depends on OF
++	select V4L2_FWNODE
++	select REGMAP
++	help
++	  Say Y here if you want support for Maxim GMSL2
++	  deserializer.
++
++config VIDEO_MAXIM_GMSL2_DES
++	tristate "Maxim GMSL2 Deserializer"
 +	depends on OF
 +	select V4L2_FWNODE
 +	select VIDEO_V4L2_SUBDEV_API
 +	select MEDIA_CONTROLLER
 +	select REGMAP_I2C
++	select VIDEO_MAXIM_GMSL2_GPIO
++	select VIDEO_MAXIM_GMSL2
 +	help
 +	  Say Y here if you want support for Maxim GMSL2
 +	  deserializer.
++
++config VIDEO_MAXIM_GMSL2_SER
++	tristate "Maxim GMSL2 Seserializer"
++	depends on OF
++	select V4L2_FWNODE
++	select VIDEO_V4L2_SUBDEV_API
++	select MEDIA_CONTROLLER
++	select REGMAP_I2C
++	select VIDEO_MAXIM_GMSL2_GPIO
++	select VIDEO_MAXIM_GMSL2
++	help
++	  Say Y here if you want support for Maxim GMSL2
++	  serializer.
++
++config VIDEO_MAXIM_GMSL2_GPIO
++	tristate "Maxim GMSL2 GPIO controller"
++	depends on OF
++	select REGMAP
++	select OF_GPIO
++
 +
  config VIDEO_ML86V7667
  	tristate "OKI ML86V7667 video decoder"
  	depends on VIDEO_DEV && I2C
 diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
-index 0a29331..6e3ed6f 100644
+index 0a29331..af6097a 100644
 --- a/drivers/media/i2c/Makefile
 +++ b/drivers/media/i2c/Makefile
-@@ -57,6 +57,8 @@ obj-$(CONFIG_VIDEO_M52790) += m52790.o
- obj-$(CONFIG_VIDEO_M5MOLS) += m5mols/
- obj-$(CONFIG_VIDEO_MAX9271_LIB) += max9271.o
- obj-$(CONFIG_VIDEO_MAX9286) += max9286.o
-+max-gmsl2-deser-driver-objs :=	max-gmsl2-deser.o max-gmsl2-patgen.o
-+obj-$(CONFIG_VIDEO_MAXIM_GMSL2) += max-gmsl2-deser-driver.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/max-gmsl2-deser.c b/drivers/media/i2c/max-gmsl2-deser.c
+@@ -142,3 +142,8 @@ obj-$(CONFIG_VIDEO_VPX3220) += vpx3220.o
+ obj-$(CONFIG_VIDEO_VS6624) += vs6624.o
+ obj-$(CONFIG_VIDEO_WM8739) += wm8739.o
+ obj-$(CONFIG_VIDEO_WM8775) += wm8775.o
++max-gmsl2-lib-objs          := max-gmsl2.o max-gmsl2-patgen.o
++obj-$(CONFIG_VIDEO_MAXIM_GMSL2) += max-gmsl2-lib.o
++obj-$(CONFIG_VIDEO_MAXIM_GMSL2_DES) += max-gmsl2-deser.o
++obj-$(CONFIG_VIDEO_MAXIM_GMSL2_SER) += max-gmsl2-ser.o
++obj-$(CONFIG_VIDEO_MAXIM_GMSL2_GPIO) += max-gmsl2-gpio.o
+diff --git a/drivers/media/i2c/gmsl2-def.h b/drivers/media/i2c/gmsl2-def.h
 new file mode 100644
-index 0000000..8a30110
+index 00000000..d67bab3
 --- /dev/null
-+++ b/drivers/media/i2c/max-gmsl2-deser.c
-@@ -0,0 +1,1592 @@
-+// SPDX-License-Identifier: GPL-2.0
-+/*
-+ * Maxim GMSL2 Deserializer Driver
-+ *
-+ * Copyright (C) 2022 Collabora Ltd.
-+ */
-+
-+#define GMSL2_MAX_LINK 4
-+#define GMSL2_MAX_PIPE 8
-+#define GMSL2_MAX_CSI  2
-+
-+#include <linux/debugfs.h>
-+#include <linux/gpio/consumer.h>
-+#include <linux/i2c.h>
-+#include <linux/mod_devicetable.h>
-+#include <linux/module.h>
-+#include <linux/of_device.h>
++++ b/drivers/media/i2c/gmsl2-def.h
+@@ -0,0 +1,153 @@
++#include <linux/device.h>
 +#include <linux/regmap.h>
-+#include <linux/v4l2-dv-timings.h>
 +#include <media/v4l2-ctrls.h>
-+#include <media/v4l2-device.h>
-+#include <media/v4l2-fwnode.h>
 +#include <media/v4l2-subdev.h>
++#ifndef __LINUX_MEDIA_MAX_GMSL2_DEFS_H
++#define __LINUX_MEDIA_MAX_GMSL2_DEFS_H
 +
-+#include "max-gmsl2-patgen.h"
++static struct regmap_config gmsl2_regmap_config = {
++	.reg_bits = 16,
++	.val_bits = 8,
++	.max_register = 0xFFFF,
++	.cache_type = REGCACHE_NONE,
++};
 +
-+struct max_gmsl2_compat_gmsl1 {
-+	struct reg_field gmsl2_link_mode;
-+	struct reg_field highimm[GMSL2_MAX_LINK];
-+	struct reg_field link_lock[GMSL2_MAX_LINK];
-+	struct reg_field forward_cc[GMSL2_MAX_LINK];
-+	u16              conv_datatype[GMSL2_MAX_LINK];
++#define MAX9295A_DEVICE_ID 0x91
++#define MAX96934_DEVICE_ID 0x92
++#define MAX96914_DEVICE_ID 0xa0
++#define MAX96724_DEVICE_ID 0xa2
++#define MAX96717F_DEVICE_ID 0xc8
++#define MAX96714F_DEVICE_ID 0xca
++
++static inline const char* gmsl2_id_to_name(u8 device_id)
++{
++	switch (device_id) {
++	case MAX9295A_DEVICE_ID:  return "max9295a";
++	case MAX96934_DEVICE_ID:  return "max96934";
++	case MAX96724_DEVICE_ID:  return "max96724";
++	case MAX96714F_DEVICE_ID: return "max96714f";
++	case MAX96717F_DEVICE_ID: return "max96717f";
++	default:
++		break;
++	}
++	return NULL;
++}
++
++struct max_gmsl2_pipe {
++	struct regmap_field *enable;
++	struct regmap_field *link_sel;
++	struct regmap_field *stream_id;
++	struct regmap_field *vid_lock;
++	struct regmap_field *override_bpp_vc_dt;
++	struct regmap_field *bpp_l;
++	struct regmap_field *bpp_h;
++	struct regmap_field *dt_l;
++	struct regmap_field *dt_h;
++	struct regmap_field *dt_sel[2];
++	struct regmap_field *vc;
++	u8 pipe_remap_idx;
++};
++
++struct max_gmsl2_csi {
++	struct v4l2_subdev sd;
++	struct media_pad pads[2];
++	struct v4l2_ctrl_handler ctrl_hdl;
++	struct v4l2_ctrl *pixel_rate;
++	void *priv;
++
++	struct regmap_field *phy_enable;
++
++	/* CSI Tx Only */
++	struct regmap_field *dpll_freq;
++
++	struct regmap_field *lane_cnt;
++	struct regmap_field *lane_polarities[2];
++	struct regmap_field *lane_map[2];
++	struct fwnode_handle *ep_fwnode;
++	u8 id;
 +};
 +
-+struct max_gmsl2_deser_data {
++struct max_gmsl2_link {
++	struct regmap_field *locked;
++	struct regmap_field *reset_one_shot;
++	struct regmap_field *reset;
++
++	/* deserializer only */
++	struct v4l2_subdev     *source_sd;	/* Connected subdev */
++	u16			source_sd_pad;
++	struct fwnode_handle   *source_ep_fwnode;
++	struct v4l2_async_subdev asd;
++	struct regulator       *vpoc;
++};
++
++#define GMSL2_MAX_LINK 4
++#define GMSL2_MAX_PIPE 8
++#define GMSL2_MAX_CSI  2
++
++struct max_gmsl2_data {
 +	u8 device_id;
 +	u8 links;
 +	u8 pipes;
 +	u8 csi;
++	u8 ngpios;
++	u16 gpio_base;
 +	struct reg_field dev_rev;
-+	struct reg_field csi_enable;
++	struct reg_field csi_force_enable;
 +	struct reg_field csi_wait_frame;
 +	struct reg_field csi_phy_enable[GMSL2_MAX_CSI];
 +	/* Each CSI 4 lanes use 2 phys for lane polarities and lane map
 +	 * declare the phy which provides D0-D1 first.
 +	 */
++	struct reg_field csi_lane_cnt[GMSL2_MAX_CSI];
 +	struct reg_field csi_lane_polarities[GMSL2_MAX_CSI * 2];
 +	struct reg_field csi_lane_map[GMSL2_MAX_CSI * 2];
-+	struct reg_field csi_phy_out_freq[GMSL2_MAX_CSI * 2];
++	struct reg_field csi_dpll_freq[GMSL2_MAX_CSI * 2];
 +	struct reg_field pattern_clk_freq;
 +	u16 patgen_base_addr[2];
 +	struct reg_field pipe_enable[GMSL2_MAX_PIPE];
@@ -129,6 +480,7 @@ index 0000000..8a30110
 +	struct reg_field pipe_vc[GMSL2_MAX_PIPE];
 +	struct reg_field pipe_dt_l[GMSL2_MAX_PIPE];
 +	struct reg_field pipe_dt_h[GMSL2_MAX_PIPE];
++	struct reg_field pipe_dt_sel[GMSL2_MAX_PIPE];
 +	struct reg_field pipe_vid_lock[GMSL2_MAX_PIPE];
 +	u8 pipe_remap_regs;
 +	u16 pipe_remap_base_addr[GMSL2_MAX_PIPE];
@@ -137,83 +489,127 @@ index 0000000..8a30110
 +	struct reg_field reset_link[GMSL2_MAX_LINK];
 +	/* GMSL1 backward compatibility */
 +	const struct max_gmsl2_compat_gmsl1 *gmsl1;
++	struct reg_field tunnel_mode;
 +};
 +
-+/* VC[7:6] DT[5:0] */
-+#define PIPE_ELEMENT(vc, dt) ( vc << 6 | dt )
++int max_gmsl2_csi_get_link_freq(struct max_gmsl2_csi *csi, s64 *link_freq);
++int max_gmsl2_csi_phy_init(struct max_gmsl2_csi *csi);
++int max_gmsl2_init_pipes_stream_id(struct max_gmsl2_pipe *pipes, u8 npipes,
++				  struct device *dev);
++void max_gmsl2_pipe_init_regfield(struct device *dev,  struct regmap *regmap,
++				  struct max_gmsl2_pipe *pipe,
++				  const struct max_gmsl2_data *data,
++				  u8 id);
++void max_gmsl2_csi_init_regfield(struct device *dev, struct regmap *regmap,
++				 struct max_gmsl2_csi *csi,
++				 const struct max_gmsl2_data *data,
++				 u8 id);
++void max_gmsl2_link_init_regfield(struct device *dev,
++				 struct regmap *regmap,
++				 struct max_gmsl2_link *link,
++				  const struct max_gmsl2_data *data, u8 id);
++void max_gmsl2_state_show_pipe(struct device *dev, struct max_gmsl2_pipe *pipe,
++			       const struct max_gmsl2_data *data, u8 pipeid);
 +
-+struct max_gmsl2_deser_pipe {
-+	struct regmap_field *enable;
-+	struct regmap_field *link_sel;
-+	struct regmap_field *stream_id;
-+	struct regmap_field *vid_lock;
-+	struct regmap_field *override_bpp_vc_dt;
-+	struct regmap_field *bpp_l;
-+	struct regmap_field *bpp_h;
-+	struct regmap_field *dt_l;
-+	struct regmap_field *dt_h;
-+	struct regmap_field *vc;
-+	struct v4l2_subdev sd;
-+	struct media_pad pad;
-+	u8 pipe_remap_idx;
-+};
-+#define max_gmsl2_deser_sd_to_pipe(sd) \
-+	container_of(sd, struct max_gmsl2_deser_pipe, sd)
++/* Some registers such as bpp and dt maybe split on two regmap_field */
++int max_gmsl2_write_h_l_val(struct regmap_field *h, struct regmap_field *l,
++			    const struct reg_field *l_field, unsigned int val);
++int max_gmsl2_read_h_l_val(struct regmap_field *h, struct regmap_field *l,
++			   const struct reg_field *l_field, unsigned int *val);
++#endif
+diff --git a/drivers/media/i2c/max-gmsl2-deser.c b/drivers/media/i2c/max-gmsl2-deser.c
+new file mode 100644
+index 00000000..de85183
+--- /dev/null
++++ b/drivers/media/i2c/max-gmsl2-deser.c
+@@ -0,0 +1,1463 @@
++// SPDX-License-Identifier: GPL-2.0
++/*
++ * Maxim GMSL2 Deserializer Driver
++ *
++ * Copyright (C) 2022 Collabora Ltd.
++ */
 +
-+struct max_gmsl2_deser_csi {
-+	struct v4l2_subdev sd;
-+	struct media_pad pads[2];
-+	struct v4l2_ctrl_handler ctrl_hdl;
-+	struct v4l2_ctrl *pixel_rate;
-+	struct v4l2_subdev_format format;
-+	struct max_gmsl2_deser_priv *priv;
-+	struct regmap_field *phy_enable;
-+	struct regmap_field *phy_out_freq[2];
-+	struct regmap_field *lane_polarities[2];
-+	struct regmap_field *lane_map[2];
-+	u8 id;
++#include <linux/gpio/consumer.h>
++#include <linux/gpio/max-gmsl2-gpio.h>
++#include <linux/i2c.h>
++#include <linux/i2c-mux.h>
++#include <linux/mod_devicetable.h>
++#include <linux/module.h>
++#include <linux/of_device.h>
++#include <linux/regmap.h>
++#include <linux/v4l2-dv-timings.h>
++#include <media/v4l2-ctrls.h>
++#include <media/v4l2-device.h>
++#include <media/v4l2-fwnode.h>
++#include <media/v4l2-subdev.h>
++#include <media/mipi-csi2.h>
++
++#include "gmsl2-def.h"
++#include <media/gmsl2/max-gmsl2-patgen.h>
++
++struct max_gmsl2_compat_gmsl1 {
++	struct reg_field gmsl2_link_mode;
++	struct reg_field highimm[GMSL2_MAX_LINK];
++	struct reg_field link_lock[GMSL2_MAX_LINK];
++	struct reg_field forward_cc[GMSL2_MAX_LINK];
++	struct reg_field shift_vid_hvd[GMSL2_MAX_LINK];
++	u16              conv_datatype[GMSL2_MAX_LINK];
 +};
 +
-+#define max_gmsl2_deser_sd_to_csi(sd) \
-+	container_of(sd, struct max_gmsl2_deser_csi, sd)
++/* VC[7:6] DT[5:0] */
++#define PIPE_ELEMENT(vc, dt) ( vc << 6 | dt )
 +
-+struct max_gmsl2_deser_priv {
++#define GMSL2_MAX_NPORTS (GMSL2_MAX_LINK + GMSL2_MAX_CSI)
++
++struct max_gmsl2_deser {
 +	struct i2c_client *client;
 +	struct regmap *regmap;
 +	struct gpio_desc *gpiod_pwdn;
 +	struct v4l2_ctrl_handler ctrls;
-+	struct v4l2_ctrl *pixelrate;
-+	struct max_gmsl2_deser_pipe pipes[GMSL2_MAX_PIPE];
-+	struct max_gmsl2_deser_csi csi[GMSL2_MAX_CSI];
-+	const struct max_gmsl2_deser_data *data;
++	struct max_gmsl2_pipe pipes[GMSL2_MAX_PIPE];
++	struct max_gmsl2_csi csi[GMSL2_MAX_CSI];
++	struct max_gmsl2_link links[GMSL2_MAX_LINK];
++	struct max_gmsl2_patgen patgen;
++	struct v4l2_subdev_format format;
++	struct i2c_mux_core *mux;
++	struct gpio_chip gpio;
++	const struct max_gmsl2_data *data;
 +	struct regmap_field *dev_rev;
 +	u8 csi_use_cnt;
-+	struct regmap_field *csi_enable;
++	struct regmap_field *csi_force_enable;
 +	/* wait a new frame before generating a MIPI packet */
 +	struct regmap_field *csi_wait_frame;
-+	struct regmap_field *pattern_clk_freq;
-+	struct regmap_field *link_locked[GMSL2_MAX_LINK];
-+	struct regmap_field *reset_one_shot_link[GMSL2_MAX_LINK];
-+	struct regmap_field *reset_link[GMSL2_MAX_LINK];
 +	struct regmap_field *gmsl2_link_mode;
 +	struct regmap_field *gmsl1_highimm[GMSL2_MAX_LINK];
 +	struct regmap_field *gmsl1_link_locked[GMSL2_MAX_LINK];
 +	struct regmap_field *gmsl1_forward_cc[GMSL2_MAX_LINK];
-+	struct dentry *dbg_dir;
++	struct regmap_field *gmsl1_shift_vid_hvd[GMSL2_MAX_LINK];
++	struct v4l2_subdev	sd;
++	struct media_pad	pads[GMSL2_MAX_NPORTS];
++	struct v4l2_ctrl_handler   ctrl_handler;
++	struct v4l2_async_notifier notifier;
++	s64 tx_link_freq[1];
++	struct regmap_field *tunnel_mode;
++	bool tunnel_mode_saved;
 +};
 +#define GMSL2_INVALID_REG_FIELD REG_FIELD(0, 0, 0)
 +
-+#define MAX96934_DEVICE_ID 0x92
-+const struct max_gmsl2_deser_data max96934_data = {
++#define sd_to_max_gmsl2_deser(sd) \
++	container_of(sd, struct max_gmsl2_deser, sd)
++
++static const struct max_gmsl2_data max96934_data = {
 +	.device_id = MAX96934_DEVICE_ID,
 +	.pipes = 4,
 +	.links = 2,
 +	.csi = 2,
++	.ngpios = 20,
++	.gpio_base = 0x2b0,
 +	.dev_rev = REG_FIELD(0xe, 0, 3),
-+	.csi_enable = REG_FIELD(0x313, 1, 1),
++	.csi_lane_cnt = {REG_FIELD(0x44a, 6, 7), REG_FIELD(0x48a, 6, 7)},
 +	.csi_wait_frame = REG_FIELD(0x325, 7, 7),
 +	.csi_phy_enable = { REG_FIELD(0x332, 4, 5), REG_FIELD(0x332, 6, 7) },
-+	.csi_phy_out_freq = { REG_FIELD(0x31d, 0, 4), REG_FIELD(0x320, 0, 4),
++	.csi_dpll_freq = { REG_FIELD(0x31d, 0, 4), REG_FIELD(0x320, 0, 4),
 +		REG_FIELD(0x323, 0, 4), REG_FIELD(0x326, 0, 4)},
 +
 +	/* phy1, phy0, phy2, phy3 in this order, because phy1 and phy2 are used
@@ -257,7 +653,7 @@ index 0000000..8a30110
 +};
 +#define IS_MAX96934(priv) (priv->data->device_id == MAX96934_DEVICE_ID)
 +
-+const struct max_gmsl2_compat_gmsl1 max96714_gmsl1_data = {
++static const struct max_gmsl2_compat_gmsl1 max96714f_gmsl1_data = {
 +	.gmsl2_link_mode = REG_FIELD(0x6, 6, 6),
 +	.highimm = { REG_FIELD(0xb06, 7, 7) },
 +	/* FIXME conv datatype register is a bit different
@@ -267,19 +663,23 @@ index 0000000..8a30110
 +	 */
 +	.conv_datatype = {0xb96 },
 +	.link_lock = { REG_FIELD(0xbcb, 0, 0) },
++	.shift_vid_hvd = { REG_FIELD(0xba7, 6, 6) },
 +};
 +
-+#define MAX96714_DEVICE_ID 0xc9
-+const struct max_gmsl2_deser_data max96714_data = {
-+	.device_id = MAX96714_DEVICE_ID,
++static const struct max_gmsl2_data max96714f_data = {
++	.device_id = MAX96714F_DEVICE_ID,
 +	.pipes = 1,
 +	.links = 1,
 +	.csi = 1,
++	.ngpios = 9,
++	.gpio_base = 0x2b0,
 +	.dev_rev = REG_FIELD(0xe, 0, 3),
-+	.csi_enable = REG_FIELD(0x313, 1, 1),
++	.csi_lane_cnt = { REG_FIELD(0x44a, 6, 7) },
++	.csi_force_enable = REG_FIELD(0x330, 7, 7),
 +	.csi_wait_frame = REG_FIELD(0x325, 7, 7),
 +	.csi_phy_enable = { REG_FIELD(0x332, 4, 5) },
-+	.csi_phy_out_freq = { GMSL2_INVALID_REG_FIELD, REG_FIELD(0x320, 0, 4), },
++	/* MAX96714 Can't do 2x2 MiPi, so DPHY0 freq can't be set */
++	.csi_dpll_freq = { GMSL2_INVALID_REG_FIELD, REG_FIELD(0x320, 0, 4), },
 +	.csi_lane_polarities = { REG_FIELD(0x335, 0, 2), REG_FIELD(0x335, 3, 5) },
 +	.csi_lane_map = { REG_FIELD(0x333, 0, 3), REG_FIELD(0x333, 4, 7) },
 +	.pattern_clk_freq = REG_FIELD(0x38, 0, 1),
@@ -300,15 +700,13 @@ index 0000000..8a30110
 +	.reset_one_shot_link = { REG_FIELD(0x10, 5, 5) },
 +	.reset_link = { REG_FIELD(0x10, 6, 6) },
 +	/* This device is backward compatible with GMSL1 */
-+	.gmsl1 = &max96714_gmsl1_data,
++	.gmsl1 = &max96714f_gmsl1_data,
++	.tunnel_mode = REG_FIELD(0x474, 0, 0),
 +
 +};
-+#define IS_MAX96714(priv) (priv->data->device_id == MAX96714_DEVICE_ID)
++#define IS_MAX96714F(priv) (priv->data->device_id == MAX96714F_DEVICE_ID)
 +
-+
-+#define MAX96914_DEVICE_ID 0xa0
-+
-+const struct max_gmsl2_compat_gmsl1 max96914_gmsl1_data = {
++static const struct max_gmsl2_compat_gmsl1 max96914_gmsl1_data = {
 +	.gmsl2_link_mode = REG_FIELD(0x6, 4, 7),
 +	.forward_cc = {REG_FIELD(0xb04, 0, 0), REG_FIELD(0xc04, 0, 0),
 +		REG_FIELD(0xd04, 0, 0), REG_FIELD(0xe04, 0, 0) },
@@ -316,20 +714,25 @@ index 0000000..8a30110
 +		REG_FIELD(0xd06, 7, 7), REG_FIELD(0xe06, 7, 7) },
 +	.conv_datatype = {0xb96, 0xc96, 0xd96, 0xe96},
 +	.link_lock = { REG_FIELD(0xbcb, 0, 0), REG_FIELD(0xccb, 0, 0),
-+		REG_FIELD(0xdcb, 0, 0), REG_FIELD(0xecb, 0, 0) }
++		REG_FIELD(0xdcb, 0, 0), REG_FIELD(0xecb, 0, 0) },
++	.shift_vid_hvd = { REG_FIELD(0xba7, 6, 6), REG_FIELD(0xca7, 6, 6),
++		REG_FIELD(0xda7, 6, 6), REG_FIELD(0xea7, 7, 7) },
 +};
 +
-+const struct max_gmsl2_deser_data max96914_data = {
-+	.device_id = MAX96914_DEVICE_ID,
++static const struct max_gmsl2_data max96914_data = {
++	.device_id = MAX96724_DEVICE_ID,
 +	.pipes = 8,
 +	.links = 4,
 +	.csi = 2,
++	.ngpios = 17,
++	.gpio_base = 0x300,
 +	.dev_rev = REG_FIELD(0x4c, 0, 3),
-+	.csi_enable = REG_FIELD(0x8a0,7, 7),
++	.csi_lane_cnt = {REG_FIELD(0x94a, 6, 7), REG_FIELD(0x98a, 6, 7)},
++	.csi_force_enable = REG_FIELD(0x8a0,7, 7),
 +	.csi_wait_frame = GMSL2_INVALID_REG_FIELD, /*FIXME */
 +
 +	.csi_phy_enable = { REG_FIELD(0x8a2, 4, 5), REG_FIELD(0x8a2, 6, 7) },
-+	.csi_phy_out_freq = { REG_FIELD(0x415, 0, 4), REG_FIELD(0x418, 0, 4),
++	.csi_dpll_freq = { REG_FIELD(0x415, 0, 4), REG_FIELD(0x418, 0, 4),
 +		REG_FIELD(0x41b, 0, 4), REG_FIELD(0x41e, 0, 4)},
 +	.csi_lane_polarities = { REG_FIELD(0x8a5, 0, 2), REG_FIELD(0x8a5, 3, 5),
 +		REG_FIELD(0x8a6, 0, 2), REG_FIELD(0x8a6, 3, 5) },
@@ -419,14 +822,83 @@ index 0000000..8a30110
 +};
 +#define IS_MAX96914(priv) (priv->data->device_id == MAX96914_DEVICE_ID)
 +
-+#define GMSL2_MIPI_DT_RGB888 0x24
-+
-+static struct regmap_config max_gmsl2_deser_regmap_config = {
-+	.reg_bits = 16,
-+	.val_bits = 8,
-+	.max_register = 0xFFFF,
-+	.cache_type = REGCACHE_NONE,
++static const struct max_gmsl2_data max96724_data = {
++	.device_id = MAX96724_DEVICE_ID,
++	.pipes = 4,
++	.csi = 2,
++	.links = 4,
++	.ngpios = 11,
++	.gpio_base = 0x300,
++	.dev_rev = REG_FIELD(0x4c, 0, 3),
++	.csi_lane_cnt = { REG_FIELD(0x90a, 6, 7), REG_FIELD(0x94a, 6, 7) },
++	.csi_force_enable = REG_FIELD(0x8a0,7, 7),
++	.csi_phy_enable = { REG_FIELD(0x8a2, 4, 5), REG_FIELD(0x8a2, 6, 7) },
++	.csi_dpll_freq = { REG_FIELD(0x415, 0, 4), REG_FIELD(0x418, 0, 4),
++		REG_FIELD(0x41b, 0, 4), REG_FIELD(0x41e, 0, 4)},
++	.csi_lane_polarities = { REG_FIELD(0x8a5, 0, 2), REG_FIELD(0x8a5, 3, 5),
++		REG_FIELD(0x8a6, 0, 2), REG_FIELD(0x8a6, 3, 5) },
++	.csi_lane_map = { REG_FIELD(0x8a3, 0, 3), REG_FIELD(0x8a3, 4, 7),
++		REG_FIELD(0x8a4, 0, 3), REG_FIELD(0x8a4, 4, 7) },
++	.patgen_base_addr = {0x1050},
++	.pipe_enable = {
++		REG_FIELD(0xf4, 0, 0), REG_FIELD(0xf4, 1, 1),
++		REG_FIELD(0xf4, 2, 2), REG_FIELD(0xf4, 3, 3),
++	},
++	.pipe_link_sel = {
++		REG_FIELD(0xf0, 2, 3), REG_FIELD(0xf0, 6, 7),
++		REG_FIELD(0xf1, 2, 3), REG_FIELD(0xf1, 6, 7),
++	},
++	.pipe_stream_id = {
++		REG_FIELD(0xf0, 0, 1), REG_FIELD(0xf0, 4, 5),
++		REG_FIELD(0xf1, 0, 1), REG_FIELD(0xf1, 4, 5),
++	},
++	.pipe_override_bpp_vc_dt = {
++		REG_FIELD(0x415, 6, 6), REG_FIELD(0x415, 7, 7),
++		REG_FIELD(0x418, 6, 6), REG_FIELD(0x418, 7, 7),
++	},
++	.pipe_bpp_l = {
++		REG_FIELD(0x40b, 3, 7), REG_FIELD(0x411, 0, 4),
++		REG_FIELD(0x412, 0, 1), REG_FIELD(0x412, 2, 6),
++	},
++	.pipe_bpp_h = {
++		GMSL2_INVALID_REG_FIELD,  GMSL2_INVALID_REG_FIELD,
++		REG_FIELD(0x411, 5, 7),   GMSL2_INVALID_REG_FIELD,
++	},
++	.pipe_dt_l = {
++		REG_FIELD(0x40e, 0, 5), REG_FIELD(0x40f, 0, 3),
++		REG_FIELD(0x410, 0, 1), REG_FIELD(0x410, 2, 7),
++	},
++	.pipe_dt_h = {
++		GMSL2_INVALID_REG_FIELD,  REG_FIELD(0x40e, 6, 7),
++		REG_FIELD(0x40f, 4, 7),   GMSL2_INVALID_REG_FIELD,
++	},
++	.pipe_vc = {
++		REG_FIELD(0x40c, 0, 3), REG_FIELD(0x40c, 4, 7),
++		REG_FIELD(0x40d, 0, 3), REG_FIELD(0x40d, 4, 7),
++	},
++	.pipe_vid_lock = {
++		REG_FIELD(0x108, 6, 6), REG_FIELD(0x11a, 6, 6),
++		REG_FIELD(0x12c, 6, 6), REG_FIELD(0x13e, 6, 6),
++	},
++	.pipe_remap_regs = 16,
++	.pipe_remap_base_addr = {
++		0x90b, 0x94b, 0x98b, 0x9cb
++	},
++	.link_lock = {
++		REG_FIELD(0x1a, 3, 3), REG_FIELD(0xa, 3, 3),
++		REG_FIELD(0xb, 3, 3), REG_FIELD(0xc, 3, 3)
++	},
++	.reset_one_shot_link = {
++		REG_FIELD(0x18, 0, 0), REG_FIELD(0x18, 1, 1),
++		REG_FIELD(0x18, 2, 2), REG_FIELD(0x18, 3, 3)
++	},
++	.reset_link = {
++		REG_FIELD(0x18, 4, 4), REG_FIELD(0x18, 5, 5),
++		REG_FIELD(0x18, 6, 6), REG_FIELD(0x18, 7, 7)
++	},
++	.gmsl1 = &max96914_gmsl1_data,
 +};
++#define IS_MAX96724(priv) (priv->data->device_id == MAX96724_DEVICE_ID)
 +
 +#define for_each_pipe(pipe_idx, priv) \
 +	for ( (pipe_idx) = 0; (pipe_idx) < (priv)->data->pipes; (pipe_idx)++ )
@@ -437,151 +909,63 @@ index 0000000..8a30110
 +#define for_each_link(link_idx, priv) \
 +	for ( (link_idx) = 0; (link_idx) < (priv)->data->links; (link_idx)++ )
 +
-+static const char* pipe_to_str_quad(u8 pipe) {
-+	switch (pipe) {
-+	case 0:
-+		return "pipe_0";
-+	case 1:
-+		return "pipe_1";
-+	case 2:
-+		return "pipe_2";
-+	case 3:
-+		return "pipe_3";
-+	case 4:
-+		return "pipe_4";
-+	case 5:
-+		return "pipe_5";
-+	case 6:
-+		return "pipe_6";
-+	case 7:
-+		return "pipe_7";
-+	}
-+
-+	return NULL;
-+}
-+
-+static const char* pipe_to_str_dual(u8 pipe) {
-+	switch (pipe) {
-+	case 0:
-+		return "pipe_X";
-+	case 1:
-+		return "pipe_Y";
-+	case 2:
-+		return "pipe_Z";
-+	case 3:
-+		return "pipe_U";
-+	}
++static void max_gmsl2_deser_init_patgen_regs(struct max_gmsl2_deser *priv)
++{
++	struct device *dev = &priv->client->dev;
++	const struct max_gmsl2_data *data = priv->data;
++	struct max_gmsl2_patgen *patgen = &priv->patgen;
++	const struct reg_field *field;
++	u16 base = data->patgen_base_addr[0];
++	struct reg_field gen_vs_hs_de = REG_FIELD(base, 5, 7);
++	struct reg_field inv_vs_hs_de = REG_FIELD(base, 2, 4);
++	struct reg_field vtg_mode = REG_FIELD(base, 0, 2);
++	struct reg_field vpg_mode_reg = REG_FIELD(base + 1, 4, 5);
 +
-+	return NULL;
-+}
++	field = &data->pattern_clk_freq;
++	if (field->reg)
++		priv->patgen.pattern_clk_freq =
++			devm_regmap_field_alloc(dev, priv->regmap, *field);
 +
-+/* Quad and dual link deserializer have different pipes naming */
-+static const char* pipe_to_str(struct max_gmsl2_deser_priv *priv, u8 pipe) {
-+	if (priv->data->links == 4)
-+		return pipe_to_str_quad(pipe);
-+	else
-+		return pipe_to_str_dual(pipe);;
++	patgen->gen_vs_hs_de = devm_regmap_field_alloc(dev, priv->regmap, gen_vs_hs_de);
++	patgen->inv_vs_hs_de = devm_regmap_field_alloc(dev, priv->regmap, inv_vs_hs_de);
++	patgen->vtg_mode = devm_regmap_field_alloc(dev, priv->regmap, vtg_mode);
++	patgen->vpg_mode = devm_regmap_field_alloc(dev, priv->regmap, vpg_mode_reg);
++	patgen->vtg_timing = base + 2;
++	patgen->vpg_patterns = base + 0x1d;
 +}
 +
-+static const char* device_id_to_name(u8 device_id)
-+{
-+	switch (device_id) {
-+	case MAX96934_DEVICE_ID:
-+		return "max96934";
-+	case MAX96914_DEVICE_ID:
-+		return "max96914";
-+	case MAX96714_DEVICE_ID:
-+		return "max96714";
-+	default:
-+		break;
-+	}
-+	return NULL;
-+}
 +
-+static int max_gmsl2_deser_init_regmap(struct max_gmsl2_deser_priv *priv)
++static int max_gmsl2_deser_init_regmap(struct max_gmsl2_deser *priv)
 +{
 +	struct device *dev = &priv->client->dev;
-+	const struct max_gmsl2_deser_data *data = priv->data;
-+	struct max_gmsl2_deser_pipe *pipe;
-+	struct max_gmsl2_deser_csi *csi;
++	const struct max_gmsl2_data *data = priv->data;
 +	const struct reg_field *field;
 +	u8 i;
 +
 +	priv->regmap = devm_regmap_init_i2c(priv->client,
-+					    &max_gmsl2_deser_regmap_config);
++					    &gmsl2_regmap_config);
 +	if (IS_ERR(priv->regmap))
 +		return PTR_ERR(priv->regmap);
 +
-+	for_each_pipe(i, priv) {
-+		pipe = &priv->pipes[i];
-+
-+		field = &data->pipe_enable[i];
-+		pipe->enable = devm_regmap_field_alloc(dev, priv->regmap,
-+						       *field);
-+		field = &data->pipe_stream_id[i];
-+		pipe->stream_id = devm_regmap_field_alloc(dev, priv->regmap,
-+							  *field);
-+		field = &data->pipe_link_sel[i];
-+		if (field->reg)
-+			pipe->link_sel = devm_regmap_field_alloc(dev, priv->regmap,
-+								 *field);
-+		field = &data->pipe_vid_lock[i];
-+		pipe->vid_lock = devm_regmap_field_alloc(dev, priv->regmap, *field);
-+
-+		field = &data->pipe_override_bpp_vc_dt[i];
-+		pipe->override_bpp_vc_dt = devm_regmap_field_alloc(dev, priv->regmap, *field);
-+
-+		field = &data->pipe_bpp_l[i];
-+		pipe->bpp_l = devm_regmap_field_alloc(dev, priv->regmap, *field);
-+		field = &data->pipe_bpp_h[i];
-+		if (field->reg)
-+			pipe->bpp_h = devm_regmap_field_alloc(dev, priv->regmap, *field);
-+
-+		field = &data->pipe_dt_l[i];
-+		pipe->dt_l = devm_regmap_field_alloc(dev, priv->regmap, *field);
-+		field = &data->pipe_dt_h[i];
-+		if (field->reg)
-+			pipe->dt_h = devm_regmap_field_alloc(dev, priv->regmap, *field);
-+
-+		field = &data->pipe_dt_l[i];
-+		pipe->dt_l = devm_regmap_field_alloc(dev, priv->regmap, *field);
-+
-+		field = &data->pipe_vc[i];
-+		pipe->vc = devm_regmap_field_alloc(dev, priv->regmap, *field);
-+	}
++	for_each_pipe(i, priv)
++		max_gmsl2_pipe_init_regfield(dev, priv->regmap,
++					     &priv->pipes[i], priv->data,
++					     i);
++
++	for_each_csi(i, priv)
++		max_gmsl2_csi_init_regfield(dev, priv->regmap,
++					     &priv->csi[i], priv->data,
++					     i);
++	for_each_link(i, priv)
++		max_gmsl2_link_init_regfield(dev, priv->regmap,
++					     &priv->links[i], priv->data,
++					     i);
++	field = &data->dev_rev;
++	priv->dev_rev = devm_regmap_field_alloc(dev, priv->regmap, *field);
 +
-+	for_each_csi(i, priv) {
-+		csi = &priv->csi[i];
-+		field = &data->csi_phy_enable[i];
-+		csi->phy_enable = devm_regmap_field_alloc(dev, priv->regmap, *field);
-+
-+		field = &data->csi_phy_out_freq[i*2];
-+		if (field->reg)
-+			csi->phy_out_freq[0] = devm_regmap_field_alloc(dev, priv->regmap, *field);
-+
-+		field = &data->csi_phy_out_freq[i * 2 + 1];
-+		if (field->reg)
-+			csi->phy_out_freq[1] = devm_regmap_field_alloc(dev, priv->regmap, *field);
-+
-+		field = &data->csi_lane_polarities[i * 2];
-+		csi->lane_polarities[0] = devm_regmap_field_alloc(dev, priv->regmap, *field);
-+
-+		field = &data->csi_lane_polarities[i * 2 +1];
-+		csi->lane_polarities[1] = devm_regmap_field_alloc(dev, priv->regmap, *field);
-+
-+		field = &data->csi_lane_map[i * 2];
-+		csi->lane_map[0] = devm_regmap_field_alloc(dev, priv->regmap, *field);
-+
-+		field = &data->csi_lane_map[i * 2 + 1];
-+		csi->lane_map[1] = devm_regmap_field_alloc(dev, priv->regmap, *field);
-+	}
-+
-+	field = &data->dev_rev;
-+	priv->dev_rev = devm_regmap_field_alloc(dev, priv->regmap, *field);
-+
-+	field = &data->csi_enable;
++	field = &data->csi_force_enable;
 +	if (field->reg)
-+		priv->csi_enable =
++		priv->csi_force_enable =
 +			devm_regmap_field_alloc(dev, priv->regmap, *field);
 +
 +	field = &data->csi_wait_frame;
@@ -589,25 +973,12 @@ index 0000000..8a30110
 +		priv->csi_wait_frame =
 +			devm_regmap_field_alloc(dev, priv->regmap, *field);
 +
-+	field = &data->pattern_clk_freq;
++	field = &data->tunnel_mode;
 +	if (field->reg)
-+		priv->pattern_clk_freq =
++		priv->tunnel_mode =
 +			devm_regmap_field_alloc(dev, priv->regmap, *field);
 +
-+	for_each_link(i, priv) {
-+		field = &data->link_lock[i];
-+		if (field->reg)
-+			priv->link_locked[i] =
-+				devm_regmap_field_alloc(dev, priv->regmap, *field);
-+		field = &data->reset_one_shot_link[i];
-+		if (field->reg)
-+			priv->reset_one_shot_link[i] =
-+				devm_regmap_field_alloc(dev, priv->regmap, *field);
-+		field = &data->reset_link[i];
-+		if (field->reg)
-+			priv->reset_link[i] =
-+				devm_regmap_field_alloc(dev, priv->regmap, *field);
-+	}
++	max_gmsl2_deser_init_patgen_regs(priv);
 +
 +	if (data->gmsl1) {
 +		field = &data->gmsl1->gmsl2_link_mode;
@@ -626,6 +997,10 @@ index 0000000..8a30110
 +			priv->gmsl1_forward_cc[i] =
 +				devm_regmap_field_alloc(dev, priv->regmap,
 +							*field);
++			field = &data->gmsl1->shift_vid_hvd[i];
++			priv->gmsl1_shift_vid_hvd[i] =
++				devm_regmap_field_alloc(dev, priv->regmap,
++							*field);
 +		}
 +	}
 +	return 0;
@@ -639,10 +1014,10 @@ index 0000000..8a30110
 +#define DST_DPHY_REG(n) (0x22 + n / 4)
 +#define DST_DPHY_SHIFT(n) ((n % 4) * 2)
 +#define DST_DPHY_MASK(n) (0x3 << DST_DPHY_SHIFT(n))
-+static int pipe_remap_element(struct max_gmsl2_deser_priv *priv, u8 pipeid,
++static int pipe_remap_element(struct max_gmsl2_deser *priv, u8 pipeid,
 +			      u8 src, u8 dst, u8 csiid)
 +{
-+	struct max_gmsl2_deser_pipe *pipe = &priv->pipes[pipeid];
++	struct max_gmsl2_pipe *pipe = &priv->pipes[pipeid];
 +	u16 base = priv->data->pipe_remap_base_addr[pipeid];
 +	u8 idx = pipe->pipe_remap_idx;
 +	int ret;
@@ -673,9 +1048,9 @@ index 0000000..8a30110
 +	return 0;
 +}
 +
-+static int pipe_clear_remap(struct max_gmsl2_deser_priv *priv, u8 pipeid)
++static int pipe_clear_remap(struct max_gmsl2_deser *priv, u8 pipeid)
 +{
-+	struct max_gmsl2_deser_pipe *pipe = &priv->pipes[pipeid];
++	struct max_gmsl2_pipe *pipe = &priv->pipes[pipeid];
 +	u16 base = priv->data->pipe_remap_base_addr[pipeid];
 +	int i, ret;
 +
@@ -689,7 +1064,7 @@ index 0000000..8a30110
 +	return 0;
 +}
 +
-+static int pipes_clear_remap(struct max_gmsl2_deser_priv *priv)
++static int pipes_clear_remap(struct max_gmsl2_deser *priv)
 +{
 +	u8 pipeid;
 +	int ret;
@@ -702,196 +1077,152 @@ index 0000000..8a30110
 +	return 0;
 +}
 +
-+/* Some registers such as bpp and dt maybe split on two regmap_field */
-+static inline int max_gmsl2_write_h_l_val(struct regmap_field *h, struct regmap_field *l,
-+					  const struct reg_field *l_field, unsigned int val)
-+{
-+	u8 shift;
-+	int ret;
-+
-+	/* if h ptr is not null then the register is splitted */
-+	if (h) {
-+		shift = l_field->msb - l_field->lsb + 1;
-+		ret = regmap_field_write(h, val >> shift);
-+		if (ret)
-+			return ret;
-+	}
-+
-+	/* regmap_field_write will take care of applying a mask on val
-+	 * to not write the h bits.
-+	 */
-+	return regmap_field_write(l, val);
-+}
-+
-+static inline int max_gmsl2_read_h_l_val(struct regmap_field *h, struct regmap_field *l,
-+					 const struct reg_field *l_field, unsigned int *val)
-+{
-+	u8 shift;
-+	unsigned int h_val;
-+	int ret;
-+
-+	ret = regmap_field_read(l, val);
-+	if (ret)
-+		return ret;
-+	if (h) {
-+		ret = regmap_field_read(h, &h_val);
-+		if (ret)
-+			return ret;
-+		shift = l_field->msb - l_field->lsb + 1;
-+		*val |= (h_val << shift);
-+	}
-+
-+	return 0;
-+}
-+
-+static int max_gmsl2_deser_state_show(struct seq_file *seq, void *p)
++static int max_gmsl2_deser_log_status(struct v4l2_subdev *sd)
 +{
-+	struct max_gmsl2_deser_priv *priv = seq->private;
-+	struct max_gmsl2_deser_pipe *pipe;
-+	struct max_gmsl2_deser_csi *csi;
-+	unsigned int link_locked, enable, stream_id, link_sel, vid_lock, bpp, dt, vc, freq1, freq2, dphy_en, link_mode;
-+	u8 link, pipeid, csiid;
++	struct max_gmsl2_deser *priv = sd_to_max_gmsl2_deser(sd);
++	struct device *dev = &priv->client->dev;
++	struct max_gmsl2_pipe *pipe;
++	struct max_gmsl2_csi *csi;
++	struct max_gmsl2_link *link;
++	unsigned int link_locked, freq, dphy_en, link_mode;
++	u8 linkid, pipeid, csiid;
 +	int ret;
 +
-+	seq_printf(seq, "Max GMSL2 deserializer %s id: %02x\n",
-+		   device_id_to_name(priv->data->device_id), priv->data->device_id);
++	dev_info(dev, "Max GMSL2 deserializer %s id: %02x\n",
++		   gmsl2_id_to_name(priv->data->device_id), priv->data->device_id);
 +
 +	/* MAX96934 has only one link lock flag */
 +	if (IS_MAX96934(priv)) {
-+		ret = regmap_field_read(priv->link_locked[0], &link_locked);
++		ret = regmap_field_read(priv->links[0].locked, &link_locked);
 +		if (!ret)
-+			seq_printf(seq, "Link locked: %u\n", link_locked);
++			dev_info(dev, "Link locked: %u\n", link_locked);
 +	} else {
 +		/* MAX96914 with one link lock per link */
 +		ret = regmap_field_read(priv->gmsl2_link_mode, &link_mode);
 +		if (ret)
 +			return ret;
-+		for_each_link(link, priv) {
-+			if (link_mode & BIT(link)) {
++		for_each_link(linkid, priv) {
++			link = &priv->links[linkid];
++			if (link_mode & BIT(linkid)) {
 +				/* gmsl2*/
-+				ret = regmap_field_read(priv->link_locked[link], &link_locked);
++				ret = regmap_field_read(link->locked, &link_locked);
 +				if (!ret)
-+					seq_printf(seq, "GMSL2 Link %c locked: %u\n", 'A' + link, link_locked);
++					dev_info(dev, "GMSL2 Link %c locked: %u\n", 'A' + linkid, link_locked);
 +			} else {
 +				/* gmsl1 backward compatibility */
-+				ret = regmap_field_read(priv->gmsl1_link_locked[link], &link_locked);
++				ret = regmap_field_read(priv->gmsl1_link_locked[linkid], &link_locked);
 +				if (!ret)
-+					seq_printf(seq, "GMSL1 Link %c locked: %u\n", 'A' + link, link_locked);
++					dev_info(dev, "GMSL1 Link %c locked: %u\n", 'A' + linkid, link_locked);
 +			}
 +		}
 +	}
 +	for_each_pipe(pipeid, priv) {
 +		pipe = &priv->pipes[pipeid];
-+		ret = regmap_field_read(pipe->enable, &enable);
-+		if (ret)
-+			break;
-+		ret = regmap_field_read(pipe->stream_id, &stream_id);
-+		if (ret)
-+			break;
-+		if (pipe->link_sel) {
-+			ret = regmap_field_read(pipe->link_sel, &link_sel);
-+			if (ret)
-+				break;
-+		}
-+
-+		ret = regmap_field_read(pipe->vid_lock, &vid_lock);
-+		if (ret)
-+			break;
-+		ret = max_gmsl2_read_h_l_val(pipe->bpp_h, pipe->bpp_l,
-+					     &priv->data->pipe_bpp_l[pipeid],
-+					     &bpp);
-+		if (ret)
-+			break;
-+		ret = max_gmsl2_read_h_l_val(pipe->dt_h, pipe->dt_l,
-+					     &priv->data->pipe_dt_l[pipeid],
-+					     &dt);
-+		if (ret)
-+			break;
-+		ret = regmap_field_read(pipe->vc, &vc);
-+		if (ret)
-+			break;
-+
-+		seq_printf(seq, "%s: enable[%u]",
-+			   pipe_to_str(priv, pipeid), enable);
-+		if (pipe->link_sel)
-+			seq_printf(seq, " link_sel[%c]", 'A' + link_sel);
-+		seq_printf(seq, " stream_id[%u]", stream_id);
-+		seq_printf(seq," vid_lock[%u] bpp[%u], dt[0x%2x], vc[%u]\n",
-+			   vid_lock, bpp, dt, vc);
++		max_gmsl2_state_show_pipe(dev, pipe, priv->data, pipeid);
 +	}
 +
 +	for_each_csi(csiid, priv) {
 +		csi = &priv->csi[csiid];
-+		ret = regmap_field_read(csi->phy_out_freq[0], &freq1);
-+		if (ret)
-+			break;
-+		ret = regmap_field_read(csi->phy_out_freq[1], &freq2);
++		ret = regmap_field_read(csi->dpll_freq, &freq);
 +		if (ret)
 +			break;
 +		ret = regmap_field_read(csi->phy_enable, &dphy_en);
 +		if (ret)
 +			break;
-+		seq_printf(seq, "CSI%d: freq DPHY[%d]:%dMHz enabled:%u DPHY[%d]:%dMHz enabled:%u\n", csiid,
-+			   csiid * 2 , freq1 * 100, (dphy_en & BIT(0)) == BIT(0),
-+			   csiid * 2 + 1, freq2 * 100, (dphy_en & BIT(1)) == BIT(1));
++		dev_info(dev, "CSI%d: DPHY%d enabled:%u DPHY%d: enabled:%u DPLL:%dMHz\n", csiid,
++			 csiid * 2 , !!(dphy_en & BIT(0)),
++			 csiid * 2 + 1, !!(dphy_en & BIT(1)),
++			 freq * 100);
 +	}
 +
 +	return ret;
 +}
 +
-+DEFINE_SHOW_ATTRIBUTE(max_gmsl2_deser_state);
++static int max_gmsl2_csi_map_pipes(struct max_gmsl2_csi *csi);
 +
-+static void max_gmsl2_deser_debug_init(struct max_gmsl2_deser_priv *priv)
++
++static int max_gmsl2_deser_pipe_override_format(struct max_gmsl2_deser *priv,
++						u8 pipe_id, u8 datatype,
++						u8 bpp, u8 vc)
 +{
-+	struct device *dev = &priv->client->dev;
++	struct max_gmsl2_pipe *pipe = &priv->pipes[pipe_id];
++	const struct reg_field *l_field;
++	int ret;
 +
-+	priv->dbg_dir = debugfs_create_dir(dev_name(dev), NULL);
++	ret = regmap_field_write(pipe->vc, vc);
++	if (ret)
++		return ret;
 +
-+	debugfs_create_file("state", 0400, priv->dbg_dir,
-+			    priv, &max_gmsl2_deser_state_fops);
-+}
++	l_field = &priv->data->pipe_dt_l[pipe_id];
++	ret = max_gmsl2_write_h_l_val(pipe->dt_h, pipe->dt_l,
++				      l_field, datatype);
 +
-+static void max_gmsl2_deser_debug_remove(struct max_gmsl2_deser_priv *priv)
-+{
-+	debugfs_remove_recursive(priv->dbg_dir);
-+	priv->dbg_dir = NULL;
++	if (ret)
++		return ret;
++
++	l_field = &priv->data->pipe_bpp_l[pipe_id];
++	ret = max_gmsl2_write_h_l_val(pipe->bpp_h, pipe->bpp_l,
++				      l_field, bpp);
++	if (ret)
++		return ret;
++	return regmap_field_write(pipe->override_bpp_vc_dt, 1);
 +}
-+static int max_gmsl2_csi_map_pipes(struct max_gmsl2_deser_csi *csi);
 +
 +#define PATTERN_CLK_FREQ_75MHz 1
-+static int max_gmsl2_deser_csi_s_ctrl_pattern(struct max_gmsl2_deser_csi *csi, int val)
++static int max_gmsl2_deser_s_ctrl_pattern(struct max_gmsl2_deser *priv, int val)
 +{
-+	struct max_gmsl2_deser_priv *priv = csi->priv;
++	struct max_gmsl2_patgen *patgen = &priv->patgen;
 +	const struct v4l2_dv_timings t_1080p = V4L2_DV_BT_CEA_1920X1080P30;
 +	const struct v4l2_dv_timings t_720p = V4L2_DV_BT_CEA_1280X720P60;
 +	const struct v4l2_bt_timings *bt;
-+	uint16_t patgen_base = priv->data->patgen_base_addr[0];
 +	enum max_gmsl2_vpg_mode mode;
 +	int ret;
 +	u8 i;
 +
 +	if (val) {
-+		regmap_field_write(priv->pattern_clk_freq,
++		regmap_field_write(patgen->pattern_clk_freq,
 +				   PATTERN_CLK_FREQ_75MHz);
 +		mode = (val == 1) ? MAX_GMSL2_VPG_CHECKERBOARD:
 +			MAX_GMSL2_VPG_GRADIENT;
 +
-+		ret = max_gmsl2_vpg_enable(priv->regmap,
-+					   patgen_base, mode);
++		ret = max_gmsl2_vpg_enable(patgen, priv->regmap, mode);
 +		if (ret)
 +			return ret;
-+		if (csi->format.format.height == 720)
++
++		if (priv->format.format.height == 720)
 +			bt = &t_720p.bt;
 +		else
 +			bt = &t_1080p.bt;
 +
-+		ret = max_gmsl2_vtg_enable(priv->regmap, patgen_base, bt);
++		/* Invert VS HS and DE */
++		ret = regmap_field_write(patgen->inv_vs_hs_de, 7);
++		if (ret)
++			return ret;
++
++		ret = max_gmsl2_vtg_enable(patgen, priv->regmap, bt);
 +		if (ret)
 +			return ret;
 +		/* Pipe remapping doesn't works with pattern generator */
 +		ret = pipes_clear_remap(priv);
 +		if (ret)
 +			return ret;
++		/* Tunnel mode should be disabled for pattern generator */
++		if (priv->tunnel_mode) {
++			ret = regmap_field_write(priv->tunnel_mode, 0);
++			if (ret)
++				return ret;
++		}
++
++		/* FIXME: Do we really need to override the format for
++		 *  the pattern generator.
++		 */
++		for_each_pipe(i, priv) {
++			ret = max_gmsl2_deser_pipe_override_format(priv, i,
++							MIPI_CSI2_DT_RGB888,
++								   24, 0);
++			if (ret)
++				return ret;
++		}
 +	} else {
 +		/* Restore CSI to Pipe mapping */
 +		ret = pipes_clear_remap(priv);
@@ -902,25 +1233,38 @@ index 0000000..8a30110
 +			if (ret)
 +				return ret;
 +		}
-+		ret = max_gmsl2_vtg_disable(priv->regmap, patgen_base);
++		ret = max_gmsl2_vtg_disable(patgen, priv->regmap);
++		if (ret)
++			return ret;
++		ret = max_gmsl2_vpg_disable(patgen);
 +		if (ret)
 +			return ret;
++		/* Restore tunnel mode */
++		if (priv->tunnel_mode && priv->tunnel_mode_saved) {
++			ret = regmap_field_write(priv->tunnel_mode, 1);
++			if (ret)
++				return ret;
++		}
++		for_each_pipe(i, priv) {
++			ret = regmap_field_write(
++				priv->pipes[i].override_bpp_vc_dt, 0);
++			if (ret)
++				return ret;
++		}
 +	}
 +
 +
 +	return 0;
 +}
 +
-+static int max_gmsl2_deser_csi_s_ctrl(struct v4l2_ctrl *ctrl)
++static int max_gmsl2_deser_s_ctrl(struct v4l2_ctrl *ctrl)
 +{
-+	struct max_gmsl2_deser_csi *csi =
-+		container_of(ctrl->handler, struct max_gmsl2_deser_csi, ctrl_hdl);
++	struct max_gmsl2_deser *priv =
++		container_of(ctrl->handler, struct max_gmsl2_deser, ctrl_handler);
 +
 +	switch (ctrl->id) {
-+	case V4L2_CID_PIXEL_RATE:
-+		return 0;
 +	case V4L2_CID_TEST_PATTERN:
-+		return max_gmsl2_deser_csi_s_ctrl_pattern(csi, ctrl->val);
++		return max_gmsl2_deser_s_ctrl_pattern(priv, ctrl->val);
 +	default:
 +		return -EINVAL;
 +	}
@@ -928,50 +1272,42 @@ index 0000000..8a30110
 +	return 0;
 +}
 +
-+static const struct v4l2_ctrl_ops max_gmsl2_deser_csi_ctrl_ops = {
-+	.s_ctrl = max_gmsl2_deser_csi_s_ctrl,
++static const struct v4l2_ctrl_ops max_gmsl2_deser_ctrl_ops = {
++	.s_ctrl = max_gmsl2_deser_s_ctrl,
 +};
 +
-+static int max_gmsl2_deser_csi_get_format(struct v4l2_subdev *sd,
-+				   struct v4l2_subdev_state *sdstate,
++static int max_gmsl2_deser_get_format(struct v4l2_subdev *sd,
++				   struct v4l2_subdev_state *sd_state,
 +				   struct v4l2_subdev_format *sdformat)
 +{
-+	struct max_gmsl2_deser_csi *csi = max_gmsl2_deser_sd_to_csi(sd);
++	struct max_gmsl2_deser *priv = sd_to_max_gmsl2_deser(sd);
 +
-+	printk("%s: sdname %s \n", __func__, sd->name);
-+	sdformat->format = csi->format.format;
++	sdformat->format = priv->format.format;
 +
 +	return 0;
 +}
 +
-+static int max_gmsl2_deser_csi_set_format(struct v4l2_subdev *sd,
-+				   struct v4l2_subdev_state *sdstate,
++static int max_gmsl2_deser_set_format(struct v4l2_subdev *sd,
++				   struct v4l2_subdev_state *sd_state,
 +				   struct v4l2_subdev_format *sdformat)
 +{
-+	struct max_gmsl2_deser_csi *csi = max_gmsl2_deser_sd_to_csi(sd);
-+
-+	if ((sdformat->format.width == 1920 && sdformat->format.height == 1080
-+	     && sdformat->format.code == MEDIA_BUS_FMT_RGB888_1X24) ||
-+	    (sdformat->format.width == 1080 && sdformat->format.height == 720
-+	     && sdformat->format.code == MEDIA_BUS_FMT_RGB888_1X24))
-+		csi->format.format = sdformat->format;
-+	else
-+		sdformat->format = csi->format.format;
++	struct max_gmsl2_deser *priv = sd_to_max_gmsl2_deser(sd);
++	priv->format.format = sdformat->format;
 +
 +	return 0;
 +}
 +
-+static const struct v4l2_subdev_pad_ops max_gmsl2_deser_csi_pad_ops = {
-+	.get_fmt = max_gmsl2_deser_csi_get_format,
-+	.set_fmt = max_gmsl2_deser_csi_set_format,
++static const struct v4l2_subdev_pad_ops max_gmsl2_deser_pad_ops = {
++	.get_fmt = max_gmsl2_deser_get_format,
++	.set_fmt = max_gmsl2_deser_set_format,
 +};
 +
-+static int max_gmsl2_deser_csi_enable(struct max_gmsl2_deser_priv *priv, bool enable)
++static int max_gmsl2_deser_csi_enable(struct max_gmsl2_deser *priv, bool enable)
 +{
 +	bool enabled = !!priv->csi_use_cnt;
 +	int ret = 0;
 +
-+	if (!priv->csi_enable)
++	if (!priv->csi_force_enable)
 +		return 0;
 +
 +	if (enable)
@@ -980,140 +1316,57 @@ index 0000000..8a30110
 +		priv->csi_use_cnt--;
 +
 +	if (enabled && !priv->csi_use_cnt)
-+		ret = regmap_field_write(priv->csi_enable, 0);
++		ret = regmap_field_write(priv->csi_force_enable, 0);
 +	else if (!enabled && priv->csi_use_cnt)
-+		ret = regmap_field_write(priv->csi_enable, 1);
++		ret = regmap_field_write(priv->csi_force_enable, 1);
 +
 +	return 0;
 +}
 +
-+static int max_gmsl2_deser_csi_s_stream(struct v4l2_subdev *sd, int enable)
-+{
-+	struct max_gmsl2_deser_csi *csi = max_gmsl2_deser_sd_to_csi(sd);
-+
-+	printk("%s csi:%u enable %d\n", __func__, csi->id, enable);
-+
-+	max_gmsl2_deser_csi_enable(csi->priv, !!enable);
-+
-+	/* Each CSI use 2 phy enable or disable both */
-+	return regmap_field_write(csi->phy_enable, enable ? 0x3 : 0);
-+}
-+
-+static const struct v4l2_subdev_video_ops max_gmsl2_deser_csi_video_ops = {
-+	.s_stream = max_gmsl2_deser_csi_s_stream,
-+};
-+
-+static const struct v4l2_subdev_ops max_gmsl2_deser_csi_ops = {
-+	.video = &max_gmsl2_deser_csi_video_ops,
-+	.pad = &max_gmsl2_deser_csi_pad_ops,
-+};
-+
-+static const char * const max_gmsl2_test_pattern[] = {
-+	"Disabled",
-+	"Checkerboard",
-+	"Gradient"
-+};
-+
-+static int max_gmsl2_deser_csi_init_controls(struct max_gmsl2_deser_csi *csi)
-+{
-+
-+	v4l2_ctrl_handler_init(&csi->ctrl_hdl, 2);
-+
-+	csi->pixel_rate = v4l2_ctrl_new_std(&csi->ctrl_hdl,
-+					   &max_gmsl2_deser_csi_ctrl_ops,
-+					   V4L2_CID_PIXEL_RATE, 1, INT_MAX,
-+					   1, 1);
-+	if (csi->priv->data->patgen_base_addr[0])
-+		v4l2_ctrl_new_std_menu_items(&csi->ctrl_hdl,
-+					     &max_gmsl2_deser_csi_ctrl_ops,
-+					     V4L2_CID_TEST_PATTERN,
-+					     ARRAY_SIZE(max_gmsl2_test_pattern) - 1,
-+					     0, 0, max_gmsl2_test_pattern);
-+
-+	csi->sd.ctrl_handler = &csi->ctrl_hdl;
-+	if (csi->ctrl_hdl.error) {
-+		v4l2_ctrl_handler_free(&csi->ctrl_hdl);
-+		return csi->ctrl_hdl.error;
-+	}
-+
-+	return v4l2_ctrl_handler_setup(&csi->ctrl_hdl);
-+}
-+
-+static int max_gmsl2_deser_csi_registered(struct v4l2_subdev *sd)
++static int max_gmsl2_deser_s_stream(struct v4l2_subdev *sd, int enable)
 +{
-+	struct max_gmsl2_deser_csi *csi = max_gmsl2_deser_sd_to_csi(sd);
-+	struct max_gmsl2_deser_priv *priv = csi->priv;
-+	struct device *dev = &priv->client->dev;
-+	struct max_gmsl2_deser_pipe *pipe;
-+	struct v4l2_subdev *src;
++	struct max_gmsl2_deser *priv = sd_to_max_gmsl2_deser(sd);
++	struct max_gmsl2_link *link;
++	struct max_gmsl2_csi *csi;
++	unsigned int i;
 +	int ret;
 +
-+	dev_info(dev, "Registered CSI%u %s", csi->id, sd->name);
-+
-+	if (csi->id == 0)
-+		/* Pipe Y goes to CSI0 */
-+		pipe = &priv->pipes[1];
-+	else
-+		/* Pipe Z goes to CSI1 */
-+		pipe = &priv->pipes[2];
-+
-+	src = &pipe->sd;
-+
-+	if (!src->v4l2_dev)
-+		ret = v4l2_device_register_subdev(sd->v4l2_dev, src);
++	max_gmsl2_deser_csi_enable(priv, enable);
 +
-+	/* Connect pipe source pad(0) to csi sink pad(0)*/
-+	ret = media_create_pad_link(&src->entity, 0,
-+				    &sd->entity, 0,
-+				    MEDIA_LNK_FL_ENABLED);
-+	return ret;
-+}
++	for_each_link(i, priv) {
++		link = &priv->links[i];
++		if (link->source_sd)
++			v4l2_subdev_call(link->source_sd, video,
++					 s_stream, enable);
++	}
 +
-+static const struct v4l2_subdev_internal_ops max_gmsl2_deser_csi_internal_ops = {
-+	.registered = max_gmsl2_deser_csi_registered,
-+};
++	for_each_csi(i, priv) {
++		csi = &priv->csi[i];
++		ret = regmap_field_write(csi->phy_enable, enable ? 0x3 : 0);
++		if (ret)
++			return ret;
++	}
 +
-+static int max_gmsl2_deser_link_setup(struct media_entity *entity,
-+				      const struct media_pad *local,
-+				      const struct media_pad *remote, u32 flags)
-+{
 +	return 0;
 +}
 +
-+static const struct media_entity_operations max_gmsl2_deser_csi_media_ops = {
-+	.link_setup	= max_gmsl2_deser_link_setup,
-+	.link_validate	= v4l2_subdev_link_validate,
++static const struct v4l2_subdev_core_ops max_gmsl2_deser_core_ops = {
++	.log_status = max_gmsl2_deser_log_status,
 +};
 +
-+static const struct media_entity_operations max_gmsl2_deser_media_ops = {
-+	.link_validate = v4l2_subdev_link_validate,
++static const struct v4l2_subdev_video_ops max_gmsl2_deser_video_ops = {
++	.s_stream = max_gmsl2_deser_s_stream,
 +};
 +
-+static void max_gmsl2_deser_subdev_init(struct v4l2_subdev *sd, struct max_gmsl2_deser_priv *priv,
-+					const struct v4l2_subdev_ops *ops, u32 function,
-+					const char *ident)
-+{
-+	struct device *dev = &priv->client->dev;
-+
-+	v4l2_subdev_init(sd, ops);
-+	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
-+
-+	/* the owner is the same as the i2c_client's driver owner */
-+	sd->owner = dev->driver->owner;
-+	sd->dev = dev;
-+
-+	v4l2_set_subdevdata(sd, priv);
-+
-+	/* initialize name */
-+	snprintf(sd->name, sizeof(sd->name), "%s %d-%04x %s",
-+		dev->driver->name,
-+		i2c_adapter_id(priv->client->adapter),
-+		priv->client->addr, ident);
++static const struct v4l2_subdev_ops max_gmsl2_deser_subdev_ops = {
++	.core = &max_gmsl2_deser_core_ops,
++	.video = &max_gmsl2_deser_video_ops,
++	.pad = &max_gmsl2_deser_pad_ops,
++};
 +
-+	sd->entity.function = function;
-+	sd->entity.ops = (function == MEDIA_ENT_F_VID_IF_BRIDGE) ?
-+			 &max_gmsl2_deser_csi_media_ops : &max_gmsl2_deser_media_ops;
-+}
++static const struct media_entity_operations max_gmsl2_deser_entity_ops = {
++	.get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1,
++};
 +
 +/*
 + * By using the default pipe to CSI controller mapping, pipe0/4 and
@@ -1126,19 +1379,21 @@ index 0000000..8a30110
 + * CSI controller 1, and data selected in PIPE 3 will go to the
 + * CSI controller 2.
 + */
-+static int max96914_remap_pipe(struct max_gmsl2_deser_priv *priv, u8 pipeid,
++static int max96914_remap_pipe(struct max_gmsl2_deser *priv, u8 pipeid,
 +			       u8 csi_controller)
 +{
-+	struct max_gmsl2_deser_pipe *pipe = &priv->pipes[pipeid];
++	struct max_gmsl2_pipe *pipe = &priv->pipes[pipeid];
 +	unsigned int vc;
 +	/* remap frame start, frame end, and rgb888 datatypes */
-+	u8 dt_to_remap[3] = {0x00, 0x01, 0x24};
++	u8 dt_to_remap[3] = {MIPI_CSI2_DT_FS, MIPI_CSI2_DT_LE,
++		MIPI_CSI2_DT_RGB888};
 +	u8 pipe_elt;
 +	int ret, i;
 +
 +	ret = regmap_field_read(pipe->vc, &vc);
 +	if (ret)
 +		return ret;
++
 +	/* route dt and vc from pipe to CSI controller*/
 +	for (i = 0; i < ARRAY_SIZE(dt_to_remap); i++) {
 +		pipe_elt = PIPE_ELEMENT(vc, dt_to_remap[i]);
@@ -1151,9 +1406,10 @@ index 0000000..8a30110
 +	return ret;
 +}
 +
-+static int max_gmsl2_csi_map_pipes(struct max_gmsl2_deser_csi *csi)
++static int max_gmsl2_csi_map_pipes(struct max_gmsl2_csi *csi)
 +{
-+	struct fwnode_handle *node = csi->sd.fwnode;
++	struct fwnode_handle *node = csi->ep_fwnode;
++	struct max_gmsl2_deser *priv = csi->priv;
 +	u32 pipes[GMSL2_MAX_PIPE];
 +	int ret, num_pipes, i;
 +
@@ -1169,7 +1425,7 @@ index 0000000..8a30110
 +		return ret;
 +
 +	for (i = 0; i < num_pipes; i++) {
-+		if (pipes[i] >= csi->priv->data->pipes)
++		if (pipes[i] >= priv->data->pipes)
 +			return -EINVAL;
 +		/* csi_id = 0 then it's CSI controller 1 if csi_id = 1 then it's the CSI
 +		 * controller 2.
@@ -1182,55 +1438,9 @@ index 0000000..8a30110
 +	return 0;
 +}
 +
-+static int max_gmsl2_csi_phy_init(struct max_gmsl2_deser_csi *csi,
-+				  struct v4l2_mbus_config_mipi_csi2 *mipi)
-+{
-+	int i, ret;
-+	u8 val = 0;
-+
-+	/* lane polarities */
-+	for (i = 0; i < mipi->num_data_lanes + 1; i++) {
-+		if (!mipi->lane_polarities[i])
-+			continue;
-+		if (i == 0)
-+			/* clock lane */
-+			val |= BIT(5);
-+		else if (i < 3)
-+			/* Lane D0 and D1 */
-+			val |= BIT(i - 1);
-+		else
-+			/* D2 and D3 */
-+			val |= BIT(i);
-+	}
-+	ret = regmap_field_write(csi->lane_polarities[0], val);
-+	if (ret)
-+		return ret;
-+	ret = regmap_field_write(csi->lane_polarities[1], val >> 3);
-+	if (ret)
-+		return ret;
-+
-+	/* lane mapping */
-+	val = 0;
-+	for (i = 0; i < mipi->num_data_lanes; i++)
-+		val |= (mipi->data_lanes[i] - 1) << (i*2);
-+
-+	ret = regmap_field_write(csi->lane_map[0], val);
-+	if (ret)
-+		return ret;
-+	ret = regmap_field_write(csi->lane_map[1], val >> 4);
-+	if (ret)
-+		return ret;
-+
-+	return 0;
-+}
-+
-+static int max_gmsl2_deser_csi_init(struct max_gmsl2_deser_priv *priv, u8 csi_id)
++static int max_gmsl2_deser_csi_init(struct max_gmsl2_deser *priv, u8 csi_id)
 +{
-+	struct max_gmsl2_deser_csi *csi = &priv->csi[csi_id];
-+	struct v4l2_fwnode_endpoint v4l2_ep = {
-+		.bus_type = V4L2_MBUS_CSI2_DPHY
-+	};
-+
++	struct max_gmsl2_csi *csi = &priv->csi[csi_id];
 +	struct device *dev = &priv->client->dev;
 +	int ret;
 +
@@ -1239,73 +1449,33 @@ index 0000000..8a30110
 +	if (ret)
 +		return ret;
 +
-+	/* FIXME: take value from dt, written value in multiple of 100MHz
-+	 * Only the master DPhy might need to be set.
-+	 */
-+	if (csi->phy_out_freq[0]) {
-+		ret = regmap_field_write(csi->phy_out_freq[0], 8);
-+		if (ret)
-+			return ret;
-+	}
-+
-+	if (csi->phy_out_freq[1]) {
-+		ret = regmap_field_write(csi->phy_out_freq[1], 8);
-+		if (ret)
-+			return ret;
-+	}
-+	csi->format.format.width = 1280;
-+	csi->format.format.height = 720;
-+	csi->format.format.code = MEDIA_BUS_FMT_RGB888_1X24;
-+	csi->format.format.field = V4L2_FIELD_NONE;
-+
-+	max_gmsl2_deser_subdev_init(&csi->sd, priv, &max_gmsl2_deser_csi_ops,
-+				    MEDIA_ENT_F_VID_IF_BRIDGE,
-+				    (csi_id == 0) ? "csia" : "csib");
++	priv->format.format.width = 1280;
++	priv->format.format.height = 720;
++	priv->format.format.code = MEDIA_BUS_FMT_RGB888_1X24;
++	priv->format.format.field = V4L2_FIELD_NONE;
 +
 +	/* CSI source enpoint ports start after gmsl2 link */
-+	csi->sd.fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
++	csi->ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
 +						  priv->data->links + csi_id,
 +						  0, 0);
++	if (!csi->ep_fwnode)
++		return -ENODEV;
 +
-+	ret = v4l2_fwnode_endpoint_parse(csi->sd.fwnode, &v4l2_ep);
-+	if (ret) {
-+		dev_err(&priv->client->dev, "Could not parse v4l2 endpoint\n");
-+		return -EINVAL;
-+	}
-+
-+	if (v4l2_ep.bus.mipi_csi2.num_data_lanes != 4) {
-+		dev_err(&priv->client->dev, "Only 4 data lanes supported\n");
-+		return -EINVAL;
-+	}
-+	ret = max_gmsl2_csi_phy_init(csi, &v4l2_ep.bus.mipi_csi2);
++	ret = max_gmsl2_csi_phy_init(csi);
 +	if (ret)
 +		return ret;
 +
-+	/* Register internal ops for incremental subdev registration */
-+	csi->sd.internal_ops = &max_gmsl2_deser_csi_internal_ops;
-+
-+	csi->pads[0].flags = MEDIA_PAD_FL_SINK;
-+	csi->pads[1].flags = MEDIA_PAD_FL_SOURCE;
-+
-+	csi->priv = priv;
-+	csi->id = csi_id;
-+
-+	ret = media_entity_pads_init(&csi->sd.entity, 2, csi->pads);
++	ret = max_gmsl2_csi_get_link_freq(csi, priv->tx_link_freq);
 +	if (ret)
 +		return ret;
 +
-+	ret = max_gmsl2_deser_csi_init_controls(csi);
-+	if (ret)
-+		goto err_cleanup_media;
++	csi->priv = priv;
++	csi->id = csi_id;
 +
 +	ret = max_gmsl2_csi_map_pipes(csi);
 +	if (ret)
 +		goto err_cleanup_media;
 +
-+	ret = v4l2_async_register_subdev(&csi->sd);
-+	if (ret)
-+		goto err_cleanup_media;
-+
 +	return 0;
 +
 +err_cleanup_media:
@@ -1314,135 +1484,148 @@ index 0000000..8a30110
 +	return ret;
 +}
 +
-+static int max_gmsl2_deser_pipe_s_stream(struct v4l2_subdev *sd, int enable)
-+{
-+	struct max_gmsl2_deser_pipe *pipe = max_gmsl2_deser_sd_to_pipe(sd);
-+	unsigned int val;
-+	int ret;
++#define GMSL1_DT_RGB888_OLDI 0
++#define CONV_GMSL1_EN        BIT(1)
++/* BIT(0) RSVD 1 */
++#define CONV_GMSL_DATATYPE(gmsl1_dt) ( gmsl1_dt << 3 | CONV_GMSL1_EN | BIT(0))
++static int max_gmsl2_link_set_gmsl1(struct max_gmsl2_deser *priv, u8 link)
++{
++	const struct max_gmsl2_compat_gmsl1 *gmsl1 = priv->data->gmsl1;
++	int ret;
++
++	if (!gmsl1) {
++		dev_err(&priv->client->dev,
++			"This device doesn't support gmsl1 mode");
++		return -EINVAL;
 +
-+	ret = regmap_field_read(pipe->vid_lock, &val);
++	}
++
++	/* Disable the forward control channel  */
++	ret = regmap_field_write(priv->gmsl1_forward_cc[link], 0);
 +	if (ret)
 +		return ret;
-+	printk("starting stream vid lock: %u", val);
 +
-+	return regmap_field_write(pipe->enable, !!enable);
-+}
++	/* clear gmsl2 bit for this link */
++	ret = regmap_field_update_bits(priv->gmsl2_link_mode,
++				       BIT(link), 0);
++	if (ret)
++		return ret;
 +
-+static int max_gmsl2_deser_pipe_g_input_status(struct v4l2_subdev *sd, u32 *status)
-+{
-+	struct max_gmsl2_deser_pipe *pipe = max_gmsl2_deser_sd_to_pipe(sd);
-+	int ret;
-+	unsigned int vid_lock;
++	/* We only support HIM capable gmsl1 device */
++	ret = regmap_field_write(priv->gmsl1_highimm[link], 1);
++	if (ret)
++		return ret;
 +
-+	ret = regmap_field_read(pipe->vid_lock, &vid_lock);
++	/*HACK: DBL=0 DRS=0 BWS=0 RSV=0 HIBW=1 HVEN=0 RSV=0 PXL_CRC=0*/
++	ret = regmap_write(priv->regmap, 0x0B07, 0x08);
++	if (ret)
++		return ret;
++
++	/*
++	 * Shift video bits to make sure that HS, VS does not appear on
++	 * pixel data
++	 */
++	ret = regmap_field_write(priv->gmsl1_shift_vid_hvd[link], 1);
 +	if (ret)
 +		return ret;
 +
-+	*status = vid_lock ? 0 : V4L2_IN_ST_NO_SIGNAL;
++	/* Datatype GMSL1 conversion only support RGB888 OLDI */
++	ret = regmap_write(priv->regmap, gmsl1->conv_datatype[link],
++			   CONV_GMSL_DATATYPE(GMSL1_DT_RGB888_OLDI));
++	return ret;
 +
-+	return 0;
 +}
 +
-+static const struct v4l2_subdev_video_ops max_gmsl2_deser_pipe_video_ops = {
-+	.g_input_status = max_gmsl2_deser_pipe_g_input_status,
-+	.s_stream = max_gmsl2_deser_pipe_s_stream,
-+};
-+
-+static const struct v4l2_subdev_pad_ops max_gmsl2_deser_pipe_pad_ops = {
-+};
++static int max_gmsl2_poll_dummy_read(struct max_gmsl2_deser *priv)
++{
++	unsigned int dummy;
++	int ret, i;
 +
-+static const struct v4l2_subdev_ops max_gmsl2_deser_pipe_ops = {
-+	.video = &max_gmsl2_deser_pipe_video_ops,
-+	.pad = &max_gmsl2_deser_pipe_pad_ops,
-+};
++	ret = regmap_read(priv->regmap, 0, &dummy);
++	for (i = 0; i < 100  && ret; i++) {
++		usleep_range(2000, 3000);
++		ret = regmap_read(priv->regmap, 0, &dummy);
++	}
++	return ret;
++}
 +
-+static int max_gmsl2_deser_pipe_set_format(struct max_gmsl2_deser_priv *priv, u8 pipe_id,
-+					   u8 datatype, u8 bpp, u8 vc)
++static int max_gmsl2_link_parse_dt_poc(struct device *dev, u8 nlink)
 +{
-+	struct max_gmsl2_deser_pipe *pipe = &priv->pipes[pipe_id];
-+	const struct reg_field *l_field;
++	char name[10];
 +	int ret;
 +
-+	ret = regmap_field_write(pipe->vc, vc);
-+	if (ret)
-+		return ret;
-+
-+	l_field = &priv->data->pipe_dt_l[pipe_id];
-+	ret = max_gmsl2_write_h_l_val(pipe->dt_h, pipe->dt_l,
-+				      l_field, datatype);
++	snprintf(name, sizeof(name), "port%u-poc", nlink);
 +
-+	if (ret)
++	ret = devm_regulator_get_enable_optional(dev, name);
++	if (ret < 0 && ret != -ENODEV)
 +		return ret;
 +
-+	l_field = &priv->data->pipe_bpp_l[pipe_id];
-+	ret = max_gmsl2_write_h_l_val(pipe->bpp_h, pipe->bpp_l,
-+				      l_field, bpp);
-+	if (ret)
-+		return ret;
-+	return regmap_field_write(pipe->override_bpp_vc_dt, 1);
++	return 0;
 +}
 +
-+static int max_gmsl2_deser_pipe_init(struct max_gmsl2_deser_priv *priv, u8 pipe_id, u8 vc)
++
++static int max_gmsl2_init_links(struct max_gmsl2_deser *priv)
 +{
-+	struct max_gmsl2_deser_pipe *pipe = &priv->pipes[pipe_id];
++	struct max_gmsl2_link *link;
++	struct device *dev = &priv->client->dev;
++	struct fwnode_handle *node;
++	bool gmsl1_mode;
 +	int ret;
++	u8 i;
 +
-+	/*
-+	 * FIXME: Only supporting RGB888 at the moment
-+	 * set MIPI vc=0 for each pipe
-+	 */
-+	ret = max_gmsl2_deser_pipe_set_format(priv, pipe_id,
-+					      GMSL2_MIPI_DT_RGB888, 3 * 8, vc);
-+	if (ret)
-+		return ret;
++	for_each_link(i, priv) {
++		link = &priv->links[i];
++		node = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), i, 0, 0);
++		if (!node)
++			continue;
 +
-+	/* FIXME: find an appropriate media type */
-+	max_gmsl2_deser_subdev_init(&pipe->sd, priv, &max_gmsl2_deser_pipe_ops,
-+				    MEDIA_ENT_F_ATV_DECODER,
-+				    pipe_to_str(priv, pipe_id));
++		link->source_ep_fwnode = node;
++		ret = max_gmsl2_link_parse_dt_poc(dev, i);
++		if (ret) {
++			dev_err(dev, "Port%u: failed to enable POC supply: %d\n",
++				i, ret);
++			fwnode_handle_put(node);
++			return ret;
++		}
 +
-+	pipe->pad.flags = MEDIA_PAD_FL_SOURCE;
++		gmsl1_mode = fwnode_property_present(node, "maxim,link-gmsl1-mode");
++		fwnode_handle_put(node);
 +
-+	ret = media_entity_pads_init(&pipe->sd.entity,
-+				     1, &pipe->pad);
-+	if (ret)
-+		return ret;
++		if (!gmsl1_mode)
++			continue;
 +
-+	ret = v4l2_async_register_subdev(&pipe->sd);
-+	if (ret)
-+		goto err_cleanup_media;
++		ret = max_gmsl2_link_set_gmsl1(priv, i);
++		if (ret)
++			return ret;
++		if (link->reset_one_shot) {
++			ret = regmap_field_write(link->reset_one_shot, 1);
++			if (ret)
++				return ret;
++			ret = max_gmsl2_poll_dummy_read(priv);
++			if (ret)
++				return ret;
++		}
++	}
 +
 +	return 0;
-+
-+err_cleanup_media:
-+	media_entity_cleanup(&pipe->sd.entity);
-+
-+	return ret;
 +}
 +
-+static int max_gmsl2_deser_v4l2_register(struct max_gmsl2_deser_priv *priv)
++static int max_gmsl2_deser_hw_init(struct max_gmsl2_deser *priv)
 +{
 +	struct device *dev = &priv->client->dev;
-+	u32 stream_id[GMSL2_MAX_PIPE];
 +	u32 pipe_vc[GMSL2_MAX_PIPE] = {};
 +	int ret = 0;
 +	int i;
 +
++	ret = max_gmsl2_init_links(priv);
++	if (ret)
++		return ret;
++
 +	/* check if we need to override default stream id mapping */
-+	ret = of_property_read_u32_array(dev->of_node, "maxim,pipe-stream-id",
-+					 stream_id, priv->data->pipes);
-+	if (!ret) {
-+		for_each_pipe(i, priv) {
-+			if (stream_id[i] >= 4) {
-+				dev_err(dev, "invalid stream id: %d for pipe %d",
-+					stream_id[i], i);
-+				return -EINVAL;
-+			}
-+			ret = regmap_field_write(priv->pipes[i].stream_id,
-+						 stream_id[i]);
-+			if (ret)
-+				return ret;
-+		}
++	ret = max_gmsl2_init_pipes_stream_id(priv->pipes, priv->data->pipes, dev);
++	if (ret) {
++		dev_err(dev, "fail to set pipe stream id");
++		return ret;
 +	}
 +
 +	/* check if we need to override default pipe virtual channel */
@@ -1453,14 +1636,8 @@ index 0000000..8a30110
 +		return ret;
 +	}
 +
-+	for_each_pipe(i, priv) {
-+		ret = max_gmsl2_deser_pipe_init(priv, i, pipe_vc[i]);
-+		if (ret)
-+			return ret;
-+	}
-+
-+	if (priv->csi_enable) {
-+		ret = regmap_field_write(priv->csi_enable, 0);
++	if (priv->csi_force_enable) {
++		ret = regmap_field_write(priv->csi_force_enable, 0);
 +		if (ret)
 +			return ret;
 +	}
@@ -1470,107 +1647,228 @@ index 0000000..8a30110
 +		if (ret)
 +			return ret;
 +	}
++
 +	for_each_csi(i, priv) {
 +		ret = max_gmsl2_deser_csi_init(priv, i);
-+		if (ret)
++		if (ret && ret != -ENODEV)
 +			return ret;
 +	}
 +
 +	return 0;
 +}
 +
-+#define GMSL1_DT_RGB888_OLDI 0
-+#define CONV_GMSL1_EN        BIT(1)
-+/* BIT(0) RSVD 1 */
-+#define CONV_GMSL_DATATYPE(gmsl1_dt) ( gmsl1_dt << 3 | CONV_GMSL1_EN | BIT(0))
-+static int max_gmsl2_link_set_gmsl1(struct max_gmsl2_deser_priv *priv, u8 link)
++struct max_gmsl2_link_asd {
++	struct v4l2_async_subdev base;
++	struct max_gmsl2_link *link;
++};
++
++static inline struct max_gmsl2_link_asd *to_max_gmsl2_link_asd(struct v4l2_async_subdev *asd)
 +{
-+	const struct max_gmsl2_compat_gmsl1 *gmsl1 = priv->data->gmsl1;
-+	int ret;
++	return container_of(asd, struct max_gmsl2_link_asd, base);
++}
 +
-+	if (!gmsl1) {
-+		dev_err(&priv->client->dev,
-+			"This device doesn't support gmsl1 mode");
-+		return -EINVAL;
++static int max_gmsl2_deser_notify_bound(struct v4l2_async_notifier *notifier,
++				      struct v4l2_subdev *source_subdev,
++				      struct v4l2_async_subdev *asd)
++{
++	struct v4l2_subdev *sd = notifier->sd;
++	struct max_gmsl2_deser *priv = sd_to_max_gmsl2_deser(sd);
++	struct max_gmsl2_link *link = to_max_gmsl2_link_asd(asd)->link;
++	struct device *dev = &priv->client->dev;
++	int ret;
 +
-+	}
-+	/* HACK disable the forward control channel  */
-+	ret = regmap_field_write(priv->gmsl1_forward_cc[link], 0);
-+	if (ret)
-+		return ret;
-+	/* clear gmsl2 bit for this link */
-+	ret = regmap_field_update_bits(priv->gmsl2_link_mode,
-+				       BIT(link), 0);
-+	if (ret)
++	ret = media_entity_get_fwnode_pad(&source_subdev->entity,
++					  source_subdev->fwnode,
++					  MEDIA_PAD_FL_SOURCE);
++	if (ret < 0)
 +		return ret;
 +
-+	/* We only support HIM capable gmsl1 device */
-+	ret = regmap_field_write(priv->gmsl1_highimm[link], 1);
-+	if (ret)
-+		return ret;
++	link->source_sd = source_subdev;
++	link->source_sd_pad = ret;
 +
-+	/*HACK: DBL=0 DRS=0 BWS=0 RSV=0 HIBW=1 HVEN=0 RSV=0 PXL_CRC=0*/
-+	ret = regmap_write(priv->regmap, 0x0B07, 0x08);
-+	if (ret)
++	ret = media_create_pad_link(&source_subdev->entity, link->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, link->source_sd_pad,
++			priv->sd.name);
 +		return ret;
-+	/* Datatype GMSL1 conversion only support RGB888 OLDI */
-+	ret = regmap_write(priv->regmap, gmsl1->conv_datatype[link],
-+			   CONV_GMSL_DATATYPE(GMSL1_DT_RGB888_OLDI));
-+	return ret;
++	}
 +
++	return 0;
 +}
 +
-+static int max_gmsl2_poll_dummy_read(struct max_gmsl2_deser_priv *priv)
++static void max_gmsl2_deser_notify_unbind(struct v4l2_async_notifier *notifier,
++					  struct v4l2_subdev *subdev,
++					  struct v4l2_async_subdev *asd)
 +{
-+	unsigned int dummy;
-+	int ret, i;
++	struct max_gmsl2_link *link = to_max_gmsl2_link_asd(asd)->link;
 +
-+	ret = regmap_read(priv->regmap, 0, &dummy);
-+	for (i = 0; i < 100  && ret; i++) {
-+		usleep_range(2000, 3000);
-+		ret = regmap_read(priv->regmap, 0, &dummy);
-+	}
-+	return ret;
++	link->source_sd = NULL;
 +}
 +
-+static int max_gmsl2_init_links(struct max_gmsl2_deser_priv *priv)
++static const struct v4l2_async_notifier_operations max_gmsl2_deser_notify_ops =
++{
++	.bound = max_gmsl2_deser_notify_bound,
++	.unbind = max_gmsl2_deser_notify_unbind,
++};
++
++static int max_gmsl2_deser_v4l2_notifier_register(struct max_gmsl2_deser *priv)
 +{
 +	struct device *dev = &priv->client->dev;
-+	struct fwnode_handle *node;
-+	bool gmsl1_mode;
++	struct max_gmsl2_link *link;
++	struct max_gmsl2_link_asd *asd;
++	unsigned int i;
 +	int ret;
-+	u8 link;
 +
-+	for (link = 0; link < priv->data->links; link++) {
-+		node = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), link, 0,
-+			FWNODE_GRAPH_DEVICE_DISABLED);
-+		if (!node)
++	v4l2_async_nf_init(&priv->notifier);
++
++	for_each_link(i, priv) {
++		link = &priv->links[i];
++		if (!link->source_ep_fwnode)
++			/* This link is not connected */
 +			continue;
-+		gmsl1_mode = fwnode_property_present(node, "maxim,link-gmsl1-mode");
-+		fwnode_handle_put(node);
-+		if (gmsl1_mode) {
-+			ret = max_gmsl2_link_set_gmsl1(priv, link);
-+			if (ret)
-+				return ret;
-+			if (priv->reset_one_shot_link[link]) {
-+				ret = regmap_field_write(
-+					priv->reset_one_shot_link[link], 1);
-+				if (ret)
-+					return ret;
-+				ret = max_gmsl2_poll_dummy_read(priv);
-+				if (ret)
-+					return ret;
-+			}
++
++		asd = v4l2_async_nf_add_fwnode_remote(&priv->notifier,
++						      link->source_ep_fwnode,
++						      struct max_gmsl2_link_asd);
++
++		if (IS_ERR(asd)) {
++			dev_err(dev, "Fail to add subdev notifier for link %d: %pe",
++				i, asd);
++			v4l2_async_nf_cleanup(&priv->notifier);
++			return PTR_ERR(asd);
++		}
++		asd->link = link;
++	}
++
++	priv->notifier.ops = &max_gmsl2_deser_notify_ops;
++
++	ret = v4l2_async_subdev_nf_register(&priv->sd, &priv->notifier);
++	if (ret) {
++		v4l2_async_nf_cleanup(&priv->notifier);
++		return ret;
++	}
++
++	return 0;
++}
++
++static void max_gmsl2_deser_v4l2_notifier_unregister(struct max_gmsl2_deser *priv)
++{
++	v4l2_async_nf_unregister(&priv->notifier);
++	v4l2_async_nf_cleanup(&priv->notifier);
++}
++
++static int max_gmsl2_deser_create_subdev(struct max_gmsl2_deser *priv)
++{
++	struct device *dev = &priv->client->dev;
++	unsigned int i;
++	int ret;
++
++	v4l2_i2c_subdev_init(&priv->sd, priv->client, &max_gmsl2_deser_subdev_ops);
++	v4l2_ctrl_handler_init(&priv->ctrl_handler, 1);
++	priv->sd.ctrl_handler = &priv->ctrl_handler;
++
++	if (priv->data->patgen_base_addr[0]) {
++		v4l2_ctrl_new_std_menu_items(&priv->ctrl_handler,
++					     &max_gmsl2_deser_ctrl_ops,
++					     V4L2_CID_TEST_PATTERN,
++					     ARRAY_SIZE(max_gmsl2_test_pattern) - 1,
++					     0, 0, max_gmsl2_test_pattern);
++		if (priv->ctrl_handler.error) {
++			ret = priv->ctrl_handler.error;
++			goto err_free_ctrl;
 +		}
 +	}
 +
++	v4l2_ctrl_new_int_menu(&priv->ctrl_handler, NULL, V4L2_CID_LINK_FREQ,
++			       ARRAY_SIZE(priv->tx_link_freq) - 1, 0,
++			       priv->tx_link_freq);
++
++	priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
++	priv->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
++	priv->sd.entity.ops = &max_gmsl2_deser_entity_ops;
++
++	for (i = 0; i < priv->data->links + priv->data->csi; i++) {
++		priv->pads[i].flags = (i < priv->data->links) ?
++			MEDIA_PAD_FL_SINK : MEDIA_PAD_FL_SOURCE;
++	}
++
++	ret = media_entity_pads_init(&priv->sd.entity,
++				     priv->data->links +
++				     priv->data->csi,
++				     priv->pads);
++	if (ret)
++		goto err_free_ctrl;
++
++	ret = max_gmsl2_deser_v4l2_notifier_register(priv);
++	if (ret) {
++		dev_err(dev, "v4l2 subdev notifier register failed: %d\n", ret);
++		goto err_entity_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:
++	v4l2_async_nf_unregister(&priv->notifier);
++err_entity_cleanup:
++	media_entity_cleanup(&priv->sd.entity);
++err_free_ctrl:
++	v4l2_ctrl_handler_free(&priv->ctrl_handler);
++
++	return ret;
++}
++
++static void max_gmsl2_deser_destroy_subdev(struct max_gmsl2_deser *priv)
++{
++	max_gmsl2_deser_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 max_gmsl2_i2c_mux_select(struct i2c_mux_core *mux, u32 chan)
++{
++	return 0;
++}
++
++static int max_gmsl2_i2c_mux_init(struct max_gmsl2_deser *priv)
++{
++	priv->mux = i2c_mux_alloc(priv->client->adapter, &priv->client->dev,
++				  1, 0, I2C_MUX_LOCKED | I2C_MUX_GATE,
++				  max_gmsl2_i2c_mux_select, NULL);
++	if (!priv->mux)
++		return -ENOMEM;
++
++	return i2c_mux_add_adapter(priv->mux, 0, 0, 0);
++}
++
++static int max96724_force_6gbps(struct max_gmsl2_deser *priv)
++{
++	/* 6 Gbps for LINK A-B-C-D */
++	regmap_write(priv->regmap, 0x10, 0x22);
++	regmap_write(priv->regmap, 0x11, 0x22);
++	/* Reset one shot the links */
++	regmap_write(priv->regmap, 0x18, 0xf);
++
 +	return 0;
 +}
 +
 +static int max_gmsl2_deser_probe(struct i2c_client *client)
 +{
-+	struct max_gmsl2_deser_priv *priv;
-+	unsigned int dev_id, dev_rev;
++	struct max_gmsl2_deser *priv;
++	unsigned int dev_id, dev_rev, val;
 +	int i;
 +	int ret;
 +
@@ -1620,28 +1918,56 @@ index 0000000..8a30110
 +
 +	dev_info(&client->dev, "GMSL2 deserializer device_id: 0x%02x rev: 0x%01x", dev_id, dev_rev);
 +	i2c_set_clientdata(client, priv);
-+	ret = max_gmsl2_init_links(priv);
++
++	/* Save tunnel mode we might have to disable it later for pattern generator */
++	if (priv->tunnel_mode) {
++		ret = regmap_field_read(priv->tunnel_mode, &val);
++		if (ret)
++			return ret;
++
++		priv->tunnel_mode_saved = !!val;
++	}
++
++	//HACK: either fix CFG1 or parse the link rate in the device tree
++	if (IS_MAX96724(priv))
++		max96724_force_6gbps(priv);
++
++	ret = max_gmsl2_gpiochip_probe(&priv->gpio, &client->dev, priv->regmap,
++				       priv->data->gpio_base,
++				       priv->data->ngpios);
++	if (ret) {
++		dev_err(&client->dev, "Failed to init gpiochip\n");
++		return ret;
++	}
++
++	ret = max_gmsl2_deser_hw_init(priv);
 +	if (ret)
 +		return ret;
 +
-+	ret = max_gmsl2_deser_v4l2_register(priv);
++	ret = max_gmsl2_i2c_mux_init(priv);
 +	if (ret)
 +		return ret;
 +
-+	max_gmsl2_deser_debug_init(priv);
-+	return ret;
-+}
++	ret = max_gmsl2_deser_create_subdev(priv);
++	if (ret)
++		return ret;
++
++	return ret;
++}
 +
 +static void max_gmsl2_deser_remove(struct i2c_client *client)
 +{
-+	struct max_gmsl2_deser_priv *priv = i2c_get_clientdata(client);
-+	max_gmsl2_deser_debug_remove(priv);
++	struct max_gmsl2_deser *priv = i2c_get_clientdata(client);
++	max_gmsl2_deser_destroy_subdev(priv);
++	gpiod_set_value_cansleep(priv->gpiod_pwdn, 0);
++
 +};
 +
 +static const struct of_device_id max_gmsl2_deser_of_ids[] = {
-+	{ .compatible = "maxim,max96934", .data = &max96934_data },
-+	{ .compatible = "maxim,max96914", .data = &max96914_data },
-+	{ .compatible = "maxim,max96714", .data = &max96714_data },
++	{ .compatible = "maxim,max96934",  .data = &max96934_data },
++	{ .compatible = "maxim,max96914",  .data = &max96914_data },
++	{ .compatible = "maxim,max96724",  .data = &max96914_data },
++	{ .compatible = "maxim,max96714f", .data = &max96714f_data },
 +	{ }
 +};
 +MODULE_DEVICE_TABLE(of, max_gmsl2_deser_of_ids);
@@ -1660,12 +1986,225 @@ index 0000000..8a30110
 +MODULE_DESCRIPTION("Maxim GMSL2 Deserializer Driver");
 +MODULE_AUTHOR("Julien Massot <julien.massot@collabora.com>");
 +MODULE_LICENSE("GPL");
+diff --git a/drivers/media/i2c/max-gmsl2-gpio.c b/drivers/media/i2c/max-gmsl2-gpio.c
+new file mode 100644
+index 00000000..ca473df
+--- /dev/null
++++ b/drivers/media/i2c/max-gmsl2-gpio.c
+@@ -0,0 +1,207 @@
++// SPDX-License-Identifier: GPL-2.0
++/*
++ * Maxim GMSL2 Deserializer Driver
++ *
++ * Copyright (C) 2023 Collabora Ltd.
++ */
++
++#include <linux/module.h>
++#include <linux/of_device.h>
++#include <linux/regmap.h>
++#include <linux/gpio/consumer.h>
++#include <linux/gpio/driver.h>
++#include <linux/gpio/max-gmsl2-gpio.h>
++
++#define GMSL2_MAX_GPIO      0x1f
++#define GMSL2_GPIO_OUT      BIT(4)
++#define GMSL2_GPIO_IN       BIT(3)
++#define GMSL2_GPIO_RX_EN    BIT(2)
++#define GMSL2_GPIO_TX_EN    BIT(1)
++#define GMSL2_GPIO_OUT_DIS  BIT(0)
++
++#define GMSL2_GPIO_REG_A(chip, gpio) (chip->base + gpio * 3)
++
++struct max_gmsl2_gpiochip {
++	struct regmap *regmap;
++	u16 base;
++};
++
++static int max_gmsl2_gpio_set_bit(struct max_gmsl2_gpiochip *chip,
++				  unsigned int offset, u8 bit, bool en)
++{
++	return regmap_update_bits(chip->regmap,
++				  GMSL2_GPIO_REG_A(chip, offset),
++				  bit, en ? bit : 0);
++}
++
++static int max_gmsl2_gpio_get_bits(struct max_gmsl2_gpiochip *chip,
++				  unsigned int offset, u8 mask)
++{
++	unsigned int val;
++	int ret;
++
++	ret = regmap_read(chip->regmap, GMSL2_GPIO_REG_A(chip, offset), &val);
++	if (ret)
++		return ret;
++	return val & mask;
++}
++
++static int max_gmsl2_gpiochip_rx_en(struct max_gmsl2_gpiochip *chip,
++				    unsigned int offset, bool en)
++{
++	return max_gmsl2_gpio_set_bit(chip, offset, GMSL2_GPIO_RX_EN, en);
++}
++
++static int max_gmsl2_gpiochip_tx_en(struct max_gmsl2_gpiochip *chip,
++				    unsigned int offset, bool en)
++{
++	return max_gmsl2_gpio_set_bit(chip, offset, GMSL2_GPIO_TX_EN, en);
++}
++
++static int max_gmsl2_gpiochip_get(struct gpio_chip *gpiochip,
++				  unsigned int offset)
++{
++	struct max_gmsl2_gpiochip *chip = gpiochip_get_data(gpiochip);
++	unsigned int val;
++	int ret;
++
++	ret = regmap_read(chip->regmap, GMSL2_GPIO_REG_A(chip, offset), &val);
++	if (ret)
++		return ret;
++	if (val & GMSL2_GPIO_OUT_DIS)
++		return !!(val & GMSL2_GPIO_IN);
++	else
++		return !!(val & GMSL2_GPIO_OUT);
++}
++
++static void max_gmsl2_gpiochip_set(struct gpio_chip *gpiochip,
++				  unsigned offset, int value)
++{
++	struct max_gmsl2_gpiochip *chip = gpiochip_get_data(gpiochip);
++
++	max_gmsl2_gpio_set_bit(chip, offset, GMSL2_GPIO_OUT, !!value);
++}
++
++static int max_gmsl2_gpio_get_direction(struct gpio_chip *gpiochip, unsigned int offset)
++{
++	struct max_gmsl2_gpiochip *chip = gpiochip_get_data(gpiochip);
++	int ret;
++
++	ret =  max_gmsl2_gpio_get_bits(chip, offset, GMSL2_GPIO_OUT_DIS);
++	if (ret < 0)
++		return ret;
++
++	return !!ret;
++}
++
++static int max_gmsl2_gpio_direction_out(struct gpio_chip *gpiochip, unsigned int offset,
++					int value)
++{
++	struct max_gmsl2_gpiochip *chip = gpiochip_get_data(gpiochip);
++
++	return regmap_update_bits(chip->regmap, GMSL2_GPIO_REG_A(chip, offset),
++				  GMSL2_GPIO_OUT_DIS | GMSL2_GPIO_OUT,
++				  value ? GMSL2_GPIO_OUT : 0);
++}
++
++static int max_gmsl2_gpio_direction_in(struct gpio_chip *gpiochip, unsigned int offset)
++{
++	struct max_gmsl2_gpiochip *chip = gpiochip_get_data(gpiochip);
++
++	return max_gmsl2_gpio_set_bit(chip, offset, GMSL2_GPIO_OUT_DIS, true);
++}
++
++int max_gmsl2_gpiochip_probe(struct gpio_chip *gpio, struct device *dev,
++			     struct regmap *regmap, u16 base, u8 ngpios)
++{
++	struct max_gmsl2_gpiochip *chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
++	struct gpio_descs *output_gpios;
++	struct gpio_descs *input_gpios;
++	u32 fw_gpio[GMSL2_MAX_GPIO];
++	int cnt, ret, i;
++
++	chip->regmap = regmap;
++	chip->base = base;
++
++	gpio->label = dev_name(dev);
++	gpio->parent = dev;
++	gpio->owner = THIS_MODULE;
++	gpio->ngpio = ngpios;
++	gpio->base = -1;
++	gpio->get_direction = max_gmsl2_gpio_get_direction;
++	gpio->direction_input = max_gmsl2_gpio_direction_in;
++	gpio->direction_output = max_gmsl2_gpio_direction_out;
++	gpio->set = max_gmsl2_gpiochip_set;
++	gpio->get = max_gmsl2_gpiochip_get;
++	gpio->can_sleep = true;
++
++	for (i = 0; i < gpio->ngpio; i++) {
++		ret = max_gmsl2_gpiochip_rx_en(chip, i, 0);
++		if (ret)
++			return ret;
++		ret = max_gmsl2_gpiochip_tx_en(chip, i, 0);
++		if (ret)
++			return ret;
++	}
++
++	cnt = of_property_count_u32_elems(dev->of_node, "maxim,gpio-forward-rx");
++	if (cnt > 0) {
++		ret = of_property_read_u32_array(dev->of_node, "maxim,gpio-forward-rx",
++						 fw_gpio, cnt);
++		if (ret) {
++			dev_err(dev, "Fail to read maxim,gpio-forward-rx property");
++			return ret;
++		}
++		for (i = 0; i < cnt; i++) {
++			if (fw_gpio[i] > gpio->ngpio) {
++				dev_err(dev, "GPIO %u is out of range", fw_gpio[i]);
++				return -EINVAL;
++			}
++
++			ret = max_gmsl2_gpiochip_rx_en(chip, fw_gpio[i], true);
++			if (ret) {
++				dev_err(dev, "Fail to enable gpio rx forwarding %u", fw_gpio[i]);
++				return ret;
++			}
++		}
++	}
++
++	cnt = of_property_count_u32_elems(dev->of_node, "maxim,gpio-forward-tx");
++	if (cnt > 0) {
++		ret = of_property_read_u32_array(dev->of_node, "maxim,gpio-forward-tx",
++						 fw_gpio, cnt);
++		if (ret) {
++			dev_err(dev, "Fail to read maxim,gpio-forward-tx property");
++			return ret;
++		}
++		for (i = 0; i < cnt; i++) {
++			if (fw_gpio[i] > gpio->ngpio) {
++				dev_err(dev, "GPIO %u is out of range", fw_gpio[i]);
++				return -EINVAL;
++			}
++
++			ret = max_gmsl2_gpiochip_tx_en(chip, fw_gpio[i], true);
++			if (ret) {
++				dev_err(dev, "Fail to enable gpio tx forwarding %u", fw_gpio[i]);
++				return ret;
++			}
++		}
++	}
++
++	ret = devm_gpiochip_add_data(dev, gpio, chip);
++	if (ret) {
++		dev_err(dev, "Unable to create gpio_chip\n");
++		return ret;
++	}
++
++	/* If we have some gpios reserved get them here */
++	output_gpios = devm_gpiod_get_array_optional(dev, "output", GPIOD_OUT_LOW);
++	input_gpios = devm_gpiod_get_array_optional(dev, "input", GPIOD_OUT_LOW);
++
++	return 0;
++}
++EXPORT_SYMBOL_GPL(max_gmsl2_gpiochip_probe);
++
++MODULE_DESCRIPTION("Maxim GMSL2 GPIO Driver");
++MODULE_AUTHOR("Julien Massot <julien.massot@collabora.com>");
++MODULE_LICENSE("GPL");
 diff --git a/drivers/media/i2c/max-gmsl2-patgen.c b/drivers/media/i2c/max-gmsl2-patgen.c
 new file mode 100644
-index 0000000..172fbde
+index 00000000..92b869e
 --- /dev/null
 +++ b/drivers/media/i2c/max-gmsl2-patgen.c
-@@ -0,0 +1,192 @@
+@@ -0,0 +1,175 @@
 +// SPDX-License-Identifier: GPL-2.0
 +/*
 + * Maxim GMSL2 Deserializer Driver
@@ -1675,7 +2214,7 @@ index 0000000..172fbde
 + * Copyright (C) 2022 Collabora Ltd.
 + */
 +
-+#include "max-gmsl2-patgen.h"
++#include <media/gmsl2/max-gmsl2-patgen.h>
 +
 +static int max_gmsl2_write_bulk(struct regmap *regmap, uint16_t reg,
 +				unsigned int val, uint8_t val_count)
@@ -1689,31 +2228,25 @@ index 0000000..172fbde
 +	return regmap_bulk_write(regmap, reg, values, val_count);
 +}
 +
-+#define VTG_CTRL              0
++/* VTG control */
 +#define VTG_MODE_FREE_RUNNING 0x3
-+#define VTG_DE_INV            BIT(2)
-+#define VTG_HS_INV            BIT(3)
-+#define VTG_VS_INV            BIT(4)
-+#define VTG_GEN_DE            BIT(5)
-+#define VTG_GEN_HS            BIT(6)
-+#define VTG_GEN_VS            BIT(7)
-+#define VTG_VS_DLY            0x2
-+#define VTG_VS_HIGH           0x5
-+#define VTG_VS_LOW            0x8
-+#define VTG_V2H               0xb
-+#define VTG_HS_HIGH           0xe
-+#define VTG_HS_LOW            0x10
-+#define VTG_HS_CNT            0x12
++
++#define VTG_VS_DLY(patgen)    (patgen->vtg_timing)
++#define VTG_VS_HIGH(patgen)   (patgen->vtg_timing + 3)
++#define VTG_VS_LOW(patgen)    (patgen->vtg_timing + 6)
++#define VTG_V2H(patgen)       (patgen->vtg_timing + 9)
++#define VTG_HS_HIGH(patgen)   (patgen->vtg_timing + 0xc)
++#define VTG_HS_LOW(patgen)    (patgen->vtg_timing + 0xe)
++#define VTG_HS_CNT(patgen)    (patgen->vtg_timing + 0x10)
 +/* VS edge to the rising edge of the first DE in terms of PCLK cycles */
-+#define VTG_V2D               0x14
-+#define VTG_DE_HIGH           0x17
-+#define VTG_DE_LOW            0x19
-+#define VTG_DE_CNT            0x1b
++#define VTG_V2D(patgen)       (patgen->vtg_timing + 0x12)
++#define VTG_DE_HIGH(patgen)   (patgen->vtg_timing + 0x15)
++#define VTG_DE_LOW(patgen)    (patgen->vtg_timing + 0x17)
++#define VTG_DE_CNT(patgen)    (patgen->vtg_timing + 0x19)
 +
-+int max_gmsl2_vtg_enable(struct regmap *regmap, uint16_t base_addr,
++int max_gmsl2_vtg_enable(struct max_gmsl2_patgen *patgen, struct regmap *regmap,
 +			 const struct v4l2_bt_timings *bt)
 +{
-+	/*1280 * 720 @ 60 fps 74.25 MHz pclk*/
 +	const u32 h_active = bt->width;
 +	const u32 h_fp = bt->hfrontporch;
 +	const u32 h_sw = bt->hsync;
@@ -1728,162 +2261,1593 @@ index 0000000..172fbde
 +
 +	int ret;
 +
-+	ret = max_gmsl2_write_bulk(regmap, base_addr + VTG_VS_DLY,
++	ret = max_gmsl2_write_bulk(regmap, VTG_VS_DLY(patgen),
 +				   0, 3);
 +	if (ret)
 +		return ret;
 +
-+	ret = max_gmsl2_write_bulk(regmap, base_addr + VTG_VS_HIGH,
++	ret = max_gmsl2_write_bulk(regmap, VTG_VS_HIGH(patgen),
 +				   v_sw * h_tot, 3);
 +	if (ret)
 +		return ret;
 +
-+	ret = max_gmsl2_write_bulk(regmap, base_addr + VTG_VS_LOW,
++	ret = max_gmsl2_write_bulk(regmap, VTG_VS_LOW(patgen),
 +				   (v_active + v_fp + v_bp) * h_tot, 3);
 +	if (ret)
 +		return ret;
 +
-+	ret = max_gmsl2_write_bulk(regmap, base_addr + VTG_V2H,
-+				   0, 3);
++	ret = max_gmsl2_write_bulk(regmap, VTG_V2H(patgen), 0, 3);
 +	if (ret)
 +		return ret;
 +
-+	ret = max_gmsl2_write_bulk(regmap, base_addr + VTG_HS_HIGH, h_sw, 2);
++	ret = max_gmsl2_write_bulk(regmap, VTG_HS_HIGH(patgen), h_sw, 2);
 +	if (ret)
 +		return ret;
 +
-+	ret = max_gmsl2_write_bulk(regmap, base_addr + VTG_HS_LOW,
++	ret = max_gmsl2_write_bulk(regmap, VTG_HS_LOW(patgen),
 +				   h_active + h_fp + h_bp, 2);
 +	if (ret)
 +		return ret;
 +
-+	ret = max_gmsl2_write_bulk(regmap, base_addr + VTG_HS_CNT,
-+				   v_tot, 2);
++	ret = max_gmsl2_write_bulk(regmap, VTG_HS_CNT(patgen), v_tot, 2);
 +	if (ret)
 +		return ret;
 +
-+	ret = max_gmsl2_write_bulk(regmap, base_addr + VTG_V2D,
++	ret = max_gmsl2_write_bulk(regmap, VTG_V2D(patgen),
 +				   h_tot * (v_sw + v_bp) + (h_sw + h_bp), 3);
 +	if (ret)
 +		return ret;
 +
-+	ret = max_gmsl2_write_bulk(regmap, base_addr + VTG_DE_HIGH,
++	ret = max_gmsl2_write_bulk(regmap, VTG_DE_HIGH(patgen),
 +				   h_active, 2);
 +	if (ret)
 +		return ret;
 +
-+	ret = max_gmsl2_write_bulk(regmap, base_addr + VTG_DE_LOW,
++	ret = max_gmsl2_write_bulk(regmap, VTG_DE_LOW(patgen),
 +				   h_fp + h_sw + h_bp, 2);
 +	if (ret)
 +		return ret;
 +
-+	ret = max_gmsl2_write_bulk(regmap, base_addr + VTG_DE_CNT,
++	ret = max_gmsl2_write_bulk(regmap, VTG_DE_CNT(patgen),
 +				   v_active, 2);
 +	if (ret)
 +		return ret;
 +
-+	return regmap_write(regmap, base_addr + VTG_CTRL,
-+			    VTG_GEN_VS | VTG_GEN_HS | VTG_GEN_DE |
-+			    VTG_VS_INV | VTG_HS_INV |
-+			    VTG_MODE_FREE_RUNNING);
++	ret = regmap_field_write(patgen->vtg_mode, VTG_MODE_FREE_RUNNING);
++	if (ret)
++		return ret;
++
++	return regmap_field_write(patgen->gen_vs_hs_de, 7);
 +}
++EXPORT_SYMBOL_GPL(max_gmsl2_vtg_enable);
 +
-+int max_gmsl2_vtg_disable(struct regmap *regmap, uint16_t base_addr)
++int max_gmsl2_vtg_disable(struct max_gmsl2_patgen *patgen, struct regmap *regmap)
 +{
-+	return regmap_write(regmap, base_addr + VTG_CTRL, 0);
++	return regmap_field_write(patgen->gen_vs_hs_de, 0);
 +}
++EXPORT_SYMBOL_GPL(max_gmsl2_vtg_disable);
 +
-+#define VPG_CTRL           1
-+#define VPG_MODE_CHKB      BIT(4)
-+#define VPG_MODE_GRAD      BIT(5)
-+#define VPG_MODE_MASK      (VPG_MODE_CHKB | VPG_MODE_GRAD)
-+#define VPG_CHKB_COLOR_A   0x1e
-+#define VPG_CHKB_COLOR_B   0x21
-+#define VPG_CHKB_RPT_CNT_A 0x24
-+#define VPG_CHKB_RPT_CNT_B 0x25
-+#define VPG_CHKB_RPT_LINES 0x26
-+/* Gradient mode increment amount.
-+ * Increment amount is the reg value / 4.
-+ */
-+#define VPG_GRAD_INCR      0x1d
++#define VPG_MODE_CHKB      BIT(0)
++#define VPG_MODE_GRAD      BIT(1)
 +
-+int max_gmsl2_vpg_enable(struct regmap *regmap, uint16_t base_addr,
++#define VPG_GRAD_INCR(patgen)      (patgen->vpg_patterns)
++#define VPG_CHKB_COLOR_A(patgen)   (patgen->vpg_patterns + 1)
++#define VPG_CHKB_COLOR_B(patgen)   (patgen->vpg_patterns + 4)
++#define VPG_CHKB_RPT_CNT_A(patgen) (patgen->vpg_patterns + 7)
++#define VPG_CHKB_RPT_CNT_B(patgen) (patgen->vpg_patterns + 8)
++#define VPG_CHKB_ALT(patgen)	   (patgen->vpg_patterns + 9)
++
++int max_gmsl2_vpg_enable(struct max_gmsl2_patgen *patgen, struct regmap *regmap,
 +			 enum max_gmsl2_vpg_mode mode)
 +{
 +	int ret;
 +
 +	if (mode == MAX_GMSL2_VPG_CHECKERBOARD) {
 +		/* Set checkerboard pattern size. */
-+		ret = regmap_write(regmap,
-+				   base_addr + VPG_CHKB_RPT_CNT_A, 0x3c);
++		ret = regmap_write(regmap, VPG_CHKB_RPT_CNT_A(patgen), 0x3c);
 +		if (ret)
 +			return ret;
-+		ret = regmap_write(regmap,
-+				   base_addr + VPG_CHKB_RPT_CNT_B, 0x3c);
++		ret = regmap_write(regmap, VPG_CHKB_RPT_CNT_B(patgen), 0x3c);
 +		if (ret)
 +			return ret;
-+		ret = regmap_write(regmap,
-+				   base_addr + VPG_CHKB_RPT_LINES, 0x3c);
++		ret = regmap_write(regmap, VPG_CHKB_ALT(patgen), 0x3c);
 +		if (ret)
 +			return ret;
 +		/* Set checkerboard pattern colors. */
-+		ret =  max_gmsl2_write_bulk(regmap,
-+					    base_addr + VPG_CHKB_COLOR_A,
++		ret =  max_gmsl2_write_bulk(regmap, VPG_CHKB_COLOR_A(patgen),
 +					    0xfecc00, 3);
 +		if (ret)
 +			return ret;
-+		ret =  max_gmsl2_write_bulk(regmap,
-+					    base_addr + VPG_CHKB_COLOR_B,
++		ret =  max_gmsl2_write_bulk(regmap, VPG_CHKB_COLOR_B(patgen),
 +					    0x006aa7, 3);
 +		if (ret)
 +			return ret;
-+
-+		ret = regmap_update_bits(regmap,
-+					 base_addr + VPG_CTRL, VPG_MODE_MASK,
-+					 VPG_MODE_CHKB);
++		ret = regmap_field_write(patgen->vpg_mode, VPG_MODE_CHKB);
 +	} else {
 +		/* Set gradient increment. */
-+		ret = regmap_write(regmap, base_addr + VPG_GRAD_INCR, 0x10);
++		ret = regmap_write(regmap, VPG_GRAD_INCR(patgen), 0x10);
 +		if (ret)
 +			return ret;
-+		ret = regmap_update_bits(regmap, base_addr + VPG_CTRL,
-+					 VPG_MODE_MASK, VPG_MODE_GRAD);
++		ret = regmap_field_write(patgen->vpg_mode, VPG_MODE_GRAD);
 +	}
 +
 +	return ret;
 +}
++EXPORT_SYMBOL_GPL(max_gmsl2_vpg_enable);
 +
-+int max_gmsl2_vpg_disable(struct regmap *regmap, uint16_t base_addr)
++int max_gmsl2_vpg_disable(struct max_gmsl2_patgen *patgen)
 +{
-+	return regmap_update_bits(regmap, base_addr + VPG_CTRL,
-+				  VPG_MODE_MASK, 0);
++	return regmap_field_write(patgen->vpg_mode, 0);
 +}
-diff --git a/drivers/media/i2c/max-gmsl2-patgen.h b/drivers/media/i2c/max-gmsl2-patgen.h
++EXPORT_SYMBOL_GPL(max_gmsl2_vpg_disable);
+diff --git a/drivers/media/i2c/max-gmsl2-ser.c b/drivers/media/i2c/max-gmsl2-ser.c
 new file mode 100644
-index 0000000..11d4b72
+index 00000000..7673e45
 --- /dev/null
-+++ b/drivers/media/i2c/max-gmsl2-patgen.h
-@@ -0,0 +1,23 @@
++++ b/drivers/media/i2c/max-gmsl2-ser.c
+@@ -0,0 +1,992 @@
 +// SPDX-License-Identifier: GPL-2.0
 +/*
-+ * Maxim GMSL2 Deserializer Driver
++ * Friver for Maxim GMSL2 video serializer
 + *
-+ * Copyright (C) 2022 Collabora Ltd.
++ * Copyright (C) 2023 Collabora Ltd.
 + */
 +
++#include <linux/clk-provider.h>
++#include <linux/debugfs.h>
++#include <linux/gpio/max-gmsl2-gpio.h>
++#include <linux/i2c-mux.h>
++#include <linux/i2c.h>
++#include <linux/mod_devicetable.h>
++#include <linux/module.h>
++#include <linux/of_device.h>
 +#include <linux/regmap.h>
-+#include <linux/types.h>
-+#include <linux/videodev2.h>
++#include <linux/v4l2-dv-timings.h>
++#include <media/mipi-csi2.h>
++#include <media/v4l2-fwnode.h>
++#include <media/v4l2-subdev.h>
++#include <media/gmsl2/max-gmsl2-patgen.h>
 +
-+enum max_gmsl2_vpg_mode
++#include "gmsl2-def.h"
++
++#define GMSL_SER_MAX_PIPE 4
++#define GMSL_SER_MAX_CSI_LANES 4
++
++struct max_gmsl2_ser {
++	struct i2c_client *client;
++	const struct max_gmsl2_data *data;
++	struct max_gmsl2_pipe pipes[GMSL_SER_MAX_PIPE];
++	struct max_gmsl2_csi csi;
++	struct max_gmsl2_link link;
++	struct max_gmsl2_patgen patgen;
++	struct v4l2_subdev_format format;
++	struct gpio_chip gpio;
++	struct regmap_field *dev_rev;
++	struct regmap *regmap;
++	struct dentry *dbg_dir;
++	struct v4l2_subdev	   sd;
++	struct media_pad	   pads[2];
++	struct v4l2_async_notifier notifier;
++	struct v4l2_async_subdev   asd;
++	struct v4l2_subdev	   *source_sd;
++	u16			   source_sd_pad;
++	struct v4l2_ctrl_handler   ctrl_handler;
++	struct i2c_mux_core *mux;
++	struct regmap_field *tunnel_mode;
++	struct clk_hw clk_hw;
++};
++
++static inline struct max_gmsl2_ser *sd_to_max_gmsl2_ser(struct v4l2_subdev *sd)
++{
++	return container_of(sd, struct max_gmsl2_ser, sd);
++}
++
++static inline struct max_gmsl2_ser *clk_hw_to_max_gmsl2_ser(struct clk_hw *hw)
++{
++	return container_of(hw, struct max_gmsl2_ser, clk_hw);
++}
++
++/* Source Pad is right after sink pads */
++#define MAX_GMSL2_SER_PAD_SINK 0
++#define MAX_GMSL2_SER_PAD_SRC  1
++
++static const struct max_gmsl2_data max96717f_data =
++{
++	.device_id = MAX96717F_DEVICE_ID,
++	.pipes = 1,
++	.csi = 1,
++	.links = 1,
++	.ngpios = 11,
++	.gpio_base = 0x2be,
++	.dev_rev = REG_FIELD(0xe, 0, 3),
++	.csi_lane_cnt = { REG_FIELD(0x331, 4, 5) },
++	/* Phy that provides D0, D1 first*/
++	.csi_lane_polarities = { REG_FIELD(0x335, 0, 2), REG_FIELD(0x334, 4, 6) },
++	.csi_lane_map = { REG_FIELD(0x333, 0, 3), REG_FIELD(0x332, 4, 7) },
++	.csi_force_enable = REG_FIELD(0x325, 7, 7) ,
++	.csi_phy_enable = { REG_FIELD(0x308, 5, 5) },
++	.pattern_clk_freq = REG_FIELD(0x24f, 1, 3),
++	.patgen_base_addr = { 0x24e },
++	.pipe_stream_id = { REG_FIELD(0x5b, 0, 1) },
++	.pipe_enable = { REG_FIELD(0x2, 6, 6) },
++	.pipe_vid_lock = { REG_FIELD(0x112, 7, 7) },
++	.link_lock = { REG_FIELD(0x13, 3, 3) },
++	.reset_one_shot_link = { REG_FIELD(0x10, 5, 5) },
++	.reset_link = { REG_FIELD(0x10, 6, 6) },
++	.tunnel_mode = REG_FIELD(0x383, 7, 7),
++	.pipe_dt_sel = {
++		REG_FIELD(0x318, 0, 6),
++		REG_FIELD(0x319, 0, 6),
++	},
++};
++
++const struct max_gmsl2_data max9295a_data =
++{
++	.device_id = MAX9295A_DEVICE_ID,
++	.pipes = 4,
++	.csi = 1,
++	.links = 1,
++	.ngpios = 11,
++	.gpio_base = 0x2be,
++	.dev_rev = REG_FIELD(0xe, 0, 3),
++	.csi_lane_cnt = { REG_FIELD(0x331, 4, 5) },
++	.csi_lane_polarities = { REG_FIELD(0x335, 0, 2), REG_FIELD(0x334, 4, 6) },
++	.csi_lane_map = { REG_FIELD(0x333, 0, 3), REG_FIELD(0x332, 4, 7) },
++	.csi_force_enable = REG_FIELD(0x325, 7, 7) ,
++	.csi_phy_enable = { REG_FIELD(0x308, 5, 5) }, //* Port B*/
++	.pattern_clk_freq = REG_FIELD(0x24f, 1, 3),
++	.patgen_base_addr = { 0x1f3 },
++	.pipe_stream_id = {   REG_FIELD(0x5b, 0, 1),
++		REG_FIELD(0x57, 0, 1),
++		REG_FIELD(0x5b, 0, 1),
++		REG_FIELD(0x5f, 0, 1) },
++	.pipe_enable = { REG_FIELD(0x2, 4, 4), REG_FIELD(0x2, 5, 5),
++		REG_FIELD(0x2, 6, 6) , REG_FIELD(0x2, 7, 7) },
++	.pipe_vid_lock = { REG_FIELD(0x102, 7, 7), REG_FIELD(0x10a, 7, 7),
++		REG_FIELD(0x112, 7, 7), REG_FIELD(0x11a, 7, 7) },
++	.link_lock = { REG_FIELD(0x13, 3, 3) },
++	.reset_one_shot_link = { REG_FIELD(0x10, 5, 5) },
++	.reset_link = { REG_FIELD(0x10, 6, 6) },
++	.pipe_dt_sel = {
++		REG_FIELD(0x314, 0, 6),
++		REG_FIELD(0x315, 0, 6),
++		REG_FIELD(0x316, 0, 6),
++		REG_FIELD(0x317, 0, 6),
++		REG_FIELD(0x318, 0, 6),
++		REG_FIELD(0x319, 0, 6),
++		REG_FIELD(0x31a, 0, 6),
++		REG_FIELD(0x31b, 0, 6),
++	},
++};
++#define IS_MAX9295A(priv) (priv->data->device_id == MAX9295A_DEVICE_ID)
++
++#define for_each_pipe(pipe_idx, priv) \
++	for ( (pipe_idx) = 0; (pipe_idx) < (priv)->data->pipes; (pipe_idx)++ )
++
++static void max_gmsl2_ser_init_patgen_regs(struct max_gmsl2_ser *priv)
++{
++	struct device *dev = &priv->client->dev;
++	const struct max_gmsl2_data *data = priv->data;
++	struct max_gmsl2_patgen *patgen = &priv->patgen;
++	const struct reg_field *field;
++	u16 base = data->patgen_base_addr[0];
++	struct reg_field gen_vs_hs_de = REG_FIELD(base, 5, 7);
++	struct reg_field inv_vs_hs_de = REG_FIELD(base, 2, 4);
++	struct reg_field vtg_mode = REG_FIELD(base, 0, 2);
++	struct reg_field vpg_mode_reg = REG_FIELD(base + 0x1c, 4, 5);
++
++	field = &data->pattern_clk_freq;
++	if (field->reg)
++		priv->patgen.pattern_clk_freq =
++			devm_regmap_field_alloc(dev, priv->regmap, *field);
++
++	patgen->gen_vs_hs_de = devm_regmap_field_alloc(dev, priv->regmap, gen_vs_hs_de);
++	patgen->inv_vs_hs_de = devm_regmap_field_alloc(dev, priv->regmap, inv_vs_hs_de);
++	patgen->vtg_mode = devm_regmap_field_alloc(dev, priv->regmap, vtg_mode);
++	patgen->vpg_mode = devm_regmap_field_alloc(dev, priv->regmap, vpg_mode_reg);
++
++	patgen->vtg_timing = base + 2;
++	patgen->vpg_patterns = base + 0x1d;
++}
++
++static int max_gmsl2_ser_init_regmap(struct max_gmsl2_ser *priv)
++{
++	const struct max_gmsl2_data *data = priv->data;
++	struct device *dev = &priv->client->dev;
++	const struct reg_field *field;
++	int i;
++
++	priv->regmap = devm_regmap_init_i2c(priv->client,
++					    &gmsl2_regmap_config);
++	if (IS_ERR(priv->regmap))
++		return PTR_ERR(priv->regmap);
++
++	for_each_pipe(i, priv)
++		max_gmsl2_pipe_init_regfield(dev, priv->regmap,
++					     &priv->pipes[i], priv->data,
++					     i);
++
++	max_gmsl2_csi_init_regfield(dev, priv->regmap,
++				    &priv->csi, priv->data, 0);
++
++	max_gmsl2_link_init_regfield(dev, priv->regmap,
++				     &priv->link, priv->data, 0);
++	priv->dev_rev =
++		devm_regmap_field_alloc(dev, priv->regmap, data->dev_rev);
++
++	field = &data->tunnel_mode;
++	if (field->reg)
++		priv->tunnel_mode =
++			devm_regmap_field_alloc(dev, priv->regmap, *field);
++
++	max_gmsl2_ser_init_patgen_regs(priv);
++
++	return 0;
++}
++
++#define for_each_pipe(pipe_idx, priv) \
++	for ( (pipe_idx) = 0; (pipe_idx) < (priv)->data->pipes; (pipe_idx)++ )
++
++static int max_gmsl2_ser_log_status(struct v4l2_subdev *sd)
++{
++	struct max_gmsl2_ser *priv = sd_to_max_gmsl2_ser(sd);
++	struct device *dev = &priv->client->dev;
++	struct max_gmsl2_pipe *pipe;
++	unsigned int link_locked;
++	u8 pipeid;
++	int ret;
++
++	dev_info(dev, "Max GMSL2 CSI Serializer %s id: %02x\n",
++		   gmsl2_id_to_name(priv->data->device_id), priv->data->device_id);
++
++	ret = regmap_field_read(priv->link.locked, &link_locked);
++	if (ret)
++		return ret;
++	dev_info(dev, "Link locked: %u\n", link_locked);
++
++	for_each_pipe(pipeid, priv) {
++		pipe = &priv->pipes[pipeid];
++		max_gmsl2_state_show_pipe(dev, pipe, priv->data, pipeid);
++	}
++
++	return 0;
++}
++
++#define PATTERN_CLK_FREQ_75MHz 0x101
++static int max_gmsl2_enable_pattern(struct max_gmsl2_ser *priv, int val)
++{
++	int ret;
++	struct max_gmsl2_patgen *patgen = &priv->patgen;
++	const struct v4l2_dv_timings t_720p = V4L2_DV_BT_CEA_1280X720P60;
++	const struct v4l2_bt_timings *bt;
++	enum max_gmsl2_vpg_mode mode;
++
++	if (val) {
++		if (priv->patgen.pattern_clk_freq) {
++		ret = regmap_field_write(priv->patgen.pattern_clk_freq,
++					 PATTERN_CLK_FREQ_75MHz);
++		if (ret)
++			return ret;
++		}
++		/* Invert VS HS and DE */
++		ret = regmap_field_write(priv->patgen.inv_vs_hs_de, 7);
++		if (ret)
++			return ret;
++		mode = (val == 1) ? MAX_GMSL2_VPG_CHECKERBOARD:
++			MAX_GMSL2_VPG_GRADIENT;
++
++		ret = max_gmsl2_vpg_enable(patgen, priv->regmap, mode);
++		if (ret)
++			return ret;
++		bt = &t_720p.bt;
++		ret = max_gmsl2_vtg_enable(patgen, priv->regmap, bt);
++		if (ret)
++			return ret;
++	}
++	return 0;
++}
++
++static int max_gmsl2_ser_notify_bound(struct v4l2_async_notifier *notifier,
++				      struct v4l2_subdev *source_subdev,
++				      struct v4l2_async_subdev *asd)
++{
++	struct max_gmsl2_ser *priv = sd_to_max_gmsl2_ser(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)
++		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, MAX_GMSL2_SER_PAD_SINK,
++				    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 max_gmsl2_ser_notify_ops =
 +{
-+	MAX_GMSL2_VPG_CHECKERBOARD,
-+	MAX_GMSL2_VPG_GRADIENT
++	.bound = max_gmsl2_ser_notify_bound,
++};
++
++static int max_gmsl2_ser_v4l2_notifier_register(struct max_gmsl2_ser *priv)
++{
++	struct device *dev = &priv->client->dev;
++	struct v4l2_async_subdev *asd;
++	struct fwnode_handle *ep_fwnode;
++	int ret;
++
++	ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
++						    0, 0, 0);
++	if (!ep_fwnode)
++		return -ENODEV;
++
++	v4l2_async_nf_init(&priv->notifier);
++
++	asd = v4l2_async_nf_add_fwnode_remote(
++		&priv->notifier, ep_fwnode,
++		struct v4l2_async_subdev);
++
++	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 ret;
++	}
++
++	priv->notifier.ops = &max_gmsl2_ser_notify_ops;
++
++	ret = v4l2_async_subdev_nf_register(&priv->sd, &priv->notifier);
++	if (ret) {
++		dev_err(dev, "Failed to register subdev_notifier");
++		v4l2_async_nf_cleanup(&priv->notifier);
++		return ret;
++	}
++
++	return 0;
++}
++
++struct max_gmsl2_fmt_desc {
++	u32 code;
++	u32 data_type;
++};
++
++static const struct max_gmsl2_fmt_desc max_gmsl2_ser_supported_codes[] = {
++	{
++		.code = MEDIA_BUS_FMT_Y8_1X8,
++		.data_type = MIPI_CSI2_DT_RAW8,
++	},
++	{
++		.code = MEDIA_BUS_FMT_Y10_1X10,
++		.data_type = MIPI_CSI2_DT_RAW10,
++	},
++	{
++		.code = MEDIA_BUS_FMT_Y12_1X12,
++		.data_type = MIPI_CSI2_DT_RAW12,
++	},
++	{
++		.code = MEDIA_BUS_FMT_Y14_1X14,
++		.data_type = MIPI_CSI2_DT_RAW14,
++	},
++	{
++		.code = MEDIA_BUS_FMT_Y16_1X16,
++		.data_type = MIPI_CSI2_DT_RAW16,
++	},
++};
++
++/*
++ * Return 1 if the device is in tunnel mode, 0 if in pixel mode.
++ * Negative errno in case of error.
++ */
++static int max_gmsl2_ser_tunnel_mode(struct max_gmsl2_ser *priv)
++{
++	unsigned int tunnel;
++	int ret;
++
++	if (!priv->tunnel_mode)
++		return 0;
++
++	ret = regmap_field_read(priv->tunnel_mode, &tunnel);
++	if (ret)
++		return ret;
++	return !!tunnel;
++}
++
++#define MAX_GMSL2_DT_SEL_EN BIT(6)
++static int max_gmsl2_pipe_set_dt_sel(struct max_gmsl2_ser *priv, u32 dt)
++{
++	struct max_gmsl2_pipe *pipe;
++	int ret;
++
++	ret = max_gmsl2_ser_tunnel_mode(priv);
++	if (ret < 0)
++		return ret;
++
++	/* We don't need to select a datatype in tunnel mode */
++	if (ret)
++		return 0;
++
++	if (priv->data->pipes == 1)
++		pipe = &priv->pipes[0];
++	else if (priv->data->pipes == 4)
++		pipe = &priv->pipes[2];
++	else
++		return -EINVAL;
++
++	return regmap_field_write(pipe->dt_sel[0],
++				  dt | MAX_GMSL2_DT_SEL_EN);
++};
++
++static int max_gmsl2_ser_get_format(struct v4l2_subdev *sd,
++				    struct v4l2_subdev_state *sd_state,
++				    struct v4l2_subdev_format *sdformat)
++{
++	struct max_gmsl2_ser *priv = sd_to_max_gmsl2_ser(sd);
++
++	sdformat->format = priv->format.format;
++
++	return 0;
++}
++
++static int max_gmsl2_ser_set_format(struct v4l2_subdev *sd,
++				    struct v4l2_subdev_state *sd_state,
++				    struct v4l2_subdev_format *sdformat)
++{
++	struct max_gmsl2_ser *priv = sd_to_max_gmsl2_ser(sd);
++	u32 dt;
++	int i, ret;
++
++	/* No transcoding, source and sink formats must match. */
++	if (sdformat->pad == MAX_GMSL2_SER_PAD_SRC)
++		return v4l2_subdev_get_fmt(sd, sd_state, sdformat);
++
++	ret = max_gmsl2_ser_tunnel_mode(priv);
++	if (ret < 0)
++		return ret;
++	else if (ret)
++		/* If we are in tunnel mode we can accept any format */
++		goto skip_dt_sel;
++
++	for (i = 0; i < ARRAY_SIZE(max_gmsl2_ser_supported_codes); i++) {
++		if (max_gmsl2_ser_supported_codes[i].code == sdformat->format.code)
++			break;
++	}
++
++	if (i == ARRAY_SIZE(max_gmsl2_ser_supported_codes)) {
++		i = 0;
++		sdformat->format.code = max_gmsl2_ser_supported_codes[i].code;
++	}
++
++	dt = max_gmsl2_ser_supported_codes[i].data_type;
++	ret = max_gmsl2_pipe_set_dt_sel(priv, dt);
++
++skip_dt_sel:
++	priv->format.format = sdformat->format;
++
++	return 0;
++}
++
++static const struct v4l2_subdev_pad_ops max_gmsl2_ser_pad_ops = {
++	.get_fmt = max_gmsl2_ser_get_format,
++	.set_fmt = max_gmsl2_ser_set_format,
++};
++
++static int max_gmsl2_ser_s_ctrl(struct v4l2_ctrl *ctrl)
++{
++	struct max_gmsl2_ser *priv =
++		container_of(ctrl->handler, struct max_gmsl2_ser, ctrl_handler);
++	int ret = 0;
++
++	switch (ctrl->id) {
++	case V4L2_CID_TEST_PATTERN:
++		ret = max_gmsl2_enable_pattern(priv, ctrl->val);
++		break;
++	}
++
++	return ret;
++}
++
++static const struct v4l2_ctrl_ops max_gmsl2_ser_ctrl_ops = {
++	.s_ctrl = max_gmsl2_ser_s_ctrl,
++};
++
++static const struct media_entity_operations max_gmsl2_ser_entity_ops = {
++};
++
++static int max_gmsl2_ser_s_stream(struct v4l2_subdev *sd, int enable)
++{
++	struct max_gmsl2_ser *priv = sd_to_max_gmsl2_ser(sd);
++	int ret, i;
++
++	for_each_pipe(i, priv) {
++		ret = regmap_field_write(priv->pipes[i].enable, !!enable);
++		if (ret)
++			return ret;
++	}
++
++	ret = regmap_field_write(priv->csi.phy_enable, !!enable);
++	if (ret)
++		return ret;
++
++	if (priv->source_sd)
++		ret = v4l2_subdev_call(priv->source_sd, video, s_stream, enable);
++
++	return ret;
++}
++
++static const struct v4l2_subdev_core_ops max_gmsl2_ser_core_ops = {
++	.log_status = max_gmsl2_ser_log_status,
++};
++
++static const struct v4l2_subdev_video_ops max_gmsl2_ser_video_ops = {
++	.s_stream = max_gmsl2_ser_s_stream,
++};
++
++static const struct v4l2_subdev_ops max_gmsl2_ser_subdev_ops = {
++	.core = &max_gmsl2_ser_core_ops,
++	.video = &max_gmsl2_ser_video_ops,
++	.pad = &max_gmsl2_ser_pad_ops,
++};
++
++static void max_gmsl2_ser_notifier_unregister(struct max_gmsl2_ser *priv)
++{
++	v4l2_async_nf_unregister(&priv->notifier);
++	v4l2_async_nf_cleanup(&priv->notifier);
++}
++
++static int max_gmsl2_ser_init_format(struct max_gmsl2_ser *priv)
++{
++	priv->format.format.width = 1920;
++	priv->format.format.height = 1080;
++	priv->format.format.code = MEDIA_BUS_FMT_Y8_1X8;
++	priv->format.format.field = V4L2_FIELD_NONE;
++
++	return max_gmsl2_pipe_set_dt_sel(priv, MIPI_CSI2_DT_RAW8);
++}
++
++static int max_gmsl2_ser_subdev_init(struct max_gmsl2_ser *priv)
++{
++	struct device *dev = &priv->client->dev;
++	int ret;
++
++	ret = max_gmsl2_ser_init_format(priv);
++	if (ret)
++		return ret;
++
++	v4l2_i2c_subdev_init(&priv->sd, priv->client, &max_gmsl2_ser_subdev_ops);
++	v4l2_ctrl_handler_init(&priv->ctrl_handler, 1);
++	priv->sd.ctrl_handler = &priv->ctrl_handler;
++
++	if (priv->data->patgen_base_addr[0])
++		v4l2_ctrl_new_std_menu_items(
++			&priv->ctrl_handler,
++			&max_gmsl2_ser_ctrl_ops,
++			V4L2_CID_TEST_PATTERN,
++			ARRAY_SIZE(max_gmsl2_test_pattern) - 1,
++			0, 0, max_gmsl2_test_pattern);
++
++	if (priv->ctrl_handler.error) {
++		ret = priv->ctrl_handler.error;
++		dev_err(dev, "Failed to set up v4l2 controls\n");
++		goto err_remove_ctrls;
++	}
++
++	priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
++	priv->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
++	priv->sd.entity.ops = &max_gmsl2_ser_entity_ops;
++
++	priv->pads[MAX_GMSL2_SER_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
++	priv->pads[MAX_GMSL2_SER_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE;
++
++	ret = media_entity_pads_init(&priv->sd.entity, 2, priv->pads);
++	if (ret) {
++		dev_err(dev, "Failed to init pads\n");
++		goto err_remove_ctrls;
++	}
++
++	priv->sd.fwnode = fwnode_graph_get_endpoint_by_id(
++				dev_fwnode(dev), MAX_GMSL2_SER_PAD_SRC,
++				0, 0);
++	if (!priv->sd.fwnode) {
++		ret = -ENODEV;
++		dev_err(dev, "Missing TX endpoint\n");
++		goto err_remove_ctrls;
++	}
++
++	ret = max_gmsl2_ser_v4l2_notifier_register(priv);
++	if (ret == -ENODEV) {
++		/* We can still do pattern generation */
++		dev_warn(dev, "v4l2 subdev no camera attached to the sink port\n");
++	} else if (ret) {
++		dev_err(dev, "v4l2 subdev notifier register failed\n");
++		goto err_fwnode_put;
++	}
++
++	ret = v4l2_async_register_subdev(&priv->sd);
++	if (ret) {
++		dev_err(dev, "v4l2_async_register_subdev error\n");
++		goto err_unreg_notif;
++	}
++
++	return 0;
++
++err_unreg_notif:
++	max_gmsl2_ser_notifier_unregister(priv);
++err_fwnode_put:
++	fwnode_handle_put(priv->sd.fwnode);
++err_remove_ctrls:
++	v4l2_ctrl_handler_free(&priv->ctrl_handler);
++
++	return ret;
++}
++
++static void max_gmsl2_ser_subdev_uninit(struct max_gmsl2_ser *priv)
++{
++	v4l2_async_unregister_subdev(&priv->sd);
++	max_gmsl2_ser_notifier_unregister(priv);
++	fwnode_handle_put(priv->sd.fwnode);
++	media_entity_cleanup(&priv->sd.entity);
++	v4l2_ctrl_handler_free(&priv->ctrl_handler);
++}
++
++static int max_gmsl2_ser_init_pipes(struct max_gmsl2_ser *priv)
++{
++	return max_gmsl2_init_pipes_stream_id(priv->pipes, priv->data->pipes,
++					     &priv->client->dev);
++}
++
++static int max_gmsl2_ser_init_csi_lanes(struct max_gmsl2_csi *csi)
++{
++	int ret;
++	struct v4l2_fwnode_endpoint v4l2_ep = {
++		.bus_type = V4L2_MBUS_CSI2_DPHY
++	};
++	struct v4l2_mbus_config_mipi_csi2 *mipi = &v4l2_ep.bus.mipi_csi2;
++	unsigned char nlanes, i;
++	u8 val = 0;
++
++	ret = v4l2_fwnode_endpoint_parse(csi->ep_fwnode, &v4l2_ep);
++	if (ret)
++		return -EINVAL;
++
++	nlanes = mipi->num_data_lanes;
++	if (nlanes < 1 || nlanes > GMSL_SER_MAX_CSI_LANES)
++		return -EINVAL;
++
++	ret = regmap_field_write(csi->lane_cnt, nlanes - 1);
++	if (ret)
++		return ret;
++
++	for (i = 0; i < nlanes + 1; i++) {
++		if (!mipi->lane_polarities[i])
++			continue;
++		if (i == 0)
++			val |= BIT(2);
++		else if (i < 3)
++			val |= BIT(i - 1);
++		else
++			val |= BIT(i);
++	}
++
++	ret = regmap_field_write(csi->lane_polarities[0], val);
++	if (ret)
++		return ret;
++	ret = regmap_field_write(csi->lane_polarities[1], val >> 3);
++	if (ret)
++		return ret;
++
++	val = 0;
++	for (i = 0; i < nlanes; i++)
++		val |= (mipi->data_lanes[i] - 1) << (i*2);
++
++	/* map unused lanes 1 to 1 */
++	for (; i < GMSL_SER_MAX_CSI_LANES; i++)
++		val |= i << (i*2);
++
++	ret = regmap_field_write(csi->lane_map[0], val);
++	if (ret)
++		return ret;
++	ret = regmap_field_write(csi->lane_map[1], val >> 4);
++	if (ret)
++		return ret;
++
++	return 0;
++}
++
++static int max_gmsl2_ser_init_csi(struct max_gmsl2_ser *priv)
++{
++	struct max_gmsl2_csi *csi = &priv->csi;
++	struct device *dev = &priv->client->dev;
++	int ret;
++
++	csi->priv = priv;
++	csi->ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
++							 0, 0, 0);
++
++	ret =  max_gmsl2_ser_init_csi_lanes(csi);
++	if (ret)
++		return ret;
++
++	/* FIXME: the plck output for the sensor should be parsed from
++	 * the dts.
++	 */
++	if (IS_MAX9295A(priv)) {
++		/* Enable pclk output on MFP4 */
++		ret = regmap_write(priv->regmap, 0x3f1, 0x89);
++		if (ret)
++			return ret;
++	}
++	return 0;
++}
++
++static int max_gmsl2_i2c_mux_select(struct i2c_mux_core *mux, u32 chan)
++{
++	return 0;
++}
++
++static int max_gmsl2_i2c_mux_init(struct max_gmsl2_ser *priv)
++{
++	priv->mux = i2c_mux_alloc(priv->client->adapter, &priv->client->dev,
++				  1, 0, I2C_MUX_LOCKED | I2C_MUX_GATE,
++				  max_gmsl2_i2c_mux_select, NULL);
++	if (!priv->mux)
++		return -ENOMEM;
++
++	return i2c_mux_add_adapter(priv->mux, 0, 0, 0);
++}
++
++/* MAX96717F */
++#define REG3 0x3
++#define RCLKSEL_REF_PLL 0x3
++#define REG6 0x6
++#define RCLKEN BIT(5)
++#define PIO_SLEW_1 0x570
++#define REF_VTG0 0x3f0
++#define REGGEN_PREDEF_EN BIT(6)
++#define REFGEN_PREDEF_FREQ_POS 4
++#define REFGEN_PREDEF_FREQ_27_24_MHZ (0x1 << REFGEN_PREDEF_FREQ_POS)
++#define REFGEN_PREDEF_FREQ_ALT BIT(3)
++#define REFGEN_RST BIT(1)
++#define REFGEN_EN BIT(0)
++
++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
++max_gmsl2_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
++{
++	struct max_gmsl2_ser *priv = clk_hw_to_max_gmsl2_ser(hw);
++	unsigned int i, val;
++	bool is_alt;
++	int ret;
++
++	ret = regmap_read(priv->regmap, REF_VTG0, &val);
++	if (ret)
++		return 0;
++
++	is_alt = !!(val & REFGEN_PREDEF_FREQ_ALT);
++	val = (val >> REFGEN_PREDEF_FREQ_POS) & 0x3;
++
++	for (i = 0; i < ARRAY_SIZE(max96717_predef_freqs); i++)
++		if (max96717_predef_freqs[i].val == val &&
++		    max96717_predef_freqs[i].is_alt == is_alt)
++			return max96717_predef_freqs[i].freq;
++
++	return 0;
++}
++
++static long max_gmsl2_clk_round_rate(struct clk_hw *hw, unsigned long rate,
++				     unsigned long *parent_rate)
++{
++	struct max_gmsl2_ser *priv = clk_hw_to_max_gmsl2_ser(hw);
++	struct device *dev = &priv->client->dev;
++	unsigned int i;
++
++	for (i = 0; i < ARRAY_SIZE(max96717_predef_freqs); i++)
++		if (rate <= max96717_predef_freqs[i].freq)
++			break;
++
++	if (rate != max96717_predef_freqs[i].freq)
++		dev_warn(dev, "Request CLK freq:%lu, found CLK freq:%lu\n",
++			 rate, max96717_predef_freqs[i].freq);
++
++	return max96717_predef_freqs[i].freq;
++}
++
++static int max_gmsl2_clk_set_rate(struct clk_hw *hw, unsigned long rate,
++				  unsigned long parent_rate)
++{
++	struct max_gmsl2_ser *priv = clk_hw_to_max_gmsl2_ser(hw);
++	unsigned int i;
++	int ret;
++	u8 val;
++
++	for (i = 0; i < ARRAY_SIZE(max96717_predef_freqs); i++)
++		if (rate <= max96717_predef_freqs[i].freq)
++			break;
++
++	val = REGGEN_PREDEF_EN |
++		( max96717_predef_freqs[i].val << REFGEN_PREDEF_FREQ_POS);
++
++	if (max96717_predef_freqs[i].is_alt)
++		val |= REFGEN_PREDEF_FREQ_ALT;
++
++	val |= REFGEN_RST;
++
++	ret = regmap_write(priv->regmap, REF_VTG0, val);
++	if (ret)
++		return ret;
++
++	ret = regmap_update_bits(priv->regmap, REF_VTG0, REFGEN_RST| REFGEN_EN, REFGEN_EN);
++	if (ret)
++		return ret;
++
++	return regmap_update_bits(priv->regmap, REG6, RCLKEN, RCLKEN);
++}
++
++static int max_gmsl2_clk_enable(struct clk_hw *hw)
++{
++	struct max_gmsl2_ser *priv = clk_hw_to_max_gmsl2_ser(hw);
++
++	return regmap_update_bits(priv->regmap, REG6, RCLKEN, RCLKEN);
++}
++
++static void max_gmsl2_clk_disable(struct clk_hw *hw)
++{
++	struct max_gmsl2_ser *priv = clk_hw_to_max_gmsl2_ser(hw);
++
++	regmap_update_bits(priv->regmap, REG6, RCLKEN, 0);
++}
++
++static const struct clk_ops max_gmsl2_clk_ops = {
++	.enable = max_gmsl2_clk_enable,
++	.disable = max_gmsl2_clk_disable,
++	.set_rate = max_gmsl2_clk_set_rate,
++	.recalc_rate = max_gmsl2_clk_recalc_rate,
++	.round_rate = max_gmsl2_clk_round_rate,
++};
++
++static int max_gmsl2_ser_init_clk_provider(struct max_gmsl2_ser *priv)
++{
++	struct device *dev = &priv->client->dev;
++	int ret;
++
++	const struct clk_init_data init = {
++		.name = kasprintf(GFP_KERNEL, "%s.%s.clk_out",
++				  gmsl2_id_to_name(priv->data->device_id),
++				  dev_name(dev)),
++		.ops = &max_gmsl2_clk_ops,
++	};
++
++	if (!init.name)
++		return -ENOMEM;
++
++	/* RCLKSEL Reference PLL output */
++	ret = regmap_update_bits(priv->regmap, REG3, 0x3, RCLKSEL_REF_PLL);
++	if (ret)
++		goto free_init_name;
++
++	/* MFP4 fastest slew rate */
++	ret = regmap_update_bits(priv->regmap, PIO_SLEW_1, BIT(5) | BIT(4), 0);
++	if (ret)
++		goto free_init_name;
++
++	priv->clk_hw.init = &init;
++
++	/* Initialize to 24 MHz */
++	ret = max_gmsl2_clk_set_rate(&priv->clk_hw, 24000000, 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 max_gmsl2_ser_probe(struct i2c_client *client)
++{
++	struct max_gmsl2_ser *priv;
++	unsigned int dev_id, dev_rev;
++	int ret;
++
++	priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL);
++	if (!priv)
++		return -ENOMEM;
++
++	priv->client = client;
++	priv->data = of_device_get_match_data(&client->dev);
++
++	ret = max_gmsl2_ser_init_regmap(priv);
++	if (ret)
++		return ret;
++
++	ret = regmap_read(priv->regmap, 0x0d, &dev_id);
++	if (ret) {
++		dev_err(&client->dev, "Fail to read device id");
++		return ret;
++	}
++
++	if ((u8)dev_id != priv->data->device_id) {
++		dev_err(&client->dev, "Wrong device id got 0x%02x expected 0x%02x",
++			dev_id, priv->data->device_id);
++		return -ENODEV;
++	}
++
++	ret = regmap_field_read(priv->dev_rev, &dev_rev);
++	if (ret)
++		return ret;
++
++	dev_info(&client->dev, "GMSL2 CSI Serializer device_id: 0x%02x rev: 0x%01x", dev_id, dev_rev);
++	i2c_set_clientdata(client, priv);
++
++	ret = max_gmsl2_gpiochip_probe(&priv->gpio, &client->dev, priv->regmap,
++				       priv->data->gpio_base,
++				       priv->data->ngpios);
++	if (ret) {
++		dev_err(&client->dev, "Failed to init gpiochip\n");
++		return ret;
++	}
++
++	ret = max_gmsl2_ser_init_csi(priv);
++	if (ret)
++		return ret;
++
++	ret = max_gmsl2_ser_init_pipes(priv);
++	if (ret)
++		return ret;
++
++	ret = max_gmsl2_i2c_mux_init(priv);
++	if (ret)
++		return ret;
++
++	if (device_property_present(&client->dev, "#clock-cells")) {
++		ret = max_gmsl2_ser_init_clk_provider(priv);
++		if (ret)
++			return ret;
++	}
++
++	ret = max_gmsl2_ser_subdev_init(priv);
++	if (ret)
++		return ret;
++
++	return 0;
++}
++
++static void max_gmsl2_ser_remove(struct i2c_client *client)
++{
++	struct max_gmsl2_ser *priv = i2c_get_clientdata(client);
++
++	max_gmsl2_ser_subdev_uninit(priv);
++};
++
++static const struct of_device_id max_gmsl2_ser_of_ids[] = {
++	{ .compatible = "maxim,max96717f", .data = &max96717f_data },
++	{ .compatible = "maxim,max9295a", .data = &max9295a_data },
++	{ }
++};
++MODULE_DEVICE_TABLE(of, max_gmsl2_ser_of_ids);
++
++static struct i2c_driver max_gmsl2_ser_i2c_driver = {
++	.driver	= {
++		.name		= "max_gmsl2_ser",
++		.of_match_table	= max_gmsl2_ser_of_ids,
++	},
++	.probe_new	= max_gmsl2_ser_probe,
++	.remove		= max_gmsl2_ser_remove,
++};
++
++module_i2c_driver(max_gmsl2_ser_i2c_driver);
++
++MODULE_DESCRIPTION("Maxim GMSL2 Serializer Driver");
++MODULE_AUTHOR("Julien Massot <julien.massot@collabora.com>");
++MODULE_LICENSE("GPL");
+diff --git a/drivers/media/i2c/max-gmsl2.c b/drivers/media/i2c/max-gmsl2.c
+new file mode 100644
+index 00000000..61db622
+--- /dev/null
++++ b/drivers/media/i2c/max-gmsl2.c
+@@ -0,0 +1,394 @@
++#include <linux/errno.h>
++#include <linux/module.h>
++#include <linux/seq_file.h>
++#include <media/v4l2-fwnode.h>
++
++#include "gmsl2-def.h"
++
++static int max_gmsl2_csi_lane_init(struct max_gmsl2_csi *csi,
++				   struct v4l2_mbus_config_mipi_csi2 *mipi)
++{
++	int i, ret;
++	u8 val = 0;
++
++	if (mipi->num_data_lanes < 1 || mipi->num_data_lanes > 4)
++		return -EINVAL;
++
++	ret = regmap_field_write(csi->lane_cnt, mipi->num_data_lanes - 1);
++	if (ret)
++		return ret;
++
++	/* lane polarities */
++	for (i = 0; i < mipi->num_data_lanes + 1; i++) {
++		if (!mipi->lane_polarities[i])
++			continue;
++		if (i == 0)
++			/* clock lane */
++			val |= BIT(5);
++		else if (i < 3)
++			/* Lane D0 and D1 */
++			val |= BIT(i - 1);
++		else
++			/* D2 and D3 */
++			val |= BIT(i);
++	}
++	ret = regmap_field_write(csi->lane_polarities[0], val);
++	if (ret)
++		return ret;
++	ret = regmap_field_write(csi->lane_polarities[1], val >> 3);
++	if (ret)
++		return ret;
++
++	/* lane mapping */
++	val = 0;
++	for (i = 0; i < mipi->num_data_lanes; i++)
++		val |= (mipi->data_lanes[i] - 1) << (i*2);
++
++	ret = regmap_field_write(csi->lane_map[0], val);
++	if (ret)
++		return ret;
++	ret = regmap_field_write(csi->lane_map[1], val >> 4);
++	if (ret)
++		return ret;
++
++	return 0;
++}
++
++/*
++ * DPHY clock frequency is half DPLL frequency.
++ */
++
++#define MHZ(v) ((u64)((v) * 1000000UL))
++int max_gmsl2_csi_get_link_freq(struct max_gmsl2_csi *csi, s64 *link_freq)
++{
++	u32 dpll_freq;
++	int ret;
++
++	if (!csi->dpll_freq)
++		return -EINVAL;
++
++	ret = regmap_field_read(csi->dpll_freq, &dpll_freq);
++	if (ret)
++		return ret;
++
++	*link_freq = dpll_freq * MHZ(100) / 2;
++
++	return 0;
++}
++EXPORT_SYMBOL_GPL(max_gmsl2_csi_get_link_freq);
++
++static int max_gmsl2_csi_set_frequency(struct max_gmsl2_csi *csi,
++				       struct v4l2_fwnode_endpoint *v4l2_ep)
++{
++	int ret;
++	u64 freq = MHZ(400);
++	u32 dpll_freq;
++
++	if (v4l2_ep->nr_of_link_frequencies == 1) {
++		freq = *v4l2_ep->link_frequencies;
++		if (freq % MHZ(100/2) || freq < MHZ(200/2) || freq > MHZ(5700/2)) {
++			return -EINVAL;
++		}
++	}
++
++	dpll_freq = freq * 2 / MHZ(100);
++
++	ret = regmap_field_write(csi->dpll_freq, dpll_freq);
++	if (ret)
++		return ret;
++
++	return 0;
++}
++
++int max_gmsl2_csi_phy_init(struct max_gmsl2_csi *csi)
++{
++	int ret;
++	struct v4l2_fwnode_endpoint v4l2_ep = {
++		.bus_type = V4L2_MBUS_CSI2_DPHY
++	};
++
++	ret = v4l2_fwnode_endpoint_parse(csi->ep_fwnode, &v4l2_ep);
++	if (ret)
++		return -EINVAL;
++
++	if (csi->dpll_freq) {
++		ret = max_gmsl2_csi_set_frequency(csi, &v4l2_ep);
++		if (ret)
++			return ret;
++	}
++
++	return max_gmsl2_csi_lane_init(csi, &v4l2_ep.bus.mipi_csi2);
++}
++EXPORT_SYMBOL_GPL(max_gmsl2_csi_phy_init);
++
++int max_gmsl2_init_pipes_stream_id(struct max_gmsl2_pipe *pipes, u8 npipes,
++				   struct device *dev)
++{
++	u8 stream_id[GMSL2_MAX_PIPE];
++	int n_stream_id, i, ret;
++
++	if (npipes > GMSL2_MAX_PIPE)
++		return -EINVAL;
++
++	if (!device_property_present(dev, "maxim,pipe-stream-id"))
++		return 0;
++
++	n_stream_id = device_property_count_u8(dev, "maxim,pipe-stream-id");
++	if (n_stream_id > npipes)
++		return -EOVERFLOW;
++
++	ret = device_property_read_u8_array(dev, "maxim,pipe-stream-id",
++					    stream_id,
++					    n_stream_id);
++	if (ret)
++		return ret;
++
++	for (i = 0; i < n_stream_id; i++, pipes++) {
++		if (stream_id[i] > 3) {
++			dev_err(dev, "invalid stream id: %d for pipe %d",
++				stream_id[i], i);
++			return -EINVAL;
++		}
++		ret = regmap_field_write(pipes->stream_id, stream_id[i]);
++		if (ret)
++			return ret;
++	}
++
++	return 0;
++}
++EXPORT_SYMBOL_GPL(max_gmsl2_init_pipes_stream_id);
++
++void max_gmsl2_pipe_init_regfield(struct device *dev,
++				  struct regmap *regmap,
++				  struct max_gmsl2_pipe *pipe,
++				  const struct max_gmsl2_data *data,
++				  u8 id)
++{
++	const struct reg_field *field;
++
++	field = &data->pipe_enable[id];
++	pipe->enable = devm_regmap_field_alloc(dev, regmap,
++					       *field);
++	field = &data->pipe_stream_id[id];
++	pipe->stream_id = devm_regmap_field_alloc(dev, regmap,
++						  *field);
++	field = &data->pipe_link_sel[id];
++	if (field->reg)
++		pipe->link_sel = devm_regmap_field_alloc(dev, regmap,
++							 *field);
++	field = &data->pipe_vid_lock[id];
++	if (field->reg)
++		pipe->vid_lock = devm_regmap_field_alloc(dev, regmap,
++							 *field);
++
++	field = &data->pipe_override_bpp_vc_dt[id];
++	if (field->reg)
++		pipe->override_bpp_vc_dt =
++			devm_regmap_field_alloc(dev, regmap, *field);
++
++	field = &data->pipe_bpp_l[id];
++	if (field->reg)
++		pipe->bpp_l = devm_regmap_field_alloc(dev, regmap,
++						      *field);
++
++	field = &data->pipe_bpp_h[id];
++	if (field->reg)
++		pipe->bpp_h = devm_regmap_field_alloc(dev, regmap,
++						      *field);
++
++	field = &data->pipe_dt_l[id];
++	if (field->reg)
++		pipe->dt_l = devm_regmap_field_alloc(dev, regmap,
++						     *field);
++
++	field = &data->pipe_dt_h[id];
++	if (field->reg)
++		pipe->dt_h = devm_regmap_field_alloc(dev, regmap,
++						     *field);
++
++	field = &data->pipe_dt_l[id];
++	if (field->reg)
++		pipe->dt_l = devm_regmap_field_alloc(dev, regmap,
++						     *field);
++
++	field = &data->pipe_vc[id];
++	if (field->reg)
++		pipe->vc = devm_regmap_field_alloc(dev, regmap, *field);
++
++	field = &data->pipe_dt_sel[id*2];
++	if (field->reg)
++		pipe->dt_sel[0] = devm_regmap_field_alloc(dev, regmap, *field);
++
++	field = &data->pipe_dt_sel[id * 2 + 1];
++	if (field->reg)
++		pipe->dt_sel[1] = devm_regmap_field_alloc(dev, regmap, *field);
++
++}
++EXPORT_SYMBOL_GPL(max_gmsl2_pipe_init_regfield);
++
++void max_gmsl2_csi_init_regfield(struct device *dev,
++				 struct regmap *regmap,
++				 struct max_gmsl2_csi *csi,
++				 const struct max_gmsl2_data *data, u8 id)
++{
++	const struct reg_field *field;
++
++	field = &data->csi_phy_enable[id];
++	if (field->reg)
++		csi->phy_enable = devm_regmap_field_alloc(dev, regmap, *field);
++
++	/* We only suport 2x4 mode, in this case the clk is provided
++	 * by the master phy, phy1 for CSI port 0 and phy2 for CSI port 1.
++	 */
++	field = &data->csi_dpll_freq[id == 0 ? 1 : 2];
++	if (field->reg)
++		csi->dpll_freq = devm_regmap_field_alloc(dev, regmap, *field);
++
++	field = &data->csi_lane_polarities[id * 2];
++	csi->lane_polarities[0] = devm_regmap_field_alloc(dev, regmap, *field);
++
++	field = &data->csi_lane_polarities[id * 2 + 1];
++	csi->lane_polarities[1] = devm_regmap_field_alloc(dev, regmap, *field);
++
++	field = &data->csi_lane_map[id * 2];
++	csi->lane_map[0] = devm_regmap_field_alloc(dev, regmap, *field);
++
++	field = &data->csi_lane_map[id * 2 + 1];
++	csi->lane_map[1] = devm_regmap_field_alloc(dev, regmap, *field);
++
++	field = &data->csi_lane_cnt[id];
++	csi->lane_cnt = devm_regmap_field_alloc(dev, regmap, *field);
++}
++EXPORT_SYMBOL_GPL(max_gmsl2_csi_init_regfield);
++
++void max_gmsl2_link_init_regfield(struct device *dev,
++				 struct regmap *regmap,
++				 struct max_gmsl2_link *link,
++				 const struct max_gmsl2_data *data, u8 id)
++{
++	const struct reg_field *field;
++
++	field = &data->link_lock[id];
++	if (field->reg)
++		link->locked =
++			devm_regmap_field_alloc(dev, regmap, *field);
++	field = &data->reset_one_shot_link[id];
++	if (field->reg)
++		link->reset_one_shot =
++			devm_regmap_field_alloc(dev, regmap, *field);
++	field = &data->reset_link[id];
++	if (field->reg)
++		link->reset =
++			devm_regmap_field_alloc(dev, regmap, *field);
++}
++EXPORT_SYMBOL_GPL(max_gmsl2_link_init_regfield);
++/* Some registers such as bpp and dt maybe split on two regmap_field */
++int max_gmsl2_write_h_l_val(struct regmap_field *h, struct regmap_field *l,
++			    const struct reg_field *l_field, unsigned int val)
++{
++	u8 shift;
++	int ret;
++
++	/* if h ptr is not null then the register is splitted */
++	if (h) {
++		shift = l_field->msb - l_field->lsb + 1;
++		ret = regmap_field_write(h, val >> shift);
++		if (ret)
++			return ret;
++	}
++
++	/* regmap_field_write will take care of applying a mask on val
++	 * to not write the h bits.
++	 */
++	return regmap_field_write(l, val);
++}
++EXPORT_SYMBOL_GPL(max_gmsl2_write_h_l_val);
++
++int max_gmsl2_read_h_l_val(struct regmap_field *h, struct regmap_field *l,
++			   const struct reg_field *l_field, unsigned int *val)
++{
++	u8 shift;
++	unsigned int h_val;
++	int ret;
++
++	ret = regmap_field_read(l, val);
++	if (ret)
++		return ret;
++	if (h) {
++		ret = regmap_field_read(h, &h_val);
++		if (ret)
++			return ret;
++		shift = l_field->msb - l_field->lsb + 1;
++		*val |= (h_val << shift);
++	}
++
++	return 0;
++}
++EXPORT_SYMBOL_GPL(max_gmsl2_read_h_l_val);
++
++void max_gmsl2_state_show_pipe(struct device *dev, struct max_gmsl2_pipe *pipe,
++			       const struct max_gmsl2_data *data, u8 pipeid)
++{
++	unsigned int enable, stream_id, link_sel, vid_lock, bpp, dt, vc;
++	int ret, i;
++
++	ret = regmap_field_read(pipe->enable, &enable);
++	if (ret)
++		return;
++	dev_info(dev, "pipe_%u: enable:%u", pipeid, enable);
++
++	if (pipe->link_sel) {
++		ret = regmap_field_read(pipe->link_sel, &link_sel);
++		if (ret)
++			return;
++		dev_info(dev, " link_sel:%c", 'A' + link_sel);
++	}
++
++	ret = regmap_field_read(pipe->stream_id, &stream_id);
++	if (ret)
++		return;
++	dev_info(dev, " stream_id:%u", stream_id);
++
++	ret = regmap_field_read(pipe->vid_lock, &vid_lock);
++	if (ret)
++		return;
++	dev_info(dev," vid_lock:%u", vid_lock);
++
++	if (pipe->override_bpp_vc_dt) {
++		ret = max_gmsl2_read_h_l_val(pipe->bpp_h, pipe->bpp_l,
++					     &data->pipe_bpp_l[pipeid],
++					     &bpp);
++		if (ret)
++			return;
++		ret = max_gmsl2_read_h_l_val(pipe->dt_h, pipe->dt_l,
++					     &data->pipe_dt_l[pipeid],
++					     &dt);
++		if (ret)
++			return;
++		ret = regmap_field_read(pipe->vc, &vc);
++		if (ret)
++			return;
++		dev_info(dev, " bpp:%u, dt:0x%2x, vc:%u",
++			   bpp, dt, vc);
++	}
++
++	if (pipe->dt_sel[0] && pipe->dt_sel[1]) {
++		for (i = 0; i < 2; i++) {
++			ret = regmap_field_read(pipe->dt_sel[i], &dt);
++			if (ret)
++				return;
++			//Enabled
++			if (dt & BIT(6))
++				dev_info(dev," dt%d_sel:%x", i, dt & 0x3F);
++			else
++				dev_info(dev," dt%d_sel:disabled", i);
++		}
++	}
++
++	dev_info(dev, "\n");
++}
++EXPORT_SYMBOL_GPL(max_gmsl2_state_show_pipe);
++
++MODULE_DESCRIPTION("Maxim GMSL2 Lib");
++MODULE_AUTHOR("Julien Massot <julien.massot@collabora.com>");
++MODULE_LICENSE("GPL");
+diff --git a/include/linux/gpio/max-gmsl2-gpio.h b/include/linux/gpio/max-gmsl2-gpio.h
+new file mode 100644
+index 00000000..363cb68
+--- /dev/null
++++ b/include/linux/gpio/max-gmsl2-gpio.h
+@@ -0,0 +1,16 @@
++// SPDX-License-Identifier: GPL-2.0
++/*
++ * Maxim GMSL2 GPIO Driver
++ *
++ * Copyright (C) 2023 Collabora Ltd.
++ */
++#ifndef __LINUX_GPIO_MAX_GMSL2_GPIO_H
++#define __LINUX_GPIO_MAX_GMSL2_GPIO_H
++
++#include <linux/regmap.h>
++#include <linux/gpio/driver.h>
++
++int max_gmsl2_gpiochip_probe(struct gpio_chip *gpio, struct device *dev,
++			     struct regmap *regmap, u16 base, u8 ngpios);
++
++#endif /* __LINUX_GPIO_MAX_GMSL2_GPIO_H */
+diff --git a/include/media/gmsl2/max-gmsl2-patgen.h b/include/media/gmsl2/max-gmsl2-patgen.h
+new file mode 100644
+index 00000000..3f675d9
+--- /dev/null
++++ b/include/media/gmsl2/max-gmsl2-patgen.h
+@@ -0,0 +1,45 @@
++// SPDX-License-Identifier: GPL-2.0
++/*
++ * Maxim GMSL2 Pattern Generator
++ *
++ * Copyright (C) 2023 Collabora Ltd.
++ */
++
++#ifndef __LINUX_MEDIA_MAX_GMSL2_PATGEN_H
++#define __LINUX_MEDIA_MAX_GMSL2_PATGEN_H
++
++#include <linux/regmap.h>
++#include <linux/types.h>
++#include <linux/videodev2.h>
++
++enum max_gmsl2_vpg_mode
++{
++	MAX_GMSL2_VPG_DISABLED = 0,
++	MAX_GMSL2_VPG_CHECKERBOARD = 1,
++	MAX_GMSL2_VPG_GRADIENT = 2,
++};
++
++static const char * const max_gmsl2_test_pattern[] = {
++	"Disabled",
++	"Checkerboard",
++	"Gradient"
++};
++
++struct max_gmsl2_patgen {
++	struct regmap_field *gen_vs_hs_de;
++	struct regmap_field *inv_vs_hs_de;
++	struct regmap_field *vtg_mode;
++	struct regmap_field *vpg_mode;
++	u16 vtg_timing;
++	u16 vpg_patterns;
++	struct regmap_field *pattern_clk_freq;
 +};
 +
-+int max_gmsl2_vtg_enable(struct regmap *regmap, uint16_t base_addr,
++int max_gmsl2_vtg_enable(struct max_gmsl2_patgen *patgen,
++			 struct regmap *regmap,
 +			 const struct v4l2_bt_timings *t);
-+int max_gmsl2_vtg_disable(struct regmap *regmap, uint16_t base_addr);
-+int max_gmsl2_vpg_enable(struct regmap *regmap, uint16_t base_addr,
++int max_gmsl2_vtg_disable(struct max_gmsl2_patgen *patgen, struct regmap *regmap);
++int max_gmsl2_vpg_enable(struct max_gmsl2_patgen *patgen, struct regmap *regmap,
 +			 enum max_gmsl2_vpg_mode mode);
-+int max_gmsl2_vpg_disable(struct regmap *regmap, uint16_t base_addr);
++int max_gmsl2_vpg_disable(struct max_gmsl2_patgen *patgen);
++#endif /* __LINUX_MEDIA_MAX_GMSL2_PATGEN_H */
diff --git a/debian/patches/apertis/maxim-gmsl2/0151-max-gmsl2-deser-report-link-frequency.patch b/debian/patches/apertis/maxim-gmsl2/0151-max-gmsl2-deser-report-link-frequency.patch
deleted file mode 100644
index a7d9907553a..00000000000
--- a/debian/patches/apertis/maxim-gmsl2/0151-max-gmsl2-deser-report-link-frequency.patch
+++ /dev/null
@@ -1,113 +0,0 @@
-From: Julien Massot <julien.massot@collabora.com>
-Date: Thu, 23 Mar 2023 12:05:47 +0100
-Subject: max-gmsl2-deser: report link frequency
-
-Report the link frequency by implementing the V4L2_CID_LINK_FREQ
-control.
----
- drivers/media/i2c/max-gmsl2-deser.c | 40 +++++++++++++++++--------------------
- 1 file changed, 18 insertions(+), 22 deletions(-)
-
-diff --git a/drivers/media/i2c/max-gmsl2-deser.c b/drivers/media/i2c/max-gmsl2-deser.c
-index 8a30110..79a4211 100644
---- a/drivers/media/i2c/max-gmsl2-deser.c
-+++ b/drivers/media/i2c/max-gmsl2-deser.c
-@@ -98,7 +98,7 @@ struct max_gmsl2_deser_csi {
- 	struct v4l2_subdev_format format;
- 	struct max_gmsl2_deser_priv *priv;
- 	struct regmap_field *phy_enable;
--	struct regmap_field *phy_out_freq[2];
-+	struct regmap_field *phy_out_freq;
- 	struct regmap_field *lane_polarities[2];
- 	struct regmap_field *lane_map[2];
- 	u8 id;
-@@ -130,6 +130,7 @@ struct max_gmsl2_deser_priv {
- 	struct regmap_field *gmsl1_link_locked[GMSL2_MAX_LINK];
- 	struct regmap_field *gmsl1_forward_cc[GMSL2_MAX_LINK];
- 	struct dentry *dbg_dir;
-+	s64 tx_link_freq[1];
- };
- #define GMSL2_INVALID_REG_FIELD REG_FIELD(0, 0, 0)
- 
-@@ -485,13 +486,9 @@ static int max_gmsl2_deser_init_regmap(struct max_gmsl2_deser_priv *priv)
- 		field = &data->csi_phy_enable[i];
- 		csi->phy_enable = devm_regmap_field_alloc(dev, priv->regmap, *field);
- 
--		field = &data->csi_phy_out_freq[i*2];
-+		field = &data->csi_phy_out_freq[i == 0 ? 1 : 2];
- 		if (field->reg)
--			csi->phy_out_freq[0] = devm_regmap_field_alloc(dev, priv->regmap, *field);
--
--		field = &data->csi_phy_out_freq[i * 2 + 1];
--		if (field->reg)
--			csi->phy_out_freq[1] = devm_regmap_field_alloc(dev, priv->regmap, *field);
-+			csi->phy_out_freq = devm_regmap_field_alloc(dev, priv->regmap, *field);
- 
- 		field = &data->csi_lane_polarities[i * 2];
- 		csi->lane_polarities[0] = devm_regmap_field_alloc(dev, priv->regmap, *field);
-@@ -679,7 +676,7 @@ static int max_gmsl2_deser_state_show(struct seq_file *seq, void *p)
- 	struct max_gmsl2_deser_priv *priv = seq->private;
- 	struct max_gmsl2_deser_pipe *pipe;
- 	struct max_gmsl2_deser_csi *csi;
--	unsigned int link_locked, enable, stream_id, link_sel, vid_lock, bpp, dt, vc, freq1, freq2, dphy_en, link_mode;
-+	unsigned int link_locked, enable, stream_id, link_sel, vid_lock, bpp, dt, vc, freq, dphy_en, link_mode;
- 	u8 link, pipeid, csiid;
- 	int ret;
- 
-@@ -752,18 +749,16 @@ static int max_gmsl2_deser_state_show(struct seq_file *seq, void *p)
- 
- 	for_each_csi(csiid, priv) {
- 		csi = &priv->csi[csiid];
--		ret = regmap_field_read(csi->phy_out_freq[0], &freq1);
--		if (ret)
--			break;
--		ret = regmap_field_read(csi->phy_out_freq[1], &freq2);
-+		ret = regmap_field_read(csi->phy_out_freq, &freq);
- 		if (ret)
- 			break;
- 		ret = regmap_field_read(csi->phy_enable, &dphy_en);
- 		if (ret)
- 			break;
--		seq_printf(seq, "CSI%d: freq DPHY[%d]:%dMHz enabled:%u DPHY[%d]:%dMHz enabled:%u\n", csiid,
--			   csiid * 2 , freq1 * 100, (dphy_en & BIT(0)) == BIT(0),
--			   csiid * 2 + 1, freq2 * 100, (dphy_en & BIT(1)) == BIT(1));
-+		seq_printf(seq, "CSI%d: freq:%dMHz DPHY[%d] enabled:%u DPHY[%d] enabled:%u\n", csiid,
-+			   freq * 100,
-+			   csiid * 2 , (dphy_en & BIT(0)) == BIT(0),
-+			   csiid * 2 + 1, (dphy_en & BIT(1)) == BIT(1));
- 	}
- 
- 	return ret;
-@@ -953,6 +948,11 @@ static int max_gmsl2_deser_csi_init_controls(struct max_gmsl2_deser_csi *csi)
- 					   &max_gmsl2_deser_csi_ctrl_ops,
- 					   V4L2_CID_PIXEL_RATE, 1, INT_MAX,
- 					   1, 1);
-+
-+	v4l2_ctrl_new_int_menu(&csi->ctrl_hdl, NULL, V4L2_CID_LINK_FREQ,
-+			       ARRAY_SIZE(csi->priv->tx_link_freq) - 1, 0,
-+			       csi->priv->tx_link_freq);
-+
- 	if (csi->priv->data->patgen_base_addr[0])
- 		v4l2_ctrl_new_std_menu_items(&csi->ctrl_hdl,
- 					     &max_gmsl2_deser_csi_ctrl_ops,
-@@ -1172,17 +1172,13 @@ static int max_gmsl2_deser_csi_init(struct max_gmsl2_deser_priv *priv, u8 csi_id
- 	/* FIXME: take value from dt, written value in multiple of 100MHz
- 	 * Only the master DPhy might need to be set.
- 	 */
--	if (csi->phy_out_freq[0]) {
--		ret = regmap_field_write(csi->phy_out_freq[0], 8);
-+	if (csi->phy_out_freq) {
-+		ret = regmap_field_write(csi->phy_out_freq, 8);
- 		if (ret)
- 			return ret;
- 	}
-+	priv->tx_link_freq[0] = 8 * 100000000;
- 
--	if (csi->phy_out_freq[1]) {
--		ret = regmap_field_write(csi->phy_out_freq[1], 8);
--		if (ret)
--			return ret;
--	}
- 	csi->format.format.width = 1280;
- 	csi->format.format.height = 720;
- 	csi->format.format.code = MEDIA_BUS_FMT_RGB888_1X24;
diff --git a/debian/patches/apertis/maxim-gmsl2/0152-max-gmsl2-deser-do-not-try-to-link-non-existing-pipe.patch b/debian/patches/apertis/maxim-gmsl2/0152-max-gmsl2-deser-do-not-try-to-link-non-existing-pipe.patch
deleted file mode 100644
index 68d17548aab..00000000000
--- a/debian/patches/apertis/maxim-gmsl2/0152-max-gmsl2-deser-do-not-try-to-link-non-existing-pipe.patch
+++ /dev/null
@@ -1,23 +0,0 @@
-From: Julien Massot <julien.massot@collabora.com>
-Date: Thu, 23 Mar 2023 12:06:39 +0100
-Subject: max-gmsl2-deser: do not try to link non existing pipe
-
----
- drivers/media/i2c/max-gmsl2-deser.c | 4 +++-
- 1 file changed, 3 insertions(+), 1 deletion(-)
-
-diff --git a/drivers/media/i2c/max-gmsl2-deser.c b/drivers/media/i2c/max-gmsl2-deser.c
-index 79a4211..fe3f724 100644
---- a/drivers/media/i2c/max-gmsl2-deser.c
-+++ b/drivers/media/i2c/max-gmsl2-deser.c
-@@ -980,7 +980,9 @@ static int max_gmsl2_deser_csi_registered(struct v4l2_subdev *sd)
- 
- 	dev_info(dev, "Registered CSI%u %s", csi->id, sd->name);
- 
--	if (csi->id == 0)
-+	if (priv->data->pipes == 1)
-+		pipe = &priv->pipes[0];
-+	else if (csi->id == 0)
- 		/* Pipe Y goes to CSI0 */
- 		pipe = &priv->pipes[1];
- 	else
diff --git a/debian/patches/apertis/maxim-gmsl2/0153-drivers-media-max-gmsl2-deser-fix-pattern-generator.patch b/debian/patches/apertis/maxim-gmsl2/0153-drivers-media-max-gmsl2-deser-fix-pattern-generator.patch
deleted file mode 100644
index e21152b09ff..00000000000
--- a/debian/patches/apertis/maxim-gmsl2/0153-drivers-media-max-gmsl2-deser-fix-pattern-generator.patch
+++ /dev/null
@@ -1,169 +0,0 @@
-From: Julien Massot <julien.massot@collabora.com>
-Date: Thu, 20 Apr 2023 14:22:53 +0200
-Subject: drivers: media: max-gmsl2-deser: fix pattern generator
-
-The pattern generator needs to force the csi clock output, and
-also requires that the deserializer is in pixel mode.
-
-Save the intial tunnel mode on device startup and switch the
-device to pixel mode when pattern generator is enabled.
-When pattern generator is disabled we restore the initial
-settings.
-
-Also fix the link frequency reporting which is half the Bps
-(two bytes per clock period).
----
- drivers/media/i2c/max-gmsl2-deser.c | 61 +++++++++++++++++++++++++++++++++++--
- 1 file changed, 58 insertions(+), 3 deletions(-)
-
-diff --git a/drivers/media/i2c/max-gmsl2-deser.c b/drivers/media/i2c/max-gmsl2-deser.c
-index fe3f724..36ef82a 100644
---- a/drivers/media/i2c/max-gmsl2-deser.c
-+++ b/drivers/media/i2c/max-gmsl2-deser.c
-@@ -39,6 +39,7 @@ struct max_gmsl2_deser_data {
- 	u8 csi;
- 	struct reg_field dev_rev;
- 	struct reg_field csi_enable;
-+	struct reg_field csi_out_force_en;
- 	struct reg_field csi_wait_frame;
- 	struct reg_field csi_phy_enable[GMSL2_MAX_CSI];
- 	/* Each CSI 4 lanes use 2 phys for lane polarities and lane map
-@@ -67,6 +68,7 @@ struct max_gmsl2_deser_data {
- 	struct reg_field reset_link[GMSL2_MAX_LINK];
- 	/* GMSL1 backward compatibility */
- 	const struct max_gmsl2_compat_gmsl1 *gmsl1;
-+	struct reg_field tunnel_mode;
- };
- 
- /* VC[7:6] DT[5:0] */
-@@ -119,6 +121,8 @@ struct max_gmsl2_deser_priv {
- 	struct regmap_field *dev_rev;
- 	u8 csi_use_cnt;
- 	struct regmap_field *csi_enable;
-+	/* In case of pattern generation we might have to force the csi output*/
-+	struct regmap_field *csi_out_force_en;
- 	/* wait a new frame before generating a MIPI packet */
- 	struct regmap_field *csi_wait_frame;
- 	struct regmap_field *pattern_clk_freq;
-@@ -129,6 +133,8 @@ struct max_gmsl2_deser_priv {
- 	struct regmap_field *gmsl1_highimm[GMSL2_MAX_LINK];
- 	struct regmap_field *gmsl1_link_locked[GMSL2_MAX_LINK];
- 	struct regmap_field *gmsl1_forward_cc[GMSL2_MAX_LINK];
-+	struct regmap_field *tunnel_mode;
-+	bool tunnel_mode_saved;
- 	struct dentry *dbg_dir;
- 	s64 tx_link_freq[1];
- };
-@@ -209,6 +215,7 @@ const struct max_gmsl2_deser_data max96714_data = {
- 	.dev_rev = REG_FIELD(0xe, 0, 3),
- 	.csi_enable = REG_FIELD(0x313, 1, 1),
- 	.csi_wait_frame = REG_FIELD(0x325, 7, 7),
-+	.csi_out_force_en = REG_FIELD(0x330, 7, 7),
- 	.csi_phy_enable = { REG_FIELD(0x332, 4, 5) },
- 	.csi_phy_out_freq = { GMSL2_INVALID_REG_FIELD, REG_FIELD(0x320, 0, 4), },
- 	.csi_lane_polarities = { REG_FIELD(0x335, 0, 2), REG_FIELD(0x335, 3, 5) },
-@@ -232,6 +239,7 @@ const struct max_gmsl2_deser_data max96714_data = {
- 	.reset_link = { REG_FIELD(0x10, 6, 6) },
- 	/* This device is backward compatible with GMSL1 */
- 	.gmsl1 = &max96714_gmsl1_data,
-+	.tunnel_mode = REG_FIELD(0x474, 0, 0),
- 
- };
- #define IS_MAX96714(priv) (priv->data->device_id == MAX96714_DEVICE_ID)
-@@ -511,6 +519,16 @@ static int max_gmsl2_deser_init_regmap(struct max_gmsl2_deser_priv *priv)
- 		priv->csi_enable =
- 			devm_regmap_field_alloc(dev, priv->regmap, *field);
- 
-+	field = &data->csi_out_force_en;
-+	if (field->reg)
-+		priv->csi_out_force_en =
-+			devm_regmap_field_alloc(dev, priv->regmap, *field);
-+
-+	field = &data->tunnel_mode;
-+	if (field->reg)
-+		priv->tunnel_mode =
-+			devm_regmap_field_alloc(dev, priv->regmap, *field);
-+
- 	field = &data->csi_wait_frame;
- 	if (field->reg)
- 		priv->csi_wait_frame =
-@@ -817,6 +835,22 @@ static int max_gmsl2_deser_csi_s_ctrl_pattern(struct max_gmsl2_deser_csi *csi, i
- 		ret = pipes_clear_remap(priv);
- 		if (ret)
- 			return ret;
-+
-+		/* Tunnel mode should be disabled for pattern generator */
-+		if (priv->tunnel_mode) {
-+			ret = regmap_field_write(priv->tunnel_mode, 0);
-+			if (ret)
-+				return ret;
-+		}
-+
-+		if (priv->csi_out_force_en) {
-+			/* Force CSI output clock for use in MIPI
-+			 * loopback test or pattern generator use.*/
-+			ret = regmap_field_write(priv->csi_out_force_en, 1);
-+			if (ret)
-+				return ret;
-+		}
-+
- 	} else {
- 		/* Restore CSI to Pipe mapping */
- 		ret = pipes_clear_remap(priv);
-@@ -830,9 +864,19 @@ static int max_gmsl2_deser_csi_s_ctrl_pattern(struct max_gmsl2_deser_csi *csi, i
- 		ret = max_gmsl2_vtg_disable(priv->regmap, patgen_base);
- 		if (ret)
- 			return ret;
-+		/* Restore tunnel mode */
-+		if (priv->tunnel_mode && priv->tunnel_mode_saved) {
-+			ret = regmap_field_write(priv->tunnel_mode, 1);
-+			if (ret)
-+				return ret;
-+		}
-+		if (priv->csi_out_force_en) {
-+			ret = regmap_field_write(priv->csi_out_force_en, 0);
-+			if (ret)
-+				return ret;
-+		}
- 	}
- 
--
- 	return 0;
- }
- 
-@@ -1179,7 +1223,8 @@ static int max_gmsl2_deser_csi_init(struct max_gmsl2_deser_priv *priv, u8 csi_id
- 		if (ret)
- 			return ret;
- 	}
--	priv->tx_link_freq[0] = 8 * 100000000;
-+	/* The link frequency is half the Bps and DPLL freq. */
-+	priv->tx_link_freq[0] = 4 * 100000000;
- 
- 	csi->format.format.width = 1280;
- 	csi->format.format.height = 720;
-@@ -1498,7 +1543,7 @@ static int max_gmsl2_init_links(struct max_gmsl2_deser_priv *priv)
- static int max_gmsl2_deser_probe(struct i2c_client *client)
- {
- 	struct max_gmsl2_deser_priv *priv;
--	unsigned int dev_id, dev_rev;
-+	unsigned int dev_id, dev_rev, val;
- 	int i;
- 	int ret;
- 
-@@ -1548,6 +1593,16 @@ static int max_gmsl2_deser_probe(struct i2c_client *client)
- 
- 	dev_info(&client->dev, "GMSL2 deserializer device_id: 0x%02x rev: 0x%01x", dev_id, dev_rev);
- 	i2c_set_clientdata(client, priv);
-+
-+	/* Save tunnel mode we might have to disable it later for pattern generator */
-+	if (priv->tunnel_mode) {
-+		ret = regmap_field_read(priv->tunnel_mode, &val);
-+		if (ret)
-+			return ret;
-+
-+		priv->tunnel_mode_saved = !!val;
-+	}
-+
- 	ret = max_gmsl2_init_links(priv);
- 	if (ret)
- 		return ret;
diff --git a/debian/patches/apertis/maxim-gmsl2/0154-max-gmsl2-deser-change-max96714-compatible-to-max967.patch b/debian/patches/apertis/maxim-gmsl2/0154-max-gmsl2-deser-change-max96714-compatible-to-max967.patch
deleted file mode 100644
index 1f72008fd50..00000000000
--- a/debian/patches/apertis/maxim-gmsl2/0154-max-gmsl2-deser-change-max96714-compatible-to-max967.patch
+++ /dev/null
@@ -1,64 +0,0 @@
-From 9be6f8a20ec2092ae1c5cb13a8ba00ad6da1a1bb Mon Sep 17 00:00:00 2001
-From: Julien Massot <julien.massot@collabora.com>
-Date: Thu, 20 Apr 2023 22:15:29 +0200
-Subject: [PATCH 2/2] max-gmsl2-deser: change max96714 compatible to max96714f
-
-The device used for testing is a max96714f and not a max96714,
-the only change in the registers is the device id: 0xca instead
-of 0xc9.
-
-We may need to support several device id for the same registers
-definition in the future.
----
- drivers/media/i2c/max-gmsl2-deser.c | 14 +++++++-------
- 1 file changed, 7 insertions(+), 7 deletions(-)
-
-diff --git a/drivers/media/i2c/max-gmsl2-deser.c b/drivers/media/i2c/max-gmsl2-deser.c
-index 36ef82a493..3df540e7ec 100644
---- a/drivers/media/i2c/max-gmsl2-deser.c
-+++ b/drivers/media/i2c/max-gmsl2-deser.c
-@@ -206,9 +206,9 @@ const struct max_gmsl2_compat_gmsl1 max96714_gmsl1_data = {
- 	.link_lock = { REG_FIELD(0xbcb, 0, 0) },
- };
- 
--#define MAX96714_DEVICE_ID 0xc9
--const struct max_gmsl2_deser_data max96714_data = {
--	.device_id = MAX96714_DEVICE_ID,
-+#define MAX96714F_DEVICE_ID 0xca
-+const struct max_gmsl2_deser_data max96714f_data = {
-+	.device_id = MAX96714F_DEVICE_ID,
- 	.pipes = 1,
- 	.links = 1,
- 	.csi = 1,
-@@ -242,7 +242,7 @@ const struct max_gmsl2_deser_data max96714_data = {
- 	.tunnel_mode = REG_FIELD(0x474, 0, 0),
- 
- };
--#define IS_MAX96714(priv) (priv->data->device_id == MAX96714_DEVICE_ID)
-+#define IS_MAX96714F(priv) (priv->data->device_id == MAX96714F_DEVICE_ID)
- 
- 
- #define MAX96914_DEVICE_ID 0xa0
-@@ -429,8 +429,8 @@ static const char* device_id_to_name(u8 device_id)
- 		return "max96934";
- 	case MAX96914_DEVICE_ID:
- 		return "max96914";
--	case MAX96714_DEVICE_ID:
--		return "max96714";
-+	case MAX96714F_DEVICE_ID:
-+		return "max96714f";
- 	default:
- 		break;
- 	}
-@@ -1624,7 +1624,7 @@ static void max_gmsl2_deser_remove(struct i2c_client *client)
- static const struct of_device_id max_gmsl2_deser_of_ids[] = {
- 	{ .compatible = "maxim,max96934", .data = &max96934_data },
- 	{ .compatible = "maxim,max96914", .data = &max96914_data },
--	{ .compatible = "maxim,max96714", .data = &max96714_data },
-+	{ .compatible = "maxim,max96714f", .data = &max96714f_data },
- 	{ }
- };
- MODULE_DEVICE_TABLE(of, max_gmsl2_deser_of_ids);
--- 
-2.39.2
-
diff --git a/debian/patches/apertis/st-vgxy61/0168-Add-raw8-video-formats.patch b/debian/patches/apertis/st-vgxy61/0168-Add-raw8-video-formats.patch
new file mode 100644
index 00000000000..050afaaecec
--- /dev/null
+++ b/debian/patches/apertis/st-vgxy61/0168-Add-raw8-video-formats.patch
@@ -0,0 +1,85 @@
+From: fvz1brg <francisco.vaz@pt.bosch.com>
+Date: Thu, 27 Apr 2023 09:54:40 +0000
+Subject: Add raw8 video formats
+
+---
+ drivers/media/platform/cadence/cdns-csi2rx.c       | 20 +++++++++++++++
+ .../media/platform/ti/j721e-csi2rx/j721e-csi2rx.c  | 30 +++++++++++++++++++---
+ 2 files changed, 47 insertions(+), 3 deletions(-)
+
+diff --git a/drivers/media/platform/cadence/cdns-csi2rx.c b/drivers/media/platform/cadence/cdns-csi2rx.c
+index 7800ded..18d5db9 100644
+--- a/drivers/media/platform/cadence/cdns-csi2rx.c
++++ b/drivers/media/platform/cadence/cdns-csi2rx.c
+@@ -123,6 +123,26 @@ static const struct csi2rx_fmt formats[] = {
+ 		.code	= MEDIA_BUS_FMT_RGB888_1X24,
+ 		.bpp	= 24,
+ 	},
++	{
++		.code	= MEDIA_BUS_FMT_SBGGR8_1X8,
++		.bpp	= 8,
++	},
++	{
++		.code	= MEDIA_BUS_FMT_SGBRG8_1X8,
++		.bpp	= 8,
++	},
++	{
++		.code	= MEDIA_BUS_FMT_SGRBG8_1X8,
++		.bpp	= 8,
++	},
++	{
++		.code	= MEDIA_BUS_FMT_SRGGB8_1X8,
++		.bpp	= 8,
++	},
++	{
++		.code	= MEDIA_BUS_FMT_Y8_1X8,
++		.bpp	= 8,
++	},
+ };
+ 
+ static const struct csi2rx_fmt *csi2rx_get_fmt_by_code(u32 code)
+diff --git a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
+index 395b610..1af7b0b 100644
+--- a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
++++ b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c
+@@ -118,13 +118,37 @@ static const struct ti_csi2rx_fmt formats[] = {
+ 		.code			= MEDIA_BUS_FMT_VYUY8_1X16,
+ 		.csi_dt			= MIPI_CSI2_DT_YUV422_8B,
+ 		.bpp			= 16,
+-	},
+-	{
++	}, {
+ 		.fourcc			= V4L2_PIX_FMT_RGB24,
+ 		.code			= MEDIA_BUS_FMT_RGB888_1X24,
+ 		.csi_dt			= MIPI_CSI2_DT_RGB888,
+ 		.bpp			= 24,
+-	},
++	}, {
++		.fourcc			= V4L2_PIX_FMT_SBGGR8,
++		.code			= MEDIA_BUS_FMT_SBGGR8_1X8,
++		.csi_dt			= MIPI_CSI2_DT_RAW8,
++		.bpp			= 8,
++	}, {
++		.fourcc			= V4L2_PIX_FMT_SGBRG8,
++		.code			= MEDIA_BUS_FMT_SGBRG8_1X8,
++		.csi_dt			= MIPI_CSI2_DT_RAW8,
++		.bpp			= 8,
++	}, {
++		.fourcc			= V4L2_PIX_FMT_SGRBG8,
++		.code			= MEDIA_BUS_FMT_SGRBG8_1X8,
++		.csi_dt			= MIPI_CSI2_DT_RAW8,
++		.bpp			= 8,
++	}, {
++		.fourcc			= V4L2_PIX_FMT_SRGGB8,
++		.code			= MEDIA_BUS_FMT_SRGGB8_1X8,
++		.csi_dt			= MIPI_CSI2_DT_RAW8,
++		.bpp			= 8,
++ 	}, {
++		.fourcc			= V4L2_PIX_FMT_GREY,
++		.code			= MEDIA_BUS_FMT_Y8_1X8,
++		.csi_dt			= MIPI_CSI2_DT_RAW8,
++		.bpp			= 8,
++ 	},
+ 
+ 	/* More formats can be supported but they are not listed for now. */
+ };
diff --git a/debian/patches/series b/debian/patches/series
index e3f10e9a422..317a5c5b20f 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -212,14 +212,11 @@ apertis/am62x/0158-Add-memory-region-and-mailbox-configuration-for-r5f-.patch
 
 # Support for Maxim GMSL devices
 apertis/maxim-gmsl2/0001-drivers-media-add-maxim-GMSL2-deserializer-driver.patch
-apertis/maxim-gmsl2/0151-max-gmsl2-deser-report-link-frequency.patch
-apertis/maxim-gmsl2/0152-max-gmsl2-deser-do-not-try-to-link-non-existing-pipe.patch
-apertis/maxim-gmsl2/0153-drivers-media-max-gmsl2-deser-fix-pattern-generator.patch
-apertis/maxim-gmsl2/0154-max-gmsl2-deser-change-max96714-compatible-to-max967.patch
 
 # Additional video capture format
 apertis/am62x-csi/0153-csi2rx-add-RGB-format.patch
 apertis/am62x-csi/0159-media-platform-ti-j721e-csi2rx-add-missing-MIPI-DT-f.patch
+apertis/am62x-csi/0160-media-platform-cadence-use-asd-fwnode.patch
 
 # Backport of VGXY61 camera driver
 apertis/st-vgxy61/0162-media-i2c-Add-driver-for-ST-VGXY61-camera-sensor.patch
@@ -228,4 +225,4 @@ apertis/st-vgxy61/0164-media-i2c-st-vgxy61-Fix-smatch-warnings.patch
 apertis/st-vgxy61/0165-media-i2c-st-vgxy61-Use-asm-intead-of-asm-generic.patch
 apertis/st-vgxy61/0166-media-v4l-Add-1X16-16-bit-greyscale-media-bus-code-d.patch
 apertis/st-vgxy61/0167-media-v4l-ctrls-Add-a-control-for-HDR-mode.patch
-
+apertis/st-vgxy61/0168-Add-raw8-video-formats.patch
-- 
GitLab